From cb6eb8230666c8f22ec62c172fe7f37bd636f22e Mon Sep 17 00:00:00 2001 From: Kamil Bukum Date: Fri, 7 Feb 2025 14:18:46 -0800 Subject: [PATCH 01/15] update namespaces to indicate language and package manager --- javascript/lib/dependabot/bun.rb | 14 +- javascript/lib/dependabot/bun/file_fetcher.rb | 170 ++-- javascript/lib/dependabot/bun/file_parser.rb | 108 +-- .../dependabot/bun/file_parser/bun_lock.rb | 232 ++--- .../bun/file_parser/lockfile_parser.rb | 64 +- javascript/lib/dependabot/bun/file_updater.rb | 104 +-- .../bun/file_updater/lockfile_updater.rb | 202 ++--- javascript/lib/dependabot/bun/helpers.rb | 106 +-- .../lib/dependabot/bun/package_manager.rb | 70 +- javascript/lib/dependabot/bun/requirement.rb | 8 +- .../lib/dependabot/bun/update_checker.rb | 720 +++++++-------- .../conflicting_dependency_resolver.rb | 100 ++- .../dependency_files_builder.rb | 62 +- .../update_checker/latest_version_finder.rb | 694 +++++++-------- .../bun/update_checker/library_detector.rb | 102 +-- .../update_checker/requirements_updater.rb | 312 +++---- .../subdependency_version_resolver.rb | 224 ++--- .../bun/update_checker/version_resolver.rb | 840 +++++++++--------- .../update_checker/vulnerability_auditor.rb | 270 +++--- javascript/lib/dependabot/bun/version.rb | 8 +- .../lib/dependabot/javascript/file_fetcher.rb | 41 +- .../javascript/file_fetcher_helper.rb | 41 +- .../lib/dependabot/javascript/file_parser.rb | 2 +- .../javascript/file_parser/lockfile_parser.rb | 2 +- .../spec/dependabot/bun/file_fetcher_spec.rb | 2 +- .../bun/file_parser/lockfile_parser_spec.rb | 2 +- .../spec/dependabot/bun/file_parser_spec.rb | 2 +- .../spec/dependabot/bun/file_updater_spec.rb | 2 +- .../dependabot/bun/package_manager_spec.rb | 2 +- .../spec/dependabot/bun/requirement_spec.rb | 2 +- .../spec/dependabot/bun/version_spec.rb | 2 +- 31 files changed, 2291 insertions(+), 2219 deletions(-) diff --git a/javascript/lib/dependabot/bun.rb b/javascript/lib/dependabot/bun.rb index 8037cb34c9..24b69522d3 100644 --- a/javascript/lib/dependabot/bun.rb +++ b/javascript/lib/dependabot/bun.rb @@ -33,12 +33,14 @@ Dependabot::Dependency.register_production_check("bun", ->(_) { true }) module Dependabot - module Bun - ECOSYSTEM = "bun" + module Javascript + module Bun + ECOSYSTEM = "bun" + end end end -Dependabot::FileFetchers.register("bun", Dependabot::Bun::FileFetcher) -Dependabot::FileParsers.register("bun", Dependabot::Bun::FileParser) -Dependabot::FileUpdaters.register("bun", Dependabot::Bun::FileUpdater) -Dependabot::UpdateCheckers.register("bun", Dependabot::Bun::UpdateChecker) +Dependabot::FileFetchers.register("bun", Dependabot::Javascript::Bun::FileFetcher) +Dependabot::FileParsers.register("bun", Dependabot::Javascript::Bun::FileParser) +Dependabot::FileUpdaters.register("bun", Dependabot::Javascript::Bun::FileUpdater) +Dependabot::UpdateCheckers.register("bun", Dependabot::Javascript::Bun::UpdateChecker) diff --git a/javascript/lib/dependabot/bun/file_fetcher.rb b/javascript/lib/dependabot/bun/file_fetcher.rb index 6b198d639d..e8f3e121ac 100644 --- a/javascript/lib/dependabot/bun/file_fetcher.rb +++ b/javascript/lib/dependabot/bun/file_fetcher.rb @@ -2,92 +2,94 @@ # frozen_string_literal: true module Dependabot - module Bun - class FileFetcher < Dependabot::FileFetchers::Base - include ::Dependabot::Javascript::FileFetcherHelper - extend T::Sig - extend T::Helpers - - sig { override.params(filenames: T::Array[String]).returns(T::Boolean) } - def self.required_files_in?(filenames) - filenames.include?("package.json") - end - - sig { override.returns(String) } - def self.required_files_message - "Repo must contain a package.json." - end - - sig { override.returns(T.nilable(T::Hash[Symbol, T.untyped])) } - def ecosystem_versions - return unknown_ecosystem_versions unless ecosystem_enabled? - - { - package_managers: { - "bun" => 1 + module Javascript + module Bun + class FileFetcher < Dependabot::FileFetchers::Base + include ::Dependabot::Javascript::FileFetcherHelper + extend T::Sig + extend T::Helpers + + sig { override.params(filenames: T::Array[String]).returns(T::Boolean) } + def self.required_files_in?(filenames) + filenames.include?("package.json") + end + + sig { override.returns(String) } + def self.required_files_message + "Repo must contain a package.json." + end + + sig { override.returns(T.nilable(T::Hash[Symbol, T.untyped])) } + def ecosystem_versions + return unknown_ecosystem_versions unless ecosystem_enabled? + + { + package_managers: { + "bun" => 1 + } } - } - end - - sig { override.returns(T::Array[DependencyFile]) } - def fetch_files - fetched_files = T.let([], T::Array[DependencyFile]) - fetched_files << package_json(self) - fetched_files += bun_files if ecosystem_enabled? - fetched_files += workspace_package_jsons(self) - fetched_files += path_dependencies(self, fetched_files) - - fetched_files.uniq - end - - sig { params(filename: String, fetch_submodules: T::Boolean).returns(DependencyFile) } - def fetch_file(filename, fetch_submodules: false) - fetch_file_from_host(filename, fetch_submodules: fetch_submodules) - end - - sig do - params( - dir: T.any(Pathname, String), - ignore_base_directory: T::Boolean, - raise_errors: T::Boolean, - fetch_submodules: T::Boolean - ) - .returns(T::Array[T.untyped]) - end - def fetch_repo_contents(dir: ".", ignore_base_directory: false, raise_errors: true, fetch_submodules: false) - repo_contents(dir: dir, ignore_base_directory:, raise_errors:, fetch_submodules:) - end - - private - - sig { returns(T::Array[DependencyFile]) } - def bun_files - [bun_lock].compact - end - - sig { returns(T.nilable(DependencyFile)) } - def bun_lock - return @bun_lock if defined?(@bun_lock) - - @bun_lock ||= T.let(fetch_file_if_present(PackageManager::LOCKFILE_NAME), T.nilable(DependencyFile)) - - return @bun_lock if @bun_lock || directory == "/" - - @bun_lock = fetch_file_from_parent_directories(self, PackageManager::LOCKFILE_NAME) - end - - sig { returns(T::Boolean) } - def ecosystem_enabled? - allow_beta_ecosystems? && Experiments.enabled?(:enable_bun_ecosystem) - end - - sig { returns(T::Hash[Symbol, String]) } - def unknown_ecosystem_versions - { - package_managers: { - "unknown" => 0 + end + + sig { override.returns(T::Array[DependencyFile]) } + def fetch_files + fetched_files = T.let([], T::Array[DependencyFile]) + fetched_files << package_json(self) + fetched_files += bun_files if ecosystem_enabled? + fetched_files += workspace_package_jsons(self) + fetched_files += path_dependencies(self, fetched_files) + + fetched_files.uniq + end + + sig { params(filename: String, fetch_submodules: T::Boolean).returns(DependencyFile) } + def fetch_file(filename, fetch_submodules: false) + fetch_file_from_host(filename, fetch_submodules: fetch_submodules) + end + + sig do + params( + dir: T.any(Pathname, String), + ignore_base_directory: T::Boolean, + raise_errors: T::Boolean, + fetch_submodules: T::Boolean + ) + .returns(T::Array[T.untyped]) + end + def fetch_repo_contents(dir: ".", ignore_base_directory: false, raise_errors: true, fetch_submodules: false) + repo_contents(dir: dir, ignore_base_directory:, raise_errors:, fetch_submodules:) + end + + private + + sig { returns(T::Array[DependencyFile]) } + def bun_files + [bun_lock].compact + end + + sig { returns(T.nilable(DependencyFile)) } + def bun_lock + return @bun_lock if defined?(@bun_lock) + + @bun_lock ||= T.let(fetch_file_if_present(PackageManager::LOCKFILE_NAME), T.nilable(DependencyFile)) + + return @bun_lock if @bun_lock || directory == "/" + + @bun_lock = fetch_file_from_parent_directories(self, PackageManager::LOCKFILE_NAME) + end + + sig { returns(T::Boolean) } + def ecosystem_enabled? + allow_beta_ecosystems? && Experiments.enabled?(:enable_bun_ecosystem) + end + + sig { returns(T::Hash[Symbol, String]) } + def unknown_ecosystem_versions + { + package_managers: { + "unknown" => 0 + } } - } + end end end end diff --git a/javascript/lib/dependabot/bun/file_parser.rb b/javascript/lib/dependabot/bun/file_parser.rb index c0e64e422f..45a972fe5e 100644 --- a/javascript/lib/dependabot/bun/file_parser.rb +++ b/javascript/lib/dependabot/bun/file_parser.rb @@ -4,69 +4,71 @@ # See https://docs.npmjs.com/files/package.json for package.json format docs. module Dependabot - module Bun - class FileParser < Dependabot::Javascript::FileParser - extend T::Sig + module Javascript + module Bun + class FileParser < Dependabot::Javascript::FileParser + extend T::Sig - sig { override.returns(Ecosystem) } - def ecosystem - @ecosystem ||= T.let( - Ecosystem.new( - name: ECOSYSTEM, - package_manager: PackageManager.new(detected_version:), - language: Javascript::Language.new(detected_version:) - ), - T.nilable(Ecosystem) - ) - end + sig { override.returns(Ecosystem) } + def ecosystem + @ecosystem ||= T.let( + Ecosystem.new( + name: ECOSYSTEM, + package_manager: PackageManager.new(detected_version:), + language: Javascript::Language.new(detected_version:) + ), + T.nilable(Ecosystem) + ) + end - private + private - sig { returns(T.nilable(String)) } - def detected_version - Helpers.local_package_manager_version(Bun::PackageManager::NAME) - end + sig { returns(T.nilable(String)) } + def detected_version + Helpers.local_package_manager_version(Bun::PackageManager::NAME) + end - sig { override.returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) } - def lockfiles - { - bun: bun_lock - } - end + sig { override.returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) } + def lockfiles + { + bun: bun_lock + } + end - sig { override.returns(LockfileParser) } - def lockfile_parser - @lockfile_parser ||= T.let(LockfileParser.new( - dependency_files: dependency_files - ), T.nilable(LockfileParser)) - end + sig { override.returns(LockfileParser) } + def lockfile_parser + @lockfile_parser ||= T.let(LockfileParser.new( + dependency_files: dependency_files + ), T.nilable(LockfileParser)) + end - sig { override.returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) } - def registry_config_files - { - npmrc: npmrc - } - end + sig { override.returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) } + def registry_config_files + { + npmrc: npmrc + } + end - sig { returns(T.nilable(Dependabot::DependencyFile)) } - def bun_lock - @bun_lock ||= T.let(dependency_files.find do |f| - f.name.end_with?(PackageManager::LOCKFILE_NAME) - end, T.nilable(Dependabot::DependencyFile)) - end + sig { returns(T.nilable(Dependabot::DependencyFile)) } + def bun_lock + @bun_lock ||= T.let(dependency_files.find do |f| + f.name.end_with?(PackageManager::LOCKFILE_NAME) + end, T.nilable(Dependabot::DependencyFile)) + end - sig { override.returns(T.class_of(Version)) } - def version_class - Version - end + sig { override.returns(T.class_of(Version)) } + def version_class + Version + end - sig { override.returns(T.class_of(Requirement)) } - def requirement_class - Requirement - end + sig { override.returns(T.class_of(Requirement)) } + def requirement_class + Requirement + end - sig { override.void } - def check_required_files; end + sig { override.void } + def check_required_files; end + end end end end diff --git a/javascript/lib/dependabot/bun/file_parser/bun_lock.rb b/javascript/lib/dependabot/bun/file_parser/bun_lock.rb index d9450eff56..85102a23f8 100644 --- a/javascript/lib/dependabot/bun/file_parser/bun_lock.rb +++ b/javascript/lib/dependabot/bun/file_parser/bun_lock.rb @@ -2,144 +2,146 @@ # frozen_string_literal: true module Dependabot - module Bun - class FileParser - class BunLock - extend T::Sig - - sig { params(dependency_file: DependencyFile).void } - def initialize(dependency_file) - @dependency_file = dependency_file - end + module Javascript + module Bun + class FileParser + class BunLock + extend T::Sig + + sig { params(dependency_file: DependencyFile).void } + def initialize(dependency_file) + @dependency_file = dependency_file + end - sig { returns(T::Hash[String, T.untyped]) } - def parsed - @parsed ||= begin - content = begin - # Since bun.lock is a JSONC file, which is a subset of YAML, we can use YAML to parse it - YAML.load(T.must(@dependency_file.content)) - rescue Psych::SyntaxError => e - raise_invalid!("malformed JSONC at line #{e.line}, column #{e.column}") + sig { returns(T::Hash[String, T.untyped]) } + def parsed + @parsed ||= begin + content = begin + # Since bun.lock is a JSONC file, which is a subset of YAML, we can use YAML to parse it + YAML.load(T.must(@dependency_file.content)) + rescue Psych::SyntaxError => e + raise_invalid!("malformed JSONC at line #{e.line}, column #{e.column}") + end + raise_invalid!("expected to be an object") unless content.is_a?(Hash) + + version = content["lockfileVersion"] + raise_invalid!("expected 'lockfileVersion' to be an integer") unless version.is_a?(Integer) + raise_invalid!("expected 'lockfileVersion' to be >= 0") unless version >= 0 + + T.let(content, T.untyped) end - raise_invalid!("expected to be an object") unless content.is_a?(Hash) + end - version = content["lockfileVersion"] - raise_invalid!("expected 'lockfileVersion' to be an integer") unless version.is_a?(Integer) - raise_invalid!("expected 'lockfileVersion' to be >= 0") unless version >= 0 + sig { returns(Dependabot::FileParsers::Base::DependencySet) } + def dependencies + dependency_set = Dependabot::FileParsers::Base::DependencySet.new - T.let(content, T.untyped) - end - end + # bun.lock v0 format: + # https://github.com/oven-sh/bun/blob/c130df6c589fdf28f9f3c7f23ed9901140bc9349/src/install/bun.lock.zig#L595-L605 - sig { returns(Dependabot::FileParsers::Base::DependencySet) } - def dependencies - dependency_set = Dependabot::FileParsers::Base::DependencySet.new + packages = parsed["packages"] + raise_invalid!("expected 'packages' to be an object") unless packages.is_a?(Hash) - # bun.lock v0 format: - # https://github.com/oven-sh/bun/blob/c130df6c589fdf28f9f3c7f23ed9901140bc9349/src/install/bun.lock.zig#L595-L605 + packages.each do |key, details| + raise_invalid!("expected 'packages.#{key}' to be an array") unless details.is_a?(Array) - packages = parsed["packages"] - raise_invalid!("expected 'packages' to be an object") unless packages.is_a?(Hash) + resolution = details.first + raise_invalid!("expected 'packages.#{key}[0]' to be a string") unless resolution.is_a?(String) - packages.each do |key, details| - raise_invalid!("expected 'packages.#{key}' to be an array") unless details.is_a?(Array) + name, version = resolution.split(/(?<=\w)\@/) + next if name.empty? - resolution = details.first - raise_invalid!("expected 'packages.#{key}[0]' to be a string") unless resolution.is_a?(String) + semver = Version.semver_for(version) + next unless semver - name, version = resolution.split(/(?<=\w)\@/) - next if name.empty? + dependency_set << Dependency.new( + name: name, + version: semver.to_s, + package_manager: "bun", + requirements: [] + ) + end - semver = Version.semver_for(version) - next unless semver - - dependency_set << Dependency.new( - name: name, - version: semver.to_s, - package_manager: "bun", - requirements: [] - ) + dependency_set end - dependency_set - end + sig do + params(dependency_name: String, requirement: T.untyped, _manifest_name: String) + .returns(T.nilable(T::Hash[String, T.untyped])) + end + def details(dependency_name, requirement, _manifest_name) + packages = parsed["packages"] + return unless packages.is_a?(Hash) + + candidates = + packages + .select { |name, _| name == dependency_name } + .values + + # If there's only one entry for this dependency, use it, even if + # the requirement in the lockfile doesn't match + if candidates.one? + parse_details(candidates.first) + else + candidate = candidates.find do |label, _| + label.scan(/(?<=\w)\@(?:npm:)?([^\s,]+)/).flatten.include?(requirement) + end&.last + parse_details(candidate) + end + end - sig do - params(dependency_name: String, requirement: T.untyped, _manifest_name: String) - .returns(T.nilable(T::Hash[String, T.untyped])) - end - def details(dependency_name, requirement, _manifest_name) - packages = parsed["packages"] - return unless packages.is_a?(Hash) - - candidates = - packages - .select { |name, _| name == dependency_name } - .values - - # If there's only one entry for this dependency, use it, even if - # the requirement in the lockfile doesn't match - if candidates.one? - parse_details(candidates.first) - else - candidate = candidates.find do |label, _| - label.scan(/(?<=\w)\@(?:npm:)?([^\s,]+)/).flatten.include?(requirement) - end&.last - parse_details(candidate) + private + + sig { params(message: String).void } + def raise_invalid!(message) + raise Dependabot::DependencyFileNotParseable.new(@dependency_file.path, "Invalid bun.lock file: #{message}") end - end - private + sig do + params(entry: T.nilable(T::Array[T.untyped])).returns(T.nilable(T::Hash[String, T.untyped])) + end + def parse_details(entry) + return unless entry.is_a?(Array) - sig { params(message: String).void } - def raise_invalid!(message) - raise Dependabot::DependencyFileNotParseable.new(@dependency_file.path, "Invalid bun.lock file: #{message}") - end + # Either: + # - "{name}@{version}", registry, details, integrity + # - "{name}@{resolution}", details + resolution = entry.first + return unless resolution.is_a?(String) - sig do - params(entry: T.nilable(T::Array[T.untyped])).returns(T.nilable(T::Hash[String, T.untyped])) - end - def parse_details(entry) - return unless entry.is_a?(Array) - - # Either: - # - "{name}@{version}", registry, details, integrity - # - "{name}@{resolution}", details - resolution = entry.first - return unless resolution.is_a?(String) - - name, version = resolution.split(/(?<=\w)\@/) - semver = Version.semver_for(version) - - if semver - registry, details, integrity = entry[1..3] - { - "name" => name, - "version" => semver.to_s, - "registry" => registry, - "details" => details, - "integrity" => integrity - } - else - details = entry[1] - { - "name" => name, - "resolution" => version, - "details" => details - } + name, version = resolution.split(/(?<=\w)\@/) + semver = Version.semver_for(version) + + if semver + registry, details, integrity = entry[1..3] + { + "name" => name, + "version" => semver.to_s, + "registry" => registry, + "details" => details, + "integrity" => integrity + } + else + details = entry[1] + { + "name" => name, + "resolution" => version, + "details" => details + } + end end end - end - sig { override.returns(T::Array[Dependabot::Dependency]) } - def parse - [] - end + sig { override.returns(T::Array[Dependabot::Dependency]) } + def parse + [] + end - private + private - sig { override.void } - def check_required_files; end + sig { override.void } + def check_required_files; end + end end end end diff --git a/javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb b/javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb index 403b331198..53fc73c004 100644 --- a/javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb +++ b/javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb @@ -2,43 +2,45 @@ # frozen_string_literal: true module Dependabot - module Bun - class FileParser - class LockfileParser < Dependabot::Javascript::FileParser::LockfileParser - extend T::Sig + module Javascript + module Bun + class FileParser + class LockfileParser < Dependabot::Javascript::FileParsers::LockfileParser + extend T::Sig - sig { override.returns(Dependabot::FileParsers::Base::DependencySet) } - def parse_set - dependency_set = Dependabot::FileParsers::Base::DependencySet.new + sig { override.returns(Dependabot::FileParsers::Base::DependencySet) } + def parse_set + dependency_set = Dependabot::FileParsers::Base::DependencySet.new - bun_locks.each do |file| - dependency_set += lockfile_for(file).dependencies - end + bun_locks.each do |file| + dependency_set += lockfile_for(file).dependencies + end - dependency_set - end + dependency_set + end - private - - sig { override.params(file: DependencyFile).returns(BunLock) } - def lockfile_for(file) - @lockfiles ||= T.let({}, T.nilable(T::Hash[String, BunLock])) - @lockfiles[file.name] ||= case file.name - when *bun_locks.map(&:name) - Bun::FileParser::BunLock.new(file) - else - raise "Unexpected lockfile: #{file.name}" - end - end + private + + sig { override.params(file: DependencyFile).returns(BunLock) } + def lockfile_for(file) + @lockfiles ||= T.let({}, T.nilable(T::Hash[String, BunLock])) + @lockfiles[file.name] ||= case file.name + when *bun_locks.map(&:name) + Bun::FileParser::BunLock.new(file) + else + raise "Unexpected lockfile: #{file.name}" + end + end - sig { returns(T::Array[DependencyFile]) } - def bun_locks - @bun_locks ||= T.let(select_files_by_extension("bun.lock"), T.nilable(T::Array[DependencyFile])) - end + sig { returns(T::Array[DependencyFile]) } + def bun_locks + @bun_locks ||= T.let(select_files_by_extension("bun.lock"), T.nilable(T::Array[DependencyFile])) + end - sig { override.returns(T.class_of(Version)) } - def version_class - Version + sig { override.returns(T.class_of(Version)) } + def version_class + Version + end end end end diff --git a/javascript/lib/dependabot/bun/file_updater.rb b/javascript/lib/dependabot/bun/file_updater.rb index a41b456f6a..abe1924553 100644 --- a/javascript/lib/dependabot/bun/file_updater.rb +++ b/javascript/lib/dependabot/bun/file_updater.rb @@ -2,66 +2,68 @@ # frozen_string_literal: true module Dependabot - module Bun - class FileUpdater < Javascript::FileUpdater - sig { override.returns(T::Array[Regexp]) } - def self.updated_files_regex - [ - %r{^(?:.*/)?package\.json$}, - %r{^(?:.*/)?bun\.lock$} # Matches bun.lock files - ] - end + module Javascript + module Bun + class FileUpdater < Javascript::FileUpdater + sig { override.returns(T::Array[Regexp]) } + def self.updated_files_regex + [ + %r{^(?:.*/)?package\.json$}, + %r{^(?:.*/)?bun\.lock$} # Matches bun.lock files + ] + end - private + private - sig { returns(T::Array[Dependabot::DependencyFile]) } - def bun_locks - @bun_locks ||= T.let( - filtered_dependency_files - .select { |f| f.name.end_with?("bun.lock") }, - T.nilable(T::Array[Dependabot::DependencyFile]) - ) - end + sig { returns(T::Array[Dependabot::DependencyFile]) } + def bun_locks + @bun_locks ||= T.let( + filtered_dependency_files + .select { |f| f.name.end_with?("bun.lock") }, + T.nilable(T::Array[Dependabot::DependencyFile]) + ) + end - sig { params(bun_lock: Dependabot::DependencyFile).returns(T::Boolean) } - def bun_lock_changed?(bun_lock) - bun_lock.content != updated_bun_lock_content(bun_lock) - end + sig { params(bun_lock: Dependabot::DependencyFile).returns(T::Boolean) } + def bun_lock_changed?(bun_lock) + bun_lock.content != updated_bun_lock_content(bun_lock) + end - sig { override.returns(T::Array[Dependabot::DependencyFile]) } - def updated_lockfiles - updated_files = [] + sig { override.returns(T::Array[Dependabot::DependencyFile]) } + def updated_lockfiles + updated_files = [] - bun_locks.each do |bun_lock| - next unless bun_lock_changed?(bun_lock) + bun_locks.each do |bun_lock| + next unless bun_lock_changed?(bun_lock) - updated_files << updated_file( - file: bun_lock, - content: updated_bun_lock_content(bun_lock) - ) - end + updated_files << updated_file( + file: bun_lock, + content: updated_bun_lock_content(bun_lock) + ) + end - updated_files - end + updated_files + end - sig { params(bun_lock: Dependabot::DependencyFile).returns(String) } - def updated_bun_lock_content(bun_lock) - @updated_bun_lock_content ||= T.let({}, T.nilable(T::Hash[String, T.nilable(String)])) - @updated_bun_lock_content[bun_lock.name] ||= - bun_lockfile_updater.updated_bun_lock_content(bun_lock) - end + sig { params(bun_lock: Dependabot::DependencyFile).returns(String) } + def updated_bun_lock_content(bun_lock) + @updated_bun_lock_content ||= T.let({}, T.nilable(T::Hash[String, T.nilable(String)])) + @updated_bun_lock_content[bun_lock.name] ||= + bun_lockfile_updater.updated_bun_lock_content(bun_lock) + end - sig { returns(Dependabot::Bun::FileUpdater::LockfileUpdater) } - def bun_lockfile_updater - @bun_lockfile_updater ||= T.let( - LockfileUpdater.new( - dependencies: dependencies, - dependency_files: dependency_files, - repo_contents_path: repo_contents_path, - credentials: credentials - ), - T.nilable(Dependabot::Bun::FileUpdater::LockfileUpdater) - ) + sig { returns(Dependabot::Javascript::Bun::FileUpdater::LockfileUpdater) } + def bun_lockfile_updater + @bun_lockfile_updater ||= T.let( + LockfileUpdater.new( + dependencies: dependencies, + dependency_files: dependency_files, + repo_contents_path: repo_contents_path, + credentials: credentials + ), + T.nilable(Dependabot::Javascript::Bun::FileUpdater::LockfileUpdater) + ) + end end end end diff --git a/javascript/lib/dependabot/bun/file_updater/lockfile_updater.rb b/javascript/lib/dependabot/bun/file_updater/lockfile_updater.rb index 28ff181133..b3155a4b1e 100644 --- a/javascript/lib/dependabot/bun/file_updater/lockfile_updater.rb +++ b/javascript/lib/dependabot/bun/file_updater/lockfile_updater.rb @@ -2,133 +2,135 @@ # frozen_string_literal: true module Dependabot - module Bun - class FileUpdater - class LockfileUpdater - def initialize(dependencies:, dependency_files:, repo_contents_path:, credentials:) - @dependencies = dependencies - @dependency_files = dependency_files - @repo_contents_path = repo_contents_path - @credentials = credentials - end + module Javascript + module Bun + class FileUpdater + class LockfileUpdater + def initialize(dependencies:, dependency_files:, repo_contents_path:, credentials:) + @dependencies = dependencies + @dependency_files = dependency_files + @repo_contents_path = repo_contents_path + @credentials = credentials + end - def updated_bun_lock_content(bun_lock) - @updated_bun_lock_content ||= {} - return @updated_bun_lock_content[bun_lock.name] if @updated_bun_lock_content[bun_lock.name] + def updated_bun_lock_content(bun_lock) + @updated_bun_lock_content ||= {} + return @updated_bun_lock_content[bun_lock.name] if @updated_bun_lock_content[bun_lock.name] - new_content = run_bun_update(bun_lock: bun_lock) - @updated_bun_lock_content[bun_lock.name] = new_content - rescue SharedHelpers::HelperSubprocessFailed => e - handle_bun_lock_updater_error(e, bun_lock) - end + new_content = run_bun_update(bun_lock: bun_lock) + @updated_bun_lock_content[bun_lock.name] = new_content + rescue SharedHelpers::HelperSubprocessFailed => e + handle_bun_lock_updater_error(e, bun_lock) + end - private + private - attr_reader :dependencies - attr_reader :dependency_files - attr_reader :repo_contents_path - attr_reader :credentials + attr_reader :dependencies + attr_reader :dependency_files + attr_reader :repo_contents_path + attr_reader :credentials - ERR_PATTERNS = { - /get .* 404/i => Dependabot::DependencyNotFound, - /installfailed cloning repository/i => Dependabot::DependencyNotFound, - /file:.* failed to resolve/i => Dependabot::DependencyNotFound, - /no version matching/i => Dependabot::DependencyFileNotResolvable, - /failed to resolve/i => Dependabot::DependencyFileNotResolvable - }.freeze + ERR_PATTERNS = { + /get .* 404/i => Dependabot::DependencyNotFound, + /installfailed cloning repository/i => Dependabot::DependencyNotFound, + /file:.* failed to resolve/i => Dependabot::DependencyNotFound, + /no version matching/i => Dependabot::DependencyFileNotResolvable, + /failed to resolve/i => Dependabot::DependencyFileNotResolvable + }.freeze - def run_bun_update(bun_lock:) - SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do - File.write(".npmrc", npmrc_content(bun_lock)) + def run_bun_update(bun_lock:) + SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do + File.write(".npmrc", npmrc_content(bun_lock)) - SharedHelpers.with_git_configured(credentials: credentials) do - run_bun_updater + SharedHelpers.with_git_configured(credentials: credentials) do + run_bun_updater - write_final_package_json_files + write_final_package_json_files - run_bun_install + run_bun_install - File.read(bun_lock.name) + File.read(bun_lock.name) + end end end - end - def run_bun_updater - dependency_updates = dependencies.map do |d| - "#{d.name}@#{d.version}" - end.join(" ") + def run_bun_updater + dependency_updates = dependencies.map do |d| + "#{d.name}@#{d.version}" + end.join(" ") - Helpers.run_bun_command( - "install #{dependency_updates} --save-text-lockfile", - fingerprint: "install --save-text-lockfile" - ) - end + Helpers.run_bun_command( + "install #{dependency_updates} --save-text-lockfile", + fingerprint: "install --save-text-lockfile" + ) + end - def run_bun_install - Helpers.run_bun_command( - "install --save-text-lockfile" - ) - end + def run_bun_install + Helpers.run_bun_command( + "install --save-text-lockfile" + ) + end - def lockfile_dependencies(lockfile) - @lockfile_dependencies ||= {} - @lockfile_dependencies[lockfile.name] ||= - FileParser.new( - dependency_files: [lockfile, *package_files], - source: nil, - credentials: credentials - ).parse - end + def lockfile_dependencies(lockfile) + @lockfile_dependencies ||= {} + @lockfile_dependencies[lockfile.name] ||= + FileParser.new( + dependency_files: [lockfile, *package_files], + source: nil, + credentials: credentials + ).parse + end - def handle_bun_lock_updater_error(error, _bun_lock) - error_message = error.message + def handle_bun_lock_updater_error(error, _bun_lock) + error_message = error.message - ERR_PATTERNS.each do |pattern, error_class| - raise error_class, error_message if error_message.match?(pattern) - end + ERR_PATTERNS.each do |pattern, error_class| + raise error_class, error_message if error_message.match?(pattern) + end - raise error - end + raise error + end - def write_final_package_json_files - package_files.each do |file| - path = file.name - FileUtils.mkdir_p(Pathname.new(path).dirname) - File.write(path, updated_package_json_content(file)) + def write_final_package_json_files + package_files.each do |file| + path = file.name + FileUtils.mkdir_p(Pathname.new(path).dirname) + File.write(path, updated_package_json_content(file)) + end end - end - def npmrc_content(bun_lock) - Javascript::FileUpdater::NpmrcBuilder.new( - credentials: credentials, - dependency_files: dependency_files, - dependencies: lockfile_dependencies(bun_lock) - ).npmrc_content - end + def npmrc_content(bun_lock) + Javascript::FileUpdater::NpmrcBuilder.new( + credentials: credentials, + dependency_files: dependency_files, + dependencies: lockfile_dependencies(bun_lock) + ).npmrc_content + end - def updated_package_json_content(file) - @updated_package_json_content ||= {} - @updated_package_json_content[file.name] ||= - Javascript::FileUpdater::PackageJsonUpdater.new( - package_json: file, - dependencies: dependencies - ).updated_package_json.content - end + def updated_package_json_content(file) + @updated_package_json_content ||= {} + @updated_package_json_content[file.name] ||= + Javascript::FileUpdater::PackageJsonUpdater.new( + package_json: file, + dependencies: dependencies + ).updated_package_json.content + end - def package_files - @package_files ||= dependency_files.select { |f| f.name.end_with?("package.json") } - end + def package_files + @package_files ||= dependency_files.select { |f| f.name.end_with?("package.json") } + end - def base_dir - dependency_files.first.directory - end + def base_dir + dependency_files.first.directory + end - def npmrc_file - dependency_files.find { |f| f.name == ".npmrc" } - end + def npmrc_file + dependency_files.find { |f| f.name == ".npmrc" } + end - def sanitize_message(message) - message.gsub(/"|\[|\]|\}|\{/, "") + def sanitize_message(message) + message.gsub(/"|\[|\]|\}|\{/, "") + end end end end diff --git a/javascript/lib/dependabot/bun/helpers.rb b/javascript/lib/dependabot/bun/helpers.rb index ab9f0d4a32..f17f0314c9 100644 --- a/javascript/lib/dependabot/bun/helpers.rb +++ b/javascript/lib/dependabot/bun/helpers.rb @@ -2,68 +2,70 @@ # frozen_string_literal: true module Dependabot - module Bun - module Helpers - extend T::Sig + module Javascript + module Bun + module Helpers + extend T::Sig - # BUN Version Constants - BUN_V1 = 1 - BUN_DEFAULT_VERSION = BUN_V1 + # BUN Version Constants + BUN_V1 = 1 + BUN_DEFAULT_VERSION = BUN_V1 - sig { params(_bun_lock: T.nilable(DependencyFile)).returns(Integer) } - def self.bun_version_numeric(_bun_lock) - BUN_DEFAULT_VERSION - end + sig { params(_bun_lock: T.nilable(DependencyFile)).returns(Integer) } + def self.bun_version_numeric(_bun_lock) + BUN_DEFAULT_VERSION + end - sig { returns(T.nilable(String)) } - def self.bun_version - run_bun_command("--version", fingerprint: "--version").strip - rescue StandardError => e - Dependabot.logger.error("Error retrieving Bun version: #{e.message}") - nil - end + sig { returns(T.nilable(String)) } + def self.bun_version + run_bun_command("--version", fingerprint: "--version").strip + rescue StandardError => e + Dependabot.logger.error("Error retrieving Bun version: #{e.message}") + nil + end - sig { params(command: String, fingerprint: T.nilable(String)).returns(String) } - def self.run_bun_command(command, fingerprint: nil) - full_command = "bun #{command}" + sig { params(command: String, fingerprint: T.nilable(String)).returns(String) } + def self.run_bun_command(command, fingerprint: nil) + full_command = "bun #{command}" - Dependabot.logger.info("Running bun command: #{full_command}") + Dependabot.logger.info("Running bun command: #{full_command}") - result = Dependabot::SharedHelpers.run_shell_command( - full_command, - fingerprint: "bun #{fingerprint || command}" - ) + result = Dependabot::SharedHelpers.run_shell_command( + full_command, + fingerprint: "bun #{fingerprint || command}" + ) - Dependabot.logger.info("Command executed successfully: #{full_command}") - result - rescue StandardError => e - Dependabot.logger.error("Error running bun command: #{full_command}, Error: #{e.message}") - raise - end + Dependabot.logger.info("Command executed successfully: #{full_command}") + result + rescue StandardError => e + Dependabot.logger.error("Error running bun command: #{full_command}, Error: #{e.message}") + raise + end - # Fetch the currently installed version of the package manager directly - # from the system - sig { params(name: String).returns(String) } - def self.local_package_manager_version(name) - Dependabot::SharedHelpers.run_shell_command( - "#{name} -v", - fingerprint: "#{name} -v" - ).strip - end + # Fetch the currently installed version of the package manager directly + # from the system + sig { params(name: String).returns(String) } + def self.local_package_manager_version(name) + Dependabot::SharedHelpers.run_shell_command( + "#{name} -v", + fingerprint: "#{name} -v" + ).strip + end - # Run single command on package manager returning stdout/stderr - sig do - params( - name: String, - command: String, - fingerprint: T.nilable(String) - ).returns(String) - end - def self.package_manager_run_command(name, command, fingerprint: nil) - return run_bun_command(command, fingerprint: fingerprint) if name == PackageManager::NAME + # Run single command on package manager returning stdout/stderr + sig do + params( + name: String, + command: String, + fingerprint: T.nilable(String) + ).returns(String) + end + def self.package_manager_run_command(name, command, fingerprint: nil) + return run_bun_command(command, fingerprint: fingerprint) if name == PackageManager::NAME - # TODO: remove this method and just use the one in the PackageManager class - "noop" + # TODO: remove this method and just use the one in the PackageManager class + "noop" + end end end end diff --git a/javascript/lib/dependabot/bun/package_manager.rb b/javascript/lib/dependabot/bun/package_manager.rb index 196e1b9817..fd62cf73ef 100644 --- a/javascript/lib/dependabot/bun/package_manager.rb +++ b/javascript/lib/dependabot/bun/package_manager.rb @@ -2,44 +2,46 @@ # frozen_string_literal: true module Dependabot - module Bun - class PackageManager < Ecosystem::VersionManager - extend T::Sig - NAME = "bun" - LOCKFILE_NAME = "bun.lock" + module Javascript + module Bun + class PackageManager < Ecosystem::VersionManager + extend T::Sig + NAME = "bun" + LOCKFILE_NAME = "bun.lock" - # In Bun 1.1.39, the lockfile format was changed from a binary bun.lockb to a text-based bun.lock. - # https://bun.sh/blog/bun-lock-text-lockfile - MIN_SUPPORTED_VERSION = T.let(Version.new("1.1.39"), Javascript::Version) - SUPPORTED_VERSIONS = T.let([MIN_SUPPORTED_VERSION].freeze, T::Array[Javascript::Version]) - DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Version]) + # In Bun 1.1.39, the lockfile format was changed from a binary bun.lockb to a text-based bun.lock. + # https://bun.sh/blog/bun-lock-text-lockfile + MIN_SUPPORTED_VERSION = T.let(Version.new("1.1.39"), Javascript::Version) + SUPPORTED_VERSIONS = T.let([MIN_SUPPORTED_VERSION].freeze, T::Array[Javascript::Version]) + DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Version]) - sig do - params( - detected_version: T.nilable(String), - raw_version: T.nilable(String), - requirement: T.nilable(Requirement) - ).void - end - def initialize(detected_version: nil, raw_version: nil, requirement: nil) - super( - name: NAME, - detected_version: detected_version ? Version.new(detected_version) : nil, - version: raw_version ? Version.new(raw_version) : nil, - deprecated_versions: DEPRECATED_VERSIONS, - supported_versions: SUPPORTED_VERSIONS, - requirement: requirement - ) - end + sig do + params( + detected_version: T.nilable(String), + raw_version: T.nilable(String), + requirement: T.nilable(Requirement) + ).void + end + def initialize(detected_version: nil, raw_version: nil, requirement: nil) + super( + name: NAME, + detected_version: detected_version ? Version.new(detected_version) : nil, + version: raw_version ? Version.new(raw_version) : nil, + deprecated_versions: DEPRECATED_VERSIONS, + supported_versions: SUPPORTED_VERSIONS, + requirement: requirement + ) + end - sig { override.returns(T::Boolean) } - def deprecated? - false - end + sig { override.returns(T::Boolean) } + def deprecated? + false + end - sig { override.returns(T::Boolean) } - def unsupported? - false + sig { override.returns(T::Boolean) } + def unsupported? + false + end end end end diff --git a/javascript/lib/dependabot/bun/requirement.rb b/javascript/lib/dependabot/bun/requirement.rb index 9c9e8528f7..23921d954c 100644 --- a/javascript/lib/dependabot/bun/requirement.rb +++ b/javascript/lib/dependabot/bun/requirement.rb @@ -2,13 +2,15 @@ # frozen_string_literal: true module Dependabot - module Bun - class Requirement < Dependabot::Javascript::Requirement + module Javascript + module Bun + class Requirement < Dependabot::Javascript::Requirement + end end end end Dependabot::Utils.register_requirement_class( "bun", - Dependabot::Bun::Requirement + Dependabot::Javascript::Bun::Requirement ) diff --git a/javascript/lib/dependabot/bun/update_checker.rb b/javascript/lib/dependabot/bun/update_checker.rb index 0b5adfcf6a..6722e14f3b 100644 --- a/javascript/lib/dependabot/bun/update_checker.rb +++ b/javascript/lib/dependabot/bun/update_checker.rb @@ -2,436 +2,438 @@ # frozen_string_literal: true module Dependabot - module Bun - class UpdateChecker < Dependabot::UpdateCheckers::Base - def up_to_date? - return false if security_update? && - dependency.version && - version_class.correct?(dependency.version) && - vulnerable_versions.any? && - !vulnerable_versions.include?(current_version) - - super - end - - def vulnerable? - super || vulnerable_versions.any? - end - - def latest_version - @latest_version ||= - if git_dependency? - latest_version_for_git_dependency - else - latest_version_details&.fetch(:version) - end - end - - def latest_resolvable_version - return unless latest_version - - @latest_resolvable_version ||= - if dependency.top_level? - version_resolver.latest_resolvable_version - else - # If the dependency is indirect its version is constrained by the - # requirements placed on it by dependencies lower down the tree - subdependency_version_resolver.latest_resolvable_version - end - end - - def lowest_security_fix_version - # This will require a full unlock to update multiple top level ancestors. - return if vulnerability_audit["fix_available"] && vulnerability_audit["top_level_ancestors"].count > 1 + module Javascript + module Bun + class UpdateChecker < Dependabot::UpdateCheckers::Base + def up_to_date? + return false if security_update? && + dependency.version && + version_class.correct?(dependency.version) && + vulnerable_versions.any? && + !vulnerable_versions.include?(current_version) + + super + end - latest_version_finder.lowest_security_fix_version - end + def vulnerable? + super || vulnerable_versions.any? + end - def lowest_resolvable_security_fix_version - raise "Dependency not vulnerable!" unless vulnerable? + def latest_version + @latest_version ||= + if git_dependency? + latest_version_for_git_dependency + else + latest_version_details&.fetch(:version) + end + end - # NOTE: Currently, we don't resolve transitive/sub-dependencies as - # npm/yarn don't provide any control over updating to a specific - # sub-dependency version. + def latest_resolvable_version + return unless latest_version - # Return nil for vulnerable transitive dependencies if there are conflicting dependencies. - # This helps catch errors in such cases. - return nil if !dependency.top_level? && conflicting_dependencies.any? + @latest_resolvable_version ||= + if dependency.top_level? + version_resolver.latest_resolvable_version + else + # If the dependency is indirect its version is constrained by the + # requirements placed on it by dependencies lower down the tree + subdependency_version_resolver.latest_resolvable_version + end + end - # For transitive dependencies without conflicts, return the latest resolvable transitive - # security fix version that does not require unlocking other dependencies. - return latest_resolvable_transitive_security_fix_version_with_no_unlock unless dependency.top_level? + def lowest_security_fix_version + # This will require a full unlock to update multiple top level ancestors. + return if vulnerability_audit["fix_available"] && vulnerability_audit["top_level_ancestors"].count > 1 - # For top-level dependencies, return the lowest security fix version. - # TODO: Consider checking resolvability here in the future. - lowest_security_fix_version - end + latest_version_finder.lowest_security_fix_version + end - def latest_resolvable_version_with_no_unlock - return latest_resolvable_version unless dependency.top_level? + def lowest_resolvable_security_fix_version + raise "Dependency not vulnerable!" unless vulnerable? - return latest_resolvable_version_with_no_unlock_for_git_dependency if git_dependency? + # NOTE: Currently, we don't resolve transitive/sub-dependencies as + # npm/yarn don't provide any control over updating to a specific + # sub-dependency version. - latest_version_finder.latest_version_with_no_unlock - end + # Return nil for vulnerable transitive dependencies if there are conflicting dependencies. + # This helps catch errors in such cases. + return nil if !dependency.top_level? && conflicting_dependencies.any? - def latest_resolvable_previous_version(updated_version) - version_resolver.latest_resolvable_previous_version(updated_version) - end + # For transitive dependencies without conflicts, return the latest resolvable transitive + # security fix version that does not require unlocking other dependencies. + return latest_resolvable_transitive_security_fix_version_with_no_unlock unless dependency.top_level? - def updated_requirements - resolvable_version = - if preferred_resolvable_version.is_a?(version_class) - preferred_resolvable_version.to_s - elsif preferred_resolvable_version.nil? - nil - else - # If the preferred_resolvable_version came back as anything other - # than a version class or `nil` it must be because this is a git - # dependency, for which we don't check resolvability. - latest_version_details&.fetch(:version, nil)&.to_s - end + # For top-level dependencies, return the lowest security fix version. + # TODO: Consider checking resolvability here in the future. + lowest_security_fix_version + end - @updated_requirements ||= - RequirementsUpdater.new( - requirements: dependency.requirements, - updated_source: updated_source, - latest_resolvable_version: resolvable_version, - update_strategy: requirements_update_strategy - ).updated_requirements - end + def latest_resolvable_version_with_no_unlock + return latest_resolvable_version unless dependency.top_level? - def requirements_unlocked_or_can_be? - !requirements_update_strategy.lockfile_only? - end + return latest_resolvable_version_with_no_unlock_for_git_dependency if git_dependency? - def requirements_update_strategy - # If passed in as an option (in the base class) honour that option - return @requirements_update_strategy if @requirements_update_strategy + latest_version_finder.latest_version_with_no_unlock + end - # Otherwise, widen ranges for libraries and bump versions for apps - library? ? RequirementsUpdateStrategy::WidenRanges : RequirementsUpdateStrategy::BumpVersions - end + def latest_resolvable_previous_version(updated_version) + version_resolver.latest_resolvable_previous_version(updated_version) + end - def conflicting_dependencies - conflicts = ConflictingDependencyResolver.new( - dependency_files: dependency_files, - credentials: credentials - ).conflicting_dependencies( - dependency: dependency, - target_version: lowest_security_fix_version - ) - return conflicts unless vulnerability_audit_performed? + def updated_requirements + resolvable_version = + if preferred_resolvable_version.is_a?(version_class) + preferred_resolvable_version.to_s + elsif preferred_resolvable_version.nil? + nil + else + # If the preferred_resolvable_version came back as anything other + # than a version class or `nil` it must be because this is a git + # dependency, for which we don't check resolvability. + latest_version_details&.fetch(:version, nil)&.to_s + end - vulnerable = [vulnerability_audit].select do |hash| - !hash["fix_available"] && hash["explanation"] + @updated_requirements ||= + RequirementsUpdater.new( + requirements: dependency.requirements, + updated_source: updated_source, + latest_resolvable_version: resolvable_version, + update_strategy: requirements_update_strategy + ).updated_requirements end - conflicts + vulnerable - end + def requirements_unlocked_or_can_be? + !requirements_update_strategy.lockfile_only? + end - private + def requirements_update_strategy + # If passed in as an option (in the base class) honour that option + return @requirements_update_strategy if @requirements_update_strategy - def vulnerability_audit_performed? - defined?(@vulnerability_audit) - end + # Otherwise, widen ranges for libraries and bump versions for apps + library? ? RequirementsUpdateStrategy::WidenRanges : RequirementsUpdateStrategy::BumpVersions + end - def vulnerability_audit - @vulnerability_audit ||= - VulnerabilityAuditor.new( + def conflicting_dependencies + conflicts = ConflictingDependencyResolver.new( dependency_files: dependency_files, credentials: credentials - ).audit( + ).conflicting_dependencies( dependency: dependency, - security_advisories: security_advisories + target_version: lowest_security_fix_version ) - end + return conflicts unless vulnerability_audit_performed? - def vulnerable_versions - @vulnerable_versions ||= - begin - all_versions = dependency.all_versions - .filter_map { |v| version_class.new(v) if version_class.correct?(v) } - - all_versions.select do |v| - security_advisories.any? { |advisory| advisory.vulnerable?(v) } - end + vulnerable = [vulnerability_audit].select do |hash| + !hash["fix_available"] && hash["explanation"] end - end - - def latest_version_resolvable_with_full_unlock? - return false unless latest_version - return version_resolver.latest_version_resolvable_with_full_unlock? if dependency.top_level? + conflicts + vulnerable + end - return false unless security_advisories.any? + private - vulnerability_audit["fix_available"] - end + def vulnerability_audit_performed? + defined?(@vulnerability_audit) + end - def updated_dependencies_after_full_unlock - return conflicting_updated_dependencies if security_advisories.any? && vulnerability_audit["fix_available"] + def vulnerability_audit + @vulnerability_audit ||= + VulnerabilityAuditor.new( + dependency_files: dependency_files, + credentials: credentials + ).audit( + dependency: dependency, + security_advisories: security_advisories + ) + end - version_resolver.dependency_updates_from_full_unlock - .map { |update_details| build_updated_dependency(update_details) } - end + def vulnerable_versions + @vulnerable_versions ||= + begin + all_versions = dependency.all_versions + .filter_map { |v| version_class.new(v) if version_class.correct?(v) } - # rubocop:disable Metrics/AbcSize - def conflicting_updated_dependencies - top_level_dependencies = top_level_dependency_lookup - - updated_deps = [] - vulnerability_audit["fix_updates"].each do |update| - dependency_name = update["dependency_name"] - requirements = top_level_dependencies[dependency_name]&.requirements || [] - - updated_deps << build_updated_dependency( - dependency: Dependency.new( - name: dependency_name, - package_manager: "npm_and_yarn", - requirements: requirements - ), - version: update["target_version"], - previous_version: update["current_version"] - ) + all_versions.select do |v| + security_advisories.any? { |advisory| advisory.vulnerable?(v) } + end + end end - # rubocop:enable Metrics/AbcSize - # We don't need to directly update the target dependency if it will - # be updated as a side effect of updating the parent. However, we need - # to include it so it's described in the PR and we'll pass validation - # that this dependency is at a non-vulnerable version. - if updated_deps.none? { |dep| dep.name == dependency.name } - target_version = vulnerability_audit["target_version"] - updated_deps << build_updated_dependency( - dependency: dependency, - version: target_version, - previous_version: dependency.version, - removed: target_version.nil?, - metadata: { information_only: true } # Instruct updater to not directly update this dependency - ) - end + def latest_version_resolvable_with_full_unlock? + return false unless latest_version - # Target dependency should be first in the result to support rebases - updated_deps.select { |dep| dep.name == dependency.name } + - updated_deps.reject { |dep| dep.name == dependency.name } - end + return version_resolver.latest_version_resolvable_with_full_unlock? if dependency.top_level? - def top_level_dependency_lookup - top_level_dependencies = FileParser.new( - dependency_files: dependency_files, - credentials: credentials, - source: nil - ).parse.select(&:top_level?) + return false unless security_advisories.any? - top_level_dependencies.to_h { |dep| [dep.name, dep] } - end + vulnerability_audit["fix_available"] + end - def build_updated_dependency(update_details) - original_dep = update_details.fetch(:dependency) - removed = update_details.fetch(:removed, false) - version = update_details.fetch(:version).to_s unless removed - previous_version = update_details.fetch(:previous_version)&.to_s - metadata = update_details.fetch(:metadata, {}) - - Dependency.new( - name: original_dep.name, - version: version, - requirements: RequirementsUpdater.new( - requirements: original_dep.requirements, - updated_source: original_dep == dependency ? updated_source : original_source(original_dep), - latest_resolvable_version: version, - update_strategy: requirements_update_strategy - ).updated_requirements, - previous_version: previous_version, - previous_requirements: original_dep.requirements, - package_manager: original_dep.package_manager, - removed: removed, - metadata: metadata - ) - end + def updated_dependencies_after_full_unlock + return conflicting_updated_dependencies if security_advisories.any? && vulnerability_audit["fix_available"] - def latest_resolvable_transitive_security_fix_version_with_no_unlock - fix_possible = Dependabot::UpdateCheckers::VersionFilters.filter_vulnerable_versions( - [latest_resolvable_version].compact, - security_advisories - ).any? - return nil unless fix_possible + version_resolver.dependency_updates_from_full_unlock + .map { |update_details| build_updated_dependency(update_details) } + end - latest_resolvable_version - end + # rubocop:disable Metrics/AbcSize + def conflicting_updated_dependencies + top_level_dependencies = top_level_dependency_lookup + + updated_deps = [] + vulnerability_audit["fix_updates"].each do |update| + dependency_name = update["dependency_name"] + requirements = top_level_dependencies[dependency_name]&.requirements || [] + + updated_deps << build_updated_dependency( + dependency: Dependency.new( + name: dependency_name, + package_manager: "npm_and_yarn", + requirements: requirements + ), + version: update["target_version"], + previous_version: update["current_version"] + ) + end + # rubocop:enable Metrics/AbcSize + + # We don't need to directly update the target dependency if it will + # be updated as a side effect of updating the parent. However, we need + # to include it so it's described in the PR and we'll pass validation + # that this dependency is at a non-vulnerable version. + if updated_deps.none? { |dep| dep.name == dependency.name } + target_version = vulnerability_audit["target_version"] + updated_deps << build_updated_dependency( + dependency: dependency, + version: target_version, + previous_version: dependency.version, + removed: target_version.nil?, + metadata: { information_only: true } # Instruct updater to not directly update this dependency + ) + end - def latest_resolvable_version_with_no_unlock_for_git_dependency - reqs = dependency.requirements.filter_map do |r| - next if r.fetch(:requirement).nil? + # Target dependency should be first in the result to support rebases + updated_deps.select { |dep| dep.name == dependency.name } + + updated_deps.reject { |dep| dep.name == dependency.name } + end - requirement_class.requirements_array(r.fetch(:requirement)) + def top_level_dependency_lookup + top_level_dependencies = FileParser.new( + dependency_files: dependency_files, + credentials: credentials, + source: nil + ).parse.select(&:top_level?) + + top_level_dependencies.to_h { |dep| [dep.name, dep] } end - current_version = - if existing_version_is_sha? || - !version_class.correct?(dependency.version) - dependency.version - else - version_class.new(dependency.version) - end + def build_updated_dependency(update_details) + original_dep = update_details.fetch(:dependency) + removed = update_details.fetch(:removed, false) + version = update_details.fetch(:version).to_s unless removed + previous_version = update_details.fetch(:previous_version)&.to_s + metadata = update_details.fetch(:metadata, {}) + + Dependency.new( + name: original_dep.name, + version: version, + requirements: RequirementsUpdater.new( + requirements: original_dep.requirements, + updated_source: original_dep == dependency ? updated_source : original_source(original_dep), + latest_resolvable_version: version, + update_strategy: requirements_update_strategy + ).updated_requirements, + previous_version: previous_version, + previous_requirements: original_dep.requirements, + package_manager: original_dep.package_manager, + removed: removed, + metadata: metadata + ) + end - return current_version if git_commit_checker.pinned? + def latest_resolvable_transitive_security_fix_version_with_no_unlock + fix_possible = Dependabot::UpdateCheckers::VersionFilters.filter_vulnerable_versions( + [latest_resolvable_version].compact, + security_advisories + ).any? + return nil unless fix_possible - # TODO: Really we should get a tag that satisfies the semver req - return current_version if reqs.any? + latest_resolvable_version + end - git_commit_checker.head_commit_for_current_branch - end + def latest_resolvable_version_with_no_unlock_for_git_dependency + reqs = dependency.requirements.filter_map do |r| + next if r.fetch(:requirement).nil? - def latest_version_for_git_dependency - @latest_version_for_git_dependency ||= - if version_class.correct?(dependency.version) - latest_git_version_details[:version] && - version_class.new(latest_git_version_details[:version]) - else - latest_git_version_details[:sha] + requirement_class.requirements_array(r.fetch(:requirement)) end - end - def latest_released_version - @latest_released_version ||= - latest_version_finder.latest_version_from_registry - end + current_version = + if existing_version_is_sha? || + !version_class.correct?(dependency.version) + dependency.version + else + version_class.new(dependency.version) + end - def latest_version_details - @latest_version_details ||= - if git_dependency? - latest_git_version_details - else - { version: latest_released_version } - end - end + return current_version if git_commit_checker.pinned? - def latest_version_finder - @latest_version_finder ||= - LatestVersionFinder.new( - dependency: dependency, - credentials: credentials, - dependency_files: dependency_files, - ignored_versions: ignored_versions, - raise_on_ignored: raise_on_ignored, - security_advisories: security_advisories - ) - end + # TODO: Really we should get a tag that satisfies the semver req + return current_version if reqs.any? - def version_resolver - @version_resolver ||= - VersionResolver.new( - dependency: dependency, - credentials: credentials, - dependency_files: dependency_files, - latest_allowable_version: latest_version, - latest_version_finder: latest_version_finder, - repo_contents_path: repo_contents_path, - dependency_group: dependency_group - ) - end + git_commit_checker.head_commit_for_current_branch + end - def subdependency_version_resolver - @subdependency_version_resolver ||= - SubdependencyVersionResolver.new( - dependency: dependency, - credentials: credentials, - dependency_files: dependency_files, - ignored_versions: ignored_versions, - latest_allowable_version: latest_version, - repo_contents_path: repo_contents_path - ) - end + def latest_version_for_git_dependency + @latest_version_for_git_dependency ||= + if version_class.correct?(dependency.version) + latest_git_version_details[:version] && + version_class.new(latest_git_version_details[:version]) + else + latest_git_version_details[:sha] + end + end - def git_dependency? - git_commit_checker.git_dependency? - end + def latest_released_version + @latest_released_version ||= + latest_version_finder.latest_version_from_registry + end - def latest_git_version_details - semver_req = - dependency.requirements - .find { |req| req.dig(:source, :type) == "git" } - &.fetch(:requirement) - - # If there was a semver requirement provided or the dependency was - # pinned to a version, look for the latest tag - if semver_req || git_commit_checker.pinned_ref_looks_like_version? - latest_tag = git_commit_checker.local_tag_for_latest_version - return { - sha: latest_tag&.fetch(:commit_sha), - version: latest_tag&.fetch(:tag)&.gsub(/^[^\d]*/, "") - } - end - - # Otherwise, if the gem isn't pinned, the latest version is just the - # latest commit for the specified branch. - return { sha: git_commit_checker.head_commit_for_current_branch } unless git_commit_checker.pinned? - - # If the dependency is pinned to a tag that doesn't look like a - # version then there's nothing we can do. - { sha: dependency.version } - end + def latest_version_details + @latest_version_details ||= + if git_dependency? + latest_git_version_details + else + { version: latest_released_version } + end + end - def updated_source - # Never need to update source, unless a git_dependency - return dependency_source_details unless git_dependency? + def latest_version_finder + @latest_version_finder ||= + LatestVersionFinder.new( + dependency: dependency, + credentials: credentials, + dependency_files: dependency_files, + ignored_versions: ignored_versions, + raise_on_ignored: raise_on_ignored, + security_advisories: security_advisories + ) + end - # Update the git tag if updating a pinned version - if git_commit_checker.pinned_ref_looks_like_version? && - !git_commit_checker.local_tag_for_latest_version.nil? - new_tag = git_commit_checker.local_tag_for_latest_version - return dependency_source_details.merge(ref: new_tag.fetch(:tag)) + def version_resolver + @version_resolver ||= + VersionResolver.new( + dependency: dependency, + credentials: credentials, + dependency_files: dependency_files, + latest_allowable_version: latest_version, + latest_version_finder: latest_version_finder, + repo_contents_path: repo_contents_path, + dependency_group: dependency_group + ) end - # Otherwise return the original source - dependency_source_details - end + def subdependency_version_resolver + @subdependency_version_resolver ||= + SubdependencyVersionResolver.new( + dependency: dependency, + credentials: credentials, + dependency_files: dependency_files, + ignored_versions: ignored_versions, + latest_allowable_version: latest_version, + repo_contents_path: repo_contents_path + ) + end - def library? - return true unless dependency.version - return true if dependency_files.any? { |f| f.name == "lerna.json" } + def git_dependency? + git_commit_checker.git_dependency? + end - @library = - LibraryDetector.new( - package_json_file: package_json, - credentials: credentials, - dependency_files: dependency_files - ).library? - end + def latest_git_version_details + semver_req = + dependency.requirements + .find { |req| req.dig(:source, :type) == "git" } + &.fetch(:requirement) + + # If there was a semver requirement provided or the dependency was + # pinned to a version, look for the latest tag + if semver_req || git_commit_checker.pinned_ref_looks_like_version? + latest_tag = git_commit_checker.local_tag_for_latest_version + return { + sha: latest_tag&.fetch(:commit_sha), + version: latest_tag&.fetch(:tag)&.gsub(/^[^\d]*/, "") + } + end - def security_update? - security_advisories.any? - end + # Otherwise, if the gem isn't pinned, the latest version is just the + # latest commit for the specified branch. + return { sha: git_commit_checker.head_commit_for_current_branch } unless git_commit_checker.pinned? - def dependency_source_details - original_source(dependency) - end + # If the dependency is pinned to a tag that doesn't look like a + # version then there's nothing we can do. + { sha: dependency.version } + end + + def updated_source + # Never need to update source, unless a git_dependency + return dependency_source_details unless git_dependency? - def original_source(updated_dependency) - sources = - updated_dependency.requirements.map { |r| r.fetch(:source) }.uniq.compact - .sort_by do |source| - Javascript::UpdateChecker::RegistryFinder.central_registry?(source[:url]) ? 1 : 0 + # Update the git tag if updating a pinned version + if git_commit_checker.pinned_ref_looks_like_version? && + !git_commit_checker.local_tag_for_latest_version.nil? + new_tag = git_commit_checker.local_tag_for_latest_version + return dependency_source_details.merge(ref: new_tag.fetch(:tag)) end - sources.first - end + # Otherwise return the original source + dependency_source_details + end - def package_json - @package_json ||= - dependency_files.find { |f| f.name == "package.json" } - end + def library? + return true unless dependency.version + return true if dependency_files.any? { |f| f.name == "lerna.json" } - def git_commit_checker - @git_commit_checker ||= - GitCommitChecker.new( - dependency: dependency, - credentials: credentials, - ignored_versions: ignored_versions, - raise_on_ignored: raise_on_ignored - ) + @library = + LibraryDetector.new( + package_json_file: package_json, + credentials: credentials, + dependency_files: dependency_files + ).library? + end + + def security_update? + security_advisories.any? + end + + def dependency_source_details + original_source(dependency) + end + + def original_source(updated_dependency) + sources = + updated_dependency.requirements.map { |r| r.fetch(:source) }.uniq.compact + .sort_by do |source| + Javascript::UpdateChecker::RegistryFinder.central_registry?(source[:url]) ? 1 : 0 + end + + sources.first + end + + def package_json + @package_json ||= + dependency_files.find { |f| f.name == "package.json" } + end + + def git_commit_checker + @git_commit_checker ||= + GitCommitChecker.new( + dependency: dependency, + credentials: credentials, + ignored_versions: ignored_versions, + raise_on_ignored: raise_on_ignored + ) + end end end end diff --git a/javascript/lib/dependabot/bun/update_checker/conflicting_dependency_resolver.rb b/javascript/lib/dependabot/bun/update_checker/conflicting_dependency_resolver.rb index 70f88b1d88..d4898cd381 100644 --- a/javascript/lib/dependabot/bun/update_checker/conflicting_dependency_resolver.rb +++ b/javascript/lib/dependabot/bun/update_checker/conflicting_dependency_resolver.rb @@ -1,61 +1,65 @@ # typed: true # frozen_string_literal: true -module Dependabot - module Bun - class UpdateChecker < Dependabot::UpdateCheckers::Base - class ConflictingDependencyResolver - def initialize(dependency_files:, credentials:) - @dependency_files = dependency_files - @credentials = credentials - end +require "dependabot/bun/update_checker/dependency_files_builder" - # Finds any dependencies in the `yarn.lock` or `package-lock.json` that - # have a subdependency on the given dependency that does not satisfly - # the target_version. - # - # @param dependency [Dependabot::Dependency] the dependency to check - # @param target_version [String] the version to check - # @return [Array String}] - # * name [String] the blocking dependencies name - # * version [String] the version of the blocking dependency - # * requirement [String] the requirement on the target_dependency - def conflicting_dependencies(dependency:, target_version:) - SharedHelpers.in_a_temporary_directory do - dependency_files_builder = DependencyFilesBuilder.new( - dependency: dependency, - dependency_files: dependency_files, - credentials: credentials - ) - dependency_files_builder.write_temporary_dependency_files +module Dependabot + module Javascript + module Bun + class UpdateChecker < Dependabot::UpdateCheckers::Base + class ConflictingDependencyResolver + def initialize(dependency_files:, credentials:) + @dependency_files = dependency_files + @credentials = credentials + end - # TODO: Look into using npm/arborist for parsing yarn lockfiles (there's currently partial yarn support) - # - # Prefer the npm conflicting dependency parser if there's both a npm lockfile and a yarn.lock file as the - # npm parser handles edge cases where the package.json is out of sync with the lockfile, something the yarn - # parser doesn't deal with at the moment. - if dependency_files_builder.lockfiles.any? - SharedHelpers.run_helper_subprocess( - command: Javascript::NativeHelpers.helper_path, - function: "npm:findConflictingDependencies", - args: [Dir.pwd, dependency.name, target_version.to_s] - ) - else - SharedHelpers.run_helper_subprocess( - command: Javascript::NativeHelpers.helper_path, - function: "yarn:findConflictingDependencies", - args: [Dir.pwd, dependency.name, target_version.to_s] + # Finds any dependencies in the `yarn.lock` or `package-lock.json` that + # have a subdependency on the given dependency that does not satisfly + # the target_version. + # + # @param dependency [Dependabot::Dependency] the dependency to check + # @param target_version [String] the version to check + # @return [Array String}] + # * name [String] the blocking dependencies name + # * version [String] the version of the blocking dependency + # * requirement [String] the requirement on the target_dependency + def conflicting_dependencies(dependency:, target_version:) + SharedHelpers.in_a_temporary_directory do + dependency_files_builder = DependencyFilesBuilder.new( + dependency: dependency, + dependency_files: dependency_files, + credentials: credentials ) + dependency_files_builder.write_temporary_dependency_files + + # TODO: Look into using npm/arborist for parsing yarn lockfiles (there's currently partial yarn support) + # + # Prefer the npm conflicting dependency parser if there's both a npm lockfile and a yarn.lock file as the + # npm parser handles edge cases where the package.json is out of sync with the lockfile, something the yarn + # parser doesn't deal with at the moment. + if dependency_files_builder.lockfiles.any? + SharedHelpers.run_helper_subprocess( + command: Dependabot::Javascript::NativeHelpers.helper_path, + function: "npm:findConflictingDependencies", + args: [Dir.pwd, dependency.name, target_version.to_s] + ) + else + SharedHelpers.run_helper_subprocess( + command: Dependabot::Javascript::NativeHelpers.helper_path, + function: "yarn:findConflictingDependencies", + args: [Dir.pwd, dependency.name, target_version.to_s] + ) + end end + rescue SharedHelpers::HelperSubprocessFailed + [] end - rescue SharedHelpers::HelperSubprocessFailed - [] - end - private + private - attr_reader :dependency_files - attr_reader :credentials + attr_reader :dependency_files + attr_reader :credentials + end end end end diff --git a/javascript/lib/dependabot/bun/update_checker/dependency_files_builder.rb b/javascript/lib/dependabot/bun/update_checker/dependency_files_builder.rb index e1da839176..bdca88ab83 100644 --- a/javascript/lib/dependabot/bun/update_checker/dependency_files_builder.rb +++ b/javascript/lib/dependabot/bun/update_checker/dependency_files_builder.rb @@ -2,41 +2,43 @@ # frozen_string_literal: true module Dependabot - module Bun - class UpdateChecker - class DependencyFilesBuilder < Javascript::UpdateChecker::DependencyFilesBuilder - extend T::Sig + module Javascript + module Bun + class UpdateChecker + class DependencyFilesBuilder < Dependabot::Javascript::UpdateChecker::DependencyFilesBuilder + extend T::Sig - sig { returns(T::Array[DependencyFile]) } - def bun_locks - @bun_locks ||= T.let( - dependency_files - .select { |f| f.name.end_with?("bun.lock") }, - T.nilable(T::Array[DependencyFile]) - ) - end + sig { returns(T::Array[Dependabot::DependencyFile]) } + def bun_locks + @bun_locks ||= T.let( + dependency_files + .select { |f| f.name.end_with?("bun.lock") }, + T.nilable(T::Array[Dependabot::DependencyFile]) + ) + end - sig { returns(T.nilable(DependencyFile)) } - def root_bun_lock - @root_bun_lock ||= T.let( - dependency_files - .find { |f| f.name == "bun.lock" }, - T.nilable(DependencyFile) - ) - end + sig { returns(T.nilable(Dependabot::DependencyFile)) } + def root_bun_lock + @root_bun_lock ||= T.let( + dependency_files + .find { |f| f.name == "bun.lock" }, + T.nilable(Dependabot::DependencyFile) + ) + end - sig { override.returns(T::Array[DependencyFile]) } - def lockfiles - [*bun_locks] - end + sig { override.returns(T::Array[Dependabot::DependencyFile]) } + def lockfiles + [*bun_locks] + end - private + private - sig { override.returns(T::Array[DependencyFile]) } - def write_lockfiles - [*bun_locks].each do |f| - FileUtils.mkdir_p(Pathname.new(f.name).dirname) - File.write(f.name, f.content) + sig { override.returns(T::Array[Dependabot::DependencyFile]) } + def write_lockfiles + [*bun_locks].each do |f| + FileUtils.mkdir_p(Pathname.new(f.name).dirname) + File.write(f.name, f.content) + end end end end diff --git a/javascript/lib/dependabot/bun/update_checker/latest_version_finder.rb b/javascript/lib/dependabot/bun/update_checker/latest_version_finder.rb index 8401e70171..5bc105a2b9 100644 --- a/javascript/lib/dependabot/bun/update_checker/latest_version_finder.rb +++ b/javascript/lib/dependabot/bun/update_checker/latest_version_finder.rb @@ -4,443 +4,445 @@ require "excon" module Dependabot - module Bun - class UpdateChecker - class LatestVersionFinder - extend T::Sig - - def initialize(dependency:, credentials:, dependency_files:, - ignored_versions:, security_advisories:, - raise_on_ignored: false) - @dependency = dependency - @credentials = credentials - @dependency_files = dependency_files - @ignored_versions = ignored_versions - @raise_on_ignored = raise_on_ignored - @security_advisories = security_advisories - end + module Javascript + module Bun + class UpdateChecker + class LatestVersionFinder + extend T::Sig + + def initialize(dependency:, credentials:, dependency_files:, + ignored_versions:, security_advisories:, + raise_on_ignored: false) + @dependency = dependency + @credentials = credentials + @dependency_files = dependency_files + @ignored_versions = ignored_versions + @raise_on_ignored = raise_on_ignored + @security_advisories = security_advisories + end - def latest_version_from_registry - return unless valid_npm_details? - return version_from_dist_tags if version_from_dist_tags - return if specified_dist_tag_requirement? + def latest_version_from_registry + return unless valid_npm_details? + return version_from_dist_tags if version_from_dist_tags + return if specified_dist_tag_requirement? - possible_versions.find { |v| !yanked?(v) } - rescue Excon::Error::Socket, Excon::Error::Timeout, RegistryError - raise if dependency_registry == "registry.npmjs.org" - # Custom registries can be flaky. We don't want to make that - # our problem, so we quietly return `nil` here. - end + possible_versions.find { |v| !yanked?(v) } + rescue Excon::Error::Socket, Excon::Error::Timeout, RegistryError + raise if dependency_registry == "registry.npmjs.org" + # Custom registries can be flaky. We don't want to make that + # our problem, so we quietly return `nil` here. + end - def latest_version_with_no_unlock - return unless valid_npm_details? - return version_from_dist_tags if specified_dist_tag_requirement? + def latest_version_with_no_unlock + return unless valid_npm_details? + return version_from_dist_tags if specified_dist_tag_requirement? - in_range_versions = filter_out_of_range_versions(possible_versions) - in_range_versions.find { |version| !yanked?(version) } - rescue Excon::Error::Socket, Excon::Error::Timeout - raise if dependency_registry == "registry.npmjs.org" - # Sometimes custom registries are flaky. We don't want to make that - # our problem, so we quietly return `nil` here. - end + in_range_versions = filter_out_of_range_versions(possible_versions) + in_range_versions.find { |version| !yanked?(version) } + rescue Excon::Error::Socket, Excon::Error::Timeout + raise if dependency_registry == "registry.npmjs.org" + # Sometimes custom registries are flaky. We don't want to make that + # our problem, so we quietly return `nil` here. + end - def lowest_security_fix_version - return unless valid_npm_details? + def lowest_security_fix_version + return unless valid_npm_details? - secure_versions = - if specified_dist_tag_requirement? - [version_from_dist_tags].compact - else - possible_versions(filter_ignored: false) - end + secure_versions = + if specified_dist_tag_requirement? + [version_from_dist_tags].compact + else + possible_versions(filter_ignored: false) + end - secure_versions = Dependabot::UpdateCheckers::VersionFilters.filter_vulnerable_versions(secure_versions, - security_advisories) - secure_versions = filter_ignored_versions(secure_versions) - secure_versions = filter_lower_versions(secure_versions) + secure_versions = Dependabot::UpdateCheckers::VersionFilters.filter_vulnerable_versions(secure_versions, + security_advisories) + secure_versions = filter_ignored_versions(secure_versions) + secure_versions = filter_lower_versions(secure_versions) - secure_versions.reverse.find { |version| !yanked?(version) } - rescue Excon::Error::Socket, Excon::Error::Timeout - raise if dependency_registry == "registry.npmjs.org" - # Sometimes custom registries are flaky. We don't want to make that - # our problem, so we quietly return `nil` here. - end + secure_versions.reverse.find { |version| !yanked?(version) } + rescue Excon::Error::Socket, Excon::Error::Timeout + raise if dependency_registry == "registry.npmjs.org" + # Sometimes custom registries are flaky. We don't want to make that + # our problem, so we quietly return `nil` here. + end - def possible_previous_versions_with_details - @possible_previous_versions_with_details ||= npm_details.fetch("versions", {}) - .transform_keys { |k| version_class.new(k) } - .reject do |v, _| - v.prerelease? && !related_to_current_pre?(v) - end - .sort_by(&:first).reverse - end + def possible_previous_versions_with_details + @possible_previous_versions_with_details ||= npm_details.fetch("versions", {}) + .transform_keys { |k| version_class.new(k) } + .reject do |v, _| + v.prerelease? && !related_to_current_pre?(v) + end + .sort_by(&:first).reverse + end - def possible_versions_with_details(filter_ignored: true) - versions = possible_previous_versions_with_details - .reject { |_, details| details["deprecated"] } + def possible_versions_with_details(filter_ignored: true) + versions = possible_previous_versions_with_details + .reject { |_, details| details["deprecated"] } - return filter_ignored_versions(versions) if filter_ignored + return filter_ignored_versions(versions) if filter_ignored - versions - end + versions + end - def possible_versions(filter_ignored: true) - possible_versions_with_details(filter_ignored: filter_ignored) - .map(&:first) - end + def possible_versions(filter_ignored: true) + possible_versions_with_details(filter_ignored: filter_ignored) + .map(&:first) + end - private + private - attr_reader :dependency - attr_reader :credentials - attr_reader :dependency_files - attr_reader :ignored_versions - attr_reader :security_advisories + attr_reader :dependency + attr_reader :credentials + attr_reader :dependency_files + attr_reader :ignored_versions + attr_reader :security_advisories - def valid_npm_details? - !npm_details&.fetch("dist-tags", nil).nil? - end - - sig { params(versions_array: T::Array[T.untyped]).returns(T::Array[T.untyped]) } - def filter_ignored_versions(versions_array) - filtered = versions_array.reject do |v, _| - ignore_requirements.any? { |r| r.satisfied_by?(v) } + def valid_npm_details? + !npm_details&.fetch("dist-tags", nil).nil? end - if @raise_on_ignored && filter_lower_versions(filtered).empty? && filter_lower_versions(versions_array).any? - raise AllVersionsIgnored - end + sig { params(versions_array: T::Array[T.untyped]).returns(T::Array[T.untyped]) } + def filter_ignored_versions(versions_array) + filtered = versions_array.reject do |v, _| + ignore_requirements.any? { |r| r.satisfied_by?(v) } + end - if versions_array.count > filtered.count - diff = versions_array.count - filtered.count - Dependabot.logger.info("Filtered out #{diff} ignored versions") - end + if @raise_on_ignored && filter_lower_versions(filtered).empty? && filter_lower_versions(versions_array).any? + raise AllVersionsIgnored + end - filtered - end + if versions_array.count > filtered.count + diff = versions_array.count - filtered.count + Dependabot.logger.info("Filtered out #{diff} ignored versions") + end - sig { params(versions_array: T::Array[T.untyped]).returns(T::Array[T.untyped]) } - def filter_out_of_range_versions(versions_array) - reqs = dependency.requirements.filter_map do |r| - NpmAndYarn::Requirement.requirements_array(r.fetch(:requirement)) + filtered end - versions_array - .select { |v| reqs.all? { |r| r.any? { |o| o.satisfied_by?(v) } } } - end + sig { params(versions_array: T::Array[T.untyped]).returns(T::Array[T.untyped]) } + def filter_out_of_range_versions(versions_array) + reqs = dependency.requirements.filter_map do |r| + NpmAndYarn::Requirement.requirements_array(r.fetch(:requirement)) + end - sig { params(versions_array: T::Array[T.untyped]).returns(T::Array[T.untyped]) } - def filter_lower_versions(versions_array) - return versions_array unless dependency.numeric_version + versions_array + .select { |v| reqs.all? { |r| r.any? { |o| o.satisfied_by?(v) } } } + end - versions_array - .select { |version, _| version > dependency.numeric_version } - end + sig { params(versions_array: T::Array[T.untyped]).returns(T::Array[T.untyped]) } + def filter_lower_versions(versions_array) + return versions_array unless dependency.numeric_version - def version_from_dist_tags - dist_tags = npm_details["dist-tags"].keys + versions_array + .select { |version, _| version > dependency.numeric_version } + end - # Check if a dist tag was specified as a requirement. If it was, and - # it exists, use it. - dist_tag_req = dependency.requirements - .find { |r| dist_tags.include?(r[:requirement]) } - &.fetch(:requirement) + def version_from_dist_tags + dist_tags = npm_details["dist-tags"].keys - if dist_tag_req - tag_vers = - version_class.new(npm_details["dist-tags"][dist_tag_req]) - return tag_vers unless yanked?(tag_vers) - end + # Check if a dist tag was specified as a requirement. If it was, and + # it exists, use it. + dist_tag_req = dependency.requirements + .find { |r| dist_tags.include?(r[:requirement]) } + &.fetch(:requirement) - # Use the latest dist tag unless there's a reason not to - return nil unless npm_details["dist-tags"]["latest"] + if dist_tag_req + tag_vers = + version_class.new(npm_details["dist-tags"][dist_tag_req]) + return tag_vers unless yanked?(tag_vers) + end - latest = version_class.new(npm_details["dist-tags"]["latest"]) + # Use the latest dist tag unless there's a reason not to + return nil unless npm_details["dist-tags"]["latest"] - wants_latest_dist_tag?(latest) ? latest : nil - end + latest = version_class.new(npm_details["dist-tags"]["latest"]) - def related_to_current_pre?(version) - current_version = dependency.numeric_version - if current_version&.prerelease? && - current_version&.release == version.release - return true + wants_latest_dist_tag?(latest) ? latest : nil end - dependency.requirements.any? do |req| - next unless req[:requirement]&.match?(/\d-[A-Za-z]/) + def related_to_current_pre?(version) + current_version = dependency.numeric_version + if current_version&.prerelease? && + current_version&.release == version.release + return true + end - NpmAndYarn::Requirement - .requirements_array(req.fetch(:requirement)) - .any? do |r| - r.requirements.any? { |a| a.last.release == version.release } - end - rescue Gem::Requirement::BadRequirementError - false + dependency.requirements.any? do |req| + next unless req[:requirement]&.match?(/\d-[A-Za-z]/) + + NpmAndYarn::Requirement + .requirements_array(req.fetch(:requirement)) + .any? do |r| + r.requirements.any? { |a| a.last.release == version.release } + end + rescue Gem::Requirement::BadRequirementError + false + end end - end - def specified_dist_tag_requirement? - dependency.requirements.any? do |req| - next false if req[:requirement].nil? - next false unless req[:requirement].match?(/^[A-Za-z]/) + def specified_dist_tag_requirement? + dependency.requirements.any? do |req| + next false if req[:requirement].nil? + next false unless req[:requirement].match?(/^[A-Za-z]/) - !req[:requirement].match?(/^v\d/i) + !req[:requirement].match?(/^v\d/i) + end end - end - def wants_latest_dist_tag?(latest_version) - ver = latest_version - return false if related_to_current_pre?(ver) ^ ver.prerelease? - return false if current_version_greater_than?(ver) - return false if current_requirement_greater_than?(ver) - return false if ignore_requirements.any? { |r| r.satisfied_by?(ver) } - return false if yanked?(ver) + def wants_latest_dist_tag?(latest_version) + ver = latest_version + return false if related_to_current_pre?(ver) ^ ver.prerelease? + return false if current_version_greater_than?(ver) + return false if current_requirement_greater_than?(ver) + return false if ignore_requirements.any? { |r| r.satisfied_by?(ver) } + return false if yanked?(ver) - true - end + true + end - def current_version_greater_than?(version) - return false unless dependency.numeric_version + def current_version_greater_than?(version) + return false unless dependency.numeric_version - dependency.numeric_version > version - end + dependency.numeric_version > version + end - def current_requirement_greater_than?(version) - dependency.requirements.any? do |req| - next false unless req[:requirement] + def current_requirement_greater_than?(version) + dependency.requirements.any? do |req| + next false unless req[:requirement] - req_version = req[:requirement].sub(/^\^|~|>=?/, "") - next false unless version_class.correct?(req_version) + req_version = req[:requirement].sub(/^\^|~|>=?/, "") + next false unless version_class.correct?(req_version) - version_class.new(req_version) > version + version_class.new(req_version) > version + end end - end - - def yanked?(version) - @yanked ||= {} - return @yanked[version] if @yanked.key?(version) - @yanked[version] = - begin - if dependency_registry == "registry.npmjs.org" - status = Dependabot::RegistryClient.head( - url: registry_finder.tarball_url(version), - headers: registry_auth_headers - ).status - else - status = Dependabot::RegistryClient.get( - url: dependency_url + "/#{version}", - headers: registry_auth_headers - ).status + def yanked?(version) + @yanked ||= {} + return @yanked[version] if @yanked.key?(version) - if status == 404 - # Some registries don't handle escaped package names properly + @yanked[version] = + begin + if dependency_registry == "registry.npmjs.org" + status = Dependabot::RegistryClient.head( + url: registry_finder.tarball_url(version), + headers: registry_auth_headers + ).status + else status = Dependabot::RegistryClient.get( - url: dependency_url.gsub("%2F", "/") + "/#{version}", + url: dependency_url + "/#{version}", headers: registry_auth_headers ).status + + if status == 404 + # Some registries don't handle escaped package names properly + status = Dependabot::RegistryClient.get( + url: dependency_url.gsub("%2F", "/") + "/#{version}", + headers: registry_auth_headers + ).status + end end - end - version_not_found = status == 404 - version_not_found && version_endpoint_working? - rescue Excon::Error::Timeout, Excon::Error::Socket - # Give the benefit of the doubt if the registry is playing up - false - end - end + version_not_found = status == 404 + version_not_found && version_endpoint_working? + rescue Excon::Error::Timeout, Excon::Error::Socket + # Give the benefit of the doubt if the registry is playing up + false + end + end - def version_endpoint_working? - return true if dependency_registry == "registry.npmjs.org" + def version_endpoint_working? + return true if dependency_registry == "registry.npmjs.org" - return @version_endpoint_working if defined?(@version_endpoint_working) + return @version_endpoint_working if defined?(@version_endpoint_working) - @version_endpoint_working = - begin - Dependabot::RegistryClient.get( - url: dependency_url + "/latest", - headers: registry_auth_headers - ).status < 400 - rescue Excon::Error::Timeout, Excon::Error::Socket - # Give the benefit of the doubt if the registry is playing up - true - end - end + @version_endpoint_working = + begin + Dependabot::RegistryClient.get( + url: dependency_url + "/latest", + headers: registry_auth_headers + ).status < 400 + rescue Excon::Error::Timeout, Excon::Error::Socket + # Give the benefit of the doubt if the registry is playing up + true + end + end - def npm_details - return @npm_details if defined?(@npm_details) + def npm_details + return @npm_details if defined?(@npm_details) - @npm_details = fetch_npm_details - end + @npm_details = fetch_npm_details + end - def fetch_npm_details - npm_response = fetch_npm_response + def fetch_npm_details + npm_response = fetch_npm_response + + check_npm_response(npm_response) + JSON.parse(npm_response.body) + rescue JSON::ParserError, + Excon::Error::Timeout, + Excon::Error::Socket, + RegistryError => e + if git_dependency? + nil + else + raise_npm_details_error(e) + end + end - check_npm_response(npm_response) - JSON.parse(npm_response.body) - rescue JSON::ParserError, - Excon::Error::Timeout, - Excon::Error::Socket, - RegistryError => e - if git_dependency? - nil - else - raise_npm_details_error(e) + def fetch_npm_response + response = Dependabot::RegistryClient.get( + url: dependency_url, + headers: registry_auth_headers + ) + return response unless response.status == 500 + return response unless registry_auth_headers["Authorization"] + + auth = registry_auth_headers["Authorization"] + return response unless auth.start_with?("Basic") + + decoded_token = Base64.decode64(auth.gsub("Basic ", "")) + return unless decoded_token.include?(":") + + username, password = decoded_token.split(":") + Dependabot::RegistryClient.get( + url: dependency_url, + options: { + user: username, + password: password + } + ) + rescue URI::InvalidURIError => e + raise DependencyFileNotResolvable, e.message end - end - def fetch_npm_response - response = Dependabot::RegistryClient.get( - url: dependency_url, - headers: registry_auth_headers - ) - return response unless response.status == 500 - return response unless registry_auth_headers["Authorization"] - - auth = registry_auth_headers["Authorization"] - return response unless auth.start_with?("Basic") - - decoded_token = Base64.decode64(auth.gsub("Basic ", "")) - return unless decoded_token.include?(":") - - username, password = decoded_token.split(":") - Dependabot::RegistryClient.get( - url: dependency_url, - options: { - user: username, - password: password - } - ) - rescue URI::InvalidURIError => e - raise DependencyFileNotResolvable, e.message - end + def check_npm_response(npm_response) + return if git_dependency? - def check_npm_response(npm_response) - return if git_dependency? + if private_dependency_not_reachable?(npm_response) + raise PrivateSourceAuthenticationFailure, dependency_registry + end - if private_dependency_not_reachable?(npm_response) - raise PrivateSourceAuthenticationFailure, dependency_registry - end + # handles scenario when private registry returns a server error 5xx + if private_dependency_server_error?(npm_response) + msg = "Server error #{npm_response.status} returned while accessing registry" \ + " #{dependency_registry}." + raise DependencyFileNotResolvable, msg + end - # handles scenario when private registry returns a server error 5xx - if private_dependency_server_error?(npm_response) - msg = "Server error #{npm_response.status} returned while accessing registry" \ - " #{dependency_registry}." - raise DependencyFileNotResolvable, msg - end + status = npm_response.status - status = npm_response.status + # handles issue when status 200 is returned from registry but with an invalid JSON object + if status.to_s.start_with?("2") && response_invalid_json?(npm_response) + msg = "Invalid JSON object returned from registry #{dependency_registry}." + Dependabot.logger.warn("#{msg} Response body (truncated) : #{npm_response.body[0..500]}...") + raise DependencyFileNotResolvable, msg + end - # handles issue when status 200 is returned from registry but with an invalid JSON object - if status.to_s.start_with?("2") && response_invalid_json?(npm_response) - msg = "Invalid JSON object returned from registry #{dependency_registry}." - Dependabot.logger.warn("#{msg} Response body (truncated) : #{npm_response.body[0..500]}...") - raise DependencyFileNotResolvable, msg - end + return if status.to_s.start_with?("2") - return if status.to_s.start_with?("2") + # Ignore 404s from the registry for updates where a lockfile doesn't + # need to be generated. The 404 won't cause problems later. + return if status == 404 && dependency.version.nil? - # Ignore 404s from the registry for updates where a lockfile doesn't - # need to be generated. The 404 won't cause problems later. - return if status == 404 && dependency.version.nil? + msg = "Got #{status} response with body #{npm_response.body}" + raise RegistryError.new(status, msg) + end - msg = "Got #{status} response with body #{npm_response.body}" - raise RegistryError.new(status, msg) - end + def raise_npm_details_error(error) + raise if dependency_registry == "registry.npmjs.org" + raise unless error.is_a?(Excon::Error::Timeout) - def raise_npm_details_error(error) - raise if dependency_registry == "registry.npmjs.org" - raise unless error.is_a?(Excon::Error::Timeout) + raise PrivateSourceTimedOut, dependency_registry + end - raise PrivateSourceTimedOut, dependency_registry - end + def private_dependency_not_reachable?(npm_response) + return true if npm_response.body.start_with?(/user ".*?" is not a /) + return false unless [401, 402, 403, 404].include?(npm_response.status) - def private_dependency_not_reachable?(npm_response) - return true if npm_response.body.start_with?(/user ".*?" is not a /) - return false unless [401, 402, 403, 404].include?(npm_response.status) + # Check whether this dependency is (likely to be) private + if dependency_registry == "registry.npmjs.org" + return false unless dependency.name.start_with?("@") - # Check whether this dependency is (likely to be) private - if dependency_registry == "registry.npmjs.org" - return false unless dependency.name.start_with?("@") + web_response = Dependabot::RegistryClient.get(url: "https://www.npmjs.com/package/#{dependency.name}") + # NOTE: returns 429 when the login page is rate limited + return web_response.body.include?("Forgot password?") || + web_response.status == 429 + end - web_response = Dependabot::RegistryClient.get(url: "https://www.npmjs.com/package/#{dependency.name}") - # NOTE: returns 429 when the login page is rate limited - return web_response.body.include?("Forgot password?") || - web_response.status == 429 + true end - true - end - - def private_dependency_server_error?(npm_response) - if [500, 501, 502, 503].include?(npm_response.status) - Dependabot.logger.warn("#{dependency_registry} returned code #{npm_response.status} with " \ - "body #{npm_response.body}.") - return true + def private_dependency_server_error?(npm_response) + if [500, 501, 502, 503].include?(npm_response.status) + Dependabot.logger.warn("#{dependency_registry} returned code #{npm_response.status} with " \ + "body #{npm_response.body}.") + return true + end + false end - false - end - def response_invalid_json?(npm_response) - result = JSON.parse(npm_response.body) - result.is_a?(Hash) || result.is_a?(Array) - false - rescue JSON::ParserError, TypeError - true - end + def response_invalid_json?(npm_response) + result = JSON.parse(npm_response.body) + result.is_a?(Hash) || result.is_a?(Array) + false + rescue JSON::ParserError, TypeError + true + end - def dependency_url - registry_finder.dependency_url - end + def dependency_url + registry_finder.dependency_url + end - def dependency_registry - registry_finder.registry - end + def dependency_registry + registry_finder.registry + end - def registry_auth_headers - registry_finder.auth_headers - end + def registry_auth_headers + registry_finder.auth_headers + end - def registry_finder - @registry_finder ||= Javascript::UpdateChecker::RegistryFinder.new( - dependency: dependency, - credentials: credentials, - rc_file: npmrc_file - ) - end + def registry_finder + @registry_finder ||= Javascript::UpdateChecker::RegistryFinder.new( + dependency: dependency, + credentials: credentials, + rc_file: npmrc_file + ) + end - def ignore_requirements - ignored_versions.flat_map { |req| requirement_class.requirements_array(req) } - end + def ignore_requirements + ignored_versions.flat_map { |req| requirement_class.requirements_array(req) } + end - def version_class - dependency.version_class - end + def version_class + dependency.version_class + end - def requirement_class - dependency.requirement_class - end + def requirement_class + dependency.requirement_class + end - def npmrc_file - dependency_files.find { |f| f.name.end_with?(".npmrc") } - end + def npmrc_file + dependency_files.find { |f| f.name.end_with?(".npmrc") } + end - def yarnrc_file - dependency_files.find { |f| f.name.end_with?(".yarnrc") } - end + def yarnrc_file + dependency_files.find { |f| f.name.end_with?(".yarnrc") } + end - def yarnrc_yml_file - dependency_files.find { |f| f.name.end_with?(".yarnrc.yml") } - end + def yarnrc_yml_file + dependency_files.find { |f| f.name.end_with?(".yarnrc.yml") } + end - # TODO: Remove need for me - def git_dependency? - # ignored_version/raise_on_ignored are irrelevant. - GitCommitChecker.new( - dependency: dependency, - credentials: credentials - ).git_dependency? + # TODO: Remove need for me + def git_dependency? + # ignored_version/raise_on_ignored are irrelevant. + GitCommitChecker.new( + dependency: dependency, + credentials: credentials + ).git_dependency? + end end end end diff --git a/javascript/lib/dependabot/bun/update_checker/library_detector.rb b/javascript/lib/dependabot/bun/update_checker/library_detector.rb index f7312c2b86..7428cc613c 100644 --- a/javascript/lib/dependabot/bun/update_checker/library_detector.rb +++ b/javascript/lib/dependabot/bun/update_checker/library_detector.rb @@ -4,69 +4,71 @@ require "excon" module Dependabot - module Bun - class UpdateChecker - class LibraryDetector - def initialize(package_json_file:, credentials:, dependency_files:) - @package_json_file = package_json_file - @credentials = credentials - @dependency_files = dependency_files - end + module Javascript + module Bun + class UpdateChecker + class LibraryDetector + def initialize(package_json_file:, credentials:, dependency_files:) + @package_json_file = package_json_file + @credentials = credentials + @dependency_files = dependency_files + end - def library? - return false unless package_json_may_be_for_library? + def library? + return false unless package_json_may_be_for_library? - npm_response_matches_package_json? - end + npm_response_matches_package_json? + end - private + private - attr_reader :package_json_file - attr_reader :credentials - attr_reader :dependency_files + attr_reader :package_json_file + attr_reader :credentials + attr_reader :dependency_files - def package_json_may_be_for_library? - return false unless project_name - return false if project_name.match?(/\{\{.*\}\}/) - return false unless parsed_package_json["version"] - return false if parsed_package_json["private"] + def package_json_may_be_for_library? + return false unless project_name + return false if project_name.match?(/\{\{.*\}\}/) + return false unless parsed_package_json["version"] + return false if parsed_package_json["private"] - true - end + true + end - def npm_response_matches_package_json? - project_description = parsed_package_json["description"] - return false unless project_description + def npm_response_matches_package_json? + project_description = parsed_package_json["description"] + return false unless project_description - # Check if the project is listed on npm. If it is, it's a library - url = "#{registry.chomp('/')}/#{escaped_project_name}" - @project_npm_response ||= Dependabot::RegistryClient.get(url: url) - return false unless @project_npm_response.status == 200 + # Check if the project is listed on npm. If it is, it's a library + url = "#{registry.chomp('/')}/#{escaped_project_name}" + @project_npm_response ||= Dependabot::RegistryClient.get(url: url) + return false unless @project_npm_response.status == 200 - @project_npm_response.body.dup.force_encoding("UTF-8").encode - .include?(project_description) - rescue Excon::Error::Socket, Excon::Error::Timeout, URI::InvalidURIError - false - end + @project_npm_response.body.dup.force_encoding("UTF-8").encode + .include?(project_description) + rescue Excon::Error::Socket, Excon::Error::Timeout, URI::InvalidURIError + false + end - def project_name - parsed_package_json.fetch("name", nil) - end + def project_name + parsed_package_json.fetch("name", nil) + end - def escaped_project_name - project_name&.gsub("/", "%2F") - end + def escaped_project_name + project_name&.gsub("/", "%2F") + end - def parsed_package_json - @parsed_package_json ||= JSON.parse(package_json_file.content) - end + def parsed_package_json + @parsed_package_json ||= JSON.parse(package_json_file.content) + end - def registry - Javascript::UpdateChecker::RegistryFinder.new( - dependency: nil, - credentials: credentials, - rc_file: dependency_files.find { |f| f.name.end_with?(".npmrc") } - ).registry_from_rc(project_name) + def registry + Javascript::UpdateChecker::RegistryFinder.new( + dependency: nil, + credentials: credentials, + rc_file: dependency_files.find { |f| f.name.end_with?(".npmrc") } + ).registry_from_rc(project_name) + end end end end diff --git a/javascript/lib/dependabot/bun/update_checker/requirements_updater.rb b/javascript/lib/dependabot/bun/update_checker/requirements_updater.rb index f0ce0190cc..1a14487c1e 100644 --- a/javascript/lib/dependabot/bun/update_checker/requirements_updater.rb +++ b/javascript/lib/dependabot/bun/update_checker/requirements_updater.rb @@ -7,193 +7,195 @@ ################################################################################ module Dependabot - module Bun - class UpdateChecker - class RequirementsUpdater - VERSION_REGEX = /[0-9]+(?:\.[A-Za-z0-9\-_]+)*/ - SEPARATOR = /(?<=[a-zA-Z0-9*])[\s|]+(?![\s|-])/ - ALLOWED_UPDATE_STRATEGIES = T.let( - [ - RequirementsUpdateStrategy::LockfileOnly, - RequirementsUpdateStrategy::WidenRanges, - RequirementsUpdateStrategy::BumpVersions, - RequirementsUpdateStrategy::BumpVersionsIfNecessary - ].freeze, - T::Array[Dependabot::RequirementsUpdateStrategy] - ) - - def initialize(requirements:, updated_source:, update_strategy:, - latest_resolvable_version:) - @requirements = requirements - @updated_source = updated_source - @update_strategy = update_strategy - - check_update_strategy - - return unless latest_resolvable_version - - @latest_resolvable_version = - version_class.new(latest_resolvable_version) - end + module Javascript + module Bun + class UpdateChecker + class RequirementsUpdater + VERSION_REGEX = /[0-9]+(?:\.[A-Za-z0-9\-_]+)*/ + SEPARATOR = /(?<=[a-zA-Z0-9*])[\s|]+(?![\s|-])/ + ALLOWED_UPDATE_STRATEGIES = T.let( + [ + RequirementsUpdateStrategy::LockfileOnly, + RequirementsUpdateStrategy::WidenRanges, + RequirementsUpdateStrategy::BumpVersions, + RequirementsUpdateStrategy::BumpVersionsIfNecessary + ].freeze, + T::Array[Dependabot::RequirementsUpdateStrategy] + ) + + def initialize(requirements:, updated_source:, update_strategy:, + latest_resolvable_version:) + @requirements = requirements + @updated_source = updated_source + @update_strategy = update_strategy + + check_update_strategy + + return unless latest_resolvable_version + + @latest_resolvable_version = + version_class.new(latest_resolvable_version) + end - def updated_requirements - return requirements if update_strategy.lockfile_only? - - requirements.map do |req| - req = req.merge(source: updated_source) - next req unless latest_resolvable_version - next initial_req_after_source_change(req) unless req[:requirement] - next req if req[:requirement].match?(/^([A-Za-uw-z]|v[^\d])/) - - case update_strategy - when RequirementsUpdateStrategy::WidenRanges then widen_requirement(req) - when RequirementsUpdateStrategy::BumpVersions then update_version_requirement(req) - when RequirementsUpdateStrategy::BumpVersionsIfNecessary - update_version_requirement_if_needed(req) - else raise "Unexpected update strategy: #{update_strategy}" + def updated_requirements + return requirements if update_strategy.lockfile_only? + + requirements.map do |req| + req = req.merge(source: updated_source) + next req unless latest_resolvable_version + next initial_req_after_source_change(req) unless req[:requirement] + next req if req[:requirement].match?(/^([A-Za-uw-z]|v[^\d])/) + + case update_strategy + when RequirementsUpdateStrategy::WidenRanges then widen_requirement(req) + when RequirementsUpdateStrategy::BumpVersions then update_version_requirement(req) + when RequirementsUpdateStrategy::BumpVersionsIfNecessary + update_version_requirement_if_needed(req) + else raise "Unexpected update strategy: #{update_strategy}" + end end end - end - private + private - attr_reader :requirements - attr_reader :updated_source - attr_reader :update_strategy - attr_reader :latest_resolvable_version + attr_reader :requirements + attr_reader :updated_source + attr_reader :update_strategy + attr_reader :latest_resolvable_version - def check_update_strategy - return if ALLOWED_UPDATE_STRATEGIES.include?(update_strategy) + def check_update_strategy + return if ALLOWED_UPDATE_STRATEGIES.include?(update_strategy) - raise "Unknown update strategy: #{update_strategy}" - end + raise "Unknown update strategy: #{update_strategy}" + end - def updating_from_git_to_npm? - return false unless updated_source.nil? + def updating_from_git_to_npm? + return false unless updated_source.nil? - original_source = requirements.filter_map { |r| r[:source] }.first - original_source&.fetch(:type) == "git" - end + original_source = requirements.filter_map { |r| r[:source] }.first + original_source&.fetch(:type) == "git" + end - def initial_req_after_source_change(req) - return req unless updating_from_git_to_npm? - return req unless req[:requirement].nil? + def initial_req_after_source_change(req) + return req unless updating_from_git_to_npm? + return req unless req[:requirement].nil? - req.merge(requirement: "^#{latest_resolvable_version}") - end + req.merge(requirement: "^#{latest_resolvable_version}") + end - def update_version_requirement(req) - current_requirement = req[:requirement] + def update_version_requirement(req) + current_requirement = req[:requirement] - if current_requirement.match?(/(<|-\s)/i) - ruby_req = ruby_requirements(current_requirement).first - return req if ruby_req.satisfied_by?(latest_resolvable_version) + if current_requirement.match?(/(<|-\s)/i) + ruby_req = ruby_requirements(current_requirement).first + return req if ruby_req.satisfied_by?(latest_resolvable_version) - updated_req = update_range_requirement(current_requirement) - return req.merge(requirement: updated_req) - end + updated_req = update_range_requirement(current_requirement) + return req.merge(requirement: updated_req) + end - reqs = current_requirement.strip.split(SEPARATOR).map(&:strip) - req.merge(requirement: update_version_string(reqs.first)) - end + reqs = current_requirement.strip.split(SEPARATOR).map(&:strip) + req.merge(requirement: update_version_string(reqs.first)) + end - def update_version_requirement_if_needed(req) - current_requirement = req[:requirement] - version = latest_resolvable_version - return req if current_requirement.strip == "" + def update_version_requirement_if_needed(req) + current_requirement = req[:requirement] + version = latest_resolvable_version + return req if current_requirement.strip == "" - ruby_reqs = ruby_requirements(current_requirement) - return req if ruby_reqs.any? { |r| r.satisfied_by?(version) } + ruby_reqs = ruby_requirements(current_requirement) + return req if ruby_reqs.any? { |r| r.satisfied_by?(version) } - update_version_requirement(req) - end + update_version_requirement(req) + end - def widen_requirement(req) - current_requirement = req[:requirement] - version = latest_resolvable_version - return req if current_requirement.strip == "" + def widen_requirement(req) + current_requirement = req[:requirement] + version = latest_resolvable_version + return req if current_requirement.strip == "" - ruby_reqs = ruby_requirements(current_requirement) - return req if ruby_reqs.any? { |r| r.satisfied_by?(version) } + ruby_reqs = ruby_requirements(current_requirement) + return req if ruby_reqs.any? { |r| r.satisfied_by?(version) } - reqs = current_requirement.strip.split(SEPARATOR).map(&:strip) + reqs = current_requirement.strip.split(SEPARATOR).map(&:strip) - updated_requirement = - if reqs.any? { |r| r.match?(/(<|-\s)/i) } - update_range_requirement(current_requirement) - elsif current_requirement.strip.split(SEPARATOR).count == 1 - update_version_string(current_requirement) - else - current_requirement - end + updated_requirement = + if reqs.any? { |r| r.match?(/(<|-\s)/i) } + update_range_requirement(current_requirement) + elsif current_requirement.strip.split(SEPARATOR).count == 1 + update_version_string(current_requirement) + else + current_requirement + end - req.merge(requirement: updated_requirement) - end + req.merge(requirement: updated_requirement) + end - def ruby_requirements(requirement_string) - Javascript::Requirement - .requirements_array(requirement_string) - end + def ruby_requirements(requirement_string) + Javascript::Requirement + .requirements_array(requirement_string) + end - def update_range_requirement(req_string) - range_requirements = - req_string.split(SEPARATOR).select { |r| r.match?(/<|(\s+-\s+)/) } - - if range_requirements.count == 1 - range_requirement = range_requirements.first - versions = range_requirement.scan(VERSION_REGEX) - upper_bound = versions.map { |v| version_class.new(v) }.max - new_upper_bound = update_greatest_version( - upper_bound, - latest_resolvable_version - ) - - req_string.sub( - upper_bound.to_s, - new_upper_bound.to_s - ) - else - req_string + " || ^#{latest_resolvable_version}" + def update_range_requirement(req_string) + range_requirements = + req_string.split(SEPARATOR).select { |r| r.match?(/<|(\s+-\s+)/) } + + if range_requirements.count == 1 + range_requirement = range_requirements.first + versions = range_requirement.scan(VERSION_REGEX) + upper_bound = versions.map { |v| version_class.new(v) }.max + new_upper_bound = update_greatest_version( + upper_bound, + latest_resolvable_version + ) + + req_string.sub( + upper_bound.to_s, + new_upper_bound.to_s + ) + else + req_string + " || ^#{latest_resolvable_version}" + end end - end - def update_version_string(req_string) - req_string - .sub(VERSION_REGEX) do |old_version| - if old_version.match?(/\d-/) || - latest_resolvable_version.to_s.match?(/\d-/) - latest_resolvable_version.to_s - else - old_parts = old_version.split(".") - new_parts = latest_resolvable_version.to_s.split(".") - .first(old_parts.count) - new_parts.map.with_index do |part, i| - old_parts[i].match?(/^x\b/) ? "x" : part - end.join(".") + def update_version_string(req_string) + req_string + .sub(VERSION_REGEX) do |old_version| + if old_version.match?(/\d-/) || + latest_resolvable_version.to_s.match?(/\d-/) + latest_resolvable_version.to_s + else + old_parts = old_version.split(".") + new_parts = latest_resolvable_version.to_s.split(".") + .first(old_parts.count) + new_parts.map.with_index do |part, i| + old_parts[i].match?(/^x\b/) ? "x" : part + end.join(".") + end end - end - end + end - def update_greatest_version(old_version, version_to_be_permitted) - version = version_class.new(old_version) - version = version.release if version.prerelease? + def update_greatest_version(old_version, version_to_be_permitted) + version = version_class.new(old_version) + version = version.release if version.prerelease? - index_to_update = - version.segments.map.with_index { |seg, i| seg.zero? ? 0 : i }.max + index_to_update = + version.segments.map.with_index { |seg, i| seg.zero? ? 0 : i }.max - version.segments.map.with_index do |_, index| - if index < index_to_update - version_to_be_permitted.segments[index] - elsif index == index_to_update - version_to_be_permitted.segments[index] + 1 - else - 0 - end - end.join(".") - end + version.segments.map.with_index do |_, index| + if index < index_to_update + version_to_be_permitted.segments[index] + elsif index == index_to_update + version_to_be_permitted.segments[index] + 1 + else + 0 + end + end.join(".") + end - def version_class - Javascript::Version + def version_class + Javascript::Version + end end end end diff --git a/javascript/lib/dependabot/bun/update_checker/subdependency_version_resolver.rb b/javascript/lib/dependabot/bun/update_checker/subdependency_version_resolver.rb index 166403a32e..5b15b4d55b 100644 --- a/javascript/lib/dependabot/bun/update_checker/subdependency_version_resolver.rb +++ b/javascript/lib/dependabot/bun/update_checker/subdependency_version_resolver.rb @@ -2,139 +2,141 @@ # frozen_string_literal: true module Dependabot - module Bun - class UpdateChecker - class SubdependencyVersionResolver - def initialize(dependency:, credentials:, dependency_files:, - ignored_versions:, latest_allowable_version:, repo_contents_path:) - @dependency = dependency - @credentials = credentials - @dependency_files = dependency_files - @ignored_versions = ignored_versions - @latest_allowable_version = latest_allowable_version - @repo_contents_path = repo_contents_path - end + module Javascript + module Bun + class UpdateChecker + class SubdependencyVersionResolver + def initialize(dependency:, credentials:, dependency_files:, + ignored_versions:, latest_allowable_version:, repo_contents_path:) + @dependency = dependency + @credentials = credentials + @dependency_files = dependency_files + @ignored_versions = ignored_versions + @latest_allowable_version = latest_allowable_version + @repo_contents_path = repo_contents_path + end - def latest_resolvable_version - raise "Not a subdependency!" if dependency.requirements.any? - return if bundled_dependency? + def latest_resolvable_version + raise "Not a subdependency!" if dependency.requirements.any? + return if bundled_dependency? - base_dir = dependency_files.first.directory - SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do - dependency_files_builder.write_temporary_dependency_files + base_dir = dependency_files.first.directory + SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do + dependency_files_builder.write_temporary_dependency_files - updated_lockfiles = filtered_lockfiles.map do |lockfile| - updated_content = update_subdependency_in_lockfile(lockfile) - updated_lockfile = lockfile.dup - updated_lockfile.content = updated_content - updated_lockfile + updated_lockfiles = filtered_lockfiles.map do |lockfile| + updated_content = update_subdependency_in_lockfile(lockfile) + updated_lockfile = lockfile.dup + updated_lockfile.content = updated_content + updated_lockfile + end + + version_from_updated_lockfiles(updated_lockfiles) end + rescue SharedHelpers::HelperSubprocessFailed + # TODO: Move error handling logic from the FileUpdater to this class - version_from_updated_lockfiles(updated_lockfiles) + # Return nil (no update possible) if an unknown error occurred + nil end - rescue SharedHelpers::HelperSubprocessFailed - # TODO: Move error handling logic from the FileUpdater to this class - # Return nil (no update possible) if an unknown error occurred - nil - end + private - private + attr_reader :dependency + attr_reader :credentials + attr_reader :dependency_files + attr_reader :ignored_versions + attr_reader :latest_allowable_version + attr_reader :repo_contents_path - attr_reader :dependency - attr_reader :credentials - attr_reader :dependency_files - attr_reader :ignored_versions - attr_reader :latest_allowable_version - attr_reader :repo_contents_path + def update_subdependency_in_lockfile(lockfile) + lockfile_name = Pathname.new(lockfile.name).basename.to_s + path = Pathname.new(lockfile.name).dirname.to_s - def update_subdependency_in_lockfile(lockfile) - lockfile_name = Pathname.new(lockfile.name).basename.to_s - path = Pathname.new(lockfile.name).dirname.to_s + updated_files = run_bun_updater(path, lockfile_name) if lockfile.name.end_with?("bun.lock") - updated_files = run_bun_updater(path, lockfile_name) if lockfile.name.end_with?("bun.lock") - - updated_files.fetch(lockfile_name) - end + updated_files.fetch(lockfile_name) + end - def version_from_updated_lockfiles(updated_lockfiles) - updated_files = dependency_files - - dependency_files_builder.lockfiles + - updated_lockfiles + def version_from_updated_lockfiles(updated_lockfiles) + updated_files = dependency_files - + dependency_files_builder.lockfiles + + updated_lockfiles - updated_version = NpmAndYarn::FileParser.new( - dependency_files: updated_files, - source: nil, - credentials: credentials - ).parse.find { |d| d.name == dependency.name }&.version - return unless updated_version + updated_version = NpmAndYarn::FileParser.new( + dependency_files: updated_files, + source: nil, + credentials: credentials + ).parse.find { |d| d.name == dependency.name }&.version + return unless updated_version - version_class.new(updated_version) - end + version_class.new(updated_version) + end - def run_bun_updater(path, lockfile_name) - SharedHelpers.with_git_configured(credentials: credentials) do - Dir.chdir(path) do - Helpers.run_bun_command( - "update #{dependency.name} --save-text-lockfile", - fingerprint: "update --save-text-lockfile" - ) - { lockfile_name => File.read(lockfile_name) } + def run_bun_updater(path, lockfile_name) + SharedHelpers.with_git_configured(credentials: credentials) do + Dir.chdir(path) do + Helpers.run_bun_command( + "update #{dependency.name} --save-text-lockfile", + fingerprint: "update --save-text-lockfile" + ) + { lockfile_name => File.read(lockfile_name) } + end end end - end - def version_class - dependency.version_class - end + def version_class + dependency.version_class + end - def updated_dependency - Dependabot::Dependency.new( - name: dependency.name, - version: latest_allowable_version, - previous_version: dependency.version, - requirements: [], - package_manager: dependency.package_manager - ) - end + def updated_dependency + Dependabot::Dependency.new( + name: dependency.name, + version: latest_allowable_version, + previous_version: dependency.version, + requirements: [], + package_manager: dependency.package_manager + ) + end - def filtered_lockfiles - @filtered_lockfiles ||= - Javascript::SubDependencyFilesFilterer.new( - dependency_files: dependency_files, - updated_dependencies: [updated_dependency] - ).files_requiring_update - end + def filtered_lockfiles + @filtered_lockfiles ||= + Javascript::SubDependencyFilesFilterer.new( + dependency_files: dependency_files, + updated_dependencies: [updated_dependency] + ).files_requiring_update + end - def dependency_files_builder - @dependency_files_builder ||= - DependencyFilesBuilder.new( - dependency: dependency, - dependency_files: dependency_files, - credentials: credentials - ) - end + def dependency_files_builder + @dependency_files_builder ||= + DependencyFilesBuilder.new( + dependency: dependency, + dependency_files: dependency_files, + credentials: credentials + ) + end - # TODO: We should try and fix this by updating the parent that's not - # bundled. For this case: `chokidar > fsevents > node-pre-gyp > tar` we - # would need to update `fsevents` - # - # We shouldn't update bundled sub-dependencies as they have been bundled - # into the release at an exact version by a parent using - # `bundledDependencies`. - # - # For example, fsevents < 2 bundles node-pre-gyp meaning all it's - # sub-dependencies get bundled into the release tarball at publish time - # so you always get the same sub-dependency versions if you re-install a - # specific version of fsevents. - # - # Updating the sub-dependency by deleting the entry works but it gets - # removed from the bundled set of dependencies and moved top level - # resulting in a bunch of package duplication which is pretty confusing. - def bundled_dependency? - dependency.subdependency_metadata - &.any? { |h| h.fetch(:npm_bundled, false) } || - false + # TODO: We should try and fix this by updating the parent that's not + # bundled. For this case: `chokidar > fsevents > node-pre-gyp > tar` we + # would need to update `fsevents` + # + # We shouldn't update bundled sub-dependencies as they have been bundled + # into the release at an exact version by a parent using + # `bundledDependencies`. + # + # For example, fsevents < 2 bundles node-pre-gyp meaning all it's + # sub-dependencies get bundled into the release tarball at publish time + # so you always get the same sub-dependency versions if you re-install a + # specific version of fsevents. + # + # Updating the sub-dependency by deleting the entry works but it gets + # removed from the bundled set of dependencies and moved top level + # resulting in a bunch of package duplication which is pretty confusing. + def bundled_dependency? + dependency.subdependency_metadata + &.any? { |h| h.fetch(:npm_bundled, false) } || + false + end end end end diff --git a/javascript/lib/dependabot/bun/update_checker/version_resolver.rb b/javascript/lib/dependabot/bun/update_checker/version_resolver.rb index 78b8420085..6e1b860815 100644 --- a/javascript/lib/dependabot/bun/update_checker/version_resolver.rb +++ b/javascript/lib/dependabot/bun/update_checker/version_resolver.rb @@ -2,519 +2,521 @@ # frozen_string_literal: true module Dependabot - module Bun - class UpdateChecker - class VersionResolver # rubocop:disable Metrics/ClassLength - extend T::Sig - - require_relative "latest_version_finder" - - TIGHTLY_COUPLED_MONOREPOS = { - "vue" => %w(vue vue-template-compiler) - }.freeze - - def initialize(dependency:, credentials:, dependency_files:, - latest_allowable_version:, latest_version_finder:, repo_contents_path:, dependency_group: nil) - @dependency = dependency - @credentials = credentials - @dependency_files = dependency_files - @latest_allowable_version = latest_allowable_version - @dependency_group = dependency_group - - @latest_version_finder = {} - @latest_version_finder[dependency] = latest_version_finder - @repo_contents_path = repo_contents_path - end + module Javascript + module Bun + class UpdateChecker + class VersionResolver # rubocop:disable Metrics/ClassLength + extend T::Sig + + require_relative "latest_version_finder" + + TIGHTLY_COUPLED_MONOREPOS = { + "vue" => %w(vue vue-template-compiler) + }.freeze + + def initialize(dependency:, credentials:, dependency_files:, + latest_allowable_version:, latest_version_finder:, repo_contents_path:, dependency_group: nil) + @dependency = dependency + @credentials = credentials + @dependency_files = dependency_files + @latest_allowable_version = latest_allowable_version + @dependency_group = dependency_group + + @latest_version_finder = {} + @latest_version_finder[dependency] = latest_version_finder + @repo_contents_path = repo_contents_path + end - def latest_resolvable_version - return latest_allowable_version if git_dependency?(dependency) - return if part_of_tightly_locked_monorepo? - return if types_update_available? - return if original_package_update_available? + def latest_resolvable_version + return latest_allowable_version if git_dependency?(dependency) + return if part_of_tightly_locked_monorepo? + return if types_update_available? + return if original_package_update_available? - return latest_allowable_version unless relevant_unmet_peer_dependencies.any? + return latest_allowable_version unless relevant_unmet_peer_dependencies.any? - satisfying_versions.first - end + satisfying_versions.first + end - def latest_version_resolvable_with_full_unlock? - return false if dependency_updates_from_full_unlock.nil? + def latest_version_resolvable_with_full_unlock? + return false if dependency_updates_from_full_unlock.nil? - true - end + true + end - def latest_resolvable_previous_version(updated_version) - resolve_latest_previous_version(dependency, updated_version) - end + def latest_resolvable_previous_version(updated_version) + resolve_latest_previous_version(dependency, updated_version) + end - # rubocop:disable Metrics/PerceivedComplexity - def dependency_updates_from_full_unlock - return if git_dependency?(dependency) - return updated_monorepo_dependencies if part_of_tightly_locked_monorepo? - return if newly_broken_peer_reqs_from_dep.any? - return if original_package_update_available? - - updates = [{ - dependency: dependency, - version: latest_allowable_version, - previous_version: latest_resolvable_previous_version( - latest_allowable_version - ) - }] - newly_broken_peer_reqs_on_dep.each do |peer_req| - dep_name = peer_req.fetch(:requiring_dep_name) - dep = top_level_dependencies.find { |d| d.name == dep_name } - - # Can't handle reqs from sub-deps or git source deps (yet) - return nil if dep.nil? - return nil if git_dependency?(dep) - - updated_version = - latest_version_of_dep_with_satisfied_peer_reqs(dep) - return nil unless updated_version - - updates << { - dependency: dep, - version: updated_version, - previous_version: resolve_latest_previous_version( - dep, updated_version + # rubocop:disable Metrics/PerceivedComplexity + def dependency_updates_from_full_unlock + return if git_dependency?(dependency) + return updated_monorepo_dependencies if part_of_tightly_locked_monorepo? + return if newly_broken_peer_reqs_from_dep.any? + return if original_package_update_available? + + updates = [{ + dependency: dependency, + version: latest_allowable_version, + previous_version: latest_resolvable_previous_version( + latest_allowable_version + ) + }] + newly_broken_peer_reqs_on_dep.each do |peer_req| + dep_name = peer_req.fetch(:requiring_dep_name) + dep = top_level_dependencies.find { |d| d.name == dep_name } + + # Can't handle reqs from sub-deps or git source deps (yet) + return nil if dep.nil? + return nil if git_dependency?(dep) + + updated_version = + latest_version_of_dep_with_satisfied_peer_reqs(dep) + return nil unless updated_version + + updates << { + dependency: dep, + version: updated_version, + previous_version: resolve_latest_previous_version( + dep, updated_version + ) + } + end + updates += updated_types_dependencies if types_update_available? + updates.uniq + end + # rubocop:enable Metrics/PerceivedComplexity + + private + + sig { returns(Dependabot::Dependency) } + attr_reader :dependency + attr_reader :credentials + attr_reader :dependency_files + attr_reader :latest_allowable_version + attr_reader :repo_contents_path + attr_reader :dependency_group + + def latest_version_finder(dep) + @latest_version_finder[dep] ||= + LatestVersionFinder.new( + dependency: dep, + credentials: credentials, + dependency_files: dependency_files, + ignored_versions: [], + security_advisories: [] ) - } end - updates += updated_types_dependencies if types_update_available? - updates.uniq - end - # rubocop:enable Metrics/PerceivedComplexity - - private - - sig { returns(Dependabot::Dependency) } - attr_reader :dependency - attr_reader :credentials - attr_reader :dependency_files - attr_reader :latest_allowable_version - attr_reader :repo_contents_path - attr_reader :dependency_group - - def latest_version_finder(dep) - @latest_version_finder[dep] ||= - LatestVersionFinder.new( - dependency: dep, - credentials: credentials, - dependency_files: dependency_files, - ignored_versions: [], - security_advisories: [] - ) - end - # rubocop:disable Metrics/PerceivedComplexity - def resolve_latest_previous_version(dep, updated_version) - return dep.version if dep.version - - @resolve_latest_previous_version ||= {} - @resolve_latest_previous_version[dep] ||= begin - relevant_versions = latest_version_finder(dependency) - .possible_previous_versions_with_details - .map(&:first) - reqs = dep.requirements.filter_map { |r| r[:requirement] } - .map { |r| requirement_class.requirements_array(r) } - - # Pick the lowest version from the max possible version from all - # requirements. This matches the logic when combining the same - # dependency in DependencySet from multiple manifest files where we - # pick the lowest version from the duplicates. - latest_previous_version = reqs.flat_map do |req| - relevant_versions.select do |version| - req.any? { |r| r.satisfied_by?(version) } - end.max - end.min&.to_s - - # Handle cases where the latest resolvable previous version is the - # latest version. This often happens if you don't have lockfiles and - # have requirements update strategy set to bump_versions, where an - # update might go from ^1.1.1 to ^1.1.2 (both resolve to 1.1.2). - if updated_version.to_s == latest_previous_version - nil - else - latest_previous_version + # rubocop:disable Metrics/PerceivedComplexity + def resolve_latest_previous_version(dep, updated_version) + return dep.version if dep.version + + @resolve_latest_previous_version ||= {} + @resolve_latest_previous_version[dep] ||= begin + relevant_versions = latest_version_finder(dependency) + .possible_previous_versions_with_details + .map(&:first) + reqs = dep.requirements.filter_map { |r| r[:requirement] } + .map { |r| requirement_class.requirements_array(r) } + + # Pick the lowest version from the max possible version from all + # requirements. This matches the logic when combining the same + # dependency in DependencySet from multiple manifest files where we + # pick the lowest version from the duplicates. + latest_previous_version = reqs.flat_map do |req| + relevant_versions.select do |version| + req.any? { |r| r.satisfied_by?(version) } + end.max + end.min&.to_s + + # Handle cases where the latest resolvable previous version is the + # latest version. This often happens if you don't have lockfiles and + # have requirements update strategy set to bump_versions, where an + # update might go from ^1.1.1 to ^1.1.2 (both resolve to 1.1.2). + if updated_version.to_s == latest_previous_version + nil + else + latest_previous_version + end end end - end - # rubocop:enable Metrics/PerceivedComplexity + # rubocop:enable Metrics/PerceivedComplexity - def part_of_tightly_locked_monorepo? - monorepo_dep_names = - TIGHTLY_COUPLED_MONOREPOS.values - .find { |deps| deps.include?(dependency.name) } - return false unless monorepo_dep_names + def part_of_tightly_locked_monorepo? + monorepo_dep_names = + TIGHTLY_COUPLED_MONOREPOS.values + .find { |deps| deps.include?(dependency.name) } + return false unless monorepo_dep_names - deps_to_update = - top_level_dependencies - .select { |d| monorepo_dep_names.include?(d.name) } + deps_to_update = + top_level_dependencies + .select { |d| monorepo_dep_names.include?(d.name) } - deps_to_update.count > 1 - end - - def updated_monorepo_dependencies - monorepo_dep_names = - TIGHTLY_COUPLED_MONOREPOS.values - .find { |deps| deps.include?(dependency.name) } - - deps_to_update = - top_level_dependencies - .select { |d| monorepo_dep_names.include?(d.name) } - - updates = [] - deps_to_update.each do |dep| - next if git_dependency?(dep) - next if dep.version && - version_class.new(dep.version) >= latest_allowable_version - - updated_version = - latest_version_finder(dep) - .possible_versions - .find { |v| v == latest_allowable_version } - next unless updated_version - - updates << { - dependency: dep, - version: updated_version, - previous_version: resolve_latest_previous_version( - dep, updated_version - ) - } + deps_to_update.count > 1 end - updates - end + def updated_monorepo_dependencies + monorepo_dep_names = + TIGHTLY_COUPLED_MONOREPOS.values + .find { |deps| deps.include?(dependency.name) } + + deps_to_update = + top_level_dependencies + .select { |d| monorepo_dep_names.include?(d.name) } + + updates = [] + deps_to_update.each do |dep| + next if git_dependency?(dep) + next if dep.version && + version_class.new(dep.version) >= latest_allowable_version + + updated_version = + latest_version_finder(dep) + .possible_versions + .find { |v| v == latest_allowable_version } + next unless updated_version + + updates << { + dependency: dep, + version: updated_version, + previous_version: resolve_latest_previous_version( + dep, updated_version + ) + } + end - def types_package - @types_package ||= begin - types_package_name = Javascript::PackageName.new(dependency.name).types_package_name - top_level_dependencies.find { |d| types_package_name.to_s == d.name } if types_package_name + updates end - end - def original_package - @original_package ||= begin - original_package_name = Javascript::PackageName.new(dependency.name).library_name - top_level_dependencies.find { |d| original_package_name.to_s == d.name } if original_package_name + def types_package + @types_package ||= begin + types_package_name = Javascript::PackageName.new(dependency.name).types_package_name + top_level_dependencies.find { |d| types_package_name.to_s == d.name } if types_package_name + end end - end - def latest_types_package_version - @latest_types_package_version ||= latest_version_finder(types_package).latest_version_from_registry - end + def original_package + @original_package ||= begin + original_package_name = Javascript::PackageName.new(dependency.name).library_name + top_level_dependencies.find { |d| original_package_name.to_s == d.name } if original_package_name + end + end - def types_update_available? - return false if types_package.nil? + def latest_types_package_version + @latest_types_package_version ||= latest_version_finder(types_package).latest_version_from_registry + end - return false if latest_types_package_version.nil? + def types_update_available? + return false if types_package.nil? - return false unless latest_allowable_version.backwards_compatible_with?(latest_types_package_version) + return false if latest_types_package_version.nil? - return false unless version_class.correct?(types_package.version) + return false unless latest_allowable_version.backwards_compatible_with?(latest_types_package_version) - current_types_package_version = version_class.new(types_package.version) + return false unless version_class.correct?(types_package.version) - return false unless current_types_package_version < latest_types_package_version + current_types_package_version = version_class.new(types_package.version) - true - end + return false unless current_types_package_version < latest_types_package_version - def original_package_update_available? - return false if original_package.nil? + true + end - return false unless version_class.correct?(original_package.version) + def original_package_update_available? + return false if original_package.nil? - original_package_version = version_class.new(original_package.version) + return false unless version_class.correct?(original_package.version) - latest_version = latest_version_finder(original_package).latest_version_from_registry + original_package_version = version_class.new(original_package.version) - # If the latest version is within the scope of the current requirements, - # latest_version will be nil. In such cases, there is no update available. - return false if latest_version.nil? + latest_version = latest_version_finder(original_package).latest_version_from_registry - original_package_version < latest_version - end + # If the latest version is within the scope of the current requirements, + # latest_version will be nil. In such cases, there is no update available. + return false if latest_version.nil? - def updated_types_dependencies - [{ - dependency: types_package, - version: latest_types_package_version, - previous_version: resolve_latest_previous_version( - types_package, latest_types_package_version - ) - }] - end + original_package_version < latest_version + end - def peer_dependency_errors - return @peer_dependency_errors if @peer_dependency_errors_checked + def updated_types_dependencies + [{ + dependency: types_package, + version: latest_types_package_version, + previous_version: resolve_latest_previous_version( + types_package, latest_types_package_version + ) + }] + end - @peer_dependency_errors_checked = true + def peer_dependency_errors + return @peer_dependency_errors if @peer_dependency_errors_checked - @peer_dependency_errors = - fetch_peer_dependency_errors(version: latest_allowable_version) - end + @peer_dependency_errors_checked = true - def old_peer_dependency_errors - return @old_peer_dependency_errors if @old_peer_dependency_errors_checked + @peer_dependency_errors = + fetch_peer_dependency_errors(version: latest_allowable_version) + end - @old_peer_dependency_errors_checked = true + def old_peer_dependency_errors + return @old_peer_dependency_errors if @old_peer_dependency_errors_checked - version = version_for_dependency(dependency) + @old_peer_dependency_errors_checked = true - @old_peer_dependency_errors = - fetch_peer_dependency_errors(version: version) - end + version = version_for_dependency(dependency) - def fetch_peer_dependency_errors(version:) - # TODO: Add all of the error handling that the FileUpdater does - # here (since problematic repos will be resolved here before they're - # seen by the FileUpdater) - base_dir = dependency_files.first.directory - SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do - dependency_files_builder.write_temporary_dependency_files - - paths_requiring_update_check.flat_map do |path| - run_checker(path: path, version: version) - end.compact - end - rescue SharedHelpers::HelperSubprocessFailed - # Fall back to allowing the version through. Whatever error - # occurred should be properly handled by the FileUpdater. We - # can slowly migrate error handling to this class over time. - [] - end + @old_peer_dependency_errors = + fetch_peer_dependency_errors(version: version) + end - def unmet_peer_dependencies - peer_dependency_errors - .map { |captures| error_details_from_captures(captures) } - end + def fetch_peer_dependency_errors(version:) + # TODO: Add all of the error handling that the FileUpdater does + # here (since problematic repos will be resolved here before they're + # seen by the FileUpdater) + base_dir = dependency_files.first.directory + SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do + dependency_files_builder.write_temporary_dependency_files + + paths_requiring_update_check.flat_map do |path| + run_checker(path: path, version: version) + end.compact + end + rescue SharedHelpers::HelperSubprocessFailed + # Fall back to allowing the version through. Whatever error + # occurred should be properly handled by the FileUpdater. We + # can slowly migrate error handling to this class over time. + [] + end - def old_unmet_peer_dependencies - old_peer_dependency_errors - .map { |captures| error_details_from_captures(captures) } - end + def unmet_peer_dependencies + peer_dependency_errors + .map { |captures| error_details_from_captures(captures) } + end - def error_details_from_captures(captures) - return {} unless captures.is_a?(Hash) + def old_unmet_peer_dependencies + old_peer_dependency_errors + .map { |captures| error_details_from_captures(captures) } + end - required_dep_captures = captures.fetch("required_dep") - requiring_dep_captures = captures.fetch("requiring_dep") - return {} unless required_dep_captures && requiring_dep_captures + def error_details_from_captures(captures) + return {} unless captures.is_a?(Hash) - { - requirement_name: required_dep_captures.sub(/@[^@]+$/, ""), - requirement_version: required_dep_captures.split("@").last.delete('"'), - requiring_dep_name: requiring_dep_captures.sub(/@[^@]+$/, "") - } - end + required_dep_captures = captures.fetch("required_dep") + requiring_dep_captures = captures.fetch("requiring_dep") + return {} unless required_dep_captures && requiring_dep_captures - def relevant_unmet_peer_dependencies - relevant_unmet_peer_dependencies = - unmet_peer_dependencies.select do |dep| - dep[:requirement_name] == dependency.name || - dep[:requiring_dep_name] == dependency.name - end + { + requirement_name: required_dep_captures.sub(/@[^@]+$/, ""), + requirement_version: required_dep_captures.split("@").last.delete('"'), + requiring_dep_name: requiring_dep_captures.sub(/@[^@]+$/, "") + } + end - unless dependency_group.nil? - # Ignore unmet peer dependencies that are in the dependency group because - # the update is also updating those dependencies. - relevant_unmet_peer_dependencies.reject! do |dep| - dependency_group.dependencies.any? do |group_dep| - dep[:requirement_name] == group_dep.name || - dep[:requiring_dep_name] == group_dep.name + def relevant_unmet_peer_dependencies + relevant_unmet_peer_dependencies = + unmet_peer_dependencies.select do |dep| + dep[:requirement_name] == dependency.name || + dep[:requiring_dep_name] == dependency.name + end + + unless dependency_group.nil? + # Ignore unmet peer dependencies that are in the dependency group because + # the update is also updating those dependencies. + relevant_unmet_peer_dependencies.reject! do |dep| + dependency_group.dependencies.any? do |group_dep| + dep[:requirement_name] == group_dep.name || + dep[:requiring_dep_name] == group_dep.name + end end end - end - return [] if relevant_unmet_peer_dependencies.empty? + return [] if relevant_unmet_peer_dependencies.empty? - # Prune out any pre-existing warnings - relevant_unmet_peer_dependencies.reject do |issue| - old_unmet_peer_dependencies.any? do |old_issue| - old_issue.slice(:requirement_name, :requiring_dep_name) == - issue.slice(:requirement_name, :requiring_dep_name) + # Prune out any pre-existing warnings + relevant_unmet_peer_dependencies.reject do |issue| + old_unmet_peer_dependencies.any? do |old_issue| + old_issue.slice(:requirement_name, :requiring_dep_name) == + issue.slice(:requirement_name, :requiring_dep_name) + end end end - end - # rubocop:disable Metrics/PerceivedComplexity - def satisfying_versions - latest_version_finder(dependency) - .possible_versions_with_details - .select do |version, details| - next false unless satisfies_peer_reqs_on_dep?(version) - next true unless details["peerDependencies"] - next true if version == version_for_dependency(dependency) - - details["peerDependencies"].all? do |dep, req| - dep = top_level_dependencies.find { |d| d.name == dep } - next false unless dep - next git_dependency?(dep) if req.include?("/") - - reqs = requirement_class.requirements_array(req) - next false unless version_for_dependency(dep) - - reqs.any? { |r| r.satisfied_by?(version_for_dependency(dep)) } - rescue Gem::Requirement::BadRequirementError - false + # rubocop:disable Metrics/PerceivedComplexity + def satisfying_versions + latest_version_finder(dependency) + .possible_versions_with_details + .select do |version, details| + next false unless satisfies_peer_reqs_on_dep?(version) + next true unless details["peerDependencies"] + next true if version == version_for_dependency(dependency) + + details["peerDependencies"].all? do |dep, req| + dep = top_level_dependencies.find { |d| d.name == dep } + next false unless dep + next git_dependency?(dep) if req.include?("/") + + reqs = requirement_class.requirements_array(req) + next false unless version_for_dependency(dep) + + reqs.any? { |r| r.satisfied_by?(version_for_dependency(dep)) } + rescue Gem::Requirement::BadRequirementError + false + end end - end - .map(&:first) - end + .map(&:first) + end - # rubocop:enable Metrics/PerceivedComplexity + # rubocop:enable Metrics/PerceivedComplexity - def satisfies_peer_reqs_on_dep?(version) - newly_broken_peer_reqs_on_dep.all? do |peer_req| - req = peer_req.fetch(:requirement_version) + def satisfies_peer_reqs_on_dep?(version) + newly_broken_peer_reqs_on_dep.all? do |peer_req| + req = peer_req.fetch(:requirement_version) - # Git requirements can't be satisfied by a version - next false if req.include?("/") + # Git requirements can't be satisfied by a version + next false if req.include?("/") - reqs = requirement_class.requirements_array(req) - reqs.any? { |r| r.satisfied_by?(version) } - rescue Gem::Requirement::BadRequirementError - false + reqs = requirement_class.requirements_array(req) + reqs.any? { |r| r.satisfied_by?(version) } + rescue Gem::Requirement::BadRequirementError + false + end end - end - def latest_version_of_dep_with_satisfied_peer_reqs(dep) - latest_version_finder(dep) - .possible_versions_with_details - .find do |version, details| - next false unless version > version_for_dependency(dep) - next true unless details["peerDependencies"] + def latest_version_of_dep_with_satisfied_peer_reqs(dep) + latest_version_finder(dep) + .possible_versions_with_details + .find do |version, details| + next false unless version > version_for_dependency(dep) + next true unless details["peerDependencies"] - details["peerDependencies"].all? do |peer_dep_name, req| - # Can't handle multiple peer dependencies - next false unless peer_dep_name == dependency.name - next git_dependency?(dependency) if req.include?("/") + details["peerDependencies"].all? do |peer_dep_name, req| + # Can't handle multiple peer dependencies + next false unless peer_dep_name == dependency.name + next git_dependency?(dependency) if req.include?("/") - reqs = requirement_class.requirements_array(req) + reqs = requirement_class.requirements_array(req) - reqs.any? { |r| r.satisfied_by?(latest_allowable_version) } - rescue Gem::Requirement::BadRequirementError - false + reqs.any? { |r| r.satisfied_by?(latest_allowable_version) } + rescue Gem::Requirement::BadRequirementError + false + end end - end - &.first - end + &.first + end - def git_dependency?(dep) - # ignored_version/raise_on_ignored are irrelevant. - GitCommitChecker - .new(dependency: dep, credentials: credentials) - .git_dependency? - end + def git_dependency?(dep) + # ignored_version/raise_on_ignored are irrelevant. + GitCommitChecker + .new(dependency: dep, credentials: credentials) + .git_dependency? + end - def newly_broken_peer_reqs_on_dep - relevant_unmet_peer_dependencies - .select { |dep| dep[:requirement_name] == dependency.name } - end + def newly_broken_peer_reqs_on_dep + relevant_unmet_peer_dependencies + .select { |dep| dep[:requirement_name] == dependency.name } + end - def newly_broken_peer_reqs_from_dep - relevant_unmet_peer_dependencies - .select { |dep| dep[:requiring_dep_name] == dependency.name } - end + def newly_broken_peer_reqs_from_dep + relevant_unmet_peer_dependencies + .select { |dep| dep[:requiring_dep_name] == dependency.name } + end - def lockfiles_for_path(lockfiles:, path:) - lockfiles.select do |lockfile| - File.dirname(lockfile.name) == File.dirname(path) + def lockfiles_for_path(lockfiles:, path:) + lockfiles.select do |lockfile| + File.dirname(lockfile.name) == File.dirname(path) + end end - end - def run_checker(path:, version:) - bun_lockfiles = lockfiles_for_path(lockfiles: dependency_files_builder.bun_locks, path: path) - return run_bun_checker(path: path, version: version) if bun_lockfiles.any? + def run_checker(path:, version:) + bun_lockfiles = lockfiles_for_path(lockfiles: dependency_files_builder.bun_locks, path: path) + return run_bun_checker(path: path, version: version) if bun_lockfiles.any? - root_bun_lock = dependency_files_builder.root_bun_lock - run_bun_checker(path: path, version: version) if root_bun_lock - end + root_bun_lock = dependency_files_builder.root_bun_lock + run_bun_checker(path: path, version: version) if root_bun_lock + end - def run_bun_checker(path:, version:) - SharedHelpers.with_git_configured(credentials: credentials) do - Dir.chdir(path) do - Helpers.run_bun_command( - "update #{dependency.name}@#{version} --save-text-lockfile", - fingerprint: "update @ --save-text-lockfile" - ) + def run_bun_checker(path:, version:) + SharedHelpers.with_git_configured(credentials: credentials) do + Dir.chdir(path) do + Helpers.run_bun_command( + "update #{dependency.name}@#{version} --save-text-lockfile", + fingerprint: "update @ --save-text-lockfile" + ) + end end end - end - def version_install_arg(version:) - git_source = dependency.requirements.find { |req| req[:source] && req[:source][:type] == "git" } + def version_install_arg(version:) + git_source = dependency.requirements.find { |req| req[:source] && req[:source][:type] == "git" } - if git_source - "#{dependency.name}@#{git_source[:source][:url]}##{version}" - else - "#{dependency.name}@#{version}" + if git_source + "#{dependency.name}@#{git_source[:source][:url]}##{version}" + else + "#{dependency.name}@#{version}" + end end - end - def requirements_for_path(requirements, path) - return requirements if path.to_s == "." + def requirements_for_path(requirements, path) + return requirements if path.to_s == "." - requirements.filter_map do |r| - next unless r[:file].start_with?("#{path}/") + requirements.filter_map do |r| + next unless r[:file].start_with?("#{path}/") - r.merge(file: r[:file].gsub(/^#{Regexp.quote("#{path}/")}/, "")) + r.merge(file: r[:file].gsub(/^#{Regexp.quote("#{path}/")}/, "")) + end end - end - # Top level dependencies are required in the peer dep checker - # to fetch the manifests for all top level deps which may contain - # "peerDependency" requirements - def top_level_dependencies - @top_level_dependencies ||= Bun::FileParser.new( - dependency_files: dependency_files, - source: nil, - credentials: credentials - ).parse.select(&:top_level?) - end - - def paths_requiring_update_check - @paths_requiring_update_check ||= - Javascript::DependencyFilesFilterer.new( - dependency_files: dependency_files, - updated_dependencies: [dependency] - ).paths_requiring_update_check - end - - def dependency_files_builder - @dependency_files_builder ||= - DependencyFilesBuilder.new( - dependency: dependency, + # Top level dependencies are required in the peer dep checker + # to fetch the manifests for all top level deps which may contain + # "peerDependency" requirements + def top_level_dependencies + @top_level_dependencies ||= Bun::FileParser.new( dependency_files: dependency_files, + source: nil, credentials: credentials - ) - end + ).parse.select(&:top_level?) + end - def version_for_dependency(dep) - return version_class.new(dep.version) if dep.version && version_class.correct?(dep.version) + def paths_requiring_update_check + @paths_requiring_update_check ||= + Javascript::DependencyFilesFilterer.new( + dependency_files: dependency_files, + updated_dependencies: [dependency] + ).paths_requiring_update_check + end - dep.requirements.filter_map { |r| r[:requirement] } - .reject { |req_string| req_string.start_with?("<") } - .select { |req_string| req_string.match?(version_regex) } - .map { |req_string| req_string.match(version_regex) } - .select { |version| version_class.correct?(version.to_s) } - .map { |version| version_class.new(version.to_s) } - .max - end + def dependency_files_builder + @dependency_files_builder ||= + DependencyFilesBuilder.new( + dependency: dependency, + dependency_files: dependency_files, + credentials: credentials + ) + end - def version_class - dependency.version_class - end + def version_for_dependency(dep) + return version_class.new(dep.version) if dep.version && version_class.correct?(dep.version) - def requirement_class - dependency.requirement_class - end + dep.requirements.filter_map { |r| r[:requirement] } + .reject { |req_string| req_string.start_with?("<") } + .select { |req_string| req_string.match?(version_regex) } + .map { |req_string| req_string.match(version_regex) } + .select { |version| version_class.correct?(version.to_s) } + .map { |version| version_class.new(version.to_s) } + .max + end + + def version_class + dependency.version_class + end - def version_regex - Dependabot::Javascript::Version::VERSION_PATTERN + def requirement_class + dependency.requirement_class + end + + def version_regex + Dependabot::Javascript::Version::VERSION_PATTERN + end end end end diff --git a/javascript/lib/dependabot/bun/update_checker/vulnerability_auditor.rb b/javascript/lib/dependabot/bun/update_checker/vulnerability_auditor.rb index 7a4666901a..8f814f1a27 100644 --- a/javascript/lib/dependabot/bun/update_checker/vulnerability_auditor.rb +++ b/javascript/lib/dependabot/bun/update_checker/vulnerability_auditor.rb @@ -4,158 +4,160 @@ require "stringio" module Dependabot - module Bun - class UpdateChecker - class VulnerabilityAuditor - def initialize(dependency_files:, credentials:) - @dependency_files = dependency_files - @credentials = credentials - end - - # rubocop:disable Metrics/MethodLength - # Finds any dependencies in the `package-lock.json` or `npm-shrinkwrap.json` that have - # a subdependency on the given dependency that is locked to a vuln version range. - # - # NOTE: yarn is currently not supported. - # - # @param dependency [Dependabot::Dependency] the dependency to check - # @param security_advisories [Array] advisories for the dependency - # @return [Hash>]>] the audit results - # * :dependency_name [String] the name of the dependency - # * :fix_available [Boolean] whether a fix is available - # * :current_version [String] the version of the dependency - # * :target_version [String] the version of the dependency after the fix - # * :fix_updates [Array>] a list of dependencies to update in order to fix - # * :dependency_name [String] the name of the blocking dependency - # * :current_version [String] the current version of the blocking dependency - # * :target_version [String] the target version of the blocking dependency - # * :top_level_ancestors [Array] the names of top-level dependencies with a transitive - # dependency on the blocking dependency - # * :top_level_ancestors [Array] the names of all top-level dependencies with a transitive - # dependency on the dependency - # * :explanation [String] an explanation for why the project failed the vulnerability auditor run - def audit(dependency:, security_advisories:) - Dependabot.logger.info("VulnerabilityAuditor: starting audit") - - fix_unavailable = { - "dependency_name" => dependency.name, - "fix_available" => false, - "fix_updates" => [], - "top_level_ancestors" => [] - } - - SharedHelpers.in_a_temporary_directory do - dependency_files_builder = DependencyFilesBuilder.new( - dependency: dependency, - dependency_files: dependency_files, - credentials: credentials - ) - dependency_files_builder.write_temporary_dependency_files - - # `npm-shrinkwrap.js`, if present, takes precedence over `package-lock.js`. - # Both files use the same format. See https://bit.ly/3lDIAJV for more. - lockfile = dependency_files_builder.lockfiles.first - unless lockfile - Dependabot.logger.info("VulnerabilityAuditor: missing lockfile") - return fix_unavailable - end - - vuln_versions = security_advisories.map do |a| - { - dependency_name: a.dependency_name, - affected_versions: a.vulnerable_version_strings - } - end + module Javascript + module Bun + class UpdateChecker + class VulnerabilityAuditor + def initialize(dependency_files:, credentials:) + @dependency_files = dependency_files + @credentials = credentials + end - audit_result = SharedHelpers.run_helper_subprocess( - command: Javascript::NativeHelpers.helper_path, - function: "npm:vulnerabilityAuditor", - args: [Dir.pwd, vuln_versions] - ) - - validation_result = validate_audit_result(audit_result, security_advisories) - if validation_result != :viable - Dependabot.logger.info("VulnerabilityAuditor: audit result not viable: #{validation_result}") - fix_unavailable["explanation"] = explain_fix_unavailable(validation_result, dependency) - return fix_unavailable + # rubocop:disable Metrics/MethodLength + # Finds any dependencies in the `package-lock.json` or `npm-shrinkwrap.json` that have + # a subdependency on the given dependency that is locked to a vuln version range. + # + # NOTE: yarn is currently not supported. + # + # @param dependency [Dependabot::Dependency] the dependency to check + # @param security_advisories [Array] advisories for the dependency + # @return [Hash>]>] the audit results + # * :dependency_name [String] the name of the dependency + # * :fix_available [Boolean] whether a fix is available + # * :current_version [String] the version of the dependency + # * :target_version [String] the version of the dependency after the fix + # * :fix_updates [Array>] a list of dependencies to update in order to fix + # * :dependency_name [String] the name of the blocking dependency + # * :current_version [String] the current version of the blocking dependency + # * :target_version [String] the target version of the blocking dependency + # * :top_level_ancestors [Array] the names of top-level dependencies with a transitive + # dependency on the blocking dependency + # * :top_level_ancestors [Array] the names of all top-level dependencies with a transitive + # dependency on the dependency + # * :explanation [String] an explanation for why the project failed the vulnerability auditor run + def audit(dependency:, security_advisories:) + Dependabot.logger.info("VulnerabilityAuditor: starting audit") + + fix_unavailable = { + "dependency_name" => dependency.name, + "fix_available" => false, + "fix_updates" => [], + "top_level_ancestors" => [] + } + + SharedHelpers.in_a_temporary_directory do + dependency_files_builder = DependencyFilesBuilder.new( + dependency: dependency, + dependency_files: dependency_files, + credentials: credentials + ) + dependency_files_builder.write_temporary_dependency_files + + # `npm-shrinkwrap.js`, if present, takes precedence over `package-lock.js`. + # Both files use the same format. See https://bit.ly/3lDIAJV for more. + lockfile = dependency_files_builder.lockfiles.first + unless lockfile + Dependabot.logger.info("VulnerabilityAuditor: missing lockfile") + return fix_unavailable + end + + vuln_versions = security_advisories.map do |a| + { + dependency_name: a.dependency_name, + affected_versions: a.vulnerable_version_strings + } + end + + audit_result = SharedHelpers.run_helper_subprocess( + command: Javascript::NativeHelpers.helper_path, + function: "npm:vulnerabilityAuditor", + args: [Dir.pwd, vuln_versions] + ) + + validation_result = validate_audit_result(audit_result, security_advisories) + if validation_result != :viable + Dependabot.logger.info("VulnerabilityAuditor: audit result not viable: #{validation_result}") + fix_unavailable["explanation"] = explain_fix_unavailable(validation_result, dependency) + return fix_unavailable + end + + Dependabot.logger.info("VulnerabilityAuditor: audit result viable") + audit_result end - - Dependabot.logger.info("VulnerabilityAuditor: audit result viable") - audit_result + rescue SharedHelpers::HelperSubprocessFailed => e + log_helper_subprocess_failure(dependency, e) + fix_unavailable end - rescue SharedHelpers::HelperSubprocessFailed => e - log_helper_subprocess_failure(dependency, e) - fix_unavailable - end - # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/MethodLength - private + private - attr_reader :dependency_files - attr_reader :credentials + attr_reader :dependency_files + attr_reader :credentials - def explain_fix_unavailable(validation_result, dependency) - case validation_result - when :fix_unavailable, :dependency_still_vulnerable, :downgrades_dependencies - "No patched version available for #{dependency.name}" - when :fix_incomplete - "The lockfile might be out of sync?" + def explain_fix_unavailable(validation_result, dependency) + case validation_result + when :fix_unavailable, :dependency_still_vulnerable, :downgrades_dependencies + "No patched version available for #{dependency.name}" + when :fix_incomplete + "The lockfile might be out of sync?" + end end - end - def validate_audit_result(audit_result, security_advisories) - return :fix_unavailable unless audit_result["fix_available"] - return :dependency_still_vulnerable if dependency_still_vulnerable?(audit_result, security_advisories) - return :downgrades_dependencies if downgrades_dependencies?(audit_result) - return :fix_incomplete if fix_incomplete?(audit_result) + def validate_audit_result(audit_result, security_advisories) + return :fix_unavailable unless audit_result["fix_available"] + return :dependency_still_vulnerable if dependency_still_vulnerable?(audit_result, security_advisories) + return :downgrades_dependencies if downgrades_dependencies?(audit_result) + return :fix_incomplete if fix_incomplete?(audit_result) - :viable - end + :viable + end - def dependency_still_vulnerable?(audit_result, security_advisories) - # vulnerable dependency is removed if the target version is nil - return false unless audit_result["target_version"] + def dependency_still_vulnerable?(audit_result, security_advisories) + # vulnerable dependency is removed if the target version is nil + return false unless audit_result["target_version"] - version = Version.new(audit_result["target_version"]) - security_advisories.any? { |a| a.vulnerable?(version) } - end + version = Version.new(audit_result["target_version"]) + security_advisories.any? { |a| a.vulnerable?(version) } + end - def downgrades_dependencies?(audit_result) - return true if downgrades_version?(audit_result["current_version"], audit_result["target_version"]) + def downgrades_dependencies?(audit_result) + return true if downgrades_version?(audit_result["current_version"], audit_result["target_version"]) - audit_result["fix_updates"].any? do |update| - downgrades_version?(update["current_version"], update["target_version"]) + audit_result["fix_updates"].any? do |update| + downgrades_version?(update["current_version"], update["target_version"]) + end end - end - def downgrades_version?(current_version, target_version) - return false unless target_version + def downgrades_version?(current_version, target_version) + return false unless target_version - current = Version.new(current_version) - target = Version.new(target_version) - current > target - end + current = Version.new(current_version) + target = Version.new(target_version) + current > target + end - def fix_incomplete?(audit_result) - audit_result["fix_updates"].any? { |update| !update.key?("target_version") } || - audit_result["fix_updates"].empty? - end + def fix_incomplete?(audit_result) + audit_result["fix_updates"].any? { |update| !update.key?("target_version") } || + audit_result["fix_updates"].empty? + end - def log_helper_subprocess_failure(dependency, error) - # See `Dependabot::SharedHelpers.run_helper_subprocess` for details on error context - context = error.error_context || {} - - builder = ::StringIO.new - builder << "VulnerabilityAuditor: " - builder << "#{context[:function]} " if context[:function] - builder << "failed" - builder << " after #{context[:time_taken].truncate(2)}s" if context[:time_taken] - builder << " while auditing #{dependency.name}: " - builder << error.message - builder << "\n" << context[:trace] - - msg = builder.string - Dependabot.logger.info(msg) # TODO: is this the right log level? + def log_helper_subprocess_failure(dependency, error) + # See `Dependabot::SharedHelpers.run_helper_subprocess` for details on error context + context = error.error_context || {} + + builder = ::StringIO.new + builder << "VulnerabilityAuditor: " + builder << "#{context[:function]} " if context[:function] + builder << "failed" + builder << " after #{context[:time_taken].truncate(2)}s" if context[:time_taken] + builder << " while auditing #{dependency.name}: " + builder << error.message + builder << "\n" << context[:trace] + + msg = builder.string + Dependabot.logger.info(msg) # TODO: is this the right log level? + end end end end diff --git a/javascript/lib/dependabot/bun/version.rb b/javascript/lib/dependabot/bun/version.rb index 91fa17c539..7ff2df36c1 100644 --- a/javascript/lib/dependabot/bun/version.rb +++ b/javascript/lib/dependabot/bun/version.rb @@ -2,11 +2,13 @@ # frozen_string_literal: true module Dependabot - module Bun - class Version < Dependabot::Javascript::Version + module Javascript + module Bun + class Version < Dependabot::Javascript::Version + end end end end Dependabot::Utils - .register_version_class("bun", Dependabot::Bun::Version) + .register_version_class("bun", Dependabot::Javascript::Bun::Version) diff --git a/javascript/lib/dependabot/javascript/file_fetcher.rb b/javascript/lib/dependabot/javascript/file_fetcher.rb index 0821a8df1a..5b50d8bd23 100644 --- a/javascript/lib/dependabot/javascript/file_fetcher.rb +++ b/javascript/lib/dependabot/javascript/file_fetcher.rb @@ -12,13 +12,13 @@ class FileFetcher < Dependabot::FileFetchers::Base PATH_DEPENDENCY_CLEAN_REGEX = /^file:|^link:/ DEPENDENCY_TYPES = T.let(%w(dependencies devDependencies optionalDependencies).freeze, T::Array[String]) - sig { params(instance: Dependabot::Bun::FileFetcher).returns(T::Array[DependencyFile]) } + sig { params(instance: Dependabot::Javascript::Bun::FileFetcher).returns(T::Array[DependencyFile]) } def workspace_package_jsons(instance) @workspace_package_jsons ||= T.let(fetch_workspace_package_jsons(instance), T.nilable(T::Array[DependencyFile])) end sig do - params(instance: Dependabot::Bun::FileFetcher, fetched_files: T::Array[DependencyFile]) + params(instance: Dependabot::Javascript::Bun::FileFetcher, fetched_files: T::Array[DependencyFile]) .returns(T::Array[DependencyFile]) end def path_dependencies(instance, fetched_files) @@ -50,7 +50,7 @@ def path_dependencies(instance, fetched_files) end sig do - params(instance: Dependabot::Bun::FileFetcher, fetched_files: T::Array[DependencyFile]) + params(instance: Dependabot::Javascript::Bun::FileFetcher, fetched_files: T::Array[DependencyFile]) .returns(T::Array[[String, String]]) end def path_dependency_details(instance, fetched_files) @@ -68,7 +68,10 @@ def path_dependency_details(instance, fetched_files) # rubocop:disable Metrics/PerceivedComplexity # rubocop:disable Metrics/AbcSize - sig { params(instance: Dependabot::Bun::FileFetcher, file: DependencyFile).returns(T::Array[[String, String]]) } + sig do + params(instance: Dependabot::Javascript::Bun::FileFetcher, + file: DependencyFile).returns(T::Array[[String, String]]) + end def path_dependency_details_from_manifest(instance, file) return [] unless file.name.end_with?(MANIFEST_FILENAME) @@ -128,7 +131,7 @@ def convert_dependency_path_to_name(path, value) [parts.join("/"), value] end - sig { params(instance: Dependabot::Bun::FileFetcher).returns(T::Array[DependencyFile]) } + sig { params(instance: Dependabot::Javascript::Bun::FileFetcher).returns(T::Array[DependencyFile]) } def fetch_workspace_package_jsons(instance) parsed_manifest = parsed_package_json(instance) return [] unless parsed_manifest["workspaces"] @@ -138,7 +141,10 @@ def fetch_workspace_package_jsons(instance) end end - sig { params(instance: Dependabot::Bun::FileFetcher, workspace_object: T.untyped).returns(T::Array[String]) } + sig do + params(instance: Dependabot::Javascript::Bun::FileFetcher, + workspace_object: T.untyped).returns(T::Array[String]) + end def workspace_paths(instance, workspace_object) paths_array = if workspace_object.is_a?(Hash) @@ -151,7 +157,7 @@ def workspace_paths(instance, workspace_object) paths_array.flat_map { |path| recursive_find_directories(instance, path) } end - sig { params(instance: Dependabot::Bun::FileFetcher, glob: String).returns(T::Array[String]) } + sig { params(instance: Dependabot::Javascript::Bun::FileFetcher, glob: String).returns(T::Array[String]) } def find_directories(instance, glob) return [glob] unless glob.include?("*") @@ -178,7 +184,10 @@ def matching_paths(glob, paths) paths.select { |filename| File.fnmatch?(glob, filename, File::FNM_PATHNAME) } end - sig { params(instance: Dependabot::Bun::FileFetcher, glob: String, prefix: String).returns(T::Array[String]) } + sig do + params(instance: Dependabot::Javascript::Bun::FileFetcher, glob: String, + prefix: String).returns(T::Array[String]) + end def recursive_find_directories(instance, glob, prefix = "") return [prefix + glob] unless glob.include?("*") @@ -197,7 +206,9 @@ def recursive_find_directories(instance, glob, prefix = "") matching_paths(prefix + glob, paths) end - sig { params(instance: Dependabot::Bun::FileFetcher, workspace: String).returns(T.nilable(DependencyFile)) } + sig do + params(instance: Dependabot::Javascript::Bun::FileFetcher, workspace: String).returns(T.nilable(DependencyFile)) + end def fetch_package_json_if_present(instance, workspace) file = File.join(workspace, MANIFEST_FILENAME) @@ -210,12 +221,12 @@ def fetch_package_json_if_present(instance, workspace) end end - sig { params(instance: Dependabot::Bun::FileFetcher).returns(DependencyFile) } + sig { params(instance: Dependabot::Javascript::Bun::FileFetcher).returns(DependencyFile) } def package_json(instance) @package_json ||= T.let(instance.fetch_file(Javascript::MANIFEST_FILENAME), T.nilable(DependencyFile)) end - sig { params(instance: Dependabot::Bun::FileFetcher).returns(T.untyped) } + sig { params(instance: Dependabot::Javascript::Bun::FileFetcher).returns(T.untyped) } def parsed_package_json(instance) manifest_file = package_json(instance) parsed = JSON.parse(T.must(manifest_file.content)) @@ -226,14 +237,18 @@ def parsed_package_json(instance) raise Dependabot::DependencyFileNotParseable, T.must(manifest_file).path end - sig { params(instance: Dependabot::Bun::FileFetcher, filename: String).returns(T.nilable(DependencyFile)) } + sig do + params(instance: Dependabot::Javascript::Bun::FileFetcher, filename: String).returns(T.nilable(DependencyFile)) + end def fetch_file_with_support(instance, filename) instance.fetch_file(filename).tap { |f| f.support_file = true } rescue Dependabot::DependencyFileNotFound nil end - sig { params(instance: Dependabot::Bun::FileFetcher, filename: String).returns(T.nilable(DependencyFile)) } + sig do + params(instance: Dependabot::Javascript::Bun::FileFetcher, filename: String).returns(T.nilable(DependencyFile)) + end def fetch_file_from_parent_directories(instance, filename) (1..instance.directory.split("/").count).each do |i| file = fetch_file_with_support(instance, ("../" * i) + filename) diff --git a/javascript/lib/dependabot/javascript/file_fetcher_helper.rb b/javascript/lib/dependabot/javascript/file_fetcher_helper.rb index bf81891be0..112d403c12 100644 --- a/javascript/lib/dependabot/javascript/file_fetcher_helper.rb +++ b/javascript/lib/dependabot/javascript/file_fetcher_helper.rb @@ -11,13 +11,13 @@ module FileFetcherHelper PATH_DEPENDENCY_CLEAN_REGEX = /^file:|^link:/ DEPENDENCY_TYPES = T.let(%w(dependencies devDependencies optionalDependencies).freeze, T::Array[String]) - sig { params(instance: Dependabot::Bun::FileFetcher).returns(T::Array[DependencyFile]) } + sig { params(instance: Dependabot::Javascript::Bun::FileFetcher).returns(T::Array[DependencyFile]) } def workspace_package_jsons(instance) @workspace_package_jsons ||= T.let(fetch_workspace_package_jsons(instance), T.nilable(T::Array[DependencyFile])) end sig do - params(instance: Dependabot::Bun::FileFetcher, fetched_files: T::Array[DependencyFile]) + params(instance: Dependabot::Javascript::Bun::FileFetcher, fetched_files: T::Array[DependencyFile]) .returns(T::Array[DependencyFile]) end def path_dependencies(instance, fetched_files) @@ -49,7 +49,7 @@ def path_dependencies(instance, fetched_files) end sig do - params(instance: Dependabot::Bun::FileFetcher, fetched_files: T::Array[DependencyFile]) + params(instance: Dependabot::Javascript::Bun::FileFetcher, fetched_files: T::Array[DependencyFile]) .returns(T::Array[[String, String]]) end def path_dependency_details(instance, fetched_files) @@ -67,7 +67,10 @@ def path_dependency_details(instance, fetched_files) # rubocop:disable Metrics/PerceivedComplexity # rubocop:disable Metrics/AbcSize - sig { params(instance: Dependabot::Bun::FileFetcher, file: DependencyFile).returns(T::Array[[String, String]]) } + sig do + params(instance: Dependabot::Javascript::Bun::FileFetcher, + file: DependencyFile).returns(T::Array[[String, String]]) + end def path_dependency_details_from_manifest(instance, file) return [] unless file.name.end_with?(MANIFEST_FILENAME) @@ -127,7 +130,7 @@ def convert_dependency_path_to_name(path, value) [parts.join("/"), value] end - sig { params(instance: Dependabot::Bun::FileFetcher).returns(T::Array[DependencyFile]) } + sig { params(instance: Dependabot::Javascript::Bun::FileFetcher).returns(T::Array[DependencyFile]) } def fetch_workspace_package_jsons(instance) parsed_manifest = parsed_package_json(instance) return [] unless parsed_manifest["workspaces"] @@ -137,7 +140,10 @@ def fetch_workspace_package_jsons(instance) end end - sig { params(instance: Dependabot::Bun::FileFetcher, workspace_object: T.untyped).returns(T::Array[String]) } + sig do + params(instance: Dependabot::Javascript::Bun::FileFetcher, + workspace_object: T.untyped).returns(T::Array[String]) + end def workspace_paths(instance, workspace_object) paths_array = if workspace_object.is_a?(Hash) @@ -150,7 +156,7 @@ def workspace_paths(instance, workspace_object) paths_array.flat_map { |path| recursive_find_directories(instance, path) } end - sig { params(instance: Dependabot::Bun::FileFetcher, glob: String).returns(T::Array[String]) } + sig { params(instance: Dependabot::Javascript::Bun::FileFetcher, glob: String).returns(T::Array[String]) } def find_directories(instance, glob) return [glob] unless glob.include?("*") @@ -177,7 +183,10 @@ def matching_paths(glob, paths) paths.select { |filename| File.fnmatch?(glob, filename, File::FNM_PATHNAME) } end - sig { params(instance: Dependabot::Bun::FileFetcher, glob: String, prefix: String).returns(T::Array[String]) } + sig do + params(instance: Dependabot::Javascript::Bun::FileFetcher, glob: String, + prefix: String).returns(T::Array[String]) + end def recursive_find_directories(instance, glob, prefix = "") return [prefix + glob] unless glob.include?("*") @@ -196,7 +205,9 @@ def recursive_find_directories(instance, glob, prefix = "") matching_paths(prefix + glob, paths) end - sig { params(instance: Dependabot::Bun::FileFetcher, workspace: String).returns(T.nilable(DependencyFile)) } + sig do + params(instance: Dependabot::Javascript::Bun::FileFetcher, workspace: String).returns(T.nilable(DependencyFile)) + end def fetch_package_json_if_present(instance, workspace) file = File.join(workspace, MANIFEST_FILENAME) @@ -209,12 +220,12 @@ def fetch_package_json_if_present(instance, workspace) end end - sig { params(instance: Dependabot::Bun::FileFetcher).returns(DependencyFile) } + sig { params(instance: Dependabot::Javascript::Bun::FileFetcher).returns(DependencyFile) } def package_json(instance) @package_json ||= T.let(instance.fetch_file(Javascript::MANIFEST_FILENAME), T.nilable(DependencyFile)) end - sig { params(instance: Dependabot::Bun::FileFetcher).returns(T.untyped) } + sig { params(instance: Dependabot::Javascript::Bun::FileFetcher).returns(T.untyped) } def parsed_package_json(instance) manifest_file = package_json(instance) parsed = JSON.parse(T.must(manifest_file.content)) @@ -225,14 +236,18 @@ def parsed_package_json(instance) raise Dependabot::DependencyFileNotParseable, T.must(manifest_file).path end - sig { params(instance: Dependabot::Bun::FileFetcher, filename: String).returns(T.nilable(DependencyFile)) } + sig do + params(instance: Dependabot::Javascript::Bun::FileFetcher, filename: String).returns(T.nilable(DependencyFile)) + end def fetch_file_with_support(instance, filename) instance.fetch_file(filename).tap { |f| f.support_file = true } rescue Dependabot::DependencyFileNotFound nil end - sig { params(instance: Dependabot::Bun::FileFetcher, filename: String).returns(T.nilable(DependencyFile)) } + sig do + params(instance: Dependabot::Javascript::Bun::FileFetcher, filename: String).returns(T.nilable(DependencyFile)) + end def fetch_file_from_parent_directories(instance, filename) (1..instance.directory.split("/").count).each do |i| file = fetch_file_with_support(instance, ("../" * i) + filename) diff --git a/javascript/lib/dependabot/javascript/file_parser.rb b/javascript/lib/dependabot/javascript/file_parser.rb index 045fe64452..1d32c47c15 100644 --- a/javascript/lib/dependabot/javascript/file_parser.rb +++ b/javascript/lib/dependabot/javascript/file_parser.rb @@ -165,7 +165,7 @@ def workspace_catalog_dependencies dependency_set end - sig { abstract.returns(LockfileParser) } + sig { abstract.returns(FileParsers::LockfileParser) } def lockfile_parser; end sig { returns(Dependabot::FileParsers::Base::DependencySet) } diff --git a/javascript/lib/dependabot/javascript/file_parser/lockfile_parser.rb b/javascript/lib/dependabot/javascript/file_parser/lockfile_parser.rb index b8016dac69..abc39abf80 100644 --- a/javascript/lib/dependabot/javascript/file_parser/lockfile_parser.rb +++ b/javascript/lib/dependabot/javascript/file_parser/lockfile_parser.rb @@ -3,7 +3,7 @@ module Dependabot module Javascript - class FileParser + module FileParsers class LockfileParser extend T::Helpers extend T::Sig diff --git a/javascript/spec/dependabot/bun/file_fetcher_spec.rb b/javascript/spec/dependabot/bun/file_fetcher_spec.rb index d32f35d95d..fd62f82e2e 100644 --- a/javascript/spec/dependabot/bun/file_fetcher_spec.rb +++ b/javascript/spec/dependabot/bun/file_fetcher_spec.rb @@ -6,7 +6,7 @@ require "dependabot/bun" require_common_spec "file_fetchers/shared_examples_for_file_fetchers" -RSpec.describe Dependabot::Bun::FileFetcher do +RSpec.describe Dependabot::Javascript::Bun::FileFetcher do let(:json_header) { { "content-type" => "application/json" } } let(:credentials) do [Dependabot::Credential.new({ diff --git a/javascript/spec/dependabot/bun/file_parser/lockfile_parser_spec.rb b/javascript/spec/dependabot/bun/file_parser/lockfile_parser_spec.rb index c976bcb103..66e379b629 100644 --- a/javascript/spec/dependabot/bun/file_parser/lockfile_parser_spec.rb +++ b/javascript/spec/dependabot/bun/file_parser/lockfile_parser_spec.rb @@ -4,7 +4,7 @@ require "spec_helper" require "dependabot/bun" -RSpec.describe Dependabot::Bun::FileParser::LockfileParser do +RSpec.describe Dependabot::Javascript::Bun::FileParser::LockfileParser do subject(:lockfile_parser) do described_class.new(dependency_files: dependency_files) end diff --git a/javascript/spec/dependabot/bun/file_parser_spec.rb b/javascript/spec/dependabot/bun/file_parser_spec.rb index 7342e647c5..a5ade61530 100644 --- a/javascript/spec/dependabot/bun/file_parser_spec.rb +++ b/javascript/spec/dependabot/bun/file_parser_spec.rb @@ -4,7 +4,7 @@ require "spec_helper" require "dependabot/bun" -RSpec.describe Dependabot::Bun::FileParser do +RSpec.describe Dependabot::Javascript::Bun::FileParser do let(:credentials) do [Dependabot::Credential.new({ "type" => "git_source", diff --git a/javascript/spec/dependabot/bun/file_updater_spec.rb b/javascript/spec/dependabot/bun/file_updater_spec.rb index 1369d99f13..ecd9d94457 100644 --- a/javascript/spec/dependabot/bun/file_updater_spec.rb +++ b/javascript/spec/dependabot/bun/file_updater_spec.rb @@ -4,7 +4,7 @@ require "spec_helper" require "dependabot/bun" -RSpec.describe Dependabot::Bun::FileUpdater do +RSpec.describe Dependabot::Javascript::Bun::FileUpdater do let(:repo_contents_path) { nil } let(:tmp_path) { Dependabot::Utils::BUMP_TMP_DIR_PATH } let(:source) { nil } diff --git a/javascript/spec/dependabot/bun/package_manager_spec.rb b/javascript/spec/dependabot/bun/package_manager_spec.rb index 9ff2d32d61..66e9e77fca 100644 --- a/javascript/spec/dependabot/bun/package_manager_spec.rb +++ b/javascript/spec/dependabot/bun/package_manager_spec.rb @@ -4,7 +4,7 @@ require "spec_helper" require "dependabot/bun" -RSpec.describe Dependabot::Bun::PackageManager do +RSpec.describe Dependabot::Javascript::Bun::PackageManager do let(:package_manager) do described_class.new( detected_version: detected_version, diff --git a/javascript/spec/dependabot/bun/requirement_spec.rb b/javascript/spec/dependabot/bun/requirement_spec.rb index e08552108f..43e67426c1 100644 --- a/javascript/spec/dependabot/bun/requirement_spec.rb +++ b/javascript/spec/dependabot/bun/requirement_spec.rb @@ -4,7 +4,7 @@ require "spec_helper" require "dependabot/bun" -RSpec.describe Dependabot::Bun::Requirement do +RSpec.describe Dependabot::Javascript::Bun::Requirement do it "inherits initialization from Javascript::Requirement" do requirement = described_class.new("1.0.0") expect(requirement).to be_a(described_class) diff --git a/javascript/spec/dependabot/bun/version_spec.rb b/javascript/spec/dependabot/bun/version_spec.rb index feb43bb134..661417051d 100644 --- a/javascript/spec/dependabot/bun/version_spec.rb +++ b/javascript/spec/dependabot/bun/version_spec.rb @@ -4,7 +4,7 @@ require "spec_helper" require "dependabot/bun" -RSpec.describe Dependabot::Bun::Version do +RSpec.describe Dependabot::Javascript::Bun::Version do it "inherits initialization from Javascript::Version" do version = described_class.new("1.0.0") expect(version).to be_a(described_class) From 1a8f81d8c31b9b897bf7ac9523265063cdb75feb Mon Sep 17 00:00:00 2001 From: Kamil Bukum Date: Fri, 7 Feb 2025 14:33:33 -0800 Subject: [PATCH 02/15] move folder to apply rspec file path, module name rule --- javascript/lib/dependabot/{ => javascript}/bun/file_fetcher.rb | 0 javascript/lib/dependabot/{ => javascript}/bun/file_parser.rb | 0 .../lib/dependabot/{ => javascript}/bun/file_parser/bun_lock.rb | 0 .../{ => javascript}/bun/file_parser/lockfile_parser.rb | 0 javascript/lib/dependabot/{ => javascript}/bun/file_updater.rb | 0 .../{ => javascript}/bun/file_updater/lockfile_updater.rb | 0 javascript/lib/dependabot/{ => javascript}/bun/helpers.rb | 0 javascript/lib/dependabot/{ => javascript}/bun/package_manager.rb | 0 javascript/lib/dependabot/{ => javascript}/bun/requirement.rb | 0 javascript/lib/dependabot/{ => javascript}/bun/update_checker.rb | 0 .../bun/update_checker/conflicting_dependency_resolver.rb | 0 .../bun/update_checker/dependency_files_builder.rb | 0 .../{ => javascript}/bun/update_checker/latest_version_finder.rb | 0 .../{ => javascript}/bun/update_checker/library_detector.rb | 0 .../{ => javascript}/bun/update_checker/requirements_updater.rb | 0 .../bun/update_checker/subdependency_version_resolver.rb | 0 .../{ => javascript}/bun/update_checker/version_resolver.rb | 0 .../{ => javascript}/bun/update_checker/vulnerability_auditor.rb | 0 javascript/lib/dependabot/{ => javascript}/bun/version.rb | 0 .../spec/dependabot/{ => javascript}/bun/file_fetcher_spec.rb | 0 .../{ => javascript}/bun/file_parser/lockfile_parser_spec.rb | 0 .../spec/dependabot/{ => javascript}/bun/file_parser_spec.rb | 0 .../spec/dependabot/{ => javascript}/bun/file_updater_spec.rb | 0 .../spec/dependabot/{ => javascript}/bun/package_manager_spec.rb | 0 .../spec/dependabot/{ => javascript}/bun/requirement_spec.rb | 0 javascript/spec/dependabot/{ => javascript}/bun/version_spec.rb | 0 26 files changed, 0 insertions(+), 0 deletions(-) rename javascript/lib/dependabot/{ => javascript}/bun/file_fetcher.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/file_parser.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/file_parser/bun_lock.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/file_parser/lockfile_parser.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/file_updater.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/file_updater/lockfile_updater.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/helpers.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/package_manager.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/requirement.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/update_checker.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/update_checker/conflicting_dependency_resolver.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/update_checker/dependency_files_builder.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/update_checker/latest_version_finder.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/update_checker/library_detector.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/update_checker/requirements_updater.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/update_checker/subdependency_version_resolver.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/update_checker/version_resolver.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/update_checker/vulnerability_auditor.rb (100%) rename javascript/lib/dependabot/{ => javascript}/bun/version.rb (100%) rename javascript/spec/dependabot/{ => javascript}/bun/file_fetcher_spec.rb (100%) rename javascript/spec/dependabot/{ => javascript}/bun/file_parser/lockfile_parser_spec.rb (100%) rename javascript/spec/dependabot/{ => javascript}/bun/file_parser_spec.rb (100%) rename javascript/spec/dependabot/{ => javascript}/bun/file_updater_spec.rb (100%) rename javascript/spec/dependabot/{ => javascript}/bun/package_manager_spec.rb (100%) rename javascript/spec/dependabot/{ => javascript}/bun/requirement_spec.rb (100%) rename javascript/spec/dependabot/{ => javascript}/bun/version_spec.rb (100%) diff --git a/javascript/lib/dependabot/bun/file_fetcher.rb b/javascript/lib/dependabot/javascript/bun/file_fetcher.rb similarity index 100% rename from javascript/lib/dependabot/bun/file_fetcher.rb rename to javascript/lib/dependabot/javascript/bun/file_fetcher.rb diff --git a/javascript/lib/dependabot/bun/file_parser.rb b/javascript/lib/dependabot/javascript/bun/file_parser.rb similarity index 100% rename from javascript/lib/dependabot/bun/file_parser.rb rename to javascript/lib/dependabot/javascript/bun/file_parser.rb diff --git a/javascript/lib/dependabot/bun/file_parser/bun_lock.rb b/javascript/lib/dependabot/javascript/bun/file_parser/bun_lock.rb similarity index 100% rename from javascript/lib/dependabot/bun/file_parser/bun_lock.rb rename to javascript/lib/dependabot/javascript/bun/file_parser/bun_lock.rb diff --git a/javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb b/javascript/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb similarity index 100% rename from javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb rename to javascript/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb diff --git a/javascript/lib/dependabot/bun/file_updater.rb b/javascript/lib/dependabot/javascript/bun/file_updater.rb similarity index 100% rename from javascript/lib/dependabot/bun/file_updater.rb rename to javascript/lib/dependabot/javascript/bun/file_updater.rb diff --git a/javascript/lib/dependabot/bun/file_updater/lockfile_updater.rb b/javascript/lib/dependabot/javascript/bun/file_updater/lockfile_updater.rb similarity index 100% rename from javascript/lib/dependabot/bun/file_updater/lockfile_updater.rb rename to javascript/lib/dependabot/javascript/bun/file_updater/lockfile_updater.rb diff --git a/javascript/lib/dependabot/bun/helpers.rb b/javascript/lib/dependabot/javascript/bun/helpers.rb similarity index 100% rename from javascript/lib/dependabot/bun/helpers.rb rename to javascript/lib/dependabot/javascript/bun/helpers.rb diff --git a/javascript/lib/dependabot/bun/package_manager.rb b/javascript/lib/dependabot/javascript/bun/package_manager.rb similarity index 100% rename from javascript/lib/dependabot/bun/package_manager.rb rename to javascript/lib/dependabot/javascript/bun/package_manager.rb diff --git a/javascript/lib/dependabot/bun/requirement.rb b/javascript/lib/dependabot/javascript/bun/requirement.rb similarity index 100% rename from javascript/lib/dependabot/bun/requirement.rb rename to javascript/lib/dependabot/javascript/bun/requirement.rb diff --git a/javascript/lib/dependabot/bun/update_checker.rb b/javascript/lib/dependabot/javascript/bun/update_checker.rb similarity index 100% rename from javascript/lib/dependabot/bun/update_checker.rb rename to javascript/lib/dependabot/javascript/bun/update_checker.rb diff --git a/javascript/lib/dependabot/bun/update_checker/conflicting_dependency_resolver.rb b/javascript/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb similarity index 100% rename from javascript/lib/dependabot/bun/update_checker/conflicting_dependency_resolver.rb rename to javascript/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb diff --git a/javascript/lib/dependabot/bun/update_checker/dependency_files_builder.rb b/javascript/lib/dependabot/javascript/bun/update_checker/dependency_files_builder.rb similarity index 100% rename from javascript/lib/dependabot/bun/update_checker/dependency_files_builder.rb rename to javascript/lib/dependabot/javascript/bun/update_checker/dependency_files_builder.rb diff --git a/javascript/lib/dependabot/bun/update_checker/latest_version_finder.rb b/javascript/lib/dependabot/javascript/bun/update_checker/latest_version_finder.rb similarity index 100% rename from javascript/lib/dependabot/bun/update_checker/latest_version_finder.rb rename to javascript/lib/dependabot/javascript/bun/update_checker/latest_version_finder.rb diff --git a/javascript/lib/dependabot/bun/update_checker/library_detector.rb b/javascript/lib/dependabot/javascript/bun/update_checker/library_detector.rb similarity index 100% rename from javascript/lib/dependabot/bun/update_checker/library_detector.rb rename to javascript/lib/dependabot/javascript/bun/update_checker/library_detector.rb diff --git a/javascript/lib/dependabot/bun/update_checker/requirements_updater.rb b/javascript/lib/dependabot/javascript/bun/update_checker/requirements_updater.rb similarity index 100% rename from javascript/lib/dependabot/bun/update_checker/requirements_updater.rb rename to javascript/lib/dependabot/javascript/bun/update_checker/requirements_updater.rb diff --git a/javascript/lib/dependabot/bun/update_checker/subdependency_version_resolver.rb b/javascript/lib/dependabot/javascript/bun/update_checker/subdependency_version_resolver.rb similarity index 100% rename from javascript/lib/dependabot/bun/update_checker/subdependency_version_resolver.rb rename to javascript/lib/dependabot/javascript/bun/update_checker/subdependency_version_resolver.rb diff --git a/javascript/lib/dependabot/bun/update_checker/version_resolver.rb b/javascript/lib/dependabot/javascript/bun/update_checker/version_resolver.rb similarity index 100% rename from javascript/lib/dependabot/bun/update_checker/version_resolver.rb rename to javascript/lib/dependabot/javascript/bun/update_checker/version_resolver.rb diff --git a/javascript/lib/dependabot/bun/update_checker/vulnerability_auditor.rb b/javascript/lib/dependabot/javascript/bun/update_checker/vulnerability_auditor.rb similarity index 100% rename from javascript/lib/dependabot/bun/update_checker/vulnerability_auditor.rb rename to javascript/lib/dependabot/javascript/bun/update_checker/vulnerability_auditor.rb diff --git a/javascript/lib/dependabot/bun/version.rb b/javascript/lib/dependabot/javascript/bun/version.rb similarity index 100% rename from javascript/lib/dependabot/bun/version.rb rename to javascript/lib/dependabot/javascript/bun/version.rb diff --git a/javascript/spec/dependabot/bun/file_fetcher_spec.rb b/javascript/spec/dependabot/javascript/bun/file_fetcher_spec.rb similarity index 100% rename from javascript/spec/dependabot/bun/file_fetcher_spec.rb rename to javascript/spec/dependabot/javascript/bun/file_fetcher_spec.rb diff --git a/javascript/spec/dependabot/bun/file_parser/lockfile_parser_spec.rb b/javascript/spec/dependabot/javascript/bun/file_parser/lockfile_parser_spec.rb similarity index 100% rename from javascript/spec/dependabot/bun/file_parser/lockfile_parser_spec.rb rename to javascript/spec/dependabot/javascript/bun/file_parser/lockfile_parser_spec.rb diff --git a/javascript/spec/dependabot/bun/file_parser_spec.rb b/javascript/spec/dependabot/javascript/bun/file_parser_spec.rb similarity index 100% rename from javascript/spec/dependabot/bun/file_parser_spec.rb rename to javascript/spec/dependabot/javascript/bun/file_parser_spec.rb diff --git a/javascript/spec/dependabot/bun/file_updater_spec.rb b/javascript/spec/dependabot/javascript/bun/file_updater_spec.rb similarity index 100% rename from javascript/spec/dependabot/bun/file_updater_spec.rb rename to javascript/spec/dependabot/javascript/bun/file_updater_spec.rb diff --git a/javascript/spec/dependabot/bun/package_manager_spec.rb b/javascript/spec/dependabot/javascript/bun/package_manager_spec.rb similarity index 100% rename from javascript/spec/dependabot/bun/package_manager_spec.rb rename to javascript/spec/dependabot/javascript/bun/package_manager_spec.rb diff --git a/javascript/spec/dependabot/bun/requirement_spec.rb b/javascript/spec/dependabot/javascript/bun/requirement_spec.rb similarity index 100% rename from javascript/spec/dependabot/bun/requirement_spec.rb rename to javascript/spec/dependabot/javascript/bun/requirement_spec.rb diff --git a/javascript/spec/dependabot/bun/version_spec.rb b/javascript/spec/dependabot/javascript/bun/version_spec.rb similarity index 100% rename from javascript/spec/dependabot/bun/version_spec.rb rename to javascript/spec/dependabot/javascript/bun/version_spec.rb From fbbb975c0b4a558d6f0db77462e678f44e3a2d7e Mon Sep 17 00:00:00 2001 From: Kamil Bukum Date: Fri, 7 Feb 2025 14:36:55 -0800 Subject: [PATCH 03/15] fix linting issue --- .../bun/update_checker/conflicting_dependency_resolver.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb b/javascript/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb index d4898cd381..f86c8e3ffb 100644 --- a/javascript/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb +++ b/javascript/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb @@ -35,8 +35,8 @@ def conflicting_dependencies(dependency:, target_version:) # TODO: Look into using npm/arborist for parsing yarn lockfiles (there's currently partial yarn support) # # Prefer the npm conflicting dependency parser if there's both a npm lockfile and a yarn.lock file as the - # npm parser handles edge cases where the package.json is out of sync with the lockfile, something the yarn - # parser doesn't deal with at the moment. + # npm parser handles edge cases where the package.json is out of sync with the lockfile, + # something the yarn parser doesn't deal with at the moment. if dependency_files_builder.lockfiles.any? SharedHelpers.run_helper_subprocess( command: Dependabot::Javascript::NativeHelpers.helper_path, From 1f195a265f1aa2968f826f4f902cf4b22d0ebbc8 Mon Sep 17 00:00:00 2001 From: Kamil Bukum Date: Fri, 7 Feb 2025 14:40:28 -0800 Subject: [PATCH 04/15] fix file parser naming --- .../dependabot/javascript/bun/file_parser/lockfile_parser.rb | 2 +- .../lib/dependabot/javascript/file_parser/lockfile_parser.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb b/javascript/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb index 53fc73c004..6f265b7689 100644 --- a/javascript/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb +++ b/javascript/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb @@ -5,7 +5,7 @@ module Dependabot module Javascript module Bun class FileParser - class LockfileParser < Dependabot::Javascript::FileParsers::LockfileParser + class LockfileParser < Dependabot::Javascript::FileParser::LockfileParser extend T::Sig sig { override.returns(Dependabot::FileParsers::Base::DependencySet) } diff --git a/javascript/lib/dependabot/javascript/file_parser/lockfile_parser.rb b/javascript/lib/dependabot/javascript/file_parser/lockfile_parser.rb index abc39abf80..b8016dac69 100644 --- a/javascript/lib/dependabot/javascript/file_parser/lockfile_parser.rb +++ b/javascript/lib/dependabot/javascript/file_parser/lockfile_parser.rb @@ -3,7 +3,7 @@ module Dependabot module Javascript - module FileParsers + class FileParser class LockfileParser extend T::Helpers extend T::Sig From 573bc3b2c40545ad5a58cfa69aed991434c3ccbc Mon Sep 17 00:00:00 2001 From: Kamil Bukum Date: Fri, 7 Feb 2025 14:41:39 -0800 Subject: [PATCH 05/15] fix file parser naming --- javascript/lib/dependabot/javascript/file_parser.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/lib/dependabot/javascript/file_parser.rb b/javascript/lib/dependabot/javascript/file_parser.rb index 1d32c47c15..457287e7f2 100644 --- a/javascript/lib/dependabot/javascript/file_parser.rb +++ b/javascript/lib/dependabot/javascript/file_parser.rb @@ -165,7 +165,7 @@ def workspace_catalog_dependencies dependency_set end - sig { abstract.returns(FileParsers::LockfileParser) } + sig { abstract.returns(FileParser::LockfileParser) } def lockfile_parser; end sig { returns(Dependabot::FileParsers::Base::DependencySet) } From ea8fca874f38c8ed63b08aa9199ef069e21ba05c Mon Sep 17 00:00:00 2001 From: Kamil Bukum Date: Fri, 7 Feb 2025 14:43:47 -0800 Subject: [PATCH 06/15] fix file parser naming issue --- .../bun/file_parser/lockfile_parser.rb | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb diff --git a/javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb b/javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb new file mode 100644 index 0000000000..6f265b7689 --- /dev/null +++ b/javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb @@ -0,0 +1,48 @@ +# typed: strict +# frozen_string_literal: true + +module Dependabot + module Javascript + module Bun + class FileParser + class LockfileParser < Dependabot::Javascript::FileParser::LockfileParser + extend T::Sig + + sig { override.returns(Dependabot::FileParsers::Base::DependencySet) } + def parse_set + dependency_set = Dependabot::FileParsers::Base::DependencySet.new + + bun_locks.each do |file| + dependency_set += lockfile_for(file).dependencies + end + + dependency_set + end + + private + + sig { override.params(file: DependencyFile).returns(BunLock) } + def lockfile_for(file) + @lockfiles ||= T.let({}, T.nilable(T::Hash[String, BunLock])) + @lockfiles[file.name] ||= case file.name + when *bun_locks.map(&:name) + Bun::FileParser::BunLock.new(file) + else + raise "Unexpected lockfile: #{file.name}" + end + end + + sig { returns(T::Array[DependencyFile]) } + def bun_locks + @bun_locks ||= T.let(select_files_by_extension("bun.lock"), T.nilable(T::Array[DependencyFile])) + end + + sig { override.returns(T.class_of(Version)) } + def version_class + Version + end + end + end + end + end +end From 30fb6c69d34640b91ba97fe321833e3a37980004 Mon Sep 17 00:00:00 2001 From: Kamil Bukum Date: Fri, 7 Feb 2025 19:37:58 -0800 Subject: [PATCH 07/15] move shared code into shared folder and replace name space accordingly --- .../bun/file_parser/lockfile_parser.rb | 2 +- .../dependabot/javascript/bun/file_fetcher.rb | 22 +- .../dependabot/javascript/bun/file_parser.rb | 4 +- .../javascript/bun/file_parser/bun_lock.rb | 23 +- .../bun/file_parser/lockfile_parser.rb | 9 +- .../dependabot/javascript/bun/file_updater.rb | 6 +- .../bun/file_updater/lockfile_updater.rb | 4 +- .../javascript/bun/package_manager.rb | 4 +- .../dependabot/javascript/bun/requirement.rb | 2 +- .../javascript/bun/update_checker.rb | 2 +- .../conflicting_dependency_resolver.rb | 4 +- .../dependency_files_builder.rb | 2 +- .../update_checker/latest_version_finder.rb | 2 +- .../bun/update_checker/library_detector.rb | 2 +- .../update_checker/requirements_updater.rb | 4 +- .../subdependency_version_resolver.rb | 2 +- .../bun/update_checker/version_resolver.rb | 8 +- .../update_checker/vulnerability_auditor.rb | 2 +- .../lib/dependabot/javascript/bun/version.rb | 2 +- .../javascript/constraint_helper.rb | 357 -------------- .../javascript/dependency_files_filterer.rb | 151 ------ .../lib/dependabot/javascript/file_fetcher.rb | 261 ---------- .../javascript/file_fetcher_helper.rb | 260 ---------- .../lib/dependabot/javascript/file_parser.rb | 451 ----------------- .../javascript/file_parser/lockfile_parser.rb | 74 --- .../lib/dependabot/javascript/file_updater.rb | 171 ------- .../javascript/file_updater/npmrc_builder.rb | 392 --------------- .../file_updater/package_json_preparer.rb | 85 ---- .../file_updater/package_json_updater.rb | 374 --------------- .../lib/dependabot/javascript/language.rb | 43 -- .../dependabot/javascript/native_helpers.rb | 19 - .../javascript/package_manager_detector.rb | 70 --- .../lib/dependabot/javascript/package_name.rb | 116 ----- .../dependabot/javascript/registry_helper.rb | 188 -------- .../dependabot/javascript/registry_parser.rb | 91 ---- .../lib/dependabot/javascript/requirement.rb | 142 ------ .../javascript/shared/constraint_helper.rb | 359 ++++++++++++++ .../shared/dependency_files_filterer.rb | 153 ++++++ .../javascript/shared/file_fetcher.rb | 283 +++++++++++ .../javascript/shared/file_fetcher_helper.rb | 264 ++++++++++ .../javascript/shared/file_parser.rb | 454 ++++++++++++++++++ .../shared/file_parser/lockfile_parser.rb | 106 ++++ .../javascript/shared/file_updater.rb | 175 +++++++ .../shared/file_updater/npmrc_builder.rb | 394 +++++++++++++++ .../file_updater/package_json_preparer.rb | 87 ++++ .../file_updater/package_json_updater.rb | 376 +++++++++++++++ .../dependabot/javascript/shared/language.rb | 45 ++ .../javascript/shared/native_helpers.rb | 21 + .../shared/package_manager_detector.rb | 72 +++ .../javascript/shared/package_name.rb | 118 +++++ .../javascript/shared/registry_helper.rb | 190 ++++++++ .../javascript/shared/registry_parser.rb | 93 ++++ .../javascript/shared/requirement.rb | 144 ++++++ .../shared/sub_dependency_files_filterer.rb | 79 +++ .../dependency_files_builder.rb | 87 ++++ .../shared/update_checker/registry_finder.rb | 358 ++++++++++++++ .../dependabot/javascript/shared/version.rb | 137 ++++++ .../javascript/shared/version_selector.rb | 60 +++ .../sub_dependency_files_filterer.rb | 77 --- .../dependency_files_builder.rb | 85 ---- .../update_checker/registry_finder.rb | 356 -------------- .../lib/dependabot/javascript/version.rb | 135 ------ .../dependabot/javascript/version_selector.rb | 58 --- 63 files changed, 4107 insertions(+), 4010 deletions(-) delete mode 100644 javascript/lib/dependabot/javascript/constraint_helper.rb delete mode 100644 javascript/lib/dependabot/javascript/dependency_files_filterer.rb delete mode 100644 javascript/lib/dependabot/javascript/file_fetcher.rb delete mode 100644 javascript/lib/dependabot/javascript/file_fetcher_helper.rb delete mode 100644 javascript/lib/dependabot/javascript/file_parser.rb delete mode 100644 javascript/lib/dependabot/javascript/file_parser/lockfile_parser.rb delete mode 100644 javascript/lib/dependabot/javascript/file_updater.rb delete mode 100644 javascript/lib/dependabot/javascript/file_updater/npmrc_builder.rb delete mode 100644 javascript/lib/dependabot/javascript/file_updater/package_json_preparer.rb delete mode 100644 javascript/lib/dependabot/javascript/file_updater/package_json_updater.rb delete mode 100644 javascript/lib/dependabot/javascript/language.rb delete mode 100644 javascript/lib/dependabot/javascript/native_helpers.rb delete mode 100644 javascript/lib/dependabot/javascript/package_manager_detector.rb delete mode 100644 javascript/lib/dependabot/javascript/package_name.rb delete mode 100644 javascript/lib/dependabot/javascript/registry_helper.rb delete mode 100644 javascript/lib/dependabot/javascript/registry_parser.rb delete mode 100644 javascript/lib/dependabot/javascript/requirement.rb create mode 100644 javascript/lib/dependabot/javascript/shared/constraint_helper.rb create mode 100644 javascript/lib/dependabot/javascript/shared/dependency_files_filterer.rb create mode 100644 javascript/lib/dependabot/javascript/shared/file_fetcher.rb create mode 100644 javascript/lib/dependabot/javascript/shared/file_fetcher_helper.rb create mode 100644 javascript/lib/dependabot/javascript/shared/file_parser.rb create mode 100644 javascript/lib/dependabot/javascript/shared/file_parser/lockfile_parser.rb create mode 100644 javascript/lib/dependabot/javascript/shared/file_updater.rb create mode 100644 javascript/lib/dependabot/javascript/shared/file_updater/npmrc_builder.rb create mode 100644 javascript/lib/dependabot/javascript/shared/file_updater/package_json_preparer.rb create mode 100644 javascript/lib/dependabot/javascript/shared/file_updater/package_json_updater.rb create mode 100644 javascript/lib/dependabot/javascript/shared/language.rb create mode 100644 javascript/lib/dependabot/javascript/shared/native_helpers.rb create mode 100644 javascript/lib/dependabot/javascript/shared/package_manager_detector.rb create mode 100644 javascript/lib/dependabot/javascript/shared/package_name.rb create mode 100644 javascript/lib/dependabot/javascript/shared/registry_helper.rb create mode 100644 javascript/lib/dependabot/javascript/shared/registry_parser.rb create mode 100644 javascript/lib/dependabot/javascript/shared/requirement.rb create mode 100644 javascript/lib/dependabot/javascript/shared/sub_dependency_files_filterer.rb create mode 100644 javascript/lib/dependabot/javascript/shared/update_checker/dependency_files_builder.rb create mode 100644 javascript/lib/dependabot/javascript/shared/update_checker/registry_finder.rb create mode 100644 javascript/lib/dependabot/javascript/shared/version.rb create mode 100644 javascript/lib/dependabot/javascript/shared/version_selector.rb delete mode 100644 javascript/lib/dependabot/javascript/sub_dependency_files_filterer.rb delete mode 100644 javascript/lib/dependabot/javascript/update_checker/dependency_files_builder.rb delete mode 100644 javascript/lib/dependabot/javascript/update_checker/registry_finder.rb delete mode 100644 javascript/lib/dependabot/javascript/version.rb delete mode 100644 javascript/lib/dependabot/javascript/version_selector.rb diff --git a/javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb b/javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb index 6f265b7689..620ac8f05a 100644 --- a/javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb +++ b/javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb @@ -5,7 +5,7 @@ module Dependabot module Javascript module Bun class FileParser - class LockfileParser < Dependabot::Javascript::FileParser::LockfileParser + class LockfileParser < Dependabot::Javascript::Shared::FileParser::LockfileParser extend T::Sig sig { override.returns(Dependabot::FileParsers::Base::DependencySet) } diff --git a/javascript/lib/dependabot/javascript/bun/file_fetcher.rb b/javascript/lib/dependabot/javascript/bun/file_fetcher.rb index e8f3e121ac..e4c48379ce 100644 --- a/javascript/lib/dependabot/javascript/bun/file_fetcher.rb +++ b/javascript/lib/dependabot/javascript/bun/file_fetcher.rb @@ -4,8 +4,8 @@ module Dependabot module Javascript module Bun - class FileFetcher < Dependabot::FileFetchers::Base - include ::Dependabot::Javascript::FileFetcherHelper + class FileFetcher < Shared::FileFetcher + include Shared::FileFetcherHelper extend T::Sig extend T::Helpers @@ -41,24 +41,6 @@ def fetch_files fetched_files.uniq end - sig { params(filename: String, fetch_submodules: T::Boolean).returns(DependencyFile) } - def fetch_file(filename, fetch_submodules: false) - fetch_file_from_host(filename, fetch_submodules: fetch_submodules) - end - - sig do - params( - dir: T.any(Pathname, String), - ignore_base_directory: T::Boolean, - raise_errors: T::Boolean, - fetch_submodules: T::Boolean - ) - .returns(T::Array[T.untyped]) - end - def fetch_repo_contents(dir: ".", ignore_base_directory: false, raise_errors: true, fetch_submodules: false) - repo_contents(dir: dir, ignore_base_directory:, raise_errors:, fetch_submodules:) - end - private sig { returns(T::Array[DependencyFile]) } diff --git a/javascript/lib/dependabot/javascript/bun/file_parser.rb b/javascript/lib/dependabot/javascript/bun/file_parser.rb index 45a972fe5e..df546d9ab6 100644 --- a/javascript/lib/dependabot/javascript/bun/file_parser.rb +++ b/javascript/lib/dependabot/javascript/bun/file_parser.rb @@ -6,7 +6,7 @@ module Dependabot module Javascript module Bun - class FileParser < Dependabot::Javascript::FileParser + class FileParser < Shared::FileParser extend T::Sig sig { override.returns(Ecosystem) } @@ -15,7 +15,7 @@ def ecosystem Ecosystem.new( name: ECOSYSTEM, package_manager: PackageManager.new(detected_version:), - language: Javascript::Language.new(detected_version:) + language: Shared::Language.new(detected_version:) ), T.nilable(Ecosystem) ) diff --git a/javascript/lib/dependabot/javascript/bun/file_parser/bun_lock.rb b/javascript/lib/dependabot/javascript/bun/file_parser/bun_lock.rb index 85102a23f8..6f5da8fb8a 100644 --- a/javascript/lib/dependabot/javascript/bun/file_parser/bun_lock.rb +++ b/javascript/lib/dependabot/javascript/bun/file_parser/bun_lock.rb @@ -5,15 +5,19 @@ module Dependabot module Javascript module Bun class FileParser - class BunLock + class BunLock < Dependabot::Javascript::Shared::FileParser::Lockfile extend T::Sig sig { params(dependency_file: DependencyFile).void } def initialize(dependency_file) - @dependency_file = dependency_file + super + @parsed = T.let( + nil, + T.nilable(T::Hash[String, T.any(Integer, String, T::Array[String], T::Hash[String, String])]) + ) end - sig { returns(T::Hash[String, T.untyped]) } + sig { override.returns(T::Hash[String, T.untyped]) } def parsed @parsed ||= begin content = begin @@ -28,11 +32,11 @@ def parsed raise_invalid!("expected 'lockfileVersion' to be an integer") unless version.is_a?(Integer) raise_invalid!("expected 'lockfileVersion' to be >= 0") unless version >= 0 - T.let(content, T.untyped) + content end end - sig { returns(Dependabot::FileParsers::Base::DependencySet) } + sig { override.returns(Dependabot::FileParsers::Base::DependencySet) } def dependencies dependency_set = Dependabot::FileParsers::Base::DependencySet.new @@ -66,10 +70,15 @@ def dependencies end sig do - params(dependency_name: String, requirement: T.untyped, _manifest_name: String) + override + .params( + dependency_name: String, + requirement: T.untyped, + manifest_name: String + ) .returns(T.nilable(T::Hash[String, T.untyped])) end - def details(dependency_name, requirement, _manifest_name) + def details(dependency_name, requirement, manifest_name) # rubocop:disable Lint/UnusedMethodArgument packages = parsed["packages"] return unless packages.is_a?(Hash) diff --git a/javascript/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb b/javascript/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb index 6f265b7689..201f36fd01 100644 --- a/javascript/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb +++ b/javascript/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb @@ -5,9 +5,11 @@ module Dependabot module Javascript module Bun class FileParser - class LockfileParser < Dependabot::Javascript::FileParser::LockfileParser + class LockfileParser < Dependabot::Javascript::Shared::FileParser::LockfileParser extend T::Sig + DEFAULT_LOCKFILES = %w(bun.lock).freeze + sig { override.returns(Dependabot::FileParsers::Base::DependencySet) } def parse_set dependency_set = Dependabot::FileParsers::Base::DependencySet.new @@ -19,6 +21,11 @@ def parse_set dependency_set end + sig { override.returns(T::Array[String]) } + def default_lockfiles + DEFAULT_LOCKFILES + end + private sig { override.params(file: DependencyFile).returns(BunLock) } diff --git a/javascript/lib/dependabot/javascript/bun/file_updater.rb b/javascript/lib/dependabot/javascript/bun/file_updater.rb index abe1924553..8efb0eff9f 100644 --- a/javascript/lib/dependabot/javascript/bun/file_updater.rb +++ b/javascript/lib/dependabot/javascript/bun/file_updater.rb @@ -4,7 +4,7 @@ module Dependabot module Javascript module Bun - class FileUpdater < Javascript::FileUpdater + class FileUpdater < Shared::FileUpdater sig { override.returns(T::Array[Regexp]) } def self.updated_files_regex [ @@ -52,7 +52,7 @@ def updated_bun_lock_content(bun_lock) bun_lockfile_updater.updated_bun_lock_content(bun_lock) end - sig { returns(Dependabot::Javascript::Bun::FileUpdater::LockfileUpdater) } + sig { returns(Bun::FileUpdater::LockfileUpdater) } def bun_lockfile_updater @bun_lockfile_updater ||= T.let( LockfileUpdater.new( @@ -61,7 +61,7 @@ def bun_lockfile_updater repo_contents_path: repo_contents_path, credentials: credentials ), - T.nilable(Dependabot::Javascript::Bun::FileUpdater::LockfileUpdater) + T.nilable(Bun::FileUpdater::LockfileUpdater) ) end end diff --git a/javascript/lib/dependabot/javascript/bun/file_updater/lockfile_updater.rb b/javascript/lib/dependabot/javascript/bun/file_updater/lockfile_updater.rb index b3155a4b1e..b7b753a0c7 100644 --- a/javascript/lib/dependabot/javascript/bun/file_updater/lockfile_updater.rb +++ b/javascript/lib/dependabot/javascript/bun/file_updater/lockfile_updater.rb @@ -100,7 +100,7 @@ def write_final_package_json_files end def npmrc_content(bun_lock) - Javascript::FileUpdater::NpmrcBuilder.new( + Dependabot::Javascript::Shared::FileUpdater::NpmrcBuilder.new( credentials: credentials, dependency_files: dependency_files, dependencies: lockfile_dependencies(bun_lock) @@ -110,7 +110,7 @@ def npmrc_content(bun_lock) def updated_package_json_content(file) @updated_package_json_content ||= {} @updated_package_json_content[file.name] ||= - Javascript::FileUpdater::PackageJsonUpdater.new( + Dependabot::Javascript::Shared::FileUpdater::PackageJsonUpdater.new( package_json: file, dependencies: dependencies ).updated_package_json.content diff --git a/javascript/lib/dependabot/javascript/bun/package_manager.rb b/javascript/lib/dependabot/javascript/bun/package_manager.rb index fd62cf73ef..bf5419086f 100644 --- a/javascript/lib/dependabot/javascript/bun/package_manager.rb +++ b/javascript/lib/dependabot/javascript/bun/package_manager.rb @@ -11,8 +11,8 @@ class PackageManager < Ecosystem::VersionManager # In Bun 1.1.39, the lockfile format was changed from a binary bun.lockb to a text-based bun.lock. # https://bun.sh/blog/bun-lock-text-lockfile - MIN_SUPPORTED_VERSION = T.let(Version.new("1.1.39"), Javascript::Version) - SUPPORTED_VERSIONS = T.let([MIN_SUPPORTED_VERSION].freeze, T::Array[Javascript::Version]) + MIN_SUPPORTED_VERSION = T.let(Version.new("1.1.39"), Dependabot::Version) + SUPPORTED_VERSIONS = T.let([MIN_SUPPORTED_VERSION].freeze, T::Array[Dependabot::Version]) DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Version]) sig do diff --git a/javascript/lib/dependabot/javascript/bun/requirement.rb b/javascript/lib/dependabot/javascript/bun/requirement.rb index 23921d954c..7ee08b1264 100644 --- a/javascript/lib/dependabot/javascript/bun/requirement.rb +++ b/javascript/lib/dependabot/javascript/bun/requirement.rb @@ -4,7 +4,7 @@ module Dependabot module Javascript module Bun - class Requirement < Dependabot::Javascript::Requirement + class Requirement < Dependabot::Javascript::Shared::Requirement end end end diff --git a/javascript/lib/dependabot/javascript/bun/update_checker.rb b/javascript/lib/dependabot/javascript/bun/update_checker.rb index 6722e14f3b..b279fbcd44 100644 --- a/javascript/lib/dependabot/javascript/bun/update_checker.rb +++ b/javascript/lib/dependabot/javascript/bun/update_checker.rb @@ -414,7 +414,7 @@ def original_source(updated_dependency) sources = updated_dependency.requirements.map { |r| r.fetch(:source) }.uniq.compact .sort_by do |source| - Javascript::UpdateChecker::RegistryFinder.central_registry?(source[:url]) ? 1 : 0 + Dependabot::Javascript::Shared::UpdateChecker::RegistryFinder.central_registry?(source[:url]) ? 1 : 0 end sources.first diff --git a/javascript/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb b/javascript/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb index f86c8e3ffb..1341f441a7 100644 --- a/javascript/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb +++ b/javascript/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb @@ -39,13 +39,13 @@ def conflicting_dependencies(dependency:, target_version:) # something the yarn parser doesn't deal with at the moment. if dependency_files_builder.lockfiles.any? SharedHelpers.run_helper_subprocess( - command: Dependabot::Javascript::NativeHelpers.helper_path, + command: Dependabot::Javascript::Shared::NativeHelpers.helper_path, function: "npm:findConflictingDependencies", args: [Dir.pwd, dependency.name, target_version.to_s] ) else SharedHelpers.run_helper_subprocess( - command: Dependabot::Javascript::NativeHelpers.helper_path, + command: Dependabot::Javascript::Shared::NativeHelpers.helper_path, function: "yarn:findConflictingDependencies", args: [Dir.pwd, dependency.name, target_version.to_s] ) diff --git a/javascript/lib/dependabot/javascript/bun/update_checker/dependency_files_builder.rb b/javascript/lib/dependabot/javascript/bun/update_checker/dependency_files_builder.rb index bdca88ab83..2cbe204aff 100644 --- a/javascript/lib/dependabot/javascript/bun/update_checker/dependency_files_builder.rb +++ b/javascript/lib/dependabot/javascript/bun/update_checker/dependency_files_builder.rb @@ -5,7 +5,7 @@ module Dependabot module Javascript module Bun class UpdateChecker - class DependencyFilesBuilder < Dependabot::Javascript::UpdateChecker::DependencyFilesBuilder + class DependencyFilesBuilder < Shared::UpdateChecker::DependencyFilesBuilder extend T::Sig sig { returns(T::Array[Dependabot::DependencyFile]) } diff --git a/javascript/lib/dependabot/javascript/bun/update_checker/latest_version_finder.rb b/javascript/lib/dependabot/javascript/bun/update_checker/latest_version_finder.rb index 5bc105a2b9..c92438efe4 100644 --- a/javascript/lib/dependabot/javascript/bun/update_checker/latest_version_finder.rb +++ b/javascript/lib/dependabot/javascript/bun/update_checker/latest_version_finder.rb @@ -404,7 +404,7 @@ def registry_auth_headers end def registry_finder - @registry_finder ||= Javascript::UpdateChecker::RegistryFinder.new( + @registry_finder ||= Dependabot::Javascript::Shared::UpdateChecker::RegistryFinder.new( dependency: dependency, credentials: credentials, rc_file: npmrc_file diff --git a/javascript/lib/dependabot/javascript/bun/update_checker/library_detector.rb b/javascript/lib/dependabot/javascript/bun/update_checker/library_detector.rb index 7428cc613c..e93173b3cd 100644 --- a/javascript/lib/dependabot/javascript/bun/update_checker/library_detector.rb +++ b/javascript/lib/dependabot/javascript/bun/update_checker/library_detector.rb @@ -63,7 +63,7 @@ def parsed_package_json end def registry - Javascript::UpdateChecker::RegistryFinder.new( + Dependabot::Javascript::Shared::UpdateChecker::RegistryFinder.new( dependency: nil, credentials: credentials, rc_file: dependency_files.find { |f| f.name.end_with?(".npmrc") } diff --git a/javascript/lib/dependabot/javascript/bun/update_checker/requirements_updater.rb b/javascript/lib/dependabot/javascript/bun/update_checker/requirements_updater.rb index 1a14487c1e..f5e4606a6d 100644 --- a/javascript/lib/dependabot/javascript/bun/update_checker/requirements_updater.rb +++ b/javascript/lib/dependabot/javascript/bun/update_checker/requirements_updater.rb @@ -132,7 +132,7 @@ def widen_requirement(req) end def ruby_requirements(requirement_string) - Javascript::Requirement + Dependabot::Javascript::Shared::Requirement .requirements_array(requirement_string) end @@ -194,7 +194,7 @@ def update_greatest_version(old_version, version_to_be_permitted) end def version_class - Javascript::Version + Dependabot::Javascript::Shared::Version end end end diff --git a/javascript/lib/dependabot/javascript/bun/update_checker/subdependency_version_resolver.rb b/javascript/lib/dependabot/javascript/bun/update_checker/subdependency_version_resolver.rb index 5b15b4d55b..6bca2f7399 100644 --- a/javascript/lib/dependabot/javascript/bun/update_checker/subdependency_version_resolver.rb +++ b/javascript/lib/dependabot/javascript/bun/update_checker/subdependency_version_resolver.rb @@ -101,7 +101,7 @@ def updated_dependency def filtered_lockfiles @filtered_lockfiles ||= - Javascript::SubDependencyFilesFilterer.new( + Dependabot::Javascript::Shared::SubDependencyFilesFilterer.new( dependency_files: dependency_files, updated_dependencies: [updated_dependency] ).files_requiring_update diff --git a/javascript/lib/dependabot/javascript/bun/update_checker/version_resolver.rb b/javascript/lib/dependabot/javascript/bun/update_checker/version_resolver.rb index 6e1b860815..69d84934a5 100644 --- a/javascript/lib/dependabot/javascript/bun/update_checker/version_resolver.rb +++ b/javascript/lib/dependabot/javascript/bun/update_checker/version_resolver.rb @@ -191,14 +191,14 @@ def updated_monorepo_dependencies def types_package @types_package ||= begin - types_package_name = Javascript::PackageName.new(dependency.name).types_package_name + types_package_name = Dependabot::Javascript::Shared::PackageName.new(dependency.name).types_package_name top_level_dependencies.find { |d| types_package_name.to_s == d.name } if types_package_name end end def original_package @original_package ||= begin - original_package_name = Javascript::PackageName.new(dependency.name).library_name + original_package_name = Dependabot::Javascript::Shared::PackageName.new(dependency.name).library_name top_level_dependencies.find { |d| original_package_name.to_s == d.name } if original_package_name end end @@ -479,7 +479,7 @@ def top_level_dependencies def paths_requiring_update_check @paths_requiring_update_check ||= - Javascript::DependencyFilesFilterer.new( + Dependabot::Javascript::Shared::DependencyFilesFilterer.new( dependency_files: dependency_files, updated_dependencies: [dependency] ).paths_requiring_update_check @@ -515,7 +515,7 @@ def requirement_class end def version_regex - Dependabot::Javascript::Version::VERSION_PATTERN + Dependabot::Javascript::Shared::Version::VERSION_PATTERN end end end diff --git a/javascript/lib/dependabot/javascript/bun/update_checker/vulnerability_auditor.rb b/javascript/lib/dependabot/javascript/bun/update_checker/vulnerability_auditor.rb index 8f814f1a27..4c9e49ce47 100644 --- a/javascript/lib/dependabot/javascript/bun/update_checker/vulnerability_auditor.rb +++ b/javascript/lib/dependabot/javascript/bun/update_checker/vulnerability_auditor.rb @@ -69,7 +69,7 @@ def audit(dependency:, security_advisories:) end audit_result = SharedHelpers.run_helper_subprocess( - command: Javascript::NativeHelpers.helper_path, + command: Dependabot::Javascript::Shared::NativeHelpers.helper_path, function: "npm:vulnerabilityAuditor", args: [Dir.pwd, vuln_versions] ) diff --git a/javascript/lib/dependabot/javascript/bun/version.rb b/javascript/lib/dependabot/javascript/bun/version.rb index 7ff2df36c1..7f94439eae 100644 --- a/javascript/lib/dependabot/javascript/bun/version.rb +++ b/javascript/lib/dependabot/javascript/bun/version.rb @@ -4,7 +4,7 @@ module Dependabot module Javascript module Bun - class Version < Dependabot::Javascript::Version + class Version < Dependabot::Javascript::Shared::Version end end end diff --git a/javascript/lib/dependabot/javascript/constraint_helper.rb b/javascript/lib/dependabot/javascript/constraint_helper.rb deleted file mode 100644 index ea3a0a77da..0000000000 --- a/javascript/lib/dependabot/javascript/constraint_helper.rb +++ /dev/null @@ -1,357 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -module Dependabot - module Javascript - module ConstraintHelper - extend T::Sig - - # Regex Components for Semantic Versioning - DIGIT = "\\d+" # Matches a single number (e.g., "1") - PRERELEASE = "(?:-[a-zA-Z0-9.-]+)?" # Matches optional pre-release tag (e.g., "-alpha") - BUILD_METADATA = "(?:\\+[a-zA-Z0-9.-]+)?" # Matches optional build metadata (e.g., "+001") - - # Matches semantic versions: - VERSION = T.let("#{DIGIT}(?:\\.#{DIGIT}){0,2}#{PRERELEASE}#{BUILD_METADATA}".freeze, String) - - VERSION_REGEX = T.let(/^#{VERSION}$/, Regexp) - - # Base regex for SemVer (major.minor.patch[-prerelease][+build]) - # This pattern extracts valid semantic versioning strings based on the SemVer 2.0 specification. - SEMVER_REGEX = T.let(/ - (?\d+\.\d+\.\d+) # Match major.minor.patch (e.g., 1.2.3) - (?:-(?[a-zA-Z0-9.-]+))? # Optional prerelease (e.g., -alpha.1, -rc.1, -beta.5) - (?:\+(?[a-zA-Z0-9.-]+))? # Optional build metadata (e.g., +build.20231101, +exp.sha.5114f85) - /x, Regexp) - - # Full SemVer validation regex (ensures the entire string is a valid SemVer) - # This ensures the entire input strictly follows SemVer, without extra characters before/after. - SEMVER_VALIDATION_REGEX = T.let(/^#{SEMVER_REGEX}$/, Regexp) - - # SemVer constraint regex (supports package.json version constraints) - # This pattern ensures proper parsing of SemVer versions with optional operators. - SEMVER_CONSTRAINT_REGEX = T.let(/ - (?: (>=|<=|>|<|=|~|\^)\s*)? # Make operators optional (e.g., >=, ^, ~) - (\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?(?:\+[a-zA-Z0-9.-]+)?) # Match full SemVer versions - | (\*|latest) # Match wildcard (*) or 'latest' - /x, Regexp) - - # /(>=|<=|>|<|=|~|\^)\s*(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?(?:\+[a-zA-Z0-9.-]+)?)|(\*|latest)/ - - SEMVER_OPERATOR_REGEX = /^(>=|<=|>|<|~|\^|=)$/ - - # Constraint Types as Constants - CARET_CONSTRAINT_REGEX = T.let(/^\^\s*(#{VERSION})$/, Regexp) - TILDE_CONSTRAINT_REGEX = T.let(/^~\s*(#{VERSION})$/, Regexp) - EXACT_CONSTRAINT_REGEX = T.let(/^\s*(#{VERSION})$/, Regexp) - GREATER_THAN_EQUAL_REGEX = T.let(/^>=\s*(#{VERSION})$/, Regexp) - LESS_THAN_EQUAL_REGEX = T.let(/^<=\s*(#{VERSION})$/, Regexp) - GREATER_THAN_REGEX = T.let(/^>\s*(#{VERSION})$/, Regexp) - LESS_THAN_REGEX = T.let(/^<\s*(#{VERSION})$/, Regexp) - WILDCARD_REGEX = T.let(/^\*$/, Regexp) - LATEST_REGEX = T.let(/^latest$/, Regexp) - SEMVER_CONSTANTS = ["*", "latest"].freeze - - # Unified Regex for Valid Constraints - VALID_CONSTRAINT_REGEX = T.let(Regexp.union( - CARET_CONSTRAINT_REGEX, - TILDE_CONSTRAINT_REGEX, - EXACT_CONSTRAINT_REGEX, - GREATER_THAN_EQUAL_REGEX, - LESS_THAN_EQUAL_REGEX, - GREATER_THAN_REGEX, - LESS_THAN_REGEX, - WILDCARD_REGEX, - LATEST_REGEX - ).freeze, Regexp) - - # Extract unique constraints from the given constraint expression. - # @param constraint_expression [T.nilable(String)] The semver constraint expression. - # @return [T::Array[String]] The list of unique Ruby-compatible constraints. - sig do - params( - constraint_expression: T.nilable(String), - dependabot_versions: T.nilable(T::Array[Dependabot::Version]) - ) - .returns(T.nilable(T::Array[String])) - end - def self.extract_ruby_constraints(constraint_expression, dependabot_versions = nil) - parsed_constraints = parse_constraints(constraint_expression, dependabot_versions) - - return nil unless parsed_constraints - - parsed_constraints.filter_map { |parsed| parsed[:constraint] } - end - - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/CyclomaticComplexity - # rubocop:disable Metrics/MethodLength - # rubocop:disable Metrics/PerceivedComplexity - sig do - params(constraint_expression: T.nilable(String)) - .returns(T.nilable(T::Array[String])) - end - def self.split_constraints(constraint_expression) - normalized_constraint = constraint_expression&.strip - return [] if normalized_constraint.nil? || normalized_constraint.empty? - - # Split constraints by logical OR (`||`) - constraint_groups = normalized_constraint.split("||") - - # Split constraints by logical AND (`,`) - constraint_groups = constraint_groups.map do |or_constraint| - or_constraint.split(",").map(&:strip) - end.flatten - - constraint_groups = constraint_groups.map do |constraint| - tokens = constraint.split(/\s+/).map(&:strip) - - and_constraints = [] - - previous = T.let(nil, T.nilable(String)) - operator = T.let(false, T.nilable(T::Boolean)) - wildcard = T.let(false, T::Boolean) - - tokens.each do |token| - token = token.strip - next if token.empty? - - # Invalid constraint if wildcard and anything else - return nil if wildcard - - # If token is one of the operators (>=, <=, >, <, ~, ^, =) - if token.match?(SEMVER_OPERATOR_REGEX) - wildcard = false - operator = true - # If token is wildcard or latest - elsif token.match?(/(\*|latest)/) - and_constraints << token - wildcard = true - operator = false - # If token is exact version (e.g., "1.2.3") - elsif token.match(VERSION_REGEX) - and_constraints << if operator - "#{previous}#{token}" - else - token - end - wildcard = false - operator = false - # If token is a valid constraint (e.g., ">=1.2.3", "<=2.0.0") - elsif token.match(VALID_CONSTRAINT_REGEX) - return nil if operator - - and_constraints << token - - wildcard = false - operator = false - else - # invalid constraint - return nil - end - previous = token - end - and_constraints.uniq - end.flatten - constraint_groups if constraint_groups.any? - end - - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/CyclomaticComplexity - # rubocop:enable Metrics/MethodLength - # rubocop:enable Metrics/PerceivedComplexity - - # Find the highest version from the given constraint expression. - # @param constraint_expression [T.nilable(String)] The semver constraint expression. - # @return [T.nilable(String)] The highest version, or nil if no versions are available. - sig do - params( - constraint_expression: T.nilable(String), - dependabot_versions: T.nilable(T::Array[Dependabot::Version]) - ) - .returns(T.nilable(String)) - end - def self.find_highest_version_from_constraint_expression(constraint_expression, dependabot_versions = nil) - parsed_constraints = parse_constraints(constraint_expression, dependabot_versions) - - return nil unless parsed_constraints - - parsed_constraints - .filter_map { |parsed| parsed[:version] } # Extract all versions - .max_by { |version| Version.new(version) } - end - - # Parse all constraints (split by logical OR `||`) and convert to Ruby-compatible constraints. - # Return: - # - `nil` if the constraint expression is invalid - # - `[]` if the constraint expression is valid but represents "no constraints" - # - An array of hashes for valid constraints with details about the constraint and version - sig do - params( - constraint_expression: T.nilable(String), - dependabot_versions: T.nilable(T::Array[Dependabot::Version]) - ) - .returns(T.nilable(T::Array[T::Hash[Symbol, T.nilable(String)]])) - end - def self.parse_constraints(constraint_expression, dependabot_versions = nil) - splitted_constraints = split_constraints(constraint_expression) - - return unless splitted_constraints - - constraints = to_ruby_constraints_with_versions(splitted_constraints, dependabot_versions) - constraints - end - - sig do - params( - constraints: T::Array[String], - dependabot_versions: T.nilable(T::Array[Dependabot::Version]) - ).returns(T::Array[T::Hash[Symbol, T.nilable(String)]]) - end - def self.to_ruby_constraints_with_versions(constraints, dependabot_versions = []) - constraints.filter_map do |constraint| - parsed = to_ruby_constraint_with_version(constraint, dependabot_versions) - parsed if parsed - end.uniq - end - - # rubocop:disable Metrics/MethodLength - # rubocop:disable Metrics/PerceivedComplexity - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/CyclomaticComplexity - # Converts a semver constraint to a Ruby-compatible constraint and extracts the version, if available. - # @param constraint [String] The semver constraint to parse. - # @return [T.nilable(T::Hash[Symbol, T.nilable(String)])] Returns the Ruby-compatible constraint and the version, - # if available, or nil if the constraint is invalid. - # - # @example - # to_ruby_constraint_with_version("=1.2.3") # => { constraint: "=1.2.3", version: "1.2.3" } - # to_ruby_constraint_with_version("^1.2.3") # => { constraint: ">=1.2.3 <2.0.0", version: "1.2.3" } - # to_ruby_constraint_with_version("*") # => { constraint: nil, version: nil } - # to_ruby_constraint_with_version("invalid") # => nil - sig do - params( - constraint: String, - dependabot_versions: T.nilable(T::Array[Dependabot::Version]) - ) - .returns(T.nilable(T::Hash[Symbol, T.nilable(String)])) - end - def self.to_ruby_constraint_with_version(constraint, dependabot_versions = []) - return nil if constraint.empty? - - case constraint - when EXACT_CONSTRAINT_REGEX # Exact version, e.g., "1.2.3-alpha" - return unless Regexp.last_match - - full_version = Regexp.last_match(1) - { constraint: "=#{full_version}", version: full_version } - when CARET_CONSTRAINT_REGEX # Caret constraint, e.g., "^1.2.3" - return unless Regexp.last_match - - full_version = Regexp.last_match(1) - _, major, minor = version_components(full_version) - return nil if major.nil? - - ruby_constraint = - if major.to_i.zero? - minor.nil? ? ">=#{full_version} <1.0.0" : ">=#{full_version} <0.#{minor.to_i + 1}.0" - else - ">=#{full_version} <#{major.to_i + 1}.0.0" - end - { constraint: ruby_constraint, version: full_version } - when TILDE_CONSTRAINT_REGEX # Tilde constraint, e.g., "~1.2.3" - return unless Regexp.last_match - - full_version = Regexp.last_match(1) - _, major, minor = version_components(full_version) - ruby_constraint = - if minor.nil? - ">=#{full_version} <#{major.to_i + 1}.0.0" - else - ">=#{full_version} <#{major}.#{minor.to_i + 1}.0" - end - { constraint: ruby_constraint, version: full_version } - when GREATER_THAN_EQUAL_REGEX # Greater than or equal, e.g., ">=1.2.3" - - return unless Regexp.last_match && Regexp.last_match(1) - - found_version = highest_matching_version( - dependabot_versions, - T.must(Regexp.last_match(1)) - ) do |version, constraint_version| - version >= Version.new(constraint_version) - end - { constraint: ">=#{Regexp.last_match(1)}", version: found_version&.to_s } - when LESS_THAN_EQUAL_REGEX # Less than or equal, e.g., "<=1.2.3" - return unless Regexp.last_match - - full_version = Regexp.last_match(1) - { constraint: "<=#{full_version}", version: full_version } - when GREATER_THAN_REGEX # Greater than, e.g., ">1.2.3" - return unless Regexp.last_match && Regexp.last_match(1) - - found_version = highest_matching_version( - dependabot_versions, - T.must(Regexp.last_match(1)) - ) do |version, constraint_version| - version > Version.new(constraint_version) - end - { constraint: ">#{Regexp.last_match(1)}", version: found_version&.to_s } - when LESS_THAN_REGEX # Less than, e.g., "<1.2.3" - return unless Regexp.last_match && Regexp.last_match(1) - - found_version = highest_matching_version( - dependabot_versions, - T.must(Regexp.last_match(1)) - ) do |version, constraint_version| - version < Version.new(constraint_version) - end - { constraint: "<#{Regexp.last_match(1)}", version: found_version&.to_s } - when WILDCARD_REGEX # No specific constraint, resolves to the highest available version - { constraint: nil, version: dependabot_versions&.max&.to_s } - when LATEST_REGEX - { constraint: nil, version: dependabot_versions&.max&.to_s } # Resolves to the latest available version - end - end - - sig do - params( - dependabot_versions: T.nilable(T::Array[Dependabot::Version]), - constraint_version: String, - condition: T.proc.params(version: Dependabot::Version, constraint: Dependabot::Version).returns(T::Boolean) - ) - .returns(T.nilable(Dependabot::Version)) - end - def self.highest_matching_version(dependabot_versions, constraint_version, &condition) - return unless dependabot_versions&.any? - - # Returns the highest version that satisfies the condition, or nil if none. - dependabot_versions - .sort - .reverse - .find { |version| condition.call(version, Version.new(constraint_version)) } # rubocop:disable Performance/RedundantBlockCall - end - - # rubocop:enable Metrics/MethodLength - # rubocop:enable Metrics/PerceivedComplexity - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/CyclomaticComplexity - - # Parses a semantic version string into its components as per the SemVer spec - # Example: "1.2.3-alpha+001" → ["1.2.3", "1", "2", "3", "alpha", "001"] - sig { params(full_version: T.nilable(String)).returns(T.nilable(T::Array[String])) } - def self.version_components(full_version) - return [] if full_version.nil? - - match = full_version.match(SEMVER_VALIDATION_REGEX) - return [] unless match - - version = match[:version] - return [] unless version - - major, minor, patch = version.split(".") - [version, major, minor, patch, match[:prerelease], match[:build]].compact - end - end - end -end diff --git a/javascript/lib/dependabot/javascript/dependency_files_filterer.rb b/javascript/lib/dependabot/javascript/dependency_files_filterer.rb deleted file mode 100644 index 135379396b..0000000000 --- a/javascript/lib/dependabot/javascript/dependency_files_filterer.rb +++ /dev/null @@ -1,151 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -# lots of sub-projects that don't all have the same dependencies. -module Dependabot - module Javascript - class DependencyFilesFilterer - extend T::Sig - - sig { params(dependency_files: T::Array[DependencyFile], updated_dependencies: T::Array[Dependency]).void } - def initialize(dependency_files:, updated_dependencies:) - @dependency_files = dependency_files - @updated_dependencies = updated_dependencies - end - - sig { returns(T::Array[String]) } - def paths_requiring_update_check - @paths_requiring_update_check ||= T.let(fetch_paths_requiring_update_check, T.nilable(T::Array[String])) - end - - sig { returns(T::Array[Dependabot::DependencyFile]) } - def files_requiring_update - @files_requiring_update ||= T.let( - dependency_files.select do |file| - package_files_requiring_update.include?(file) || - package_required_lockfile?(file) || - workspaces_lockfile?(file) - end, T.nilable(T::Array[DependencyFile]) - ) - end - - sig { returns(T::Array[Dependabot::DependencyFile]) } - def package_files_requiring_update - @package_files_requiring_update ||= T.let( - dependency_files.select do |file| - dependency_manifest_requirements.include?(file.name) - end, T.nilable(T::Array[DependencyFile]) - ) - end - - private - - sig { returns(T::Array[DependencyFile]) } - attr_reader :dependency_files - - sig { returns(T::Array[Dependency]) } - attr_reader :updated_dependencies - - sig { returns(T::Array[String]) } - def fetch_paths_requiring_update_check - # if only a root lockfile exists, it tracks all dependencies - return [File.dirname(T.must(root_lockfile).name)] if lockfiles == [root_lockfile] - - package_files_requiring_update.map do |file| - File.dirname(file.name) - end - end - - sig { returns(T::Array[String]) } - def dependency_manifest_requirements - @dependency_manifest_requirements ||= T.let( - updated_dependencies.flat_map do |dep| - dep.requirements.map { |requirement| requirement[:file] } - end, T.nilable(T::Array[String]) - ) - end - - sig { params(lockfile: DependencyFile).returns(T::Boolean) } - def package_required_lockfile?(lockfile) - return false unless lockfile?(lockfile) - - package_files_requiring_update.any? do |package_file| - File.dirname(package_file.name) == File.dirname(lockfile.name) - end - end - - sig { params(lockfile: DependencyFile).returns(T::Boolean) } - def workspaces_lockfile?(lockfile) - return false unless ["yarn.lock", "package-lock.json", "pnpm-lock.yaml", "bun.lock"].include?(lockfile.name) - - return false unless parsed_root_package_json["workspaces"] || dependency_files.any? do |file| - file.name.end_with?("pnpm-workspace.yaml") && File.dirname(file.name) == File.dirname(lockfile.name) - end - - updated_dependencies_in_lockfile?(lockfile) - end - - sig { returns(T.nilable(DependencyFile)) } - def root_lockfile - @root_lockfile ||= T.let( - lockfiles.find do |file| - File.dirname(file.name) == "." - end, T.nilable(DependencyFile) - ) - end - - sig { returns(T::Array[DependencyFile]) } - def lockfiles - @lockfiles ||= T.let( - dependency_files.select do |file| - lockfile?(file) - end, T.nilable(T::Array[DependencyFile]) - ) - end - - sig { returns(T::Hash[String, T.untyped]) } - def parsed_root_package_json - @parsed_root_package_json ||= T.let( - begin - package = T.must(dependency_files.find { |f| f.name == "package.json" }) - JSON.parse(T.must(package.content)) - end, T.nilable(T::Hash[String, T.untyped]) - ) - end - - sig { params(lockfile: Dependabot::DependencyFile).returns(T::Boolean) } - def updated_dependencies_in_lockfile?(lockfile) - lockfile_dependencies(lockfile).any? do |sub_dep| - updated_dependencies.any? do |updated_dep| - sub_dep.name == updated_dep.name - end - end - end - - sig { params(lockfile: DependencyFile).returns(T::Array[Dependency]) } - def lockfile_dependencies(lockfile) - @lockfile_dependencies ||= T.let({}, T.nilable(T::Hash[String, T::Array[Dependency]])) - @lockfile_dependencies[lockfile.name] ||= - NpmAndYarn::FileParser::LockfileParser.new( - dependency_files: [lockfile] - ).parse - end - - sig { params(file: DependencyFile).returns(T::Boolean) } - def manifest?(file) - file.name.end_with?("package.json") - end - - sig { params(file: DependencyFile).returns(T::Boolean) } - def lockfile?(file) - file.name.end_with?( - "package-lock.json", - "yarn.lock", - "pnpm-lock.yaml", - "bun.lock", - "npm-shrinkwrap.json" - ) - end - end - end -end diff --git a/javascript/lib/dependabot/javascript/file_fetcher.rb b/javascript/lib/dependabot/javascript/file_fetcher.rb deleted file mode 100644 index 5b50d8bd23..0000000000 --- a/javascript/lib/dependabot/javascript/file_fetcher.rb +++ /dev/null @@ -1,261 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -module Dependabot - module Javascript - class FileFetcher < Dependabot::FileFetchers::Base - extend T::Sig - - abstract! - - PATH_DEPENDENCY_STARTS = T.let(%w(file: / ./ ../ ~/).freeze, [String, String, String, String, String]) - PATH_DEPENDENCY_CLEAN_REGEX = /^file:|^link:/ - DEPENDENCY_TYPES = T.let(%w(dependencies devDependencies optionalDependencies).freeze, T::Array[String]) - - sig { params(instance: Dependabot::Javascript::Bun::FileFetcher).returns(T::Array[DependencyFile]) } - def workspace_package_jsons(instance) - @workspace_package_jsons ||= T.let(fetch_workspace_package_jsons(instance), T.nilable(T::Array[DependencyFile])) - end - - sig do - params(instance: Dependabot::Javascript::Bun::FileFetcher, fetched_files: T::Array[DependencyFile]) - .returns(T::Array[DependencyFile]) - end - def path_dependencies(instance, fetched_files) - package_json_files = T.let([], T::Array[DependencyFile]) - - path_dependency_details(instance, fetched_files).each do |name, path| - # This happens with relative paths in the package-lock. Skipping it since it results - # in /package.json which is outside of the project directory. - next if path == "file:" - - path = path.gsub(PATH_DEPENDENCY_CLEAN_REGEX, "") - raise PathDependenciesNotReachable, "#{name} at #{path}" if path.start_with?("/") - - filename = path - filename = File.join(filename, MANIFEST_FILENAME) - cleaned_name = Pathname.new(filename).cleanpath.to_path - next if fetched_files.map(&:name).include?(cleaned_name) - - file = instance.fetch_file(filename, fetch_submodules: true) - package_json_files << file - end - - if package_json_files.any? - package_json_files += - path_dependencies(instance, fetched_files + package_json_files) - end - - package_json_files.tap { |fs| fs.each { |f| f.support_file = true } } - end - - sig do - params(instance: Dependabot::Javascript::Bun::FileFetcher, fetched_files: T::Array[DependencyFile]) - .returns(T::Array[[String, String]]) - end - def path_dependency_details(instance, fetched_files) - package_json_path_deps = T.let([], T::Array[[String, String]]) - - fetched_files.each do |file| - package_json_path_deps += - path_dependency_details_from_manifest(instance, file) - end - - [ - *package_json_path_deps - ].uniq - end - - # rubocop:disable Metrics/PerceivedComplexity - # rubocop:disable Metrics/AbcSize - sig do - params(instance: Dependabot::Javascript::Bun::FileFetcher, - file: DependencyFile).returns(T::Array[[String, String]]) - end - def path_dependency_details_from_manifest(instance, file) - return [] unless file.name.end_with?(MANIFEST_FILENAME) - - current_dir = file.name.rpartition("/").first - current_dir = nil if current_dir == "" - - current_depth = File.join(instance.directory, file.name).split("/").count { |path| !path.empty? } - path_to_directory = "../" * current_depth - - dep_types = DEPENDENCY_TYPES # TODO: Is this needed? - parsed_manifest = JSON.parse(T.must(file.content)) - dependency_objects = parsed_manifest.values_at(*dep_types).compact - # Fetch yarn "file:" path "resolutions" so the lockfile can be resolved - resolution_objects = parsed_manifest.values_at("resolutions").compact - manifest_objects = dependency_objects + resolution_objects - - raise Dependabot::DependencyFileNotParseable, file.path unless manifest_objects.all?(Hash) - - resolution_deps = resolution_objects.flat_map(&:to_a) - .map do |path, value| - # skip dependencies that contain invalid values such as inline comments, null, etc. - - unless value.is_a?(String) - Dependabot.logger.warn("File fetcher: Skipping dependency \"#{path}\" " \ - "with value: \"#{value}\"") - - next - end - - convert_dependency_path_to_name(path, value) - end - - path_starts = PATH_DEPENDENCY_STARTS - (dependency_objects.flat_map(&:to_a) + resolution_deps) - .select { |_, v| v.is_a?(String) && v.start_with?(*path_starts) } - .map do |name, path| - path = path.gsub(PATH_DEPENDENCY_CLEAN_REGEX, "") - raise PathDependenciesNotReachable, "#{name} at #{path}" if path.start_with?("/", "#{path_to_directory}..") - - path = File.join(current_dir, path) unless current_dir.nil? - [name, Pathname.new(path).cleanpath.to_path] - end - rescue JSON::ParserError - raise Dependabot::DependencyFileNotParseable, file.path - end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/PerceivedComplexity - - # Re-write the glob name to the targeted dependency name (which is used - # in the lockfile), for example "parent-package/**/sub-dep/target-dep" > - # "target-dep" - sig { params(path: String, value: String).returns([String, String]) } - def convert_dependency_path_to_name(path, value) - # Picking the last two parts that might include a scope - parts = path.split("/").last(2) - parts.shift if parts.count == 2 && !T.must(parts.first).start_with?("@") - [parts.join("/"), value] - end - - sig { params(instance: Dependabot::Javascript::Bun::FileFetcher).returns(T::Array[DependencyFile]) } - def fetch_workspace_package_jsons(instance) - parsed_manifest = parsed_package_json(instance) - return [] unless parsed_manifest["workspaces"] - - workspace_paths(instance, parsed_manifest["workspaces"]).filter_map do |workspace| - fetch_package_json_if_present(instance, workspace) - end - end - - sig do - params(instance: Dependabot::Javascript::Bun::FileFetcher, - workspace_object: T.untyped).returns(T::Array[String]) - end - def workspace_paths(instance, workspace_object) - paths_array = - if workspace_object.is_a?(Hash) - workspace_object.values_at("packages", "nohoist").flatten.compact - elsif workspace_object.is_a?(Array) then workspace_object - else - [] # Invalid lerna.json, which must not be in use - end - - paths_array.flat_map { |path| recursive_find_directories(instance, path) } - end - - sig { params(instance: Dependabot::Javascript::Bun::FileFetcher, glob: String).returns(T::Array[String]) } - def find_directories(instance, glob) - return [glob] unless glob.include?("*") - - unglobbed_path = - glob.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*") - .split("*") - .first&.gsub(%r{(?<=/)[^/]*$}, "") || "." - - dir = instance.directory.gsub(%r{(^/|/$)}, "") - - paths = - instance.fetch_repo_contents(dir: unglobbed_path, raise_errors: false) - .select { |file| file.type == "dir" } - .map { |f| f.path.gsub(%r{^/?#{Regexp.escape(dir)}/?}, "") } - - matching_paths(glob, paths) - end - - sig { params(glob: String, paths: T::Array[String]).returns(T::Array[String]) } - def matching_paths(glob, paths) - glob = glob.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*") - glob = "#{glob}/*" if glob.end_with?("**") - - paths.select { |filename| File.fnmatch?(glob, filename, File::FNM_PATHNAME) } - end - - sig do - params(instance: Dependabot::Javascript::Bun::FileFetcher, glob: String, - prefix: String).returns(T::Array[String]) - end - def recursive_find_directories(instance, glob, prefix = "") - return [prefix + glob] unless glob.include?("*") - - glob = glob.gsub(%r{^\./}, "") - glob_parts = glob.split("/") - - current_glob = glob_parts.first - paths = find_directories(instance, prefix + T.must(current_glob)) - next_parts = current_glob == "**" ? glob_parts : glob_parts.drop(1) - return paths if next_parts.empty? - - paths += paths.flat_map do |expanded_path| - recursive_find_directories(instance, next_parts.join("/"), "#{expanded_path}/") - end - - matching_paths(prefix + glob, paths) - end - - sig do - params(instance: Dependabot::Javascript::Bun::FileFetcher, workspace: String).returns(T.nilable(DependencyFile)) - end - def fetch_package_json_if_present(instance, workspace) - file = File.join(workspace, MANIFEST_FILENAME) - - begin - instance.fetch_file(file) - rescue Dependabot::DependencyFileNotFound - # Not all paths matched by a workspace glob may contain a package.json - # file. Ignore if that's the case - nil - end - end - - sig { params(instance: Dependabot::Javascript::Bun::FileFetcher).returns(DependencyFile) } - def package_json(instance) - @package_json ||= T.let(instance.fetch_file(Javascript::MANIFEST_FILENAME), T.nilable(DependencyFile)) - end - - sig { params(instance: Dependabot::Javascript::Bun::FileFetcher).returns(T.untyped) } - def parsed_package_json(instance) - manifest_file = package_json(instance) - parsed = JSON.parse(T.must(manifest_file.content)) - raise Dependabot::DependencyFileNotParseable, manifest_file.path unless parsed.is_a?(Hash) - - parsed - rescue JSON::ParserError - raise Dependabot::DependencyFileNotParseable, T.must(manifest_file).path - end - - sig do - params(instance: Dependabot::Javascript::Bun::FileFetcher, filename: String).returns(T.nilable(DependencyFile)) - end - def fetch_file_with_support(instance, filename) - instance.fetch_file(filename).tap { |f| f.support_file = true } - rescue Dependabot::DependencyFileNotFound - nil - end - - sig do - params(instance: Dependabot::Javascript::Bun::FileFetcher, filename: String).returns(T.nilable(DependencyFile)) - end - def fetch_file_from_parent_directories(instance, filename) - (1..instance.directory.split("/").count).each do |i| - file = fetch_file_with_support(instance, ("../" * i) + filename) - return file if file - end - nil - end - end - end -end diff --git a/javascript/lib/dependabot/javascript/file_fetcher_helper.rb b/javascript/lib/dependabot/javascript/file_fetcher_helper.rb deleted file mode 100644 index 112d403c12..0000000000 --- a/javascript/lib/dependabot/javascript/file_fetcher_helper.rb +++ /dev/null @@ -1,260 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -module Dependabot - module Javascript - module FileFetcherHelper - include Kernel - extend T::Sig - - PATH_DEPENDENCY_STARTS = T.let(%w(file: / ./ ../ ~/).freeze, [String, String, String, String, String]) - PATH_DEPENDENCY_CLEAN_REGEX = /^file:|^link:/ - DEPENDENCY_TYPES = T.let(%w(dependencies devDependencies optionalDependencies).freeze, T::Array[String]) - - sig { params(instance: Dependabot::Javascript::Bun::FileFetcher).returns(T::Array[DependencyFile]) } - def workspace_package_jsons(instance) - @workspace_package_jsons ||= T.let(fetch_workspace_package_jsons(instance), T.nilable(T::Array[DependencyFile])) - end - - sig do - params(instance: Dependabot::Javascript::Bun::FileFetcher, fetched_files: T::Array[DependencyFile]) - .returns(T::Array[DependencyFile]) - end - def path_dependencies(instance, fetched_files) - package_json_files = T.let([], T::Array[DependencyFile]) - - path_dependency_details(instance, fetched_files).each do |name, path| - # This happens with relative paths in the package-lock. Skipping it since it results - # in /package.json which is outside of the project directory. - next if path == "file:" - - path = path.gsub(PATH_DEPENDENCY_CLEAN_REGEX, "") - raise PathDependenciesNotReachable, "#{name} at #{path}" if path.start_with?("/") - - filename = path - filename = File.join(filename, MANIFEST_FILENAME) - cleaned_name = Pathname.new(filename).cleanpath.to_path - next if fetched_files.map(&:name).include?(cleaned_name) - - file = instance.fetch_file(filename, fetch_submodules: true) - package_json_files << file - end - - if package_json_files.any? - package_json_files += - path_dependencies(instance, fetched_files + package_json_files) - end - - package_json_files.tap { |fs| fs.each { |f| f.support_file = true } } - end - - sig do - params(instance: Dependabot::Javascript::Bun::FileFetcher, fetched_files: T::Array[DependencyFile]) - .returns(T::Array[[String, String]]) - end - def path_dependency_details(instance, fetched_files) - package_json_path_deps = T.let([], T::Array[[String, String]]) - - fetched_files.each do |file| - package_json_path_deps += - path_dependency_details_from_manifest(instance, file) - end - - [ - *package_json_path_deps - ].uniq - end - - # rubocop:disable Metrics/PerceivedComplexity - # rubocop:disable Metrics/AbcSize - sig do - params(instance: Dependabot::Javascript::Bun::FileFetcher, - file: DependencyFile).returns(T::Array[[String, String]]) - end - def path_dependency_details_from_manifest(instance, file) - return [] unless file.name.end_with?(MANIFEST_FILENAME) - - current_dir = file.name.rpartition("/").first - current_dir = nil if current_dir == "" - - current_depth = File.join(instance.directory, file.name).split("/").count { |path| !path.empty? } - path_to_directory = "../" * current_depth - - dep_types = DEPENDENCY_TYPES # TODO: Is this needed? - parsed_manifest = JSON.parse(T.must(file.content)) - dependency_objects = parsed_manifest.values_at(*dep_types).compact - # Fetch yarn "file:" path "resolutions" so the lockfile can be resolved - resolution_objects = parsed_manifest.values_at("resolutions").compact - manifest_objects = dependency_objects + resolution_objects - - raise Dependabot::DependencyFileNotParseable, file.path unless manifest_objects.all?(Hash) - - resolution_deps = resolution_objects.flat_map(&:to_a) - .map do |path, value| - # skip dependencies that contain invalid values such as inline comments, null, etc. - - unless value.is_a?(String) - Dependabot.logger.warn("File fetcher: Skipping dependency \"#{path}\" " \ - "with value: \"#{value}\"") - - next - end - - convert_dependency_path_to_name(path, value) - end - - path_starts = PATH_DEPENDENCY_STARTS - (dependency_objects.flat_map(&:to_a) + resolution_deps) - .select { |_, v| v.is_a?(String) && v.start_with?(*path_starts) } - .map do |name, path| - path = path.gsub(PATH_DEPENDENCY_CLEAN_REGEX, "") - raise PathDependenciesNotReachable, "#{name} at #{path}" if path.start_with?("/", "#{path_to_directory}..") - - path = File.join(current_dir, path) unless current_dir.nil? - [name, Pathname.new(path).cleanpath.to_path] - end - rescue JSON::ParserError - raise Dependabot::DependencyFileNotParseable, file.path - end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/PerceivedComplexity - - # Re-write the glob name to the targeted dependency name (which is used - # in the lockfile), for example "parent-package/**/sub-dep/target-dep" > - # "target-dep" - sig { params(path: String, value: String).returns([String, String]) } - def convert_dependency_path_to_name(path, value) - # Picking the last two parts that might include a scope - parts = path.split("/").last(2) - parts.shift if parts.count == 2 && !T.must(parts.first).start_with?("@") - [parts.join("/"), value] - end - - sig { params(instance: Dependabot::Javascript::Bun::FileFetcher).returns(T::Array[DependencyFile]) } - def fetch_workspace_package_jsons(instance) - parsed_manifest = parsed_package_json(instance) - return [] unless parsed_manifest["workspaces"] - - workspace_paths(instance, parsed_manifest["workspaces"]).filter_map do |workspace| - fetch_package_json_if_present(instance, workspace) - end - end - - sig do - params(instance: Dependabot::Javascript::Bun::FileFetcher, - workspace_object: T.untyped).returns(T::Array[String]) - end - def workspace_paths(instance, workspace_object) - paths_array = - if workspace_object.is_a?(Hash) - workspace_object.values_at("packages", "nohoist").flatten.compact - elsif workspace_object.is_a?(Array) then workspace_object - else - [] # Invalid lerna.json, which must not be in use - end - - paths_array.flat_map { |path| recursive_find_directories(instance, path) } - end - - sig { params(instance: Dependabot::Javascript::Bun::FileFetcher, glob: String).returns(T::Array[String]) } - def find_directories(instance, glob) - return [glob] unless glob.include?("*") - - unglobbed_path = - glob.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*") - .split("*") - .first&.gsub(%r{(?<=/)[^/]*$}, "") || "." - - dir = instance.directory.gsub(%r{(^/|/$)}, "") - - paths = - instance.fetch_repo_contents(dir: unglobbed_path, raise_errors: false) - .select { |file| file.type == "dir" } - .map { |f| f.path.gsub(%r{^/?#{Regexp.escape(dir)}/?}, "") } - - matching_paths(glob, paths) - end - - sig { params(glob: String, paths: T::Array[String]).returns(T::Array[String]) } - def matching_paths(glob, paths) - glob = glob.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*") - glob = "#{glob}/*" if glob.end_with?("**") - - paths.select { |filename| File.fnmatch?(glob, filename, File::FNM_PATHNAME) } - end - - sig do - params(instance: Dependabot::Javascript::Bun::FileFetcher, glob: String, - prefix: String).returns(T::Array[String]) - end - def recursive_find_directories(instance, glob, prefix = "") - return [prefix + glob] unless glob.include?("*") - - glob = glob.gsub(%r{^\./}, "") - glob_parts = glob.split("/") - - current_glob = glob_parts.first - paths = find_directories(instance, prefix + T.must(current_glob)) - next_parts = current_glob == "**" ? glob_parts : glob_parts.drop(1) - return paths if next_parts.empty? - - paths += paths.flat_map do |expanded_path| - recursive_find_directories(instance, next_parts.join("/"), "#{expanded_path}/") - end - - matching_paths(prefix + glob, paths) - end - - sig do - params(instance: Dependabot::Javascript::Bun::FileFetcher, workspace: String).returns(T.nilable(DependencyFile)) - end - def fetch_package_json_if_present(instance, workspace) - file = File.join(workspace, MANIFEST_FILENAME) - - begin - instance.fetch_file(file) - rescue Dependabot::DependencyFileNotFound - # Not all paths matched by a workspace glob may contain a package.json - # file. Ignore if that's the case - nil - end - end - - sig { params(instance: Dependabot::Javascript::Bun::FileFetcher).returns(DependencyFile) } - def package_json(instance) - @package_json ||= T.let(instance.fetch_file(Javascript::MANIFEST_FILENAME), T.nilable(DependencyFile)) - end - - sig { params(instance: Dependabot::Javascript::Bun::FileFetcher).returns(T.untyped) } - def parsed_package_json(instance) - manifest_file = package_json(instance) - parsed = JSON.parse(T.must(manifest_file.content)) - raise Dependabot::DependencyFileNotParseable, manifest_file.path unless parsed.is_a?(Hash) - - parsed - rescue JSON::ParserError - raise Dependabot::DependencyFileNotParseable, T.must(manifest_file).path - end - - sig do - params(instance: Dependabot::Javascript::Bun::FileFetcher, filename: String).returns(T.nilable(DependencyFile)) - end - def fetch_file_with_support(instance, filename) - instance.fetch_file(filename).tap { |f| f.support_file = true } - rescue Dependabot::DependencyFileNotFound - nil - end - - sig do - params(instance: Dependabot::Javascript::Bun::FileFetcher, filename: String).returns(T.nilable(DependencyFile)) - end - def fetch_file_from_parent_directories(instance, filename) - (1..instance.directory.split("/").count).each do |i| - file = fetch_file_with_support(instance, ("../" * i) + filename) - return file if file - end - nil - end - end - end -end diff --git a/javascript/lib/dependabot/javascript/file_parser.rb b/javascript/lib/dependabot/javascript/file_parser.rb deleted file mode 100644 index 457287e7f2..0000000000 --- a/javascript/lib/dependabot/javascript/file_parser.rb +++ /dev/null @@ -1,451 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -# See https://docs.npmjs.com/files/package.json for package.json format docs. - -module Dependabot - module Javascript - class FileParser < Dependabot::FileParsers::Base - extend T::Sig - - abstract! - - DEPENDENCY_TYPES = T.let(%w(dependencies devDependencies optionalDependencies).freeze, T::Array[String]) - GIT_URL_REGEX = %r{ - (?^|^git.*?|^github:|^bitbucket:|^gitlab:|github\.com/) - (?[a-z0-9-]+)/ - (?[a-z0-9_.-]+) - ( - (?:\#semver:(?.+))| - (?:\#(?=[\^~=<>*])(?.+))| - (?:\#(?.+)) - )?$ - }ix - RC_FILENAME = T.let(".npmrc", String) - - sig do - params( - json: T::Hash[String, T.untyped], - _block: T.proc.params(arg0: String, arg1: String, arg2: String).void - ) - .void - end - def self.each_dependency(json, &_block) - DEPENDENCY_TYPES.each do |type| - deps = json[type] || {} - deps.each do |name, requirement| - yield(name, requirement, type) - end - end - end - - sig { override.returns(T::Array[Dependency]) } - def parse # rubocop:disable Metrics/PerceivedComplexity - dependency_set = DependencySet.new - dependency_set += manifest_dependencies - dependency_set += lockfile_dependencies - dependency_set += workspace_catalog_dependencies if pnpm_workspace_yml - - dependencies = self.class.dependencies_with_all_versions_metadata(dependency_set) - - dependencies.reject do |dep| - reqs = dep.requirements - - # Ignore dependencies defined in support files, since we don't want PRs for those - support_reqs = reqs.select { |r| support_package_files.any? { |f| f.name == r[:file] } } - next true if support_reqs.any? - - # TODO: Currently, Dependabot can't handle dependencies that have both - # a git source *and* a non-git source. Fix that! - git_reqs = reqs.select { |r| r.dig(:source, :type) == "git" } - next false if git_reqs.none? - next true if git_reqs.map { |r| r.fetch(:source) }.uniq.count > 1 - - dep.requirements.any? { |r| r.dig(:source, :type) != "git" } - end - end - - sig { abstract.returns(Ecosystem) } - def ecosystem; end - - sig { params(dependency_set: Dependabot::FileParsers::Base::DependencySet).returns(T::Array[Dependency]) } - def self.dependencies_with_all_versions_metadata(dependency_set) - dependency_set.dependencies.map do |dependency| - dependency.metadata[:all_versions] = dependency_set.all_versions_for_name(dependency.name) - dependency - end - end - - private - - sig { abstract.returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) } - def lockfiles; end - - sig { abstract.returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) } - def registry_config_files; end - - sig { returns(T.untyped) } - def parsed_package_json - JSON.parse(T.must(package_json.content)) - rescue JSON::ParserError - raise Dependabot::DependencyFileNotParseable, package_json.path - end - - sig { returns(Dependabot::DependencyFile) } - def package_json - # Declare the instance variable with T.let and the correct type - @package_json ||= T.let( - T.must(dependency_files.find { |f| f.name == MANIFEST_FILENAME }), - T.nilable(Dependabot::DependencyFile) - ) - end - - sig { returns(T.nilable(Dependabot::DependencyFile)) } - def npmrc - @npmrc ||= T.let(dependency_files.find do |f| - f.name.end_with?(RC_FILENAME) - end, T.nilable(Dependabot::DependencyFile)) - end - - sig { returns(T.nilable(Dependabot::DependencyFile)) } - def pnpm_workspace_yml - nil - end - - sig { returns(Dependabot::FileParsers::Base::DependencySet) } - def manifest_dependencies - dependency_set = DependencySet.new - - package_files.each do |file| - json = JSON.parse(T.must(file.content)) - - # TODO: Currently, Dependabot can't handle flat dependency files - # (and will error at the FileUpdater stage, because the - # UpdateChecker doesn't take account of flat resolution). - next if json["flat"] - - self.class.each_dependency(json) do |name, requirement, type| - next unless requirement.is_a?(String) - - # Skip dependencies using Yarn workspace cross-references as requirements - next if requirement.start_with?("workspace:", "catalog:") - - requirement = "*" if requirement == "" - dep = build_dependency( - file: file, type: type, name: name, requirement: requirement - ) - dependency_set << dep if dep - end - end - - dependency_set - end - - sig { returns(Dependabot::FileParsers::Base::DependencySet) } - def workspace_catalog_dependencies - dependency_set = DependencySet.new - workspace_config = YAML.safe_load(T.must(pnpm_workspace_yml&.content), aliases: true) - - workspace_config["catalog"]&.each do |name, version| - dep = build_dependency( - file: T.must(pnpm_workspace_yml), type: "dependencies", name: name, requirement: version - ) - dependency_set << dep if dep - end - - workspace_config["catalogs"]&.each do |_, group_depenencies| - group_depenencies.each do |name, version| - dep = build_dependency( - file: T.must(pnpm_workspace_yml), type: "dependencies", name: name, requirement: version - ) - dependency_set << dep if dep - end - end - - dependency_set - end - - sig { abstract.returns(FileParser::LockfileParser) } - def lockfile_parser; end - - sig { returns(Dependabot::FileParsers::Base::DependencySet) } - def lockfile_dependencies - lockfile_parser.parse_set - end - - sig do - params(file: DependencyFile, type: T.untyped, name: String, requirement: String) - .returns(T.nilable(Dependency)) - end - def build_dependency(file:, type:, name:, requirement:) - lockfile_details = lockfile_parser.lockfile_details( - dependency_name: name, - requirement: requirement, - manifest_name: file.name - ) - version = version_for(requirement, lockfile_details) - converted_version = T.let(if version.nil? - nil - elsif version.is_a?(String) - version - else - Dependabot::Version.new(version) - end, T.nilable(T.any(String, Dependabot::Version))) - - return if lockfile_details && !version - return if ignore_requirement?(requirement) - return if workspace_package_names.include?(name) - - # TODO: Handle aliased packages: - # https://github.com/dependabot/dependabot-core/pull/1115 - # - # Ignore dependencies with an alias in the name - # Example: "my-fetch-factory@npm:fetch-factory" - return if aliased_package_name?(name) - - Dependency.new( - name: name, - version: converted_version, - package_manager: ecosystem.name, - requirements: [{ - requirement: requirement_for(requirement), - file: file.name, - groups: [type], - source: source_for(name, requirement, lockfile_details) - }] - ) - end - - sig { override.void } - def check_required_files - return if get_original_file(MANIFEST_FILENAME) - - raise DependencyFileNotFound.new(nil, - "#{MANIFEST_FILENAME} not found.") - end - - sig { params(requirement: String).returns(T::Boolean) } - def ignore_requirement?(requirement) - return true if local_path?(requirement) - return true if non_git_url?(requirement) - - # TODO: Handle aliased packages: - # https://github.com/dependabot/dependabot-core/pull/1115 - alias_package?(requirement) - end - - sig { params(requirement: String).returns(T::Boolean) } - def local_path?(requirement) - requirement.start_with?("link:", "file:", "/", "./", "../", "~/") - end - - sig { params(requirement: String).returns(T::Boolean) } - def alias_package?(requirement) - requirement.start_with?("npm:") - end - - sig { params(requirement: String).returns(T::Boolean) } - def non_git_url?(requirement) - requirement.include?("://") && !git_url?(requirement) - end - - sig { params(requirement: String).returns(T::Boolean) } - def git_url?(requirement) - requirement.match?(GIT_URL_REGEX) - end - - sig { params(requirement: String).returns(T::Boolean) } - def git_url_with_semver?(requirement) - return false unless git_url?(requirement) - - !T.must(requirement.match(GIT_URL_REGEX)).named_captures.fetch("semver").nil? - end - - sig { params(name: String).returns(T::Boolean) } - def aliased_package_name?(name) - name.include?("@npm:") - end - - sig { returns(T::Array[String]) } - def workspace_package_names - @workspace_package_names ||= T.let(package_files.filter_map do |f| - JSON.parse(T.must(f.content))["name"] - end, T.nilable(T::Array[String])) - end - - sig do - params(requirement: String, lockfile_details: T.nilable(T::Hash[String, T.untyped])) - .returns(T.nilable(T.any(String, Integer, Gem::Version))) - end - def version_for(requirement, lockfile_details) - if git_url_with_semver?(requirement) - semver_version = lockfile_version_for(lockfile_details) - return semver_version if semver_version - - git_revision = git_revision_for(lockfile_details) - version_from_git_revision(requirement, git_revision) || git_revision - elsif git_url?(requirement) - git_revision_for(lockfile_details) - elsif lockfile_details - lockfile_version_for(lockfile_details) - else - exact_version = exact_version_for(requirement) - return unless exact_version - - semver_version_for(exact_version) - end - end - - sig { params(lockfile_details: T.nilable(T::Hash[String, T.untyped])).returns(T.nilable(String)) } - def git_revision_for(lockfile_details) - version = T.cast(lockfile_details&.fetch("version", nil), T.nilable(String)) - resolved = T.cast(lockfile_details&.fetch("resolved", nil), T.nilable(String)) - [ - version&.split("#")&.last, - resolved&.split("#")&.last, - resolved&.split("/")&.last - ].find { |str| commit_sha?(str) } - end - - sig { params(string: T.nilable(String)).returns(T::Boolean) } - def commit_sha?(string) - return false unless string.is_a?(String) - - string.match?(/^[0-9a-f]{40}$/) - end - - sig { params(requirement: String, git_revision: T.nilable(String)).returns(T.nilable(String)) } - def version_from_git_revision(requirement, git_revision) - tags = - Dependabot::GitMetadataFetcher.new( - url: git_source_for(requirement).fetch(:url), - credentials: credentials - ).tags - .select { |t| [t.commit_sha, t.tag_sha].include?(git_revision) } - - tags.each do |t| - next unless t.name.match?(Dependabot::GitCommitChecker::VERSION_REGEX) - - version = T.must(t.name.match(Dependabot::GitCommitChecker::VERSION_REGEX)) - .named_captures.fetch("version") - next unless version_class.correct?(version) - - return version - end - - nil - rescue Dependabot::GitDependenciesNotReachable - nil - end - - sig do - params(lockfile_details: T.nilable(T::Hash[String, T.untyped])) - .returns(T.nilable(T.any(String, Integer, Gem::Version))) - end - def lockfile_version_for(lockfile_details) - semver_version_for(lockfile_details&.fetch("version", "")) - end - - sig { params(version: T.nilable(String)).returns(T.nilable(T.any(String, Integer, Gem::Version))) } - def semver_version_for(version) - version_class.semver_for(version) - end - - sig { params(requirement: String).returns(T.nilable(String)) } - def exact_version_for(requirement) - req = requirement_class.new([requirement]) - return unless req.exact? - - req.requirements.first.last.to_s - rescue Gem::Requirement::BadRequirementError - # If it doesn't parse, it's definitely not exact - end - - sig do - params(name: String, requirement: String, lockfile_details: T.nilable(T::Hash[String, T.untyped])) - .returns(T.nilable(T::Hash[Symbol, T.untyped])) - end - def source_for(name, requirement, lockfile_details) - return git_source_for(requirement) if git_url?(requirement) - - resolved_url = lockfile_details&.fetch("resolved", nil) - - resolution = lockfile_details&.fetch("resolution", nil) - package_match = resolution&.match(/__archiveUrl=(?.+)/) - resolved_url = CGI.unescape(package_match.named_captures.fetch("package_url", "")) if package_match - - return unless resolved_url - return unless resolved_url.start_with?("http") - return if resolved_url.match?(/(?\S+):registry\s*=\s*(?\S+)/ - - sig do - params( - dependency_files: T::Array[Dependabot::DependencyFile], - credentials: T::Array[Dependabot::Credential], - dependencies: T::Array[Dependabot::Dependency] - ).void - end - def initialize(dependency_files:, credentials:, dependencies: []) - @dependency_files = dependency_files - @credentials = credentials - @dependencies = dependencies - end - - # PROXY WORK - sig { returns(String) } - def npmrc_content - initial_content = - if npmrc_file then complete_npmrc_from_credentials - else - build_npmrc_content_from_lockfile - end - - final_content = initial_content || "" - - return final_content unless registry_credentials.any? - - credential_lines_for_npmrc.each do |credential_line| - next if final_content.include?(credential_line) - - final_content = [final_content, credential_line].reject(&:empty?).join("\n") - end - - final_content - end - - private - - sig { returns(T::Array[Dependabot::DependencyFile]) } - attr_reader :dependency_files - - sig { returns(T::Array[Dependabot::Credential]) } - attr_reader :credentials - - sig { returns(T::Array[Dependabot::Dependency]) } - attr_reader :dependencies - - sig { returns(T.nilable(String)) } - def build_npmrc_content_from_lockfile - return unless yarn_lock || package_lock || shrinkwrap - return unless global_registry - - registry = T.must(global_registry)["registry"] - registry = "https://#{registry}" unless registry&.start_with?("http") - "registry = #{registry}\n" \ - "#{npmrc_global_registry_auth_line}" \ - "always-auth = true" - end - - sig { returns(T.nilable(String)) } - def build_yarnrc_content_from_lockfile - return unless yarn_lock || package_lock - return unless global_registry - - "registry \"https://#{T.must(global_registry)['registry']}\"\n" \ - "#{yarnrc_global_registry_auth_line}" \ - "npmAlwaysAuth: true" - end - - # rubocop:disable Metrics/PerceivedComplexity - # rubocop:disable Metrics/CyclomaticComplexity - # rubocop:disable Metrics/AbcSize - sig { returns(T.nilable(Dependabot::Credential)) } - def global_registry - return @global_registry if defined?(@global_registry) - - @global_registry = T.let( - registry_credentials.find do |cred| - next false if CENTRAL_REGISTRIES.include?(cred["registry"]) - - # If all the URLs include this registry, it's global - next true if dependency_urls&.size&.positive? && dependency_urls&.all? do |url| - url.include?(T.must(cred["registry"])) - end - - # Check if this registry has already been defined in .npmrc as a scoped registry - next false if npmrc_scoped_registries&.any? { |sr| sr.include?(T.must(cred["registry"])) } - - next false if yarnrc_scoped_registries&.any? { |sr| sr.include?(T.must(cred["registry"])) } - - # If any unscoped URLs include this registry, assume it's global - dependency_urls - &.reject { |u| u.include?("@") || u.include?("%40") } - &.any? { |url| url.include?(T.must(cred["registry"])) } - end, - T.nilable(Dependabot::Credential) - ) - end - # rubocop:enable Metrics/PerceivedComplexity - # rubocop:enable Metrics/CyclomaticComplexity - # rubocop:enable Metrics/AbcSize - - sig { returns(String) } - def npmrc_global_registry_auth_line - # This token is passed in from the Dependabot Config - # We write it to the .npmrc file so that it can be used by the VulnerabilityAuditor - token = global_registry&.fetch("token", nil) - return "" unless token - - auth_line(token, global_registry&.fetch("registry")) + "\n" - end - - sig { returns(String) } - def yarnrc_global_registry_auth_line - token = global_registry&.fetch("token", nil) - return "" unless token - - if token.include?(":") - encoded_token = Base64.encode64(token).delete("\n") - "npmAuthIdent: \"#{encoded_token}\"" - elsif Base64.decode64(token).ascii_only? && - Base64.decode64(token).include?(":") - "npmAuthIdent: \"#{token.delete("\n")}\"" - else - "npmAuthToken: \"#{token}\"" - end - end - - sig { returns(T.nilable(T::Array[String])) } - def dependency_urls - return @dependency_urls if defined?(@dependency_urls) - - @dependency_urls = [] - - if dependencies.any? - @dependency_urls = dependencies.map do |dependency| - UpdateChecker::RegistryFinder.new( - dependency: dependency, - credentials: credentials, - rc_file: npmrc_file - ).dependency_url - end - return @dependency_urls - end - - # The registry URL for Bintray goes into the lockfile in a - # modified format, so we modify it back before checking against - # our credentials - @dependency_urls = T.let( - @dependency_urls.map do |url| - url.gsub("dl.bintray.com//", "api.bintray.com/npm/") - end, - T.nilable(T::Array[String]) - ) - end - - sig { returns(String) } - def complete_npmrc_from_credentials - # removes attribute timeout to allow for job update, - # having a timeout=xxxxx value is causing some jobs to fail - initial_content = T.must(T.must(npmrc_file).content) - .gsub(/^.*\$\{.*\}.*/, "").strip.gsub(/^timeout.*/, "").strip + "\n" - - return initial_content unless yarn_lock || package_lock - return initial_content unless global_registry - - registry = T.must(global_registry)["registry"] - registry = "https://#{registry}" unless registry&.start_with?("http") - initial_content + - "registry = #{registry}\n" \ - "#{npmrc_global_registry_auth_line}" \ - "always-auth = true\n" - end - - sig { returns(String) } - def complete_yarnrc_from_credentials - initial_content = T.must(T.must(yarnrc_file).content) - .gsub(/^.*\$\{.*\}.*/, "").strip + "\n" - return initial_content unless yarn_lock || package_lock - return initial_content unless global_registry - - initial_content + - "registry: \"https://#{T.must(global_registry)['registry']}\"\n" \ - "#{yarnrc_global_registry_auth_line}" \ - "npmAlwaysAuth: true\n" - end - - sig { returns(T.nilable(String)) } - def build_npmrc_from_yarnrc - yarnrc_global_registry = - yarnrc_file&.content - &.lines - &.find { |line| line.match?(/^\s*registry\s/) } - &.match(UpdateChecker::RegistryFinder::YARN_GLOBAL_REGISTRY_REGEX) - &.named_captures&.fetch("registry") - - return "registry = #{yarnrc_global_registry}\n" if yarnrc_global_registry - - build_npmrc_content_from_lockfile - end - - sig { returns(T.nilable(String)) } - def build_yarnrc_from_yarnrc - yarnrc_global_registry = - yarnrc_file&.content - &.lines - &.find { |line| line.match?(/^\s*registry\s/) } - &.match(/^\s*registry\s+"(?[^"]+)"/) - &.named_captures&.fetch("registry") - - return "registry \"#{yarnrc_global_registry}\"\n" if yarnrc_global_registry - - build_yarnrc_content_from_lockfile - end - - sig { returns(T::Array[String]) } - def credential_lines_for_npmrc - lines = T.let([], T::Array[String]) - registry_credentials.each do |cred| - registry = cred.fetch("registry") - - lines += T.must(registry_scopes(registry)) if registry_scopes(registry) - - token = cred.fetch("token", nil) - next unless token - - lines << auth_line(token, registry) - end - - return lines unless lines.any? { |str| str.include?("auth=") } - - # Work around a suspected yarn bug - ["always-auth = true"] + lines - end - - sig { params(token: String, registry: T.nilable(String)).returns(String) } - def auth_line(token, registry = nil) - auth = if token.include?(":") - encoded_token = Base64.encode64(token).delete("\n") - "_auth=#{encoded_token}" - elsif Base64.decode64(token).ascii_only? && - Base64.decode64(token).include?(":") - "_auth=#{token.delete("\n")}" - else - "_authToken=#{token}" - end - - return auth unless registry - - # We need to ensure the registry uri ends with a trailing slash in the npmrc file - # but we do not want to add one if it already exists - registry_with_trailing_slash = registry.sub(%r{\/?$}, "/") - - "//#{registry_with_trailing_slash}:#{auth}" - end - - sig { returns(T.nilable(T::Array[String])) } - def npmrc_scoped_registries - return [] unless npmrc_file - - @npmrc_scoped_registries ||= T.let( - T.must(T.must(npmrc_file).content).lines.select { |line| line.match?(SCOPED_REGISTRY) } - .filter_map { |line| line.match(SCOPED_REGISTRY)&.named_captures&.fetch("registry") }, - T.nilable(T::Array[String]) - ) - end - - sig { returns(T.nilable(T::Array[String])) } - def yarnrc_scoped_registries - return [] unless yarnrc_file - - @yarnrc_scoped_registries ||= T.let( - T.must(T.must(yarnrc_file).content).lines.select { |line| line.match?(SCOPED_REGISTRY) } - .filter_map { |line| line.match(SCOPED_REGISTRY)&.named_captures&.fetch("registry") }, - T.nilable(T::Array[String]) - ) - end - - # rubocop:disable Metrics/PerceivedComplexity - sig { params(registry: String).returns(T.nilable(T::Array[String])) } - def registry_scopes(registry) - # Central registries don't just apply to scopes - return if CENTRAL_REGISTRIES.include?(registry) - return unless dependency_urls - - other_regs = - registry_credentials.map { |c| c.fetch("registry") } - - [registry] - affected_urls = - dependency_urls - &.select do |url| - next false unless url.include?(registry) - - other_regs.none? { |r| r.include?(registry) && url.include?(r) } - end - - scopes = T.must(affected_urls).map do |url| - url.split(/\%40|@/)[1]&.split(%r{\%2[fF]|/})&.first - end.uniq - - # Registry used for unscoped packages - return if scopes.include?(nil) - - scopes.map { |scope| "@#{scope}:registry=https://#{registry}" } - end - # rubocop:enable Metrics/PerceivedComplexity - - sig { returns(T::Array[Dependabot::Credential]) } - def registry_credentials - credentials.select { |cred| cred.fetch("type") == "npm_registry" } - end - - sig { returns(T.nilable(T::Hash[String, T.untyped])) } - def parsed_package_lock - @parsed_package_lock ||= T.let( - JSON.parse(T.must(T.must(package_lock).content)), - T.nilable(T::Hash[String, T.untyped]) - ) - end - - sig { returns(T.nilable(Dependabot::DependencyFile)) } - def npmrc_file - @npmrc_file ||= T.let( - dependency_files.find { |f| f.name.end_with?(".npmrc") }, - T.nilable(Dependabot::DependencyFile) - ) - end - - sig { returns(T.nilable(Dependabot::DependencyFile)) } - def yarnrc_file - @yarnrc_file ||= T.let( - dependency_files.find { |f| f.name.end_with?(".yarnrc") }, - T.nilable(Dependabot::DependencyFile) - ) - end - - sig { returns(T.nilable(Dependabot::DependencyFile)) } - def yarnrc_yml_file - @yarnrc_yml_file ||= T.let( - dependency_files.find { |f| f.name.end_with?(".yarnrc.yml") }, - T.nilable(Dependabot::DependencyFile) - ) - end - - sig { returns(T.nilable(Dependabot::DependencyFile)) } - def yarn_lock - @yarn_lock ||= T.let( - dependency_files.find { |f| f.name == "yarn.lock" }, - T.nilable(Dependabot::DependencyFile) - ) - end - - sig { returns(T.nilable(Dependabot::DependencyFile)) } - def package_lock - @package_lock ||= T.let( - dependency_files.find { |f| f.name == "package-lock.json" }, - T.nilable(Dependabot::DependencyFile) - ) - end - - sig { returns(T.nilable(Dependabot::DependencyFile)) } - def shrinkwrap - @shrinkwrap ||= T.let( - dependency_files.find { |f| f.name == "npm-shrinkwrap.json" }, - T.nilable(Dependabot::DependencyFile) - ) - end - end - end - end -end diff --git a/javascript/lib/dependabot/javascript/file_updater/package_json_preparer.rb b/javascript/lib/dependabot/javascript/file_updater/package_json_preparer.rb deleted file mode 100644 index a395f1d537..0000000000 --- a/javascript/lib/dependabot/javascript/file_updater/package_json_preparer.rb +++ /dev/null @@ -1,85 +0,0 @@ -# typed: true -# frozen_string_literal: true - -module Dependabot - module Javascript - class FileUpdater - class PackageJsonPreparer - def initialize(package_json_content:) - @package_json_content = package_json_content - end - - def prepared_content - content = package_json_content - content = replace_ssh_sources(content) - content = remove_workspace_path_prefixes(content) - content = remove_invalid_characters(content) - content - end - - def replace_ssh_sources(content) - updated_content = content - - git_ssh_requirements_to_swap.each do |req| - new_req = req.gsub(%r{git\+ssh://git@(.*?)[:/]}, 'https://\1/') - updated_content = updated_content.gsub(req, new_req) - end - - updated_content - end - - # A bug prevents Yarn recognising that a directory is part of a - # workspace if it is specified with a `./` prefix. - def remove_workspace_path_prefixes(content) - json = JSON.parse(content) - return content unless json.key?("workspaces") - - workspace_object = json.fetch("workspaces") - paths_array = - if workspace_object.is_a?(Hash) - workspace_object.values_at("packages", "nohoist") - .flatten.compact - elsif workspace_object.is_a?(Array) then workspace_object - else - raise "Unexpected workspace object" - end - - paths_array.each { |path| path.gsub!(%r{^\./}, "") } - - JSON.pretty_generate(json) - end - - def remove_invalid_characters(content) - content - .gsub(/\{\{[^\}]*?\}\}/, "something") # {{ nm }} syntax not allowed - .gsub(/(? 1) - - # (we observed that) package.json does not always contains the same dependencies compared to - # "dependencies" list, for example, dependencies object can contain same name dependency "dep"=> "1.0.0" - # and "dev" => "1.0.1" while package.json can only contain "dep" => "1.0.0",the other dependency is - # not present in package.json so we don't have to update it, this is most likely (as observed) - # a transitive dependency which only needs update in lockfile, So we avoid throwing exception and let - # the update continue. - - Dependabot.logger.info("experiment: avoid_duplicate_updates_package_json. - Updating package.json for #{dep.name} ") - - raise "Expected content to change!" - end - - if !Dependabot::Experiments.enabled?(:avoid_duplicate_updates_package_json) && (content == new_content) - raise "Expected content to change!" - end - - content = new_content - end - - new_requirements(dep).each do |new_req| - old_req = old_requirement(dep, new_req) - - content = update_package_json_resolutions( - package_json_content: T.must(content), - new_req: new_req, - dependency: dep, - old_req: old_req - ) - end - - content - end - end - # rubocop:enable Metrics/PerceivedComplexity - sig do - params( - dependency: Dependabot::Dependency, - new_requirement: T::Hash[Symbol, T.untyped] - ) - .returns(T.nilable(T::Hash[Symbol, T.untyped])) - end - def old_requirement(dependency, new_requirement) - T.must(dependency.previous_requirements) - .select { |r| r[:file] == package_json.name } - .find { |r| r[:groups] == new_requirement[:groups] } - end - - sig { params(dependency: Dependabot::Dependency).returns(T::Array[T::Hash[Symbol, T.untyped]]) } - def new_requirements(dependency) - dependency.requirements.select { |r| r[:file] == package_json.name } - end - - sig { params(dependency: Dependabot::Dependency).returns(T.nilable(T::Array[T::Hash[Symbol, T.untyped]])) } - def updated_requirements(dependency) - return unless dependency.previous_requirements - - preliminary_check_for_update(dependency) - - updated_requirement_pairs = - dependency.requirements.zip(T.must(dependency.previous_requirements)) - .reject do |new_req, old_req| - next true if new_req == old_req - next false unless old_req&.fetch(:source).nil? - - new_req[:requirement] == old_req&.fetch(:requirement) - end - - updated_requirement_pairs - .map(&:first) - .select { |r| r[:file] == package_json.name } - end - - sig do - params( - package_json_content: String, - new_req: T::Hash[Symbol, T.untyped], - dependency_name: String, - old_req: T.nilable(T::Hash[Symbol, T.untyped]) - ) - .returns(String) - end - def update_package_json_declaration(package_json_content:, new_req:, dependency_name:, old_req:) - original_line = declaration_line( - dependency_name: dependency_name, - dependency_req: old_req, - content: package_json_content - ) - - replacement_line = replacement_declaration_line( - original_line: original_line, - old_req: old_req, - new_req: new_req - ) - - groups = new_req.fetch(:groups) - - update_package_json_sections( - groups, - package_json_content, - original_line, - replacement_line - ) - end - - # For full details on how Yarn resolutions work, see - # https://github.com/yarnpkg/rfcs/blob/master/implemented/ - # 0000-selective-versions-resolutions.md - sig do - params( - package_json_content: String, - new_req: T::Hash[Symbol, T.untyped], - dependency: Dependabot::Dependency, - old_req: T.nilable(T::Hash[Symbol, T.untyped]) - ) - .returns(String) - end - def update_package_json_resolutions(package_json_content:, new_req:, dependency:, old_req:) - dep = dependency - parsed_json_content = JSON.parse(package_json_content) - resolutions = - parsed_json_content.fetch("resolutions", parsed_json_content.dig("pnpm", "overrides") || {}) - .reject { |_, v| v != old_req && v != dep.previous_version } - .select { |k, _| k == dep.name || k.end_with?("/#{dep.name}") } - - return package_json_content unless resolutions.any? - - content = package_json_content - resolutions.each do |_, resolution| - original_line = declaration_line( - dependency_name: dep.name, - dependency_req: { requirement: resolution }, - content: content - ) - - new_resolution = resolution == old_req ? new_req : dep.version - - replacement_line = replacement_declaration_line( - original_line: original_line, - old_req: { requirement: resolution }, - new_req: { requirement: new_resolution } - ) - - content = update_package_json_sections( - %w(resolutions overrides), content, original_line, replacement_line - ) - end - content - end - - sig do - params( - dependency_name: String, - dependency_req: T.nilable(T::Hash[Symbol, T.untyped]), - content: String - ) - .returns(String) - end - def declaration_line(dependency_name:, dependency_req:, content:) - git_dependency = dependency_req&.dig(:source, :type) == "git" - - unless git_dependency - requirement = dependency_req&.fetch(:requirement) - return content.match(/"#{Regexp.escape(dependency_name)}"\s*:\s* - "#{Regexp.escape(requirement)}"/x).to_s - end - - username, repo = - dependency_req&.dig(:source, :url)&.split("/")&.last(2) - - content.match( - %r{"#{Regexp.escape(dependency_name)}"\s*:\s* - ".*?#{Regexp.escape(username)}/#{Regexp.escape(repo)}.*"}x - ).to_s - end - - sig do - params( - original_line: String, - old_req: T.nilable(T::Hash[Symbol, T.untyped]), - new_req: T::Hash[Symbol, T.untyped] - ) - .returns(String) - end - def replacement_declaration_line(original_line:, old_req:, new_req:) - was_git_dependency = old_req&.dig(:source, :type) == "git" - now_git_dependency = new_req.dig(:source, :type) == "git" - - unless was_git_dependency - return original_line.gsub( - %("#{old_req&.fetch(:requirement)}"), - %("#{new_req.fetch(:requirement)}") - ) - end - - unless now_git_dependency - return original_line.gsub( - /(?<=\s").*[^\\](?=")/, - new_req.fetch(:requirement) - ) - end - - if original_line.match?(/#[\^~=<>]|semver:/) - return update_git_semver_requirement( - original_line: original_line, - old_req: old_req, - new_req: new_req - ) - end - - original_line.gsub( - %(##{old_req&.dig(:source, :ref)}"), - %(##{new_req.dig(:source, :ref)}") - ) - end - - sig do - params( - original_line: String, - old_req: T.nilable(T::Hash[Symbol, String]), - new_req: T::Hash[Symbol, String] - ) - .returns(String) - end - def update_git_semver_requirement(original_line:, old_req:, new_req:) - if original_line.include?("semver:") - return original_line.gsub( - %(semver:#{old_req&.fetch(:requirement)}"), - %(semver:#{new_req.fetch(:requirement)}") - ) - end - - raise "Not a semver req!" unless original_line.match?(/#[\^~=<>]/) - - original_line.gsub( - %(##{old_req&.fetch(:requirement)}"), - %(##{new_req.fetch(:requirement)}") - ) - end - - sig do - params( - sections: T::Array[String], - content: String, - old_line: String, - new_line: String - ) - .returns(String) - end - def update_package_json_sections(sections, content, old_line, new_line) - # Currently, Dependabot doesn't update peerDependencies. However, - # if a development dependency is being updated and its requirement - # matches the requirement on a peer dependency we probably want to - # update the peer too. - # - # TODO: Move this logic to the UpdateChecker (and parse peer deps) - sections += ["peerDependencies"] - sections_regex = /#{sections.join('|')}/ - - declaration_blocks = T.let([], T::Array[String]) - - content.scan(/['"]#{sections_regex}['"]\s*:\s*\{/m) do - mtch = T.must(Regexp.last_match) - declaration_blocks << - (mtch.to_s + T.must(mtch.post_match[0..closing_bracket_index(mtch.post_match)])) - end - - declaration_blocks.reduce(content.dup) do |new_content, block| - updated_block = block.sub(old_line, new_line) - new_content.sub(block, updated_block) - end - end - - sig { params(string: String).returns(Integer) } - def closing_bracket_index(string) - closes_required = 1 - - string.chars.each_with_index do |char, index| - closes_required += 1 if char == "{" - closes_required -= 1 if char == "}" - return index if closes_required.zero? - end - - 0 - end - - sig { params(dependency: Dependabot::Dependency).void } - def preliminary_check_for_update(dependency) - T.must(dependency.previous_requirements).each do |req, _dep| - next if req.fetch(:requirement).nil? - - # some deps are patched with local patches, we don't need to update them - if req.fetch(:requirement).match?(Regexp.union(PATCH_PACKAGE)) - Dependabot.logger.info("Func: updated_requirements. dependency patched #{dependency.name}," \ - " Requirement: '#{req.fetch(:requirement)}'") - - raise DependencyFileNotResolvable, - "Dependency is patched locally, Update not required." - end - - # some deps are added as local packages, we don't need to update them as they are referred to a local path - next unless req.fetch(:requirement).match?(Regexp.union(LOCAL_PACKAGE)) - - Dependabot.logger.info("Func: updated_requirements. local package #{dependency.name}," \ - " Requirement: '#{req.fetch(:requirement)}'") - - raise DependencyFileNotResolvable, - "Local package, Update not required." - end - end - end - end - end -end diff --git a/javascript/lib/dependabot/javascript/language.rb b/javascript/lib/dependabot/javascript/language.rb deleted file mode 100644 index ac608b71c8..0000000000 --- a/javascript/lib/dependabot/javascript/language.rb +++ /dev/null @@ -1,43 +0,0 @@ -# typed: strong -# frozen_string_literal: true - -module Dependabot - module Javascript - class Language < Ecosystem::VersionManager - extend T::Sig - NAME = "javascript" - - SUPPORTED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version]) - - DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version]) - - sig do - params( - detected_version: T.nilable(String), - raw_version: T.nilable(String), - requirement: T.nilable(Requirement) - ).void - end - def initialize(detected_version: nil, raw_version: nil, requirement: nil) - super( - name: NAME, - detected_version: detected_version ? Version.new(detected_version) : nil, - version: raw_version ? Version.new(raw_version) : nil, - deprecated_versions: DEPRECATED_VERSIONS, - supported_versions: SUPPORTED_VERSIONS, - requirement: requirement - ) - end - - sig { override.returns(T::Boolean) } - def deprecated? - false - end - - sig { override.returns(T::Boolean) } - def unsupported? - false - end - end - end -end diff --git a/javascript/lib/dependabot/javascript/native_helpers.rb b/javascript/lib/dependabot/javascript/native_helpers.rb deleted file mode 100644 index 4d0d232d51..0000000000 --- a/javascript/lib/dependabot/javascript/native_helpers.rb +++ /dev/null @@ -1,19 +0,0 @@ -# typed: true -# frozen_string_literal: true - -module Dependabot - module Javascript - module NativeHelpers - def self.helper_path - "node #{File.join(native_helpers_root, 'run.js')}" - end - - def self.native_helpers_root - helpers_root = ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", nil) - return File.join(helpers_root, "npm_and_yarn") unless helpers_root.nil? - - File.join(__dir__, "../../../helpers") - end - end - end -end diff --git a/javascript/lib/dependabot/javascript/package_manager_detector.rb b/javascript/lib/dependabot/javascript/package_manager_detector.rb deleted file mode 100644 index d547761d3c..0000000000 --- a/javascript/lib/dependabot/javascript/package_manager_detector.rb +++ /dev/null @@ -1,70 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -module Dependabot - module Javascript - class PackageManagerDetector - extend T::Sig - extend T::Helpers - - sig do - params( - lockfiles: T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)], - package_json: T.nilable(T::Hash[String, T.untyped]) - ).void - end - def initialize(lockfiles, package_json) - @lockfiles = lockfiles - @package_json = package_json - @manifest_package_manager = T.let(package_json&.fetch(MANIFEST_PACKAGE_MANAGER_KEY, nil), T.nilable(String)) - @engines = T.let(package_json&.fetch(MANIFEST_ENGINES_KEY, {}), T::Hash[String, T.untyped]) - end - - # Returns npm, yarn, or pnpm based on the lockfiles, package.json, and engines - # Defaults to npm if no package manager is detected - sig { returns(String) } - def detect_package_manager - package_manager = name_from_lockfiles || - name_from_package_manager_attr || - name_from_engines - - if package_manager - Dependabot.logger.info("Detected package manager: #{package_manager}") - else - package_manager = DEFAULT_PACKAGE_MANAGER - Dependabot.logger.info("Default package manager used: #{package_manager}") - end - package_manager - rescue StandardError => e - Dependabot.logger.error("Error detecting package manager: #{e.message}") - DEFAULT_PACKAGE_MANAGER - end - - private - - sig { returns(T.nilable(String)) } - def name_from_lockfiles - PACKAGE_MANAGER_CLASSES.keys.map(&:to_s).find { |manager_name| @lockfiles[manager_name.to_sym] } - end - - sig { returns(T.nilable(String)) } - def name_from_package_manager_attr - return unless @manifest_package_manager - - PACKAGE_MANAGER_CLASSES.keys.map(&:to_s).find do |manager_name| - @manifest_package_manager.start_with?("#{manager_name}@") - end - end - - sig { returns(T.nilable(String)) } - def name_from_engines - return unless @engines.is_a?(Hash) - - PACKAGE_MANAGER_CLASSES.each_key do |manager_name| - return manager_name if @engines[manager_name] - end - nil - end - end - end -end diff --git a/javascript/lib/dependabot/javascript/package_name.rb b/javascript/lib/dependabot/javascript/package_name.rb deleted file mode 100644 index f36e051c35..0000000000 --- a/javascript/lib/dependabot/javascript/package_name.rb +++ /dev/null @@ -1,116 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -module Dependabot - module Javascript - class PackageName - extend T::Sig - - # NPM package naming rules are defined by the following projects: - # - https://github.com/npm/npm-user-validate - # - https://github.com/npm/validate-npm-package-name - PACKAGE_NAME_REGEX = %r{ - \A # beginning of string - (?=.{1,214}\z) # enforce length (1 - 214) - (@(? # capture 'scope' if present - [a-z0-9\-\_\.\!\~\*\'\(\)]+ # URL-safe characters in scope - )\/)? # scope must be followed by slash - (? # capture package name - (?() # if scope is present - [a-z0-9\-\_\.\!\~\*\'\(\)]+ # scoped names can start with any URL-safe character - | # if no scope - [a-z0-9\-\!\~\*\'\(\)] # non-scoped names cannot start with . or _ - [a-z0-9\-\_\.\!\~\*\'\(\)]* # subsequent characters can be any URL-safe character - ) - ) - \z # end of string - }xi # multi-line/case-insensitive - - TYPES_PACKAGE_NAME_REGEX = %r{ - \A # beginning of string - @types\/ # starts with @types/ - ((?.+)__)? # capture scope - (?.+) # capture name - \z # end of string - }xi # multi-line/case-insensitive - - class InvalidPackageName < StandardError; end - - sig { params(string: String).void } - def initialize(string) - match = PACKAGE_NAME_REGEX.match(string.to_s) - raise InvalidPackageName unless match - - @scope = T.let(match[:scope], T.nilable(String)) - @name = T.let(match[:name], T.nilable(String)) - end - - sig { returns(String) } - def to_s - if scoped? - "@#{@scope}/#{@name}" - else - @name.to_s - end - end - - sig { params(other: PackageName).returns(T::Boolean) } - def eql?(other) - self.class == other.class && to_s == other.to_s - end - - sig { returns(Integer) } - def hash - to_s.downcase.hash - end - - sig { params(other: PackageName).returns(T.nilable(Integer)) } - def <=>(other) - to_s.casecmp(other.to_s) - end - - sig { returns(T.nilable(PackageName)) } - def library_name - return unless types_package? - return @library_name if defined?(@library_name) - - lib_name = - begin - match = T.must(TYPES_PACKAGE_NAME_REGEX.match(to_s)) - if match[:scope] - self.class.new("@#{match[:scope]}/#{match[:name]}") - else - self.class.new(match[:name].to_s) - end - end - - @library_name ||= T.let(lib_name, T.nilable(PackageName)) - end - - sig { returns(T.nilable(PackageName)) } - def types_package_name - return if types_package? - - @types_package_name ||= T.let( - if scoped? - self.class.new("@types/#{@scope}__#{@name}") - else - self.class.new("@types/#{@name}") - end, T.nilable(PackageName) - ) - end - - private - - sig { returns(T::Boolean) } - def scoped? - !@scope.nil? - end - - sig { returns(T.nilable(T::Boolean)) } - def types_package? - "types".casecmp?(@scope) - end - end - end -end diff --git a/javascript/lib/dependabot/javascript/registry_helper.rb b/javascript/lib/dependabot/javascript/registry_helper.rb deleted file mode 100644 index 085e971f81..0000000000 --- a/javascript/lib/dependabot/javascript/registry_helper.rb +++ /dev/null @@ -1,188 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -require "yaml" -require "dependabot/dependency_file" -require "sorbet-runtime" - -module Dependabot - module Javascript - class RegistryHelper - extend T::Sig - - # Keys for configurations - REGISTRY_KEY = "registry" - AUTH_KEY = "authToken" - - # Yarn-specific keys - NPM_AUTH_TOKEN_KEY_FOR_YARN = "npmAuthToken" - NPM_SCOPE_KEY_FOR_YARN = "npmScopes" - NPM_REGISTER_KEY_FOR_YARN = "npmRegistryServer" - - # Environment variable keys - COREPACK_NPM_REGISTRY_ENV = "COREPACK_NPM_REGISTRY" - COREPACK_NPM_TOKEN_ENV = "COREPACK_NPM_TOKEN" - - sig do - params( - registry_config_files: T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)], - credentials: T.nilable(T::Array[Dependabot::Credential]) - ).void - end - def initialize(registry_config_files, credentials) - @registry_config_files = T.let(registry_config_files, T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) - @credentials = T.let(credentials, T.nilable(T::Array[Dependabot::Credential])) - end - - sig { returns(T::Hash[String, String]) } - def find_corepack_env_variables - registry_info = find_registry_and_token - - env_variables = {} - env_variables[COREPACK_NPM_REGISTRY_ENV] = registry_info[:registry] if registry_info[:registry] - env_variables[COREPACK_NPM_TOKEN_ENV] = registry_info[:auth_token] if registry_info[:auth_token] - - env_variables - end - - private - - sig { returns(T::Hash[Symbol, T.nilable(String)]) } - def find_registry_and_token - # Step 1: Check dependabot.yml configuration - dependabot_config = config_npm_registry_and_token - return dependabot_config if dependabot_config[:registry] - - # Step 2: Check .npmrc - npmrc_config = @registry_config_files[:npmrc] - npmrc_result = parse_registry_from_npmrc_yarnrc(npmrc_config, "=", "npm") - - return npmrc_result if npmrc_result[:registry] - - # Step 3: Check .yarnrc - yarnrc_config = @registry_config_files[:yarnrc] - yarnrc_result = parse_registry_from_npmrc_yarnrc(yarnrc_config, " ", "npm") - return yarnrc_result if yarnrc_result[:registry] - - # Step 4: Check yarnrc.yml - yarnrc_yml_config = @registry_config_files[:yarnrc_yml] - yarnrc_yml_result = parse_npm_from_yarnrc_yml(yarnrc_yml_config) - return yarnrc_yml_result if yarnrc_yml_result[:registry] - - # Default values if no registry is found - {} - end - - sig { returns(T::Hash[Symbol, T.nilable(String)]) } - def config_npm_registry_and_token - registries = {} - - return registries unless @credentials&.any? - - @credentials.each do |cred| - next unless cred["type"] == "npm_registry" # Skip if not an npm registry - next unless cred["replaces-base"] # Skip if not a reverse-proxy registry - - # Set the registry if it's not already set - registries[:registry] ||= cred["registry"] - - # Set the token if it's not already set - registries[:auth_token] ||= cred["token"] - end - registries - end - - # Find registry and token in .npmrc or .yarnrc file - sig do - params( - file: T.nilable(Dependabot::DependencyFile), - separator: String - ).returns(T::Hash[Symbol, T.nilable(String)]) - end - def parse_npm_from_npm_or_yarn_rc(file, separator = "=") - parse_registry_from_npmrc_yarnrc(file, separator, "npm") - end - - # Find registry and token in .npmrc or .yarnrc file - sig do - params( - file: T.nilable(Dependabot::DependencyFile), - separator: String, - scope: T.nilable(String) - ).returns(T::Hash[Symbol, T.nilable(String)]) - end - def parse_registry_from_npmrc_yarnrc(file, separator = "=", scope = nil) - content = file&.content - return { registry: nil, auth_token: nil } unless content - - global_registry = T.let(nil, T.nilable(String)) - scoped_registry = T.let(nil, T.nilable(String)) - auth_token = T.let(nil, T.nilable(String)) - - content.split("\n").each do |line| - # Split using the provided separator - key, value = line.strip.split(separator, 2) - next unless key && value - - # Remove surrounding quotes from keys and values - cleaned_key = key.strip.gsub(/\A["']|["']\z/, "") - cleaned_value = value.strip.gsub(/\A["']|["']\z/, "") - - case cleaned_key - when "registry" - # Case 1: Found a global registry - global_registry = cleaned_value - when "_authToken" - # Case 2: Found an auth token - auth_token = cleaned_value - else - # Handle scoped registry if a scope is provided - scoped_registry = cleaned_value if scope && cleaned_key == "@#{scope}:registry" - end - end - - # Determine the registry to return (global first, fallback to scoped) - registry = global_registry || scoped_registry - - { registry: registry, auth_token: auth_token } - end - - # rubocop:disable Metrics/PerceivedComplexity - sig { params(file: T.nilable(Dependabot::DependencyFile)).returns(T::Hash[Symbol, T.nilable(String)]) } - def parse_npm_from_yarnrc_yml(file) - content = file&.content - return { registry: nil, auth_token: nil } unless content - - result = {} - yaml_data = safe_load_yaml(content) - - # Step 1: Extract global registry and auth token - result[:registry] = yaml_data[NPM_REGISTER_KEY_FOR_YARN] if yaml_data.key?(NPM_REGISTER_KEY_FOR_YARN) - result[:auth_token] = yaml_data[NPM_AUTH_TOKEN_KEY_FOR_YARN] if yaml_data.key?(NPM_AUTH_TOKEN_KEY_FOR_YARN) - - # Step 2: Fallback to any scoped registry and auth token if global is missing - if result[:registry].nil? && yaml_data.key?(NPM_SCOPE_KEY_FOR_YARN) - yaml_data[NPM_SCOPE_KEY_FOR_YARN].each do |_current_scope, config| - next unless config.is_a?(Hash) - - result[:registry] ||= config[NPM_REGISTER_KEY_FOR_YARN] - result[:auth_token] ||= config[NPM_AUTH_TOKEN_KEY_FOR_YARN] - end - end - - result - end - # rubocop:enable Metrics/PerceivedComplexity - - # Safely loads the YAML content and logs any parsing errors - sig { params(content: String).returns(T::Hash[String, T.untyped]) } - def safe_load_yaml(content) - YAML.safe_load(content, permitted_classes: [Symbol, String]) || {} - rescue Psych::SyntaxError => e - # Log the error instead of raising it - Dependabot.logger.error("YAML parsing error: #{e.message}") - {} - end - end - end -end diff --git a/javascript/lib/dependabot/javascript/registry_parser.rb b/javascript/lib/dependabot/javascript/registry_parser.rb deleted file mode 100644 index 23110ef427..0000000000 --- a/javascript/lib/dependabot/javascript/registry_parser.rb +++ /dev/null @@ -1,91 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -module Dependabot - module Javascript - class RegistryParser - extend T::Sig - - sig { params(resolved_url: String, credentials: T::Array[Dependabot::Credential]).void } - def initialize(resolved_url:, credentials:) - @resolved_url = resolved_url - @credentials = credentials - end - - sig { params(name: String).returns(T::Hash[Symbol, T.untyped]) } - def registry_source_for(name) - url = - if resolved_url.include?("/~/") - # Gemfury format - resolved_url.split("/~/").first - elsif resolved_url.include?("/#{name}/-/#{name}") - # MyGet / Bintray format - T.must(resolved_url.split("/#{name}/-/#{name}").first) - .gsub("dl.bintray.com//", "api.bintray.com/npm/"). - # GitLab format - gsub(%r{\/projects\/\d+}, "") - elsif resolved_url.include?("/#{name}/-/#{name.split('/').last}") - # Sonatype Nexus / Artifactory JFrog format - resolved_url.split("/#{name}/-/#{name.split('/').last}").first - elsif (cred_url = url_for_relevant_cred) then cred_url - else - T.must(resolved_url.split("/")[0..2]).join("/") - end - - { type: "registry", url: url } - end - - sig { returns(String) } - def dependency_name - url_base = if resolved_url.include?("/-/") - T.must(resolved_url.split("/-/").first) - else - resolved_url - end - - package_name = url_base.gsub("%2F", "/").match(%r{@.*/}) - - return T.must(url_base.gsub("%2F", "/").split("/").last) unless package_name - - "#{package_name}#{T.must(url_base.gsub('%2F', '/').split('/').last)}" - end - - private - - sig { returns(String) } - attr_reader :resolved_url - - sig { returns(T::Array[Dependabot::Credential]) } - attr_reader :credentials - - # rubocop:disable Metrics/PerceivedComplexity - sig { returns(T.nilable(String)) } - def url_for_relevant_cred - resolved_url_host = URI(resolved_url).host - - credential_matching_url = - credentials - .select { |cred| cred["type"] == "npm_registry" && cred["registry"] } - .sort_by { |cred| cred.fetch("registry").length } - .find do |details| - next true if resolved_url_host == details["registry"] - - uri = if details["registry"]&.include?("://") - URI(details.fetch("registry")) - else - URI("https://#{details['registry']}") - end - resolved_url_host == uri.host && resolved_url.include?(details.fetch("registry")) - end - - return unless credential_matching_url - - # Trim the resolved URL so that it ends at the same point as the - # credential registry - reg = credential_matching_url.fetch("registry") - resolved_url.gsub(/#{Regexp.quote(reg)}.*/, "") + reg - end - # rubocop:enable Metrics/PerceivedComplexity - end - end -end diff --git a/javascript/lib/dependabot/javascript/requirement.rb b/javascript/lib/dependabot/javascript/requirement.rb deleted file mode 100644 index 1973a53838..0000000000 --- a/javascript/lib/dependabot/javascript/requirement.rb +++ /dev/null @@ -1,142 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -module Dependabot - module Javascript - class Requirement < Dependabot::Requirement - extend T::Sig - - AND_SEPARATOR = /(?<=[a-zA-Z0-9*])\s+(?:&+\s+)?(?!\s*[|-])/ - OR_SEPARATOR = /(?<=[a-zA-Z0-9*])\s*\|+/ - - # Override the version pattern to allow a 'v' prefix - quoted = OPS.keys.map { |k| Regexp.quote(k) }.join("|") - version_pattern = "v?#{Javascript::Version::VERSION_PATTERN}" - - PATTERN_RAW = T.let("\\s*(#{quoted})?\\s*(#{version_pattern})\\s*".freeze, String) - PATTERN = /\A#{PATTERN_RAW}\z/ - - sig { params(obj: T.untyped).returns(T::Array[T.untyped]) } - def self.parse(obj) - return ["=", nil] if obj.is_a?(String) && Version::VERSION_TAGS.include?(obj.strip) - return ["=", Javascript::Version.new(obj.to_s)] if obj.is_a?(Gem::Version) - - unless (matches = PATTERN.match(obj.to_s)) - msg = "Illformed requirement [#{obj.inspect}]" - raise BadRequirementError, msg - end - - return DefaultRequirement if matches[1] == ">=" && matches[2] == "0" - - [matches[1] || "=", Javascript::Version.new(T.must(matches[2]))] - end - - # Returns an array of requirements. At least one requirement from the - # returned array must be satisfied for a version to be valid. - sig { override.params(requirement_string: T.nilable(String)).returns(T::Array[Requirement]) } - def self.requirements_array(requirement_string) - return [new([])] if requirement_string.nil? - - # Removing parentheses is technically wrong but they are extremely - # rarely used. - # TODO: Handle complicated parenthesised requirements - requirement_string = requirement_string.gsub(/[()]/, "") - requirement_string.strip.split(OR_SEPARATOR).map do |req_string| - requirements = req_string.strip.split(AND_SEPARATOR) - new(requirements) - end - end - - sig { params(requirements: T.any(String, T::Array[String])).void } - def initialize(*requirements) - requirements = requirements.flatten - .flat_map { |req_string| req_string.split(",").map(&:strip) } - .flat_map { |req_string| convert_js_constraint_to_ruby_constraint(req_string) } - - super(requirements) - end - - private - - sig { params(req_string: String).returns(T.any(String, T::Array[String])) } - def convert_js_constraint_to_ruby_constraint(req_string) - return req_string if req_string.match?(/^([A-Za-uw-z]|v[^\d])/) - - req_string = req_string.gsub(/(?:\.|^)[xX*]/, "") - - if req_string.empty? then ">= 0" - elsif req_string.start_with?("~>") then req_string - elsif req_string.start_with?("=") then req_string.gsub(/^=*/, "") - elsif req_string.start_with?("~") then convert_tilde_req(req_string) - elsif req_string.start_with?("^") then convert_caret_req(req_string) - elsif req_string.include?(" - ") then convert_hyphen_req(req_string) - elsif req_string.match?(/[<>]/) then req_string - else - ruby_range(req_string) - end - end - - sig { params(req_string: String).returns(String) } - def convert_tilde_req(req_string) - version = req_string.gsub(/^~\>?[\s=]*/, "") - parts = version.split(".") - parts << "0" if parts.count < 3 - "~> #{parts.join('.')}" - end - - sig { params(req_string: String).returns(T::Array[String]) } - def convert_hyphen_req(req_string) - lower_bound, upper_bound = req_string.split(/\s+-\s+/) - lower_bound_parts = lower_bound&.split(".") - lower_bound_parts&.fill("0", lower_bound_parts.length...3) - - upper_bound_parts = upper_bound&.split(".") - upper_bound_range = - if upper_bound_parts && upper_bound_parts.length < 3 - # When upper bound is a partial version treat these as an X-range - upper_bound_parts[-1] = upper_bound_parts[-1].to_i + 1 if upper_bound_parts[-1].to_i.positive? - upper_bound_parts.fill("0", upper_bound_parts.length...3) - "< #{upper_bound_parts.join('.')}.a" - else - "<= #{upper_bound_parts&.join('.')}" - end - - [">= #{lower_bound_parts&.join('.')}", upper_bound_range] - end - - sig { params(req_string: String).returns(String) } - def ruby_range(req_string) - parts = req_string.split(".") - # If we have three or more parts then this is an exact match - return req_string if parts.count >= 3 - - # If we have fewer than three parts we do a partial match - parts << "0" - "~> #{parts.join('.')}" - end - - sig { params(req_string: String).returns(T::Array[String]) } - def convert_caret_req(req_string) # rubocop:disable Metrics/PerceivedComplexity - version = req_string.gsub(/^\^[\s=]*/, "") - parts = version.split(".") - parts.fill("x", parts.length...3) - first_non_zero = parts.find { |d| d != "0" } - first_non_zero_index = - first_non_zero ? parts.index(first_non_zero) : parts.count - 1 - # If the requirement has a blank minor or patch version increment the - # previous index value with 1 - first_non_zero_index -= 1 if first_non_zero_index && first_non_zero == "x" - upper_bound = parts.map.with_index do |part, i| - if i < T.must(first_non_zero_index) then part - elsif i == first_non_zero_index then (part.to_i + 1).to_s - elsif i > T.must(first_non_zero_index) && i == 2 then "0.a" - else - 0 - end - end.join(".") - - [">= #{version}", "< #{upper_bound}"] - end - end - end -end diff --git a/javascript/lib/dependabot/javascript/shared/constraint_helper.rb b/javascript/lib/dependabot/javascript/shared/constraint_helper.rb new file mode 100644 index 0000000000..f012a5f9b8 --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/constraint_helper.rb @@ -0,0 +1,359 @@ +# typed: strict +# frozen_string_literal: true + +module Dependabot + module Javascript + module Shared + module ConstraintHelper + extend T::Sig + + # Regex Components for Semantic Versioning + DIGIT = "\\d+" # Matches a single number (e.g., "1") + PRERELEASE = "(?:-[a-zA-Z0-9.-]+)?" # Matches optional pre-release tag (e.g., "-alpha") + BUILD_METADATA = "(?:\\+[a-zA-Z0-9.-]+)?" # Matches optional build metadata (e.g., "+001") + + # Matches semantic versions: + VERSION = T.let("#{DIGIT}(?:\\.#{DIGIT}){0,2}#{PRERELEASE}#{BUILD_METADATA}".freeze, String) + + VERSION_REGEX = T.let(/^#{VERSION}$/, Regexp) + + # Base regex for SemVer (major.minor.patch[-prerelease][+build]) + # This pattern extracts valid semantic versioning strings based on the SemVer 2.0 specification. + SEMVER_REGEX = T.let(/ + (?\d+\.\d+\.\d+) # Match major.minor.patch (e.g., 1.2.3) + (?:-(?[a-zA-Z0-9.-]+))? # Optional prerelease (e.g., -alpha.1, -rc.1, -beta.5) + (?:\+(?[a-zA-Z0-9.-]+))? # Optional build metadata (e.g., +build.20231101, +exp.sha.5114f85) + /x, Regexp) + + # Full SemVer validation regex (ensures the entire string is a valid SemVer) + # This ensures the entire input strictly follows SemVer, without extra characters before/after. + SEMVER_VALIDATION_REGEX = T.let(/^#{SEMVER_REGEX}$/, Regexp) + + # SemVer constraint regex (supports package.json version constraints) + # This pattern ensures proper parsing of SemVer versions with optional operators. + SEMVER_CONSTRAINT_REGEX = T.let(/ + (?: (>=|<=|>|<|=|~|\^)\s*)? # Make operators optional (e.g., >=, ^, ~) + (\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?(?:\+[a-zA-Z0-9.-]+)?) # Match full SemVer versions + | (\*|latest) # Match wildcard (*) or 'latest' + /x, Regexp) + + # /(>=|<=|>|<|=|~|\^)\s*(\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?(?:\+[a-zA-Z0-9.-]+)?)|(\*|latest)/ + + SEMVER_OPERATOR_REGEX = /^(>=|<=|>|<|~|\^|=)$/ + + # Constraint Types as Constants + CARET_CONSTRAINT_REGEX = T.let(/^\^\s*(#{VERSION})$/, Regexp) + TILDE_CONSTRAINT_REGEX = T.let(/^~\s*(#{VERSION})$/, Regexp) + EXACT_CONSTRAINT_REGEX = T.let(/^\s*(#{VERSION})$/, Regexp) + GREATER_THAN_EQUAL_REGEX = T.let(/^>=\s*(#{VERSION})$/, Regexp) + LESS_THAN_EQUAL_REGEX = T.let(/^<=\s*(#{VERSION})$/, Regexp) + GREATER_THAN_REGEX = T.let(/^>\s*(#{VERSION})$/, Regexp) + LESS_THAN_REGEX = T.let(/^<\s*(#{VERSION})$/, Regexp) + WILDCARD_REGEX = T.let(/^\*$/, Regexp) + LATEST_REGEX = T.let(/^latest$/, Regexp) + SEMVER_CONSTANTS = ["*", "latest"].freeze + + # Unified Regex for Valid Constraints + VALID_CONSTRAINT_REGEX = T.let(Regexp.union( + CARET_CONSTRAINT_REGEX, + TILDE_CONSTRAINT_REGEX, + EXACT_CONSTRAINT_REGEX, + GREATER_THAN_EQUAL_REGEX, + LESS_THAN_EQUAL_REGEX, + GREATER_THAN_REGEX, + LESS_THAN_REGEX, + WILDCARD_REGEX, + LATEST_REGEX + ).freeze, Regexp) + + # Extract unique constraints from the given constraint expression. + # @param constraint_expression [T.nilable(String)] The semver constraint expression. + # @return [T::Array[String]] The list of unique Ruby-compatible constraints. + sig do + params( + constraint_expression: T.nilable(String), + dependabot_versions: T.nilable(T::Array[Dependabot::Version]) + ) + .returns(T.nilable(T::Array[String])) + end + def self.extract_ruby_constraints(constraint_expression, dependabot_versions = nil) + parsed_constraints = parse_constraints(constraint_expression, dependabot_versions) + + return nil unless parsed_constraints + + parsed_constraints.filter_map { |parsed| parsed[:constraint] } + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/PerceivedComplexity + sig do + params(constraint_expression: T.nilable(String)) + .returns(T.nilable(T::Array[String])) + end + def self.split_constraints(constraint_expression) + normalized_constraint = constraint_expression&.strip + return [] if normalized_constraint.nil? || normalized_constraint.empty? + + # Split constraints by logical OR (`||`) + constraint_groups = normalized_constraint.split("||") + + # Split constraints by logical AND (`,`) + constraint_groups = constraint_groups.map do |or_constraint| + or_constraint.split(",").map(&:strip) + end.flatten + + constraint_groups = constraint_groups.map do |constraint| + tokens = constraint.split(/\s+/).map(&:strip) + + and_constraints = [] + + previous = T.let(nil, T.nilable(String)) + operator = T.let(false, T.nilable(T::Boolean)) + wildcard = T.let(false, T::Boolean) + + tokens.each do |token| + token = token.strip + next if token.empty? + + # Invalid constraint if wildcard and anything else + return nil if wildcard + + # If token is one of the operators (>=, <=, >, <, ~, ^, =) + if token.match?(SEMVER_OPERATOR_REGEX) + wildcard = false + operator = true + # If token is wildcard or latest + elsif token.match?(/(\*|latest)/) + and_constraints << token + wildcard = true + operator = false + # If token is exact version (e.g., "1.2.3") + elsif token.match(VERSION_REGEX) + and_constraints << if operator + "#{previous}#{token}" + else + token + end + wildcard = false + operator = false + # If token is a valid constraint (e.g., ">=1.2.3", "<=2.0.0") + elsif token.match(VALID_CONSTRAINT_REGEX) + return nil if operator + + and_constraints << token + + wildcard = false + operator = false + else + # invalid constraint + return nil + end + previous = token + end + and_constraints.uniq + end.flatten + constraint_groups if constraint_groups.any? + end + + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/PerceivedComplexity + + # Find the highest version from the given constraint expression. + # @param constraint_expression [T.nilable(String)] The semver constraint expression. + # @return [T.nilable(String)] The highest version, or nil if no versions are available. + sig do + params( + constraint_expression: T.nilable(String), + dependabot_versions: T.nilable(T::Array[Dependabot::Version]) + ) + .returns(T.nilable(String)) + end + def self.find_highest_version_from_constraint_expression(constraint_expression, dependabot_versions = nil) + parsed_constraints = parse_constraints(constraint_expression, dependabot_versions) + + return nil unless parsed_constraints + + parsed_constraints + .filter_map { |parsed| parsed[:version] } # Extract all versions + .max_by { |version| Version.new(version) } + end + + # Parse all constraints (split by logical OR `||`) and convert to Ruby-compatible constraints. + # Return: + # - `nil` if the constraint expression is invalid + # - `[]` if the constraint expression is valid but represents "no constraints" + # - An array of hashes for valid constraints with details about the constraint and version + sig do + params( + constraint_expression: T.nilable(String), + dependabot_versions: T.nilable(T::Array[Dependabot::Version]) + ) + .returns(T.nilable(T::Array[T::Hash[Symbol, T.nilable(String)]])) + end + def self.parse_constraints(constraint_expression, dependabot_versions = nil) + splitted_constraints = split_constraints(constraint_expression) + + return unless splitted_constraints + + constraints = to_ruby_constraints_with_versions(splitted_constraints, dependabot_versions) + constraints + end + + sig do + params( + constraints: T::Array[String], + dependabot_versions: T.nilable(T::Array[Dependabot::Version]) + ).returns(T::Array[T::Hash[Symbol, T.nilable(String)]]) + end + def self.to_ruby_constraints_with_versions(constraints, dependabot_versions = []) + constraints.filter_map do |constraint| + parsed = to_ruby_constraint_with_version(constraint, dependabot_versions) + parsed if parsed + end.uniq + end + + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/PerceivedComplexity + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/CyclomaticComplexity + # Converts a semver constraint to a Ruby-compatible constraint and extracts the version, if available. + # @param constraint [String] The semver constraint to parse. + # @return [T.nilable(T::Hash[Symbol, T.nilable(String)])] Returns the Ruby-compatible + # constraint and the version, if available, or nil if the constraint is invalid. + # + # @example + # to_ruby_constraint_with_version("=1.2.3") # => { constraint: "=1.2.3", version: "1.2.3" } + # to_ruby_constraint_with_version("^1.2.3") # => { constraint: ">=1.2.3 <2.0.0", version: "1.2.3" } + # to_ruby_constraint_with_version("*") # => { constraint: nil, version: nil } + # to_ruby_constraint_with_version("invalid") # => nil + sig do + params( + constraint: String, + dependabot_versions: T.nilable(T::Array[Dependabot::Version]) + ) + .returns(T.nilable(T::Hash[Symbol, T.nilable(String)])) + end + def self.to_ruby_constraint_with_version(constraint, dependabot_versions = []) + return nil if constraint.empty? + + case constraint + when EXACT_CONSTRAINT_REGEX # Exact version, e.g., "1.2.3-alpha" + return unless Regexp.last_match + + full_version = Regexp.last_match(1) + { constraint: "=#{full_version}", version: full_version } + when CARET_CONSTRAINT_REGEX # Caret constraint, e.g., "^1.2.3" + return unless Regexp.last_match + + full_version = Regexp.last_match(1) + _, major, minor = version_components(full_version) + return nil if major.nil? + + ruby_constraint = + if major.to_i.zero? + minor.nil? ? ">=#{full_version} <1.0.0" : ">=#{full_version} <0.#{minor.to_i + 1}.0" + else + ">=#{full_version} <#{major.to_i + 1}.0.0" + end + { constraint: ruby_constraint, version: full_version } + when TILDE_CONSTRAINT_REGEX # Tilde constraint, e.g., "~1.2.3" + return unless Regexp.last_match + + full_version = Regexp.last_match(1) + _, major, minor = version_components(full_version) + ruby_constraint = + if minor.nil? + ">=#{full_version} <#{major.to_i + 1}.0.0" + else + ">=#{full_version} <#{major}.#{minor.to_i + 1}.0" + end + { constraint: ruby_constraint, version: full_version } + when GREATER_THAN_EQUAL_REGEX # Greater than or equal, e.g., ">=1.2.3" + + return unless Regexp.last_match && Regexp.last_match(1) + + found_version = highest_matching_version( + dependabot_versions, + T.must(Regexp.last_match(1)) + ) do |version, constraint_version| + version >= Version.new(constraint_version) + end + { constraint: ">=#{Regexp.last_match(1)}", version: found_version&.to_s } + when LESS_THAN_EQUAL_REGEX # Less than or equal, e.g., "<=1.2.3" + return unless Regexp.last_match + + full_version = Regexp.last_match(1) + { constraint: "<=#{full_version}", version: full_version } + when GREATER_THAN_REGEX # Greater than, e.g., ">1.2.3" + return unless Regexp.last_match && Regexp.last_match(1) + + found_version = highest_matching_version( + dependabot_versions, + T.must(Regexp.last_match(1)) + ) do |version, constraint_version| + version > Version.new(constraint_version) + end + { constraint: ">#{Regexp.last_match(1)}", version: found_version&.to_s } + when LESS_THAN_REGEX # Less than, e.g., "<1.2.3" + return unless Regexp.last_match && Regexp.last_match(1) + + found_version = highest_matching_version( + dependabot_versions, + T.must(Regexp.last_match(1)) + ) do |version, constraint_version| + version < Version.new(constraint_version) + end + { constraint: "<#{Regexp.last_match(1)}", version: found_version&.to_s } + when WILDCARD_REGEX # No specific constraint, resolves to the highest available version + { constraint: nil, version: dependabot_versions&.max&.to_s } + when LATEST_REGEX + { constraint: nil, version: dependabot_versions&.max&.to_s } # Resolves to the latest available version + end + end + + sig do + params( + dependabot_versions: T.nilable(T::Array[Dependabot::Version]), + constraint_version: String, + condition: T.proc.params(version: Dependabot::Version, constraint: Dependabot::Version).returns(T::Boolean) + ) + .returns(T.nilable(Dependabot::Version)) + end + def self.highest_matching_version(dependabot_versions, constraint_version, &condition) + return unless dependabot_versions&.any? + + # Returns the highest version that satisfies the condition, or nil if none. + dependabot_versions + .sort + .reverse + .find { |version| condition.call(version, Version.new(constraint_version)) } # rubocop:disable Performance/RedundantBlockCall + end + + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/PerceivedComplexity + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/CyclomaticComplexity + + # Parses a semantic version string into its components as per the SemVer spec + # Example: "1.2.3-alpha+001" → ["1.2.3", "1", "2", "3", "alpha", "001"] + sig { params(full_version: T.nilable(String)).returns(T.nilable(T::Array[String])) } + def self.version_components(full_version) + return [] if full_version.nil? + + match = full_version.match(SEMVER_VALIDATION_REGEX) + return [] unless match + + version = match[:version] + return [] unless version + + major, minor, patch = version.split(".") + [version, major, minor, patch, match[:prerelease], match[:build]].compact + end + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/shared/dependency_files_filterer.rb b/javascript/lib/dependabot/javascript/shared/dependency_files_filterer.rb new file mode 100644 index 0000000000..6441f2ffb0 --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/dependency_files_filterer.rb @@ -0,0 +1,153 @@ +# typed: strict +# frozen_string_literal: true + +# lots of sub-projects that don't all have the same dependencies. +module Dependabot + module Javascript + module Shared + class DependencyFilesFilterer + extend T::Sig + + sig { params(dependency_files: T::Array[DependencyFile], updated_dependencies: T::Array[Dependency]).void } + def initialize(dependency_files:, updated_dependencies:) + @dependency_files = dependency_files + @updated_dependencies = updated_dependencies + end + + sig { returns(T::Array[String]) } + def paths_requiring_update_check + @paths_requiring_update_check ||= T.let(fetch_paths_requiring_update_check, T.nilable(T::Array[String])) + end + + sig { returns(T::Array[Dependabot::DependencyFile]) } + def files_requiring_update + @files_requiring_update ||= T.let( + dependency_files.select do |file| + package_files_requiring_update.include?(file) || + package_required_lockfile?(file) || + workspaces_lockfile?(file) + end, T.nilable(T::Array[DependencyFile]) + ) + end + + sig { returns(T::Array[Dependabot::DependencyFile]) } + def package_files_requiring_update + @package_files_requiring_update ||= T.let( + dependency_files.select do |file| + dependency_manifest_requirements.include?(file.name) + end, T.nilable(T::Array[DependencyFile]) + ) + end + + private + + sig { returns(T::Array[DependencyFile]) } + attr_reader :dependency_files + + sig { returns(T::Array[Dependency]) } + attr_reader :updated_dependencies + + sig { returns(T::Array[String]) } + def fetch_paths_requiring_update_check + # if only a root lockfile exists, it tracks all dependencies + return [File.dirname(T.must(root_lockfile).name)] if lockfiles == [root_lockfile] + + package_files_requiring_update.map do |file| + File.dirname(file.name) + end + end + + sig { returns(T::Array[String]) } + def dependency_manifest_requirements + @dependency_manifest_requirements ||= T.let( + updated_dependencies.flat_map do |dep| + dep.requirements.map { |requirement| requirement[:file] } + end, T.nilable(T::Array[String]) + ) + end + + sig { params(lockfile: DependencyFile).returns(T::Boolean) } + def package_required_lockfile?(lockfile) + return false unless lockfile?(lockfile) + + package_files_requiring_update.any? do |package_file| + File.dirname(package_file.name) == File.dirname(lockfile.name) + end + end + + sig { params(lockfile: DependencyFile).returns(T::Boolean) } + def workspaces_lockfile?(lockfile) + return false unless ["yarn.lock", "package-lock.json", "pnpm-lock.yaml", "bun.lock"].include?(lockfile.name) + + return false unless parsed_root_package_json["workspaces"] || dependency_files.any? do |file| + file.name.end_with?("pnpm-workspace.yaml") && File.dirname(file.name) == File.dirname(lockfile.name) + end + + updated_dependencies_in_lockfile?(lockfile) + end + + sig { returns(T.nilable(DependencyFile)) } + def root_lockfile + @root_lockfile ||= T.let( + lockfiles.find do |file| + File.dirname(file.name) == "." + end, T.nilable(DependencyFile) + ) + end + + sig { returns(T::Array[DependencyFile]) } + def lockfiles + @lockfiles ||= T.let( + dependency_files.select do |file| + lockfile?(file) + end, T.nilable(T::Array[DependencyFile]) + ) + end + + sig { returns(T::Hash[String, T.untyped]) } + def parsed_root_package_json + @parsed_root_package_json ||= T.let( + begin + package = T.must(dependency_files.find { |f| f.name == "package.json" }) + JSON.parse(T.must(package.content)) + end, T.nilable(T::Hash[String, T.untyped]) + ) + end + + sig { params(lockfile: Dependabot::DependencyFile).returns(T::Boolean) } + def updated_dependencies_in_lockfile?(lockfile) + lockfile_dependencies(lockfile).any? do |sub_dep| + updated_dependencies.any? do |updated_dep| + sub_dep.name == updated_dep.name + end + end + end + + sig { params(lockfile: DependencyFile).returns(T::Array[Dependency]) } + def lockfile_dependencies(lockfile) + @lockfile_dependencies ||= T.let({}, T.nilable(T::Hash[String, T::Array[Dependency]])) + @lockfile_dependencies[lockfile.name] ||= + NpmAndYarn::FileParser::LockfileParser.new( + dependency_files: [lockfile] + ).parse + end + + sig { params(file: DependencyFile).returns(T::Boolean) } + def manifest?(file) + file.name.end_with?("package.json") + end + + sig { params(file: DependencyFile).returns(T::Boolean) } + def lockfile?(file) + file.name.end_with?( + "package-lock.json", + "yarn.lock", + "pnpm-lock.yaml", + "bun.lock", + "npm-shrinkwrap.json" + ) + end + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/shared/file_fetcher.rb b/javascript/lib/dependabot/javascript/shared/file_fetcher.rb new file mode 100644 index 0000000000..a10202b172 --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/file_fetcher.rb @@ -0,0 +1,283 @@ +# typed: strict +# frozen_string_literal: true + +module Dependabot + module Javascript + module Shared + class FileFetcher < Dependabot::FileFetchers::Base + extend T::Sig + + abstract! + + PATH_DEPENDENCY_STARTS = T.let(%w(file: / ./ ../ ~/).freeze, [String, String, String, String, String]) + PATH_DEPENDENCY_CLEAN_REGEX = /^file:|^link:/ + DEPENDENCY_TYPES = T.let(%w(dependencies devDependencies optionalDependencies).freeze, T::Array[String]) + + sig { params(instance: FileFetcher).returns(T::Array[DependencyFile]) } + def workspace_package_jsons(instance) + @workspace_package_jsons ||= T.let(fetch_workspace_package_jsons(instance), + T.nilable(T::Array[DependencyFile])) + end + + sig do + params(instance: FileFetcher, fetched_files: T::Array[DependencyFile]) + .returns(T::Array[DependencyFile]) + end + def path_dependencies(instance, fetched_files) + package_json_files = T.let([], T::Array[DependencyFile]) + + path_dependency_details(instance, fetched_files).each do |name, path| + # This happens with relative paths in the package-lock. Skipping it since it results + # in /package.json which is outside of the project directory. + next if path == "file:" + + path = path.gsub(PATH_DEPENDENCY_CLEAN_REGEX, "") + raise PathDependenciesNotReachable, "#{name} at #{path}" if path.start_with?("/") + + filename = path + filename = File.join(filename, MANIFEST_FILENAME) + cleaned_name = Pathname.new(filename).cleanpath.to_path + next if fetched_files.map(&:name).include?(cleaned_name) + + file = instance.fetch_file(filename, fetch_submodules: true) + package_json_files << file + end + + if package_json_files.any? + package_json_files += + path_dependencies(instance, fetched_files + package_json_files) + end + + package_json_files.tap { |fs| fs.each { |f| f.support_file = true } } + end + + sig do + params(instance: FileFetcher, fetched_files: T::Array[DependencyFile]) + .returns(T::Array[[String, String]]) + end + def path_dependency_details(instance, fetched_files) + package_json_path_deps = T.let([], T::Array[[String, String]]) + + fetched_files.each do |file| + package_json_path_deps += + path_dependency_details_from_manifest(instance, file) + end + + [ + *package_json_path_deps + ].uniq + end + + # rubocop:disable Metrics/PerceivedComplexity + # rubocop:disable Metrics/AbcSize + sig do + params(instance: FileFetcher, + file: DependencyFile).returns(T::Array[[String, String]]) + end + def path_dependency_details_from_manifest(instance, file) + return [] unless file.name.end_with?(MANIFEST_FILENAME) + + current_dir = file.name.rpartition("/").first + current_dir = nil if current_dir == "" + + current_depth = File.join(instance.directory, file.name).split("/").count { |path| !path.empty? } + path_to_directory = "../" * current_depth + + dep_types = DEPENDENCY_TYPES # TODO: Is this needed? + parsed_manifest = JSON.parse(T.must(file.content)) + dependency_objects = parsed_manifest.values_at(*dep_types).compact + # Fetch yarn "file:" path "resolutions" so the lockfile can be resolved + resolution_objects = parsed_manifest.values_at("resolutions").compact + manifest_objects = dependency_objects + resolution_objects + + raise Dependabot::DependencyFileNotParseable, file.path unless manifest_objects.all?(Hash) + + resolution_deps = resolution_objects.flat_map(&:to_a) + .map do |path, value| + # skip dependencies that contain invalid values such as inline comments, null, etc. + + unless value.is_a?(String) + Dependabot.logger.warn("File fetcher: Skipping dependency \"#{path}\" " \ + "with value: \"#{value}\"") + + next + end + + convert_dependency_path_to_name(path, value) + end + + path_starts = PATH_DEPENDENCY_STARTS + (dependency_objects.flat_map(&:to_a) + resolution_deps) + .select { |_, v| v.is_a?(String) && v.start_with?(*path_starts) } + .map do |name, path| + path = path.gsub(PATH_DEPENDENCY_CLEAN_REGEX, "") + raise PathDependenciesNotReachable, "#{name} at #{path}" if path.start_with?("/", + "#{path_to_directory}..") + + path = File.join(current_dir, path) unless current_dir.nil? + [name, Pathname.new(path).cleanpath.to_path] + end + rescue JSON::ParserError + raise Dependabot::DependencyFileNotParseable, file.path + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/PerceivedComplexity + + # Re-write the glob name to the targeted dependency name (which is used + # in the lockfile), for example "parent-package/**/sub-dep/target-dep" > + # "target-dep" + sig { params(path: String, value: String).returns([String, String]) } + def convert_dependency_path_to_name(path, value) + # Picking the last two parts that might include a scope + parts = path.split("/").last(2) + parts.shift if parts.count == 2 && !T.must(parts.first).start_with?("@") + [parts.join("/"), value] + end + + sig { params(instance: FileFetcher).returns(T::Array[DependencyFile]) } + def fetch_workspace_package_jsons(instance) + parsed_manifest = parsed_package_json(instance) + return [] unless parsed_manifest["workspaces"] + + workspace_paths(instance, parsed_manifest["workspaces"]).filter_map do |workspace| + fetch_package_json_if_present(instance, workspace) + end + end + + sig do + params(instance: FileFetcher, + workspace_object: T.untyped).returns(T::Array[String]) + end + def workspace_paths(instance, workspace_object) + paths_array = + if workspace_object.is_a?(Hash) + workspace_object.values_at("packages", "nohoist").flatten.compact + elsif workspace_object.is_a?(Array) then workspace_object + else + [] # Invalid lerna.json, which must not be in use + end + + paths_array.flat_map { |path| recursive_find_directories(instance, path) } + end + + sig { params(instance: FileFetcher, glob: String).returns(T::Array[String]) } + def find_directories(instance, glob) + return [glob] unless glob.include?("*") + + unglobbed_path = + glob.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*") + .split("*") + .first&.gsub(%r{(?<=/)[^/]*$}, "") || "." + + dir = instance.directory.gsub(%r{(^/|/$)}, "") + + paths = + instance.fetch_repo_contents(dir: unglobbed_path, raise_errors: false) + .select { |file| file.type == "dir" } + .map { |f| f.path.gsub(%r{^/?#{Regexp.escape(dir)}/?}, "") } + + matching_paths(glob, paths) + end + + sig { params(glob: String, paths: T::Array[String]).returns(T::Array[String]) } + def matching_paths(glob, paths) + glob = glob.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*") + glob = "#{glob}/*" if glob.end_with?("**") + + paths.select { |filename| File.fnmatch?(glob, filename, File::FNM_PATHNAME) } + end + + sig do + params(instance: FileFetcher, glob: String, + prefix: String).returns(T::Array[String]) + end + def recursive_find_directories(instance, glob, prefix = "") + return [prefix + glob] unless glob.include?("*") + + glob = glob.gsub(%r{^\./}, "") + glob_parts = glob.split("/") + + current_glob = glob_parts.first + paths = find_directories(instance, prefix + T.must(current_glob)) + next_parts = current_glob == "**" ? glob_parts : glob_parts.drop(1) + return paths if next_parts.empty? + + paths += paths.flat_map do |expanded_path| + recursive_find_directories(instance, next_parts.join("/"), "#{expanded_path}/") + end + + matching_paths(prefix + glob, paths) + end + + sig do + params(instance: FileFetcher, workspace: String).returns(T.nilable(DependencyFile)) + end + def fetch_package_json_if_present(instance, workspace) + file = File.join(workspace, MANIFEST_FILENAME) + + begin + instance.fetch_file(file) + rescue Dependabot::DependencyFileNotFound + # Not all paths matched by a workspace glob may contain a package.json + # file. Ignore if that's the case + nil + end + end + + sig { params(instance: FileFetcher).returns(DependencyFile) } + def package_json(instance) + @package_json ||= T.let(instance.fetch_file(Javascript::MANIFEST_FILENAME), T.nilable(DependencyFile)) + end + + sig { params(instance: FileFetcher).returns(T.untyped) } + def parsed_package_json(instance) + manifest_file = package_json(instance) + parsed = JSON.parse(T.must(manifest_file.content)) + raise Dependabot::DependencyFileNotParseable, manifest_file.path unless parsed.is_a?(Hash) + + parsed + rescue JSON::ParserError + raise Dependabot::DependencyFileNotParseable, T.must(manifest_file).path + end + + sig { params(filename: String, fetch_submodules: T::Boolean).returns(DependencyFile) } + def fetch_file(filename, fetch_submodules: false) + fetch_file_from_host(filename, fetch_submodules: fetch_submodules) + end + + sig do + params( + dir: T.any(Pathname, String), + ignore_base_directory: T::Boolean, + raise_errors: T::Boolean, + fetch_submodules: T::Boolean + ) + .returns(T::Array[T.untyped]) + end + def fetch_repo_contents(dir: ".", ignore_base_directory: false, raise_errors: true, fetch_submodules: false) + repo_contents(dir: dir, ignore_base_directory:, raise_errors:, fetch_submodules:) + end + + sig do + params(instance: FileFetcher, filename: String).returns(T.nilable(DependencyFile)) + end + def fetch_file_with_support(instance, filename) + instance.fetch_file(filename).tap { |f| f.support_file = true } + rescue Dependabot::DependencyFileNotFound + nil + end + + sig do + params(instance: FileFetcher, filename: String).returns(T.nilable(DependencyFile)) + end + def fetch_file_from_parent_directories(instance, filename) + (1..instance.directory.split("/").count).each do |i| + file = fetch_file_with_support(instance, ("../" * i) + filename) + return file if file + end + nil + end + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/shared/file_fetcher_helper.rb b/javascript/lib/dependabot/javascript/shared/file_fetcher_helper.rb new file mode 100644 index 0000000000..3c39839d25 --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/file_fetcher_helper.rb @@ -0,0 +1,264 @@ +# typed: strict +# frozen_string_literal: true + +module Dependabot + module Javascript + module Shared + module FileFetcherHelper + include Kernel + extend T::Sig + + PATH_DEPENDENCY_STARTS = T.let(%w(file: / ./ ../ ~/).freeze, [String, String, String, String, String]) + PATH_DEPENDENCY_CLEAN_REGEX = /^file:|^link:/ + DEPENDENCY_TYPES = T.let(%w(dependencies devDependencies optionalDependencies).freeze, T::Array[String]) + + sig { params(instance: FileFetcher).returns(T::Array[DependencyFile]) } + def workspace_package_jsons(instance) + @workspace_package_jsons ||= T.let(fetch_workspace_package_jsons(instance), + T.nilable(T::Array[DependencyFile])) + end + + sig do + params(instance: FileFetcher, fetched_files: T::Array[DependencyFile]) + .returns(T::Array[DependencyFile]) + end + def path_dependencies(instance, fetched_files) + package_json_files = T.let([], T::Array[DependencyFile]) + + path_dependency_details(instance, fetched_files).each do |name, path| + # This happens with relative paths in the package-lock. Skipping it since it results + # in /package.json which is outside of the project directory. + next if path == "file:" + + path = path.gsub(PATH_DEPENDENCY_CLEAN_REGEX, "") + raise PathDependenciesNotReachable, "#{name} at #{path}" if path.start_with?("/") + + filename = path + filename = File.join(filename, MANIFEST_FILENAME) + cleaned_name = Pathname.new(filename).cleanpath.to_path + next if fetched_files.map(&:name).include?(cleaned_name) + + file = instance.fetch_file(filename, fetch_submodules: true) + package_json_files << file + end + + if package_json_files.any? + package_json_files += + path_dependencies(instance, fetched_files + package_json_files) + end + + package_json_files.tap { |fs| fs.each { |f| f.support_file = true } } + end + + sig do + params(instance: FileFetcher, fetched_files: T::Array[DependencyFile]) + .returns(T::Array[[String, String]]) + end + def path_dependency_details(instance, fetched_files) + package_json_path_deps = T.let([], T::Array[[String, String]]) + + fetched_files.each do |file| + package_json_path_deps += + path_dependency_details_from_manifest(instance, file) + end + + [ + *package_json_path_deps + ].uniq + end + + # rubocop:disable Metrics/PerceivedComplexity + # rubocop:disable Metrics/AbcSize + sig do + params(instance: FileFetcher, + file: DependencyFile).returns(T::Array[[String, String]]) + end + def path_dependency_details_from_manifest(instance, file) + return [] unless file.name.end_with?(MANIFEST_FILENAME) + + current_dir = file.name.rpartition("/").first + current_dir = nil if current_dir == "" + + current_depth = File.join(instance.directory, file.name).split("/").count { |path| !path.empty? } + path_to_directory = "../" * current_depth + + dep_types = DEPENDENCY_TYPES # TODO: Is this needed? + parsed_manifest = JSON.parse(T.must(file.content)) + dependency_objects = parsed_manifest.values_at(*dep_types).compact + # Fetch yarn "file:" path "resolutions" so the lockfile can be resolved + resolution_objects = parsed_manifest.values_at("resolutions").compact + manifest_objects = dependency_objects + resolution_objects + + raise Dependabot::DependencyFileNotParseable, file.path unless manifest_objects.all?(Hash) + + resolution_deps = resolution_objects.flat_map(&:to_a) + .map do |path, value| + # skip dependencies that contain invalid values such as inline comments, null, etc. + + unless value.is_a?(String) + Dependabot.logger.warn("File fetcher: Skipping dependency \"#{path}\" " \ + "with value: \"#{value}\"") + + next + end + + convert_dependency_path_to_name(path, value) + end + + path_starts = PATH_DEPENDENCY_STARTS + (dependency_objects.flat_map(&:to_a) + resolution_deps) + .select { |_, v| v.is_a?(String) && v.start_with?(*path_starts) } + .map do |name, path| + path = path.gsub(PATH_DEPENDENCY_CLEAN_REGEX, "") + raise PathDependenciesNotReachable, "#{name} at #{path}" if path.start_with?("/", + "#{path_to_directory}..") + + path = File.join(current_dir, path) unless current_dir.nil? + [name, Pathname.new(path).cleanpath.to_path] + end + rescue JSON::ParserError + raise Dependabot::DependencyFileNotParseable, file.path + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/PerceivedComplexity + + # Re-write the glob name to the targeted dependency name (which is used + # in the lockfile), for example "parent-package/**/sub-dep/target-dep" > + # "target-dep" + sig { params(path: String, value: String).returns([String, String]) } + def convert_dependency_path_to_name(path, value) + # Picking the last two parts that might include a scope + parts = path.split("/").last(2) + parts.shift if parts.count == 2 && !T.must(parts.first).start_with?("@") + [parts.join("/"), value] + end + + sig { params(instance: FileFetcher).returns(T::Array[DependencyFile]) } + def fetch_workspace_package_jsons(instance) + parsed_manifest = parsed_package_json(instance) + return [] unless parsed_manifest["workspaces"] + + workspace_paths(instance, parsed_manifest["workspaces"]).filter_map do |workspace| + fetch_package_json_if_present(instance, workspace) + end + end + + sig do + params(instance: FileFetcher, + workspace_object: T.untyped).returns(T::Array[String]) + end + def workspace_paths(instance, workspace_object) + paths_array = + if workspace_object.is_a?(Hash) + workspace_object.values_at("packages", "nohoist").flatten.compact + elsif workspace_object.is_a?(Array) then workspace_object + else + [] # Invalid lerna.json, which must not be in use + end + + paths_array.flat_map { |path| recursive_find_directories(instance, path) } + end + + sig { params(instance: FileFetcher, glob: String).returns(T::Array[String]) } + def find_directories(instance, glob) + return [glob] unless glob.include?("*") + + unglobbed_path = + glob.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*") + .split("*") + .first&.gsub(%r{(?<=/)[^/]*$}, "") || "." + + dir = instance.directory.gsub(%r{(^/|/$)}, "") + + paths = + instance.fetch_repo_contents(dir: unglobbed_path, raise_errors: false) + .select { |file| file.type == "dir" } + .map { |f| f.path.gsub(%r{^/?#{Regexp.escape(dir)}/?}, "") } + + matching_paths(glob, paths) + end + + sig { params(glob: String, paths: T::Array[String]).returns(T::Array[String]) } + def matching_paths(glob, paths) + glob = glob.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*") + glob = "#{glob}/*" if glob.end_with?("**") + + paths.select { |filename| File.fnmatch?(glob, filename, File::FNM_PATHNAME) } + end + + sig do + params(instance: FileFetcher, glob: String, + prefix: String).returns(T::Array[String]) + end + def recursive_find_directories(instance, glob, prefix = "") + return [prefix + glob] unless glob.include?("*") + + glob = glob.gsub(%r{^\./}, "") + glob_parts = glob.split("/") + + current_glob = glob_parts.first + paths = find_directories(instance, prefix + T.must(current_glob)) + next_parts = current_glob == "**" ? glob_parts : glob_parts.drop(1) + return paths if next_parts.empty? + + paths += paths.flat_map do |expanded_path| + recursive_find_directories(instance, next_parts.join("/"), "#{expanded_path}/") + end + + matching_paths(prefix + glob, paths) + end + + sig do + params(instance: FileFetcher, workspace: String).returns(T.nilable(DependencyFile)) + end + def fetch_package_json_if_present(instance, workspace) + file = File.join(workspace, MANIFEST_FILENAME) + + begin + instance.fetch_file(file) + rescue Dependabot::DependencyFileNotFound + # Not all paths matched by a workspace glob may contain a package.json + # file. Ignore if that's the case + nil + end + end + + sig { params(instance: FileFetcher).returns(DependencyFile) } + def package_json(instance) + @package_json ||= T.let(instance.fetch_file(Javascript::MANIFEST_FILENAME), T.nilable(DependencyFile)) + end + + sig { params(instance: FileFetcher).returns(T.untyped) } + def parsed_package_json(instance) + manifest_file = package_json(instance) + parsed = JSON.parse(T.must(manifest_file.content)) + raise Dependabot::DependencyFileNotParseable, manifest_file.path unless parsed.is_a?(Hash) + + parsed + rescue JSON::ParserError + raise Dependabot::DependencyFileNotParseable, T.must(manifest_file).path + end + + sig do + params(instance: FileFetcher, filename: String).returns(T.nilable(DependencyFile)) + end + def fetch_file_with_support(instance, filename) + instance.fetch_file(filename).tap { |f| f.support_file = true } + rescue Dependabot::DependencyFileNotFound + nil + end + + sig do + params(instance: FileFetcher, filename: String).returns(T.nilable(DependencyFile)) + end + def fetch_file_from_parent_directories(instance, filename) + (1..instance.directory.split("/").count).each do |i| + file = fetch_file_with_support(instance, ("../" * i) + filename) + return file if file + end + nil + end + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/shared/file_parser.rb b/javascript/lib/dependabot/javascript/shared/file_parser.rb new file mode 100644 index 0000000000..de581d995b --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/file_parser.rb @@ -0,0 +1,454 @@ +# typed: strict +# frozen_string_literal: true + +# See https://docs.npmjs.com/files/package.json for package.json format docs. + +module Dependabot + module Javascript + module Shared + class FileParser < Dependabot::FileParsers::Base + extend T::Sig + + abstract! + + DEPENDENCY_TYPES = T.let(%w(dependencies devDependencies optionalDependencies).freeze, T::Array[String]) + GIT_URL_REGEX = %r{ + (?^|^git.*?|^github:|^bitbucket:|^gitlab:|github\.com/) + (?[a-z0-9-]+)/ + (?[a-z0-9_.-]+) + ( + (?:\#semver:(?.+))| + (?:\#(?=[\^~=<>*])(?.+))| + (?:\#(?.+)) + )?$ + }ix + RC_FILENAME = T.let(".npmrc", String) + + sig do + params( + json: T::Hash[String, T.untyped], + _block: T.proc.params(arg0: String, arg1: String, arg2: String).void + ) + .void + end + def self.each_dependency(json, &_block) + DEPENDENCY_TYPES.each do |type| + deps = json[type] || {} + deps.each do |name, requirement| + yield(name, requirement, type) + end + end + end + + sig { override.returns(T::Array[Dependency]) } + def parse # rubocop:disable Metrics/PerceivedComplexity + dependency_set = DependencySet.new + dependency_set += manifest_dependencies + dependency_set += lockfile_dependencies + dependency_set += workspace_catalog_dependencies if pnpm_workspace_yml + + dependencies = self.class.dependencies_with_all_versions_metadata(dependency_set) + + dependencies.reject do |dep| + reqs = dep.requirements + + # Ignore dependencies defined in support files, since we don't want PRs for those + support_reqs = reqs.select { |r| support_package_files.any? { |f| f.name == r[:file] } } + next true if support_reqs.any? + + # TODO: Currently, Dependabot can't handle dependencies that have both + # a git source *and* a non-git source. Fix that! + git_reqs = reqs.select { |r| r.dig(:source, :type) == "git" } + next false if git_reqs.none? + next true if git_reqs.map { |r| r.fetch(:source) }.uniq.count > 1 + + dep.requirements.any? { |r| r.dig(:source, :type) != "git" } + end + end + + sig { abstract.returns(Ecosystem) } + def ecosystem; end + + sig { params(dependency_set: Dependabot::FileParsers::Base::DependencySet).returns(T::Array[Dependency]) } + def self.dependencies_with_all_versions_metadata(dependency_set) + dependency_set.dependencies.map do |dependency| + dependency.metadata[:all_versions] = dependency_set.all_versions_for_name(dependency.name) + dependency + end + end + + private + + sig { abstract.returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) } + def lockfiles; end + + sig { abstract.returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) } + def registry_config_files; end + + sig { returns(T.untyped) } + def parsed_package_json + JSON.parse(T.must(package_json.content)) + rescue JSON::ParserError + raise Dependabot::DependencyFileNotParseable, package_json.path + end + + sig { returns(Dependabot::DependencyFile) } + def package_json + # Declare the instance variable with T.let and the correct type + @package_json ||= T.let( + T.must(dependency_files.find { |f| f.name == MANIFEST_FILENAME }), + T.nilable(Dependabot::DependencyFile) + ) + end + + sig { returns(T.nilable(Dependabot::DependencyFile)) } + def npmrc + @npmrc ||= T.let(dependency_files.find do |f| + f.name.end_with?(RC_FILENAME) + end, T.nilable(Dependabot::DependencyFile)) + end + + sig { returns(T.nilable(Dependabot::DependencyFile)) } + def pnpm_workspace_yml + nil + end + + sig { returns(Dependabot::FileParsers::Base::DependencySet) } + def manifest_dependencies + dependency_set = DependencySet.new + + package_files.each do |file| + json = JSON.parse(T.must(file.content)) + + # TODO: Currently, Dependabot can't handle flat dependency files + # (and will error at the FileUpdater stage, because the + # UpdateChecker doesn't take account of flat resolution). + next if json["flat"] + + self.class.each_dependency(json) do |name, requirement, type| + next unless requirement.is_a?(String) + + # Skip dependencies using Yarn workspace cross-references as requirements + next if requirement.start_with?("workspace:", "catalog:") + + requirement = "*" if requirement == "" + dep = build_dependency( + file: file, type: type, name: name, requirement: requirement + ) + dependency_set << dep if dep + end + end + + dependency_set + end + + sig { returns(Dependabot::FileParsers::Base::DependencySet) } + def workspace_catalog_dependencies + dependency_set = DependencySet.new + workspace_config = YAML.safe_load(T.must(pnpm_workspace_yml&.content), aliases: true) + + workspace_config["catalog"]&.each do |name, version| + dep = build_dependency( + file: T.must(pnpm_workspace_yml), type: "dependencies", name: name, requirement: version + ) + dependency_set << dep if dep + end + + workspace_config["catalogs"]&.each do |_, group_depenencies| + group_depenencies.each do |name, version| + dep = build_dependency( + file: T.must(pnpm_workspace_yml), type: "dependencies", name: name, requirement: version + ) + dependency_set << dep if dep + end + end + + dependency_set + end + + sig { abstract.returns(FileParser::LockfileParser) } + def lockfile_parser; end + + sig { returns(Dependabot::FileParsers::Base::DependencySet) } + def lockfile_dependencies + lockfile_parser.parse_set + end + + sig do + params(file: DependencyFile, type: T.untyped, name: String, requirement: String) + .returns(T.nilable(Dependency)) + end + def build_dependency(file:, type:, name:, requirement:) + lockfile_details = lockfile_parser.lockfile_details( + dependency_name: name, + requirement: requirement, + manifest_name: file.name + ) + version = version_for(requirement, lockfile_details) + converted_version = T.let(if version.nil? + nil + elsif version.is_a?(String) + version + else + Dependabot::Version.new(version) + end, T.nilable(T.any(String, Dependabot::Version))) + + return if lockfile_details && !version + return if ignore_requirement?(requirement) + return if workspace_package_names.include?(name) + + # TODO: Handle aliased packages: + # https://github.com/dependabot/dependabot-core/pull/1115 + # + # Ignore dependencies with an alias in the name + # Example: "my-fetch-factory@npm:fetch-factory" + return if aliased_package_name?(name) + + Dependency.new( + name: name, + version: converted_version, + package_manager: ecosystem.name, + requirements: [{ + requirement: requirement_for(requirement), + file: file.name, + groups: [type], + source: source_for(name, requirement, lockfile_details) + }] + ) + end + + sig { override.void } + def check_required_files + return if get_original_file(MANIFEST_FILENAME) + + raise DependencyFileNotFound.new(nil, + "#{MANIFEST_FILENAME} not found.") + end + + sig { params(requirement: String).returns(T::Boolean) } + def ignore_requirement?(requirement) + return true if local_path?(requirement) + return true if non_git_url?(requirement) + + # TODO: Handle aliased packages: + # https://github.com/dependabot/dependabot-core/pull/1115 + alias_package?(requirement) + end + + sig { params(requirement: String).returns(T::Boolean) } + def local_path?(requirement) + requirement.start_with?("link:", "file:", "/", "./", "../", "~/") + end + + sig { params(requirement: String).returns(T::Boolean) } + def alias_package?(requirement) + requirement.start_with?("npm:") + end + + sig { params(requirement: String).returns(T::Boolean) } + def non_git_url?(requirement) + requirement.include?("://") && !git_url?(requirement) + end + + sig { params(requirement: String).returns(T::Boolean) } + def git_url?(requirement) + requirement.match?(GIT_URL_REGEX) + end + + sig { params(requirement: String).returns(T::Boolean) } + def git_url_with_semver?(requirement) + return false unless git_url?(requirement) + + !T.must(requirement.match(GIT_URL_REGEX)).named_captures.fetch("semver").nil? + end + + sig { params(name: String).returns(T::Boolean) } + def aliased_package_name?(name) + name.include?("@npm:") + end + + sig { returns(T::Array[String]) } + def workspace_package_names + @workspace_package_names ||= T.let(package_files.filter_map do |f| + JSON.parse(T.must(f.content))["name"] + end, T.nilable(T::Array[String])) + end + + sig do + params(requirement: String, lockfile_details: T.nilable(T::Hash[String, T.untyped])) + .returns(T.nilable(T.any(String, Integer, Gem::Version))) + end + def version_for(requirement, lockfile_details) + if git_url_with_semver?(requirement) + semver_version = lockfile_version_for(lockfile_details) + return semver_version if semver_version + + git_revision = git_revision_for(lockfile_details) + version_from_git_revision(requirement, git_revision) || git_revision + elsif git_url?(requirement) + git_revision_for(lockfile_details) + elsif lockfile_details + lockfile_version_for(lockfile_details) + else + exact_version = exact_version_for(requirement) + return unless exact_version + + semver_version_for(exact_version) + end + end + + sig { params(lockfile_details: T.nilable(T::Hash[String, T.untyped])).returns(T.nilable(String)) } + def git_revision_for(lockfile_details) + version = T.cast(lockfile_details&.fetch("version", nil), T.nilable(String)) + resolved = T.cast(lockfile_details&.fetch("resolved", nil), T.nilable(String)) + [ + version&.split("#")&.last, + resolved&.split("#")&.last, + resolved&.split("/")&.last + ].find { |str| commit_sha?(str) } + end + + sig { params(string: T.nilable(String)).returns(T::Boolean) } + def commit_sha?(string) + return false unless string.is_a?(String) + + string.match?(/^[0-9a-f]{40}$/) + end + + sig { params(requirement: String, git_revision: T.nilable(String)).returns(T.nilable(String)) } + def version_from_git_revision(requirement, git_revision) + tags = + Dependabot::GitMetadataFetcher.new( + url: git_source_for(requirement).fetch(:url), + credentials: credentials + ).tags + .select { |t| [t.commit_sha, t.tag_sha].include?(git_revision) } + + tags.each do |t| + next unless t.name.match?(Dependabot::GitCommitChecker::VERSION_REGEX) + + version = T.must(t.name.match(Dependabot::GitCommitChecker::VERSION_REGEX)) + .named_captures.fetch("version") + next unless version_class.correct?(version) + + return version + end + + nil + rescue Dependabot::GitDependenciesNotReachable + nil + end + + sig do + params(lockfile_details: T.nilable(T::Hash[String, T.untyped])) + .returns(T.nilable(T.any(String, Integer, Gem::Version))) + end + def lockfile_version_for(lockfile_details) + semver_version_for(lockfile_details&.fetch("version", "")) + end + + sig { params(version: T.nilable(String)).returns(T.nilable(T.any(String, Integer, Gem::Version))) } + def semver_version_for(version) + version_class.semver_for(version) + end + + sig { params(requirement: String).returns(T.nilable(String)) } + def exact_version_for(requirement) + req = requirement_class.new([requirement]) + return unless req.exact? + + req.requirements.first.last.to_s + rescue Gem::Requirement::BadRequirementError + # If it doesn't parse, it's definitely not exact + end + + sig do + params(name: String, requirement: String, lockfile_details: T.nilable(T::Hash[String, T.untyped])) + .returns(T.nilable(T::Hash[Symbol, T.untyped])) + end + def source_for(name, requirement, lockfile_details) + return git_source_for(requirement) if git_url?(requirement) + + resolved_url = lockfile_details&.fetch("resolved", nil) + + resolution = lockfile_details&.fetch("resolution", nil) + package_match = resolution&.match(/__archiveUrl=(?.+)/) + resolved_url = CGI.unescape(package_match.named_captures.fetch("package_url", "")) if package_match + + return unless resolved_url + return unless resolved_url.start_with?("http") + return if resolved_url.match?(/(?\S+):registry\s*=\s*(?\S+)/ + + sig do + params( + dependency_files: T::Array[Dependabot::DependencyFile], + credentials: T::Array[Dependabot::Credential], + dependencies: T::Array[Dependabot::Dependency] + ).void + end + def initialize(dependency_files:, credentials:, dependencies: []) + @dependency_files = dependency_files + @credentials = credentials + @dependencies = dependencies + end + + # PROXY WORK + sig { returns(String) } + def npmrc_content + initial_content = + if npmrc_file then complete_npmrc_from_credentials + else + build_npmrc_content_from_lockfile + end + + final_content = initial_content || "" + + return final_content unless registry_credentials.any? + + credential_lines_for_npmrc.each do |credential_line| + next if final_content.include?(credential_line) + + final_content = [final_content, credential_line].reject(&:empty?).join("\n") + end + + final_content + end + + private + + sig { returns(T::Array[Dependabot::DependencyFile]) } + attr_reader :dependency_files + + sig { returns(T::Array[Dependabot::Credential]) } + attr_reader :credentials + + sig { returns(T::Array[Dependabot::Dependency]) } + attr_reader :dependencies + + sig { returns(T.nilable(String)) } + def build_npmrc_content_from_lockfile + return unless yarn_lock || package_lock || shrinkwrap + return unless global_registry + + registry = T.must(global_registry)["registry"] + registry = "https://#{registry}" unless registry&.start_with?("http") + "registry = #{registry}\n" \ + "#{npmrc_global_registry_auth_line}" \ + "always-auth = true" + end + + sig { returns(T.nilable(String)) } + def build_yarnrc_content_from_lockfile + return unless yarn_lock || package_lock + return unless global_registry + + "registry \"https://#{T.must(global_registry)['registry']}\"\n" \ + "#{yarnrc_global_registry_auth_line}" \ + "npmAlwaysAuth: true" + end + + # rubocop:disable Metrics/PerceivedComplexity + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/AbcSize + sig { returns(T.nilable(Dependabot::Credential)) } + def global_registry + return @global_registry if defined?(@global_registry) + + @global_registry = T.let( + registry_credentials.find do |cred| + next false if CENTRAL_REGISTRIES.include?(cred["registry"]) + + # If all the URLs include this registry, it's global + next true if dependency_urls&.size&.positive? && dependency_urls&.all? do |url| + url.include?(T.must(cred["registry"])) + end + + # Check if this registry has already been defined in .npmrc as a scoped registry + next false if npmrc_scoped_registries&.any? { |sr| sr.include?(T.must(cred["registry"])) } + + next false if yarnrc_scoped_registries&.any? { |sr| sr.include?(T.must(cred["registry"])) } + + # If any unscoped URLs include this registry, assume it's global + dependency_urls + &.reject { |u| u.include?("@") || u.include?("%40") } + &.any? { |url| url.include?(T.must(cred["registry"])) } + end, + T.nilable(Dependabot::Credential) + ) + end + # rubocop:enable Metrics/PerceivedComplexity + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/AbcSize + + sig { returns(String) } + def npmrc_global_registry_auth_line + # This token is passed in from the Dependabot Config + # We write it to the .npmrc file so that it can be used by the VulnerabilityAuditor + token = global_registry&.fetch("token", nil) + return "" unless token + + auth_line(token, global_registry&.fetch("registry")) + "\n" + end + + sig { returns(String) } + def yarnrc_global_registry_auth_line + token = global_registry&.fetch("token", nil) + return "" unless token + + if token.include?(":") + encoded_token = Base64.encode64(token).delete("\n") + "npmAuthIdent: \"#{encoded_token}\"" + elsif Base64.decode64(token).ascii_only? && + Base64.decode64(token).include?(":") + "npmAuthIdent: \"#{token.delete("\n")}\"" + else + "npmAuthToken: \"#{token}\"" + end + end + + sig { returns(T.nilable(T::Array[String])) } + def dependency_urls + return @dependency_urls if defined?(@dependency_urls) + + @dependency_urls = [] + + if dependencies.any? + @dependency_urls = dependencies.map do |dependency| + UpdateChecker::RegistryFinder.new( + dependency: dependency, + credentials: credentials, + rc_file: npmrc_file + ).dependency_url + end + return @dependency_urls + end + + # The registry URL for Bintray goes into the lockfile in a + # modified format, so we modify it back before checking against + # our credentials + @dependency_urls = T.let( + @dependency_urls.map do |url| + url.gsub("dl.bintray.com//", "api.bintray.com/npm/") + end, + T.nilable(T::Array[String]) + ) + end + + sig { returns(String) } + def complete_npmrc_from_credentials + # removes attribute timeout to allow for job update, + # having a timeout=xxxxx value is causing some jobs to fail + initial_content = T.must(T.must(npmrc_file).content) + .gsub(/^.*\$\{.*\}.*/, "").strip.gsub(/^timeout.*/, "").strip + "\n" + + return initial_content unless yarn_lock || package_lock + return initial_content unless global_registry + + registry = T.must(global_registry)["registry"] + registry = "https://#{registry}" unless registry&.start_with?("http") + initial_content + + "registry = #{registry}\n" \ + "#{npmrc_global_registry_auth_line}" \ + "always-auth = true\n" + end + + sig { returns(String) } + def complete_yarnrc_from_credentials + initial_content = T.must(T.must(yarnrc_file).content) + .gsub(/^.*\$\{.*\}.*/, "").strip + "\n" + return initial_content unless yarn_lock || package_lock + return initial_content unless global_registry + + initial_content + + "registry: \"https://#{T.must(global_registry)['registry']}\"\n" \ + "#{yarnrc_global_registry_auth_line}" \ + "npmAlwaysAuth: true\n" + end + + sig { returns(T.nilable(String)) } + def build_npmrc_from_yarnrc + yarnrc_global_registry = + yarnrc_file&.content + &.lines + &.find { |line| line.match?(/^\s*registry\s/) } + &.match(UpdateChecker::RegistryFinder::YARN_GLOBAL_REGISTRY_REGEX) + &.named_captures&.fetch("registry") + + return "registry = #{yarnrc_global_registry}\n" if yarnrc_global_registry + + build_npmrc_content_from_lockfile + end + + sig { returns(T.nilable(String)) } + def build_yarnrc_from_yarnrc + yarnrc_global_registry = + yarnrc_file&.content + &.lines + &.find { |line| line.match?(/^\s*registry\s/) } + &.match(/^\s*registry\s+"(?[^"]+)"/) + &.named_captures&.fetch("registry") + + return "registry \"#{yarnrc_global_registry}\"\n" if yarnrc_global_registry + + build_yarnrc_content_from_lockfile + end + + sig { returns(T::Array[String]) } + def credential_lines_for_npmrc + lines = T.let([], T::Array[String]) + registry_credentials.each do |cred| + registry = cred.fetch("registry") + + lines += T.must(registry_scopes(registry)) if registry_scopes(registry) + + token = cred.fetch("token", nil) + next unless token + + lines << auth_line(token, registry) + end + + return lines unless lines.any? { |str| str.include?("auth=") } + + # Work around a suspected yarn bug + ["always-auth = true"] + lines + end + + sig { params(token: String, registry: T.nilable(String)).returns(String) } + def auth_line(token, registry = nil) + auth = if token.include?(":") + encoded_token = Base64.encode64(token).delete("\n") + "_auth=#{encoded_token}" + elsif Base64.decode64(token).ascii_only? && + Base64.decode64(token).include?(":") + "_auth=#{token.delete("\n")}" + else + "_authToken=#{token}" + end + + return auth unless registry + + # We need to ensure the registry uri ends with a trailing slash in the npmrc file + # but we do not want to add one if it already exists + registry_with_trailing_slash = registry.sub(%r{\/?$}, "/") + + "//#{registry_with_trailing_slash}:#{auth}" + end + + sig { returns(T.nilable(T::Array[String])) } + def npmrc_scoped_registries + return [] unless npmrc_file + + @npmrc_scoped_registries ||= T.let( + T.must(T.must(npmrc_file).content).lines.select { |line| line.match?(SCOPED_REGISTRY) } + .filter_map { |line| line.match(SCOPED_REGISTRY)&.named_captures&.fetch("registry") }, + T.nilable(T::Array[String]) + ) + end + + sig { returns(T.nilable(T::Array[String])) } + def yarnrc_scoped_registries + return [] unless yarnrc_file + + @yarnrc_scoped_registries ||= T.let( + T.must(T.must(yarnrc_file).content).lines.select { |line| line.match?(SCOPED_REGISTRY) } + .filter_map { |line| line.match(SCOPED_REGISTRY)&.named_captures&.fetch("registry") }, + T.nilable(T::Array[String]) + ) + end + + # rubocop:disable Metrics/PerceivedComplexity + sig { params(registry: String).returns(T.nilable(T::Array[String])) } + def registry_scopes(registry) + # Central registries don't just apply to scopes + return if CENTRAL_REGISTRIES.include?(registry) + return unless dependency_urls + + other_regs = + registry_credentials.map { |c| c.fetch("registry") } - + [registry] + affected_urls = + dependency_urls + &.select do |url| + next false unless url.include?(registry) + + other_regs.none? { |r| r.include?(registry) && url.include?(r) } + end + + scopes = T.must(affected_urls).map do |url| + url.split(/\%40|@/)[1]&.split(%r{\%2[fF]|/})&.first + end.uniq + + # Registry used for unscoped packages + return if scopes.include?(nil) + + scopes.map { |scope| "@#{scope}:registry=https://#{registry}" } + end + # rubocop:enable Metrics/PerceivedComplexity + + sig { returns(T::Array[Dependabot::Credential]) } + def registry_credentials + credentials.select { |cred| cred.fetch("type") == "npm_registry" } + end + + sig { returns(T.nilable(T::Hash[String, T.untyped])) } + def parsed_package_lock + @parsed_package_lock ||= T.let( + JSON.parse(T.must(T.must(package_lock).content)), + T.nilable(T::Hash[String, T.untyped]) + ) + end + + sig { returns(T.nilable(Dependabot::DependencyFile)) } + def npmrc_file + @npmrc_file ||= T.let( + dependency_files.find { |f| f.name.end_with?(".npmrc") }, + T.nilable(Dependabot::DependencyFile) + ) + end + + sig { returns(T.nilable(Dependabot::DependencyFile)) } + def yarnrc_file + @yarnrc_file ||= T.let( + dependency_files.find { |f| f.name.end_with?(".yarnrc") }, + T.nilable(Dependabot::DependencyFile) + ) + end + + sig { returns(T.nilable(Dependabot::DependencyFile)) } + def yarnrc_yml_file + @yarnrc_yml_file ||= T.let( + dependency_files.find { |f| f.name.end_with?(".yarnrc.yml") }, + T.nilable(Dependabot::DependencyFile) + ) + end + + sig { returns(T.nilable(Dependabot::DependencyFile)) } + def yarn_lock + @yarn_lock ||= T.let( + dependency_files.find { |f| f.name == "yarn.lock" }, + T.nilable(Dependabot::DependencyFile) + ) + end + + sig { returns(T.nilable(Dependabot::DependencyFile)) } + def package_lock + @package_lock ||= T.let( + dependency_files.find { |f| f.name == "package-lock.json" }, + T.nilable(Dependabot::DependencyFile) + ) + end + + sig { returns(T.nilable(Dependabot::DependencyFile)) } + def shrinkwrap + @shrinkwrap ||= T.let( + dependency_files.find { |f| f.name == "npm-shrinkwrap.json" }, + T.nilable(Dependabot::DependencyFile) + ) + end + end + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/shared/file_updater/package_json_preparer.rb b/javascript/lib/dependabot/javascript/shared/file_updater/package_json_preparer.rb new file mode 100644 index 0000000000..90f1573bc0 --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/file_updater/package_json_preparer.rb @@ -0,0 +1,87 @@ +# typed: true +# frozen_string_literal: true + +module Dependabot + module Javascript + module Shared + class FileUpdater + class PackageJsonPreparer + def initialize(package_json_content:) + @package_json_content = package_json_content + end + + def prepared_content + content = package_json_content + content = replace_ssh_sources(content) + content = remove_workspace_path_prefixes(content) + content = remove_invalid_characters(content) + content + end + + def replace_ssh_sources(content) + updated_content = content + + git_ssh_requirements_to_swap.each do |req| + new_req = req.gsub(%r{git\+ssh://git@(.*?)[:/]}, 'https://\1/') + updated_content = updated_content.gsub(req, new_req) + end + + updated_content + end + + # A bug prevents Yarn recognising that a directory is part of a + # workspace if it is specified with a `./` prefix. + def remove_workspace_path_prefixes(content) + json = JSON.parse(content) + return content unless json.key?("workspaces") + + workspace_object = json.fetch("workspaces") + paths_array = + if workspace_object.is_a?(Hash) + workspace_object.values_at("packages", "nohoist") + .flatten.compact + elsif workspace_object.is_a?(Array) then workspace_object + else + raise "Unexpected workspace object" + end + + paths_array.each { |path| path.gsub!(%r{^\./}, "") } + + JSON.pretty_generate(json) + end + + def remove_invalid_characters(content) + content + .gsub(/\{\{[^\}]*?\}\}/, "something") # {{ nm }} syntax not allowed + .gsub(/(? 1) + + # (we observed that) package.json does not always contains the same dependencies compared to + # "dependencies" list, for example, dependencies object can contain same name dependency + # "dep"=> "1.0.0" and "dev" => "1.0.1" while package.json can only contain "dep" => "1.0.0", + # the other dependency is not present in package.json so we don't have to update it, this is most + # likely (as observed) a transitive dependency which only needs update in lockfile, So we avoid + # throwing exception and let the update continue. + + Dependabot.logger.info("experiment: avoid_duplicate_updates_package_json. + Updating package.json for #{dep.name} ") + + raise "Expected content to change!" + end + + if !Dependabot::Experiments.enabled?(:avoid_duplicate_updates_package_json) && (content == new_content) + raise "Expected content to change!" + end + + content = new_content + end + + new_requirements(dep).each do |new_req| + old_req = old_requirement(dep, new_req) + + content = update_package_json_resolutions( + package_json_content: T.must(content), + new_req: new_req, + dependency: dep, + old_req: old_req + ) + end + + content + end + end + # rubocop:enable Metrics/PerceivedComplexity + sig do + params( + dependency: Dependabot::Dependency, + new_requirement: T::Hash[Symbol, T.untyped] + ) + .returns(T.nilable(T::Hash[Symbol, T.untyped])) + end + def old_requirement(dependency, new_requirement) + T.must(dependency.previous_requirements) + .select { |r| r[:file] == package_json.name } + .find { |r| r[:groups] == new_requirement[:groups] } + end + + sig { params(dependency: Dependabot::Dependency).returns(T::Array[T::Hash[Symbol, T.untyped]]) } + def new_requirements(dependency) + dependency.requirements.select { |r| r[:file] == package_json.name } + end + + sig { params(dependency: Dependabot::Dependency).returns(T.nilable(T::Array[T::Hash[Symbol, T.untyped]])) } + def updated_requirements(dependency) + return unless dependency.previous_requirements + + preliminary_check_for_update(dependency) + + updated_requirement_pairs = + dependency.requirements.zip(T.must(dependency.previous_requirements)) + .reject do |new_req, old_req| + next true if new_req == old_req + next false unless old_req&.fetch(:source).nil? + + new_req[:requirement] == old_req&.fetch(:requirement) + end + + updated_requirement_pairs + .map(&:first) + .select { |r| r[:file] == package_json.name } + end + + sig do + params( + package_json_content: String, + new_req: T::Hash[Symbol, T.untyped], + dependency_name: String, + old_req: T.nilable(T::Hash[Symbol, T.untyped]) + ) + .returns(String) + end + def update_package_json_declaration(package_json_content:, new_req:, dependency_name:, old_req:) + original_line = declaration_line( + dependency_name: dependency_name, + dependency_req: old_req, + content: package_json_content + ) + + replacement_line = replacement_declaration_line( + original_line: original_line, + old_req: old_req, + new_req: new_req + ) + + groups = new_req.fetch(:groups) + + update_package_json_sections( + groups, + package_json_content, + original_line, + replacement_line + ) + end + + # For full details on how Yarn resolutions work, see + # https://github.com/yarnpkg/rfcs/blob/master/implemented/ + # 0000-selective-versions-resolutions.md + sig do + params( + package_json_content: String, + new_req: T::Hash[Symbol, T.untyped], + dependency: Dependabot::Dependency, + old_req: T.nilable(T::Hash[Symbol, T.untyped]) + ) + .returns(String) + end + def update_package_json_resolutions(package_json_content:, new_req:, dependency:, old_req:) + dep = dependency + parsed_json_content = JSON.parse(package_json_content) + resolutions = + parsed_json_content.fetch("resolutions", parsed_json_content.dig("pnpm", "overrides") || {}) + .reject { |_, v| v != old_req && v != dep.previous_version } + .select { |k, _| k == dep.name || k.end_with?("/#{dep.name}") } + + return package_json_content unless resolutions.any? + + content = package_json_content + resolutions.each do |_, resolution| + original_line = declaration_line( + dependency_name: dep.name, + dependency_req: { requirement: resolution }, + content: content + ) + + new_resolution = resolution == old_req ? new_req : dep.version + + replacement_line = replacement_declaration_line( + original_line: original_line, + old_req: { requirement: resolution }, + new_req: { requirement: new_resolution } + ) + + content = update_package_json_sections( + %w(resolutions overrides), content, original_line, replacement_line + ) + end + content + end + + sig do + params( + dependency_name: String, + dependency_req: T.nilable(T::Hash[Symbol, T.untyped]), + content: String + ) + .returns(String) + end + def declaration_line(dependency_name:, dependency_req:, content:) + git_dependency = dependency_req&.dig(:source, :type) == "git" + + unless git_dependency + requirement = dependency_req&.fetch(:requirement) + return content.match(/"#{Regexp.escape(dependency_name)}"\s*:\s* + "#{Regexp.escape(requirement)}"/x).to_s + end + + username, repo = + dependency_req&.dig(:source, :url)&.split("/")&.last(2) + + content.match( + %r{"#{Regexp.escape(dependency_name)}"\s*:\s* + ".*?#{Regexp.escape(username)}/#{Regexp.escape(repo)}.*"}x + ).to_s + end + + sig do + params( + original_line: String, + old_req: T.nilable(T::Hash[Symbol, T.untyped]), + new_req: T::Hash[Symbol, T.untyped] + ) + .returns(String) + end + def replacement_declaration_line(original_line:, old_req:, new_req:) + was_git_dependency = old_req&.dig(:source, :type) == "git" + now_git_dependency = new_req.dig(:source, :type) == "git" + + unless was_git_dependency + return original_line.gsub( + %("#{old_req&.fetch(:requirement)}"), + %("#{new_req.fetch(:requirement)}") + ) + end + + unless now_git_dependency + return original_line.gsub( + /(?<=\s").*[^\\](?=")/, + new_req.fetch(:requirement) + ) + end + + if original_line.match?(/#[\^~=<>]|semver:/) + return update_git_semver_requirement( + original_line: original_line, + old_req: old_req, + new_req: new_req + ) + end + + original_line.gsub( + %(##{old_req&.dig(:source, :ref)}"), + %(##{new_req.dig(:source, :ref)}") + ) + end + + sig do + params( + original_line: String, + old_req: T.nilable(T::Hash[Symbol, String]), + new_req: T::Hash[Symbol, String] + ) + .returns(String) + end + def update_git_semver_requirement(original_line:, old_req:, new_req:) + if original_line.include?("semver:") + return original_line.gsub( + %(semver:#{old_req&.fetch(:requirement)}"), + %(semver:#{new_req.fetch(:requirement)}") + ) + end + + raise "Not a semver req!" unless original_line.match?(/#[\^~=<>]/) + + original_line.gsub( + %(##{old_req&.fetch(:requirement)}"), + %(##{new_req.fetch(:requirement)}") + ) + end + + sig do + params( + sections: T::Array[String], + content: String, + old_line: String, + new_line: String + ) + .returns(String) + end + def update_package_json_sections(sections, content, old_line, new_line) + # Currently, Dependabot doesn't update peerDependencies. However, + # if a development dependency is being updated and its requirement + # matches the requirement on a peer dependency we probably want to + # update the peer too. + # + # TODO: Move this logic to the UpdateChecker (and parse peer deps) + sections += ["peerDependencies"] + sections_regex = /#{sections.join('|')}/ + + declaration_blocks = T.let([], T::Array[String]) + + content.scan(/['"]#{sections_regex}['"]\s*:\s*\{/m) do + mtch = T.must(Regexp.last_match) + declaration_blocks << + (mtch.to_s + T.must(mtch.post_match[0..closing_bracket_index(mtch.post_match)])) + end + + declaration_blocks.reduce(content.dup) do |new_content, block| + updated_block = block.sub(old_line, new_line) + new_content.sub(block, updated_block) + end + end + + sig { params(string: String).returns(Integer) } + def closing_bracket_index(string) + closes_required = 1 + + string.chars.each_with_index do |char, index| + closes_required += 1 if char == "{" + closes_required -= 1 if char == "}" + return index if closes_required.zero? + end + + 0 + end + + sig { params(dependency: Dependabot::Dependency).void } + def preliminary_check_for_update(dependency) + T.must(dependency.previous_requirements).each do |req, _dep| + next if req.fetch(:requirement).nil? + + # some deps are patched with local patches, we don't need to update them + if req.fetch(:requirement).match?(Regexp.union(PATCH_PACKAGE)) + Dependabot.logger.info("Func: updated_requirements. dependency patched #{dependency.name}," \ + " Requirement: '#{req.fetch(:requirement)}'") + + raise DependencyFileNotResolvable, + "Dependency is patched locally, Update not required." + end + + # some deps are added as local packages, we don't need to update them as they are referred to a local path + next unless req.fetch(:requirement).match?(Regexp.union(LOCAL_PACKAGE)) + + Dependabot.logger.info("Func: updated_requirements. local package #{dependency.name}," \ + " Requirement: '#{req.fetch(:requirement)}'") + + raise DependencyFileNotResolvable, + "Local package, Update not required." + end + end + end + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/shared/language.rb b/javascript/lib/dependabot/javascript/shared/language.rb new file mode 100644 index 0000000000..74971fba1b --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/language.rb @@ -0,0 +1,45 @@ +# typed: strong +# frozen_string_literal: true + +module Dependabot + module Javascript + module Shared + class Language < Ecosystem::VersionManager + extend T::Sig + NAME = "javascript" + + SUPPORTED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version]) + + DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version]) + + sig do + params( + detected_version: T.nilable(String), + raw_version: T.nilable(String), + requirement: T.nilable(Requirement) + ).void + end + def initialize(detected_version: nil, raw_version: nil, requirement: nil) + super( + name: NAME, + detected_version: detected_version ? Version.new(detected_version) : nil, + version: raw_version ? Version.new(raw_version) : nil, + deprecated_versions: DEPRECATED_VERSIONS, + supported_versions: SUPPORTED_VERSIONS, + requirement: requirement + ) + end + + sig { override.returns(T::Boolean) } + def deprecated? + false + end + + sig { override.returns(T::Boolean) } + def unsupported? + false + end + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/shared/native_helpers.rb b/javascript/lib/dependabot/javascript/shared/native_helpers.rb new file mode 100644 index 0000000000..56179a9169 --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/native_helpers.rb @@ -0,0 +1,21 @@ +# typed: true +# frozen_string_literal: true + +module Dependabot + module Javascript + module Shared + module NativeHelpers + def self.helper_path + "node #{File.join(native_helpers_root, 'run.js')}" + end + + def self.native_helpers_root + helpers_root = ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", nil) + return File.join(helpers_root, "npm_and_yarn") unless helpers_root.nil? + + File.join(__dir__, "../../../helpers") + end + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/shared/package_manager_detector.rb b/javascript/lib/dependabot/javascript/shared/package_manager_detector.rb new file mode 100644 index 0000000000..1219c03861 --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/package_manager_detector.rb @@ -0,0 +1,72 @@ +# typed: strict +# frozen_string_literal: true + +module Dependabot + module Javascript + module Shared + class PackageManagerDetector + extend T::Sig + extend T::Helpers + + sig do + params( + lockfiles: T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)], + package_json: T.nilable(T::Hash[String, T.untyped]) + ).void + end + def initialize(lockfiles, package_json) + @lockfiles = lockfiles + @package_json = package_json + @manifest_package_manager = T.let(package_json&.fetch(MANIFEST_PACKAGE_MANAGER_KEY, nil), T.nilable(String)) + @engines = T.let(package_json&.fetch(MANIFEST_ENGINES_KEY, {}), T::Hash[String, T.untyped]) + end + + # Returns npm, yarn, or pnpm based on the lockfiles, package.json, and engines + # Defaults to npm if no package manager is detected + sig { returns(String) } + def detect_package_manager + package_manager = name_from_lockfiles || + name_from_package_manager_attr || + name_from_engines + + if package_manager + Dependabot.logger.info("Detected package manager: #{package_manager}") + else + package_manager = DEFAULT_PACKAGE_MANAGER + Dependabot.logger.info("Default package manager used: #{package_manager}") + end + package_manager + rescue StandardError => e + Dependabot.logger.error("Error detecting package manager: #{e.message}") + DEFAULT_PACKAGE_MANAGER + end + + private + + sig { returns(T.nilable(String)) } + def name_from_lockfiles + PACKAGE_MANAGER_CLASSES.keys.map(&:to_s).find { |manager_name| @lockfiles[manager_name.to_sym] } + end + + sig { returns(T.nilable(String)) } + def name_from_package_manager_attr + return unless @manifest_package_manager + + PACKAGE_MANAGER_CLASSES.keys.map(&:to_s).find do |manager_name| + @manifest_package_manager.start_with?("#{manager_name}@") + end + end + + sig { returns(T.nilable(String)) } + def name_from_engines + return unless @engines.is_a?(Hash) + + PACKAGE_MANAGER_CLASSES.each_key do |manager_name| + return manager_name if @engines[manager_name] + end + nil + end + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/shared/package_name.rb b/javascript/lib/dependabot/javascript/shared/package_name.rb new file mode 100644 index 0000000000..7ecaef840f --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/package_name.rb @@ -0,0 +1,118 @@ +# typed: strict +# frozen_string_literal: true + +module Dependabot + module Javascript + module Shared + class PackageName + extend T::Sig + + # NPM package naming rules are defined by the following projects: + # - https://github.com/npm/npm-user-validate + # - https://github.com/npm/validate-npm-package-name + PACKAGE_NAME_REGEX = %r{ + \A # beginning of string + (?=.{1,214}\z) # enforce length (1 - 214) + (@(? # capture 'scope' if present + [a-z0-9\-\_\.\!\~\*\'\(\)]+ # URL-safe characters in scope + )\/)? # scope must be followed by slash + (? # capture package name + (?() # if scope is present + [a-z0-9\-\_\.\!\~\*\'\(\)]+ # scoped names can start with any URL-safe character + | # if no scope + [a-z0-9\-\!\~\*\'\(\)] # non-scoped names cannot start with . or _ + [a-z0-9\-\_\.\!\~\*\'\(\)]* # subsequent characters can be any URL-safe character + ) + ) + \z # end of string + }xi # multi-line/case-insensitive + + TYPES_PACKAGE_NAME_REGEX = %r{ + \A # beginning of string + @types\/ # starts with @types/ + ((?.+)__)? # capture scope + (?.+) # capture name + \z # end of string + }xi # multi-line/case-insensitive + + class InvalidPackageName < StandardError; end + + sig { params(string: String).void } + def initialize(string) + match = PACKAGE_NAME_REGEX.match(string.to_s) + raise InvalidPackageName unless match + + @scope = T.let(match[:scope], T.nilable(String)) + @name = T.let(match[:name], T.nilable(String)) + end + + sig { returns(String) } + def to_s + if scoped? + "@#{@scope}/#{@name}" + else + @name.to_s + end + end + + sig { params(other: PackageName).returns(T::Boolean) } + def eql?(other) + self.class == other.class && to_s == other.to_s + end + + sig { returns(Integer) } + def hash + to_s.downcase.hash + end + + sig { params(other: PackageName).returns(T.nilable(Integer)) } + def <=>(other) + to_s.casecmp(other.to_s) + end + + sig { returns(T.nilable(PackageName)) } + def library_name + return unless types_package? + return @library_name if defined?(@library_name) + + lib_name = + begin + match = T.must(TYPES_PACKAGE_NAME_REGEX.match(to_s)) + if match[:scope] + self.class.new("@#{match[:scope]}/#{match[:name]}") + else + self.class.new(match[:name].to_s) + end + end + + @library_name ||= T.let(lib_name, T.nilable(PackageName)) + end + + sig { returns(T.nilable(PackageName)) } + def types_package_name + return if types_package? + + @types_package_name ||= T.let( + if scoped? + self.class.new("@types/#{@scope}__#{@name}") + else + self.class.new("@types/#{@name}") + end, T.nilable(PackageName) + ) + end + + private + + sig { returns(T::Boolean) } + def scoped? + !@scope.nil? + end + + sig { returns(T.nilable(T::Boolean)) } + def types_package? + "types".casecmp?(@scope) + end + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/shared/registry_helper.rb b/javascript/lib/dependabot/javascript/shared/registry_helper.rb new file mode 100644 index 0000000000..9b3417ae9d --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/registry_helper.rb @@ -0,0 +1,190 @@ +# typed: strict +# frozen_string_literal: true + +require "yaml" +require "dependabot/dependency_file" +require "sorbet-runtime" + +module Dependabot + module Javascript + module Shared + class RegistryHelper + extend T::Sig + + # Keys for configurations + REGISTRY_KEY = "registry" + AUTH_KEY = "authToken" + + # Yarn-specific keys + NPM_AUTH_TOKEN_KEY_FOR_YARN = "npmAuthToken" + NPM_SCOPE_KEY_FOR_YARN = "npmScopes" + NPM_REGISTER_KEY_FOR_YARN = "npmRegistryServer" + + # Environment variable keys + COREPACK_NPM_REGISTRY_ENV = "COREPACK_NPM_REGISTRY" + COREPACK_NPM_TOKEN_ENV = "COREPACK_NPM_TOKEN" + + sig do + params( + registry_config_files: T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)], + credentials: T.nilable(T::Array[Dependabot::Credential]) + ).void + end + def initialize(registry_config_files, credentials) + @registry_config_files = T.let(registry_config_files, T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) + @credentials = T.let(credentials, T.nilable(T::Array[Dependabot::Credential])) + end + + sig { returns(T::Hash[String, String]) } + def find_corepack_env_variables + registry_info = find_registry_and_token + + env_variables = {} + env_variables[COREPACK_NPM_REGISTRY_ENV] = registry_info[:registry] if registry_info[:registry] + env_variables[COREPACK_NPM_TOKEN_ENV] = registry_info[:auth_token] if registry_info[:auth_token] + + env_variables + end + + private + + sig { returns(T::Hash[Symbol, T.nilable(String)]) } + def find_registry_and_token + # Step 1: Check dependabot.yml configuration + dependabot_config = config_npm_registry_and_token + return dependabot_config if dependabot_config[:registry] + + # Step 2: Check .npmrc + npmrc_config = @registry_config_files[:npmrc] + npmrc_result = parse_registry_from_npmrc_yarnrc(npmrc_config, "=", "npm") + + return npmrc_result if npmrc_result[:registry] + + # Step 3: Check .yarnrc + yarnrc_config = @registry_config_files[:yarnrc] + yarnrc_result = parse_registry_from_npmrc_yarnrc(yarnrc_config, " ", "npm") + return yarnrc_result if yarnrc_result[:registry] + + # Step 4: Check yarnrc.yml + yarnrc_yml_config = @registry_config_files[:yarnrc_yml] + yarnrc_yml_result = parse_npm_from_yarnrc_yml(yarnrc_yml_config) + return yarnrc_yml_result if yarnrc_yml_result[:registry] + + # Default values if no registry is found + {} + end + + sig { returns(T::Hash[Symbol, T.nilable(String)]) } + def config_npm_registry_and_token + registries = {} + + return registries unless @credentials&.any? + + @credentials.each do |cred| + next unless cred["type"] == "npm_registry" # Skip if not an npm registry + next unless cred["replaces-base"] # Skip if not a reverse-proxy registry + + # Set the registry if it's not already set + registries[:registry] ||= cred["registry"] + + # Set the token if it's not already set + registries[:auth_token] ||= cred["token"] + end + registries + end + + # Find registry and token in .npmrc or .yarnrc file + sig do + params( + file: T.nilable(Dependabot::DependencyFile), + separator: String + ).returns(T::Hash[Symbol, T.nilable(String)]) + end + def parse_npm_from_npm_or_yarn_rc(file, separator = "=") + parse_registry_from_npmrc_yarnrc(file, separator, "npm") + end + + # Find registry and token in .npmrc or .yarnrc file + sig do + params( + file: T.nilable(Dependabot::DependencyFile), + separator: String, + scope: T.nilable(String) + ).returns(T::Hash[Symbol, T.nilable(String)]) + end + def parse_registry_from_npmrc_yarnrc(file, separator = "=", scope = nil) + content = file&.content + return { registry: nil, auth_token: nil } unless content + + global_registry = T.let(nil, T.nilable(String)) + scoped_registry = T.let(nil, T.nilable(String)) + auth_token = T.let(nil, T.nilable(String)) + + content.split("\n").each do |line| + # Split using the provided separator + key, value = line.strip.split(separator, 2) + next unless key && value + + # Remove surrounding quotes from keys and values + cleaned_key = key.strip.gsub(/\A["']|["']\z/, "") + cleaned_value = value.strip.gsub(/\A["']|["']\z/, "") + + case cleaned_key + when "registry" + # Case 1: Found a global registry + global_registry = cleaned_value + when "_authToken" + # Case 2: Found an auth token + auth_token = cleaned_value + else + # Handle scoped registry if a scope is provided + scoped_registry = cleaned_value if scope && cleaned_key == "@#{scope}:registry" + end + end + + # Determine the registry to return (global first, fallback to scoped) + registry = global_registry || scoped_registry + + { registry: registry, auth_token: auth_token } + end + + # rubocop:disable Metrics/PerceivedComplexity + sig { params(file: T.nilable(Dependabot::DependencyFile)).returns(T::Hash[Symbol, T.nilable(String)]) } + def parse_npm_from_yarnrc_yml(file) + content = file&.content + return { registry: nil, auth_token: nil } unless content + + result = {} + yaml_data = safe_load_yaml(content) + + # Step 1: Extract global registry and auth token + result[:registry] = yaml_data[NPM_REGISTER_KEY_FOR_YARN] if yaml_data.key?(NPM_REGISTER_KEY_FOR_YARN) + result[:auth_token] = yaml_data[NPM_AUTH_TOKEN_KEY_FOR_YARN] if yaml_data.key?(NPM_AUTH_TOKEN_KEY_FOR_YARN) + + # Step 2: Fallback to any scoped registry and auth token if global is missing + if result[:registry].nil? && yaml_data.key?(NPM_SCOPE_KEY_FOR_YARN) + yaml_data[NPM_SCOPE_KEY_FOR_YARN].each do |_current_scope, config| + next unless config.is_a?(Hash) + + result[:registry] ||= config[NPM_REGISTER_KEY_FOR_YARN] + result[:auth_token] ||= config[NPM_AUTH_TOKEN_KEY_FOR_YARN] + end + end + + result + end + # rubocop:enable Metrics/PerceivedComplexity + + # Safely loads the YAML content and logs any parsing errors + sig { params(content: String).returns(T::Hash[String, T.untyped]) } + def safe_load_yaml(content) + YAML.safe_load(content, permitted_classes: [Symbol, String]) || {} + rescue Psych::SyntaxError => e + # Log the error instead of raising it + Dependabot.logger.error("YAML parsing error: #{e.message}") + {} + end + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/shared/registry_parser.rb b/javascript/lib/dependabot/javascript/shared/registry_parser.rb new file mode 100644 index 0000000000..c4af74e477 --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/registry_parser.rb @@ -0,0 +1,93 @@ +# typed: strict +# frozen_string_literal: true + +module Dependabot + module Javascript + module Shared + class RegistryParser + extend T::Sig + + sig { params(resolved_url: String, credentials: T::Array[Dependabot::Credential]).void } + def initialize(resolved_url:, credentials:) + @resolved_url = resolved_url + @credentials = credentials + end + + sig { params(name: String).returns(T::Hash[Symbol, T.untyped]) } + def registry_source_for(name) + url = + if resolved_url.include?("/~/") + # Gemfury format + resolved_url.split("/~/").first + elsif resolved_url.include?("/#{name}/-/#{name}") + # MyGet / Bintray format + T.must(resolved_url.split("/#{name}/-/#{name}").first) + .gsub("dl.bintray.com//", "api.bintray.com/npm/"). + # GitLab format + gsub(%r{\/projects\/\d+}, "") + elsif resolved_url.include?("/#{name}/-/#{name.split('/').last}") + # Sonatype Nexus / Artifactory JFrog format + resolved_url.split("/#{name}/-/#{name.split('/').last}").first + elsif (cred_url = url_for_relevant_cred) then cred_url + else + T.must(resolved_url.split("/")[0..2]).join("/") + end + + { type: "registry", url: url } + end + + sig { returns(String) } + def dependency_name + url_base = if resolved_url.include?("/-/") + T.must(resolved_url.split("/-/").first) + else + resolved_url + end + + package_name = url_base.gsub("%2F", "/").match(%r{@.*/}) + + return T.must(url_base.gsub("%2F", "/").split("/").last) unless package_name + + "#{package_name}#{T.must(url_base.gsub('%2F', '/').split('/').last)}" + end + + private + + sig { returns(String) } + attr_reader :resolved_url + + sig { returns(T::Array[Dependabot::Credential]) } + attr_reader :credentials + + # rubocop:disable Metrics/PerceivedComplexity + sig { returns(T.nilable(String)) } + def url_for_relevant_cred + resolved_url_host = URI(resolved_url).host + + credential_matching_url = + credentials + .select { |cred| cred["type"] == "npm_registry" && cred["registry"] } + .sort_by { |cred| cred.fetch("registry").length } + .find do |details| + next true if resolved_url_host == details["registry"] + + uri = if details["registry"]&.include?("://") + URI(details.fetch("registry")) + else + URI("https://#{details['registry']}") + end + resolved_url_host == uri.host && resolved_url.include?(details.fetch("registry")) + end + + return unless credential_matching_url + + # Trim the resolved URL so that it ends at the same point as the + # credential registry + reg = credential_matching_url.fetch("registry") + resolved_url.gsub(/#{Regexp.quote(reg)}.*/, "") + reg + end + # rubocop:enable Metrics/PerceivedComplexity + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/shared/requirement.rb b/javascript/lib/dependabot/javascript/shared/requirement.rb new file mode 100644 index 0000000000..bf9a25e5f1 --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/requirement.rb @@ -0,0 +1,144 @@ +# typed: strict +# frozen_string_literal: true + +module Dependabot + module Javascript + module Shared + class Requirement < Dependabot::Requirement + extend T::Sig + + AND_SEPARATOR = /(?<=[a-zA-Z0-9*])\s+(?:&+\s+)?(?!\s*[|-])/ + OR_SEPARATOR = /(?<=[a-zA-Z0-9*])\s*\|+/ + + # Override the version pattern to allow a 'v' prefix + quoted = OPS.keys.map { |k| Regexp.quote(k) }.join("|") + version_pattern = "v?#{Version::VERSION_PATTERN}" + + PATTERN_RAW = T.let("\\s*(#{quoted})?\\s*(#{version_pattern})\\s*".freeze, String) + PATTERN = /\A#{PATTERN_RAW}\z/ + + sig { params(obj: T.untyped).returns(T::Array[T.untyped]) } + def self.parse(obj) + return ["=", nil] if obj.is_a?(String) && Version::VERSION_TAGS.include?(obj.strip) + return ["=", Version.new(obj.to_s)] if obj.is_a?(Gem::Version) + + unless (matches = PATTERN.match(obj.to_s)) + msg = "Illformed requirement [#{obj.inspect}]" + raise BadRequirementError, msg + end + + return DefaultRequirement if matches[1] == ">=" && matches[2] == "0" + + [matches[1] || "=", Version.new(T.must(matches[2]))] + end + + # Returns an array of requirements. At least one requirement from the + # returned array must be satisfied for a version to be valid. + sig { override.params(requirement_string: T.nilable(String)).returns(T::Array[Requirement]) } + def self.requirements_array(requirement_string) + return [new([])] if requirement_string.nil? + + # Removing parentheses is technically wrong but they are extremely + # rarely used. + # TODO: Handle complicated parenthesised requirements + requirement_string = requirement_string.gsub(/[()]/, "") + requirement_string.strip.split(OR_SEPARATOR).map do |req_string| + requirements = req_string.strip.split(AND_SEPARATOR) + new(requirements) + end + end + + sig { params(requirements: T.any(String, T::Array[String])).void } + def initialize(*requirements) + requirements = requirements.flatten + .flat_map { |req_string| req_string.split(",").map(&:strip) } + .flat_map { |req_string| convert_js_constraint_to_ruby_constraint(req_string) } + + super(requirements) + end + + private + + sig { params(req_string: String).returns(T.any(String, T::Array[String])) } + def convert_js_constraint_to_ruby_constraint(req_string) + return req_string if req_string.match?(/^([A-Za-uw-z]|v[^\d])/) + + req_string = req_string.gsub(/(?:\.|^)[xX*]/, "") + + if req_string.empty? then ">= 0" + elsif req_string.start_with?("~>") then req_string + elsif req_string.start_with?("=") then req_string.gsub(/^=*/, "") + elsif req_string.start_with?("~") then convert_tilde_req(req_string) + elsif req_string.start_with?("^") then convert_caret_req(req_string) + elsif req_string.include?(" - ") then convert_hyphen_req(req_string) + elsif req_string.match?(/[<>]/) then req_string + else + ruby_range(req_string) + end + end + + sig { params(req_string: String).returns(String) } + def convert_tilde_req(req_string) + version = req_string.gsub(/^~\>?[\s=]*/, "") + parts = version.split(".") + parts << "0" if parts.count < 3 + "~> #{parts.join('.')}" + end + + sig { params(req_string: String).returns(T::Array[String]) } + def convert_hyphen_req(req_string) + lower_bound, upper_bound = req_string.split(/\s+-\s+/) + lower_bound_parts = lower_bound&.split(".") + lower_bound_parts&.fill("0", lower_bound_parts.length...3) + + upper_bound_parts = upper_bound&.split(".") + upper_bound_range = + if upper_bound_parts && upper_bound_parts.length < 3 + # When upper bound is a partial version treat these as an X-range + upper_bound_parts[-1] = upper_bound_parts[-1].to_i + 1 if upper_bound_parts[-1].to_i.positive? + upper_bound_parts.fill("0", upper_bound_parts.length...3) + "< #{upper_bound_parts.join('.')}.a" + else + "<= #{upper_bound_parts&.join('.')}" + end + + [">= #{lower_bound_parts&.join('.')}", upper_bound_range] + end + + sig { params(req_string: String).returns(String) } + def ruby_range(req_string) + parts = req_string.split(".") + # If we have three or more parts then this is an exact match + return req_string if parts.count >= 3 + + # If we have fewer than three parts we do a partial match + parts << "0" + "~> #{parts.join('.')}" + end + + sig { params(req_string: String).returns(T::Array[String]) } + def convert_caret_req(req_string) # rubocop:disable Metrics/PerceivedComplexity + version = req_string.gsub(/^\^[\s=]*/, "") + parts = version.split(".") + parts.fill("x", parts.length...3) + first_non_zero = parts.find { |d| d != "0" } + first_non_zero_index = + first_non_zero ? parts.index(first_non_zero) : parts.count - 1 + # If the requirement has a blank minor or patch version increment the + # previous index value with 1 + first_non_zero_index -= 1 if first_non_zero_index && first_non_zero == "x" + upper_bound = parts.map.with_index do |part, i| + if i < T.must(first_non_zero_index) then part + elsif i == first_non_zero_index then (part.to_i + 1).to_s + elsif i > T.must(first_non_zero_index) && i == 2 then "0.a" + else + 0 + end + end.join(".") + + [">= #{version}", "< #{upper_bound}"] + end + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/shared/sub_dependency_files_filterer.rb b/javascript/lib/dependabot/javascript/shared/sub_dependency_files_filterer.rb new file mode 100644 index 0000000000..e2d54ebcf7 --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/sub_dependency_files_filterer.rb @@ -0,0 +1,79 @@ +# typed: strong +# frozen_string_literal: true + +# Used in the sub dependency version resolver and file updater to only run +# yarn/npm helpers on dependency files that require updates. This is useful for +# large monorepos with lots of sub-projects that don't all have the same +# dependencies. +module Dependabot + module Javascript + module Shared + class SubDependencyFilesFilterer + extend T::Sig + + sig { params(dependency_files: T::Array[DependencyFile], updated_dependencies: T::Array[Dependency]).void } + def initialize(dependency_files:, updated_dependencies:) + @dependency_files = dependency_files + @updated_dependencies = updated_dependencies + end + + sig { returns(T::Array[Dependabot::DependencyFile]) } + def files_requiring_update + return T.must(@files_requiring_update) if defined? @files_requiring_update + + files_requiring_update = + lockfiles.select do |lockfile| + lockfile_dependencies(lockfile).any? do |sub_dep| + updated_dependencies.any? do |updated_dep| + next false unless sub_dep.name == updated_dep.name + + version_class.new(updated_dep.version) > + version_class.new(sub_dep.version) + end + end + end + + @files_requiring_update ||= T.let(files_requiring_update, T.nilable(T::Array[DependencyFile])) + end + + private + + sig { returns(T::Array[DependencyFile]) } + attr_reader :dependency_files + + sig { returns(T::Array[Dependency]) } + attr_reader :updated_dependencies + + sig { params(lockfile: DependencyFile).returns(T::Array[Dependabot::Dependency]) } + def lockfile_dependencies(lockfile) + @lockfile_dependencies ||= T.let({}, T.nilable(T::Hash[String, T::Array[Dependency]])) + @lockfile_dependencies[lockfile.name] ||= + NpmAndYarn::FileParser::LockfileParser.new( + dependency_files: [lockfile] + ).parse + end + + sig { returns(T::Array[Dependabot::DependencyFile]) } + def lockfiles + dependency_files.select { |file| lockfile?(file) } + end + + sig { params(file: DependencyFile).returns(T::Boolean) } + def lockfile?(file) + file.name.end_with?( + "package-lock.json", + "yarn.lock", + "npm-shrinkwrap.json", + "bun.lock", + "pnpm-lock.yaml" + ) + end + + sig { returns(T.class_of(Dependabot::NpmAndYarn::Version)) } + def version_class + NpmAndYarn::Version + end + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/shared/update_checker/dependency_files_builder.rb b/javascript/lib/dependabot/javascript/shared/update_checker/dependency_files_builder.rb new file mode 100644 index 0000000000..bf07c69d0e --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/update_checker/dependency_files_builder.rb @@ -0,0 +1,87 @@ +# typed: strict +# frozen_string_literal: true + +module Dependabot + module Javascript + module Shared + module UpdateChecker + class DependencyFilesBuilder + extend T::Helpers + extend T::Sig + + abstract! + + Credentials = T.type_alias { T::Array[Credential] } + + sig do + params( + dependency: Dependency, + dependency_files: T::Array[DependencyFile], + credentials: Credentials + ) + .void + end + def initialize(dependency:, dependency_files:, credentials:) + @dependency = T.let(dependency, Dependency) + @dependency_files = T.let(dependency_files, T::Array[DependencyFile]) + @credentials = T.let(credentials, Credentials) + end + + sig { void } + def write_temporary_dependency_files + write_lockfiles + + File.write(".npmrc", npmrc_content) + + package_files.each do |file| + path = file.name + FileUtils.mkdir_p(Pathname.new(path).dirname) + File.write(file.name, prepared_package_json_content(file)) + end + end + + sig { abstract.returns(T::Array[DependencyFile]) } + def lockfiles; end + + sig { returns(T::Array[DependencyFile]) } + def package_files + @package_files ||= T.let( + dependency_files + .select { |f| f.name.end_with?("package.json") }, + T.nilable(T::Array[DependencyFile]) + ) + end + + private + + sig { returns(Dependency) } + attr_reader :dependency + + sig { returns(T::Array[DependencyFile]) } + attr_reader :dependency_files + + sig { returns(Credentials) } + attr_reader :credentials + + sig { abstract.returns(T::Array[DependencyFile]) } + def write_lockfiles; end + + sig { params(file: DependencyFile).returns(String) } + def prepared_package_json_content(file) + FileUpdater::PackageJsonPreparer.new( + package_json_content: file.content + ).prepared_content + end + + sig { returns(String) } + def npmrc_content + FileUpdater::NpmrcBuilder.new( + credentials: credentials, + dependency_files: dependency_files + ).npmrc_content + end + end + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/shared/update_checker/registry_finder.rb b/javascript/lib/dependabot/javascript/shared/update_checker/registry_finder.rb new file mode 100644 index 0000000000..7ed4135016 --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/update_checker/registry_finder.rb @@ -0,0 +1,358 @@ +# typed: strict +# frozen_string_literal: true + +require "excon" + +module Dependabot + module Javascript + module Shared + module UpdateChecker + class RegistryFinder + extend T::Sig + + CENTRAL_REGISTRIES = %w( + https://registry.npmjs.org + http://registry.npmjs.org + https://registry.yarnpkg.com + http://registry.yarnpkg.com + ).freeze + NPM_AUTH_TOKEN_REGEX = %r{//(?.*)/:_authToken=(?.*)$} + NPM_GLOBAL_REGISTRY_REGEX = /^registry\s*=\s*['"]?(?.*?)['"]?$/ + YARN_GLOBAL_REGISTRY_REGEX = /^(?:--)?registry\s+((['"](?.*)['"])|(?.*))/ + NPM_SCOPED_REGISTRY_REGEX = /^(?@[^:]+)\s*:registry\s*=\s*['"]?(?.*?)['"]?$/ + YARN_SCOPED_REGISTRY_REGEX = /['"](?@[^:]+):registry['"]\s((['"](?.*)['"])|(?.*))/ + + Registry = T.type_alias { String } + RegistrySyntax = T.type_alias { T.any(Regexp, String) } + + sig do + params( + dependency: T.nilable(Dependency), + credentials: T::Array[Credential], + rc_file: T.nilable(DependencyFile) + ).void + end + def initialize(dependency:, credentials:, rc_file:) + @dependency = dependency + @credentials = credentials + @rc_file = rc_file + + @npmrc_file = T.let( + rc_file&.name&.end_with?(".npmrc") ? rc_file : nil, + T.nilable(DependencyFile) + ) + end + + sig { returns(T.nilable(Registry)) } + def registry + @registry ||= T.let( + locked_registry || configured_registry || first_registry_with_dependency_details, + T.nilable(Registry) + ) + end + + sig { returns(T::Hash[String, String]) } + def auth_headers + auth_header_for(auth_token) + end + + sig { returns(String) } + def dependency_url + "#{registry_url}/#{escaped_dependency_name}" + end + + sig { params(version: Version).returns(String) } + def tarball_url(version) + version_without_build_metadata = version.to_s.gsub(/\+.*/, "") + + # Dependency name needs to be unescaped since tarball URLs don't always work with escaped slashes + "#{registry_url}/#{dependency&.name}/-/#{scopeless_name}-#{version_without_build_metadata}.tgz" + end + + sig { params(registry: String).returns(T::Boolean) } + def self.central_registry?(registry) + CENTRAL_REGISTRIES.any? do |r| + r.include?(registry) + end + end + + sig { params(dependency_name: String).returns(T.nilable(String)) } + def registry_from_rc(dependency_name) + explicit_registry_from_rc(dependency_name) || global_registry + end + + private + + sig { returns(T.nilable(Dependency)) } + attr_reader :dependency + + sig { returns(T::Array[Credential]) } + attr_reader :credentials + + sig { returns(T.nilable(DependencyFile)) } + attr_reader :rc_file + + sig { returns(T.nilable(DependencyFile)) } + attr_reader :npmrc_file + + sig { params(dependency_name: T.nilable(String)).returns(T.nilable(String)) } + def explicit_registry_from_rc(dependency_name) + if dependency_name&.start_with?("@") && dependency_name.include?("/") + scope = dependency_name.split("/").first + scoped_registry(scope) || configured_global_registry + else + configured_global_registry + end + end + + sig { returns(T.nilable(Registry)) } + def first_registry_with_dependency_details + @first_registry_with_dependency_details ||= T.let( + known_registries.find do |details| + url = "#{details['registry'].gsub(%r{/+$}, '')}/#{escaped_dependency_name}" + url = "https://#{url}" unless url.start_with?("http") + response = Dependabot::RegistryClient.get( + url: url, + headers: auth_header_for(details["token"]) + ) + response.status < 400 && JSON.parse(response.body) + rescue Excon::Error::Timeout, + Excon::Error::Socket, + JSON::ParserError + nil + rescue URI::InvalidURIError => e + raise DependencyFileNotResolvable, e.message + end&.fetch("registry"), + T.nilable(Registry) + ) + + @first_registry_with_dependency_details ||= global_registry.to_s.sub(%r{/+$}, "").sub(%r{^.*?//}, "") + end + + sig { returns(T.nilable(String)) } + def registry_url + url = + if registry&.start_with?("http") + registry + else + protocol = + if registry_source_url + registry_source_url&.split("://")&.first + else + "https" + end + + "#{protocol}://#{registry}" + end + + url&.gsub(%r{/+$}, "") + end + + sig { params(token: T.nilable(String)).returns(T::Hash[String, String]) } + def auth_header_for(token) + return {} unless token + + if token.include?(":") + encoded_token = Base64.encode64(token).delete("\n") + { "Authorization" => "Basic #{encoded_token}" } + elsif Base64.decode64(token).ascii_only? && + Base64.decode64(token).include?(":") + { "Authorization" => "Basic #{token.delete("\n")}" } + else + { "Authorization" => "Bearer #{token}" } + end + end + + sig { returns(T.nilable(String)) } + def auth_token + known_registries + .find { |cred| cred["registry"] == registry } + &.fetch("token", nil) + end + + sig { returns(T.nilable(Registry)) } + def locked_registry + return unless registry_source_url + + lockfile_registry = + T.must(registry_source_url) + .gsub("https://", "") + .gsub("http://", "") + detailed_registry = + known_registries + .find { |h| h["registry"].include?(lockfile_registry) } + &.fetch("registry") + + detailed_registry || lockfile_registry + end + + sig { returns(T.nilable(String)) } + def configured_registry + configured_registry_url = explicit_registry_from_rc(dependency&.name) + return unless configured_registry_url + + normalize_configured_registry(configured_registry_url) + end + + sig { returns(T::Array[T::Hash[String, T.untyped]]) } + def known_registries + @known_registries ||= T.let( + begin + registries = [] + registries += credentials + .select { |cred| cred["type"] == "npm_registry" && cred["registry"] } + .tap { |arr| arr.each { |c| c["token"] ||= nil } } + registries += npmrc_registries + + unique_registries(registries) + end, + T.nilable(T::Array[T::Hash[String, T.untyped]]) + ) + end + + sig { returns(T::Array[T::Hash[String, T.untyped]]) } + def npmrc_registries + return [] unless npmrc_file + + registries = [] + T.must(npmrc_file).content&.scan(NPM_AUTH_TOKEN_REGEX) do + next if Regexp.last_match&.[](:registry)&.include?("${") + + registry = T.must(Regexp.last_match)[:registry] + token = T.must(Regexp.last_match)[:token]&.strip + + registries << { + "type" => "npm_registry", + "registry" => registry&.gsub(/\s+/, "%20"), + "token" => token + } + end + + registries += npmrc_global_registries + end + + sig { params(registries: T::Array[T::Hash[String, T.untyped]]).returns(T::Array[T::Hash[String, T.untyped]]) } + def unique_registries(registries) + registries.uniq.reject do |registry| + next if registry["token"] + + # Reject this entry if an identical one with a token exists + registries.any? do |r| + r["token"] && r["registry"] == registry["registry"] + end + end + end + + sig { returns(T.nilable(String)) } + def global_registry + return @global_registry if defined? @global_registry + + @global_registry ||= T.let(configured_global_registry || "https://registry.npmjs.org", T.nilable(String)) + end + + sig { returns(T.nilable(String)) } + def configured_global_registry + return @configured_global_registry if defined? @configured_global_registry + + @configured_global_registry = T.let( + npmrc_file && npmrc_global_registries.first&.fetch("url"), + T.nilable(String) + ) + return @configured_global_registry if @configured_global_registry + + replaces_base = credentials.find { |cred| cred["type"] == "npm_registry" && cred.replaces_base? } + if replaces_base + registry = replaces_base["registry"] + registry = "https://#{registry}" unless registry&.start_with?("http") + return @configured_global_registry = registry + end + + @configured_global_registry = nil + end + + sig { returns(T::Array[T::Hash[String, T.nilable(String)]]) } + def npmrc_global_registries + return [] unless npmrc_file + + global_rc_registries(T.must(npmrc_file), syntax: NPM_GLOBAL_REGISTRY_REGEX) + end + + sig { params(scope: T.nilable(String)).returns(T.nilable(String)) } + def scoped_registry(scope) + scoped_rc_registry(npmrc_file, syntax: NPM_SCOPED_REGISTRY_REGEX, scope: scope) + end + + sig do + params(file: DependencyFile, syntax: RegistrySyntax) + .returns(T::Array[T::Hash[String, T.nilable(String)]]) + end + def global_rc_registries(file, syntax:) + registries = [] + + file.content&.scan(syntax) do + next if Regexp.last_match&.[](:registry)&.include?("${") + + url = T.must(T.must(Regexp.last_match)[:registry]).strip + registry = normalize_configured_registry(url) + registries << { + "type" => "npm_registry", + "registry" => registry, + "url" => url, + "token" => nil + } + end + + registries + end + + sig do + params(file: T.nilable(DependencyFile), syntax: RegistrySyntax, scope: T.nilable(String)) + .returns(T.nilable(String)) + end + def scoped_rc_registry(file, syntax:, scope:) + file&.content.to_s.scan(syntax) do + next if Regexp.last_match&.[](:registry)&.include?("${") || Regexp.last_match&.[](:scope) != scope + + return T.must(T.must(Regexp.last_match)[:registry]).strip + end + + nil + end + + # npm registries expect slashes to be escaped + sig { returns(T.nilable(String)) } + def escaped_dependency_name + return unless dependency + + T.must(dependency).name.gsub("/", "%2F") + end + + sig { returns(T.nilable(String)) } + def scopeless_name + return unless dependency + + T.must(dependency).name.split("/").last + end + + sig { returns(T.nilable(String)) } + def registry_source_url + return unless dependency + + sources = T.must(dependency).requirements + .map { |r| r.fetch(:source) }.uniq.compact + .sort_by { |source| self.class.central_registry?(source[:url]) ? 1 : 0 } + + sources.find { |s| s[:type] == "registry" }&.fetch(:url) + end + + sig { params(url: String).returns(String) } + def normalize_configured_registry(url) + url.sub(%r{/+$}, "") + .sub(%r{^.*?//}, "") + .gsub(/\s+/, "%20") + end + end + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/shared/version.rb b/javascript/lib/dependabot/javascript/shared/version.rb new file mode 100644 index 0000000000..bd9fd221fe --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/version.rb @@ -0,0 +1,137 @@ +# typed: strict +# frozen_string_literal: true + +require "dependabot/version" +require "dependabot/utils" +require "sorbet-runtime" + +# JavaScript pre-release versions use 1.0.1-rc1 syntax, which Gem::Version +# converts into 1.0.1.pre.rc1. We override the `to_s` method to stop that +# alteration. +# +# See https://semver.org/ for details of node's version syntax. + +module Dependabot + module Javascript + module Shared + class Version < Dependabot::Version + extend T::Sig + + sig { returns(T.nilable(String)) } + attr_reader :build_info + + # These are possible npm versioning tags that can be used in place of a version. + # See https://docs.npmjs.com/cli/v10/commands/npm-dist-tag#purpose for more details. + VERSION_TAGS = T.let([ + "alpha", # Alpha version, early testing phase + "beta", # Beta version, more stable than alpha + "canary", # Canary version, often used for cutting-edge builds + "dev", # Development version, ongoing development + "experimental", # Experimental version, unstable and new features + "latest", # Latest stable version, used by npm to identify the current version of a package + "legacy", # Legacy version, older version maintained for compatibility + "next", # Next version, used by some projects to identify the upcoming version + "nightly", # Nightly build, daily builds often including latest changes + "rc", # Release candidate, potential final version + "release", # General release version + "stable" # Stable version, thoroughly tested and stable + ].freeze.map(&:freeze), T::Array[String]) + + VERSION_PATTERN = T.let(Gem::Version::VERSION_PATTERN + '(\+[0-9a-zA-Z\-.]+)?', String) + ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z/ + + sig { override.params(version: VersionParameter).returns(T::Boolean) } + def self.correct?(version) + version = version.gsub(/^v/, "") if version.is_a?(String) + + return false if version.nil? + + version.to_s.match?(ANCHORED_VERSION_PATTERN) + end + + sig { params(version: VersionParameter).returns(VersionParameter) } + def self.semver_for(version) + # The next two lines are to guard against improperly formatted + # versions in a lockfile, such as an empty string or additional + # characters. NPM/yarn fixes these when running an update, so we can + # safely ignore these versions. + return if version == "" + return unless correct?(version) + + version + end + + sig { override.params(version: VersionParameter).void } + def initialize(version) + version = clean_version(version) + + @version_string = T.let(version.to_s, String) + + @build_info = T.let(nil, T.nilable(String)) + + version, @build_info = version.to_s.split("+") if version.to_s.include?("+") + + super(T.must(version)) + end + + sig { params(version: VersionParameter).returns(VersionParameter) } + def clean_version(version) + # Check if version is a string before attempting to match + if version.is_a?(String) + # Matches @ followed by x.y.z (digits separated by dots) + if (match = version.match(/@(\d+\.\d+\.\d+)/)) + version = match[1] # Just "4.5.3" + + # Extract version in case the output contains Corepack verbose data + elsif version.include?("Corepack") + version = T.must(T.must(version.tr("\n", " ").match(/(\d+\.\d+\.\d+)/))[-1]) + end + version = version&.gsub(/^v/, "") + end + + version + end + + sig { override.params(version: VersionParameter).returns(Version) } + def self.new(version) + T.cast(super, Version) + end + + sig { returns(Integer) } + def major + @major ||= T.let(segments[0].to_i, T.nilable(Integer)) + end + + sig { returns(Integer) } + def minor + @minor ||= T.let(segments[1].to_i, T.nilable(Integer)) + end + + sig { returns(Integer) } + def patch + @patch ||= T.let(segments[2].to_i, T.nilable(Integer)) + end + + sig { params(other: Version).returns(T::Boolean) } + def backwards_compatible_with?(other) + case major + when 0 + self == other + else + major == other.major && minor >= other.minor + end + end + + sig { override.returns(String) } + def to_s + @version_string + end + + sig { override.returns(String) } + def inspect + "#<#{self.class} #{@version_string}>" + end + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/shared/version_selector.rb b/javascript/lib/dependabot/javascript/shared/version_selector.rb new file mode 100644 index 0000000000..e0902a4fdf --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/version_selector.rb @@ -0,0 +1,60 @@ +# typed: strict +# frozen_string_literal: true + +module Dependabot + module Javascript + module Shared + class VersionSelector + extend T::Sig + extend T::Helpers + + # For limited testing, allowing only specific versions defined in engines in package.json + # such as "20.8.7", "8.1.2", "8.21.2", + NODE_ENGINE_SUPPORTED_REGEX = /^\d+(?:\.\d+)*$/ + + # Sets up engine versions from the given manifest JSON. + # + # @param manifest_json [Hash] The manifest JSON containing version information. + # @param name [String] The engine name to match. + # @return [Hash] A hash with selected versions, if found. + sig do + params( + manifest_json: T::Hash[String, T.untyped], + name: String, + dependabot_versions: T.nilable(T::Array[Dependabot::Version]) + ) + .returns(T::Hash[Symbol, T.untyped]) + end + def setup(manifest_json, name, dependabot_versions = nil) + engine_versions = manifest_json["engines"] + + # Return an empty hash if no engine versions are specified + return {} if engine_versions.nil? + + versions = {} + + if Dependabot::Experiments.enabled?(:enable_engine_version_detection) + engine_versions.each do |engine, value| + next unless engine.to_s.match(name) + + versions[name] = ConstraintHelper.find_highest_version_from_constraint_expression( + value, dependabot_versions + ) + end + else + versions = engine_versions.select do |engine, value| + engine.to_s.match(name) && valid_extracted_version?(value) + end + end + + versions + end + + sig { params(version: String).returns(T::Boolean) } + def valid_extracted_version?(version) + version.match?(NODE_ENGINE_SUPPORTED_REGEX) + end + end + end + end +end diff --git a/javascript/lib/dependabot/javascript/sub_dependency_files_filterer.rb b/javascript/lib/dependabot/javascript/sub_dependency_files_filterer.rb deleted file mode 100644 index 9837eaab7d..0000000000 --- a/javascript/lib/dependabot/javascript/sub_dependency_files_filterer.rb +++ /dev/null @@ -1,77 +0,0 @@ -# typed: strong -# frozen_string_literal: true - -# Used in the sub dependency version resolver and file updater to only run -# yarn/npm helpers on dependency files that require updates. This is useful for -# large monorepos with lots of sub-projects that don't all have the same -# dependencies. -module Dependabot - module Javascript - class SubDependencyFilesFilterer - extend T::Sig - - sig { params(dependency_files: T::Array[DependencyFile], updated_dependencies: T::Array[Dependency]).void } - def initialize(dependency_files:, updated_dependencies:) - @dependency_files = dependency_files - @updated_dependencies = updated_dependencies - end - - sig { returns(T::Array[Dependabot::DependencyFile]) } - def files_requiring_update - return T.must(@files_requiring_update) if defined? @files_requiring_update - - files_requiring_update = - lockfiles.select do |lockfile| - lockfile_dependencies(lockfile).any? do |sub_dep| - updated_dependencies.any? do |updated_dep| - next false unless sub_dep.name == updated_dep.name - - version_class.new(updated_dep.version) > - version_class.new(sub_dep.version) - end - end - end - - @files_requiring_update ||= T.let(files_requiring_update, T.nilable(T::Array[DependencyFile])) - end - - private - - sig { returns(T::Array[DependencyFile]) } - attr_reader :dependency_files - - sig { returns(T::Array[Dependency]) } - attr_reader :updated_dependencies - - sig { params(lockfile: DependencyFile).returns(T::Array[Dependabot::Dependency]) } - def lockfile_dependencies(lockfile) - @lockfile_dependencies ||= T.let({}, T.nilable(T::Hash[String, T::Array[Dependency]])) - @lockfile_dependencies[lockfile.name] ||= - NpmAndYarn::FileParser::LockfileParser.new( - dependency_files: [lockfile] - ).parse - end - - sig { returns(T::Array[Dependabot::DependencyFile]) } - def lockfiles - dependency_files.select { |file| lockfile?(file) } - end - - sig { params(file: DependencyFile).returns(T::Boolean) } - def lockfile?(file) - file.name.end_with?( - "package-lock.json", - "yarn.lock", - "npm-shrinkwrap.json", - "bun.lock", - "pnpm-lock.yaml" - ) - end - - sig { returns(T.class_of(Dependabot::NpmAndYarn::Version)) } - def version_class - NpmAndYarn::Version - end - end - end -end diff --git a/javascript/lib/dependabot/javascript/update_checker/dependency_files_builder.rb b/javascript/lib/dependabot/javascript/update_checker/dependency_files_builder.rb deleted file mode 100644 index 8ec9d469bf..0000000000 --- a/javascript/lib/dependabot/javascript/update_checker/dependency_files_builder.rb +++ /dev/null @@ -1,85 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -module Dependabot - module Javascript - module UpdateChecker - class DependencyFilesBuilder - extend T::Helpers - extend T::Sig - - abstract! - - Credentials = T.type_alias { T::Array[Credential] } - - sig do - params( - dependency: Dependency, - dependency_files: T::Array[DependencyFile], - credentials: Credentials - ) - .void - end - def initialize(dependency:, dependency_files:, credentials:) - @dependency = T.let(dependency, Dependency) - @dependency_files = T.let(dependency_files, T::Array[DependencyFile]) - @credentials = T.let(credentials, Credentials) - end - - sig { void } - def write_temporary_dependency_files - write_lockfiles - - File.write(".npmrc", npmrc_content) - - package_files.each do |file| - path = file.name - FileUtils.mkdir_p(Pathname.new(path).dirname) - File.write(file.name, prepared_package_json_content(file)) - end - end - - sig { abstract.returns(T::Array[DependencyFile]) } - def lockfiles; end - - sig { returns(T::Array[DependencyFile]) } - def package_files - @package_files ||= T.let( - dependency_files - .select { |f| f.name.end_with?("package.json") }, - T.nilable(T::Array[DependencyFile]) - ) - end - - private - - sig { returns(Dependency) } - attr_reader :dependency - - sig { returns(T::Array[DependencyFile]) } - attr_reader :dependency_files - - sig { returns(Credentials) } - attr_reader :credentials - - sig { abstract.returns(T::Array[DependencyFile]) } - def write_lockfiles; end - - sig { params(file: DependencyFile).returns(String) } - def prepared_package_json_content(file) - FileUpdater::PackageJsonPreparer.new( - package_json_content: file.content - ).prepared_content - end - - sig { returns(String) } - def npmrc_content - FileUpdater::NpmrcBuilder.new( - credentials: credentials, - dependency_files: dependency_files - ).npmrc_content - end - end - end - end -end diff --git a/javascript/lib/dependabot/javascript/update_checker/registry_finder.rb b/javascript/lib/dependabot/javascript/update_checker/registry_finder.rb deleted file mode 100644 index 526b84abc9..0000000000 --- a/javascript/lib/dependabot/javascript/update_checker/registry_finder.rb +++ /dev/null @@ -1,356 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -require "excon" - -module Dependabot - module Javascript - module UpdateChecker - class RegistryFinder - extend T::Sig - - CENTRAL_REGISTRIES = %w( - https://registry.npmjs.org - http://registry.npmjs.org - https://registry.yarnpkg.com - http://registry.yarnpkg.com - ).freeze - NPM_AUTH_TOKEN_REGEX = %r{//(?.*)/:_authToken=(?.*)$} - NPM_GLOBAL_REGISTRY_REGEX = /^registry\s*=\s*['"]?(?.*?)['"]?$/ - YARN_GLOBAL_REGISTRY_REGEX = /^(?:--)?registry\s+((['"](?.*)['"])|(?.*))/ - NPM_SCOPED_REGISTRY_REGEX = /^(?@[^:]+)\s*:registry\s*=\s*['"]?(?.*?)['"]?$/ - YARN_SCOPED_REGISTRY_REGEX = /['"](?@[^:]+):registry['"]\s((['"](?.*)['"])|(?.*))/ - - Registry = T.type_alias { String } - RegistrySyntax = T.type_alias { T.any(Regexp, String) } - - sig do - params( - dependency: T.nilable(Dependency), - credentials: T::Array[Credential], - rc_file: T.nilable(DependencyFile) - ).void - end - def initialize(dependency:, credentials:, rc_file:) - @dependency = dependency - @credentials = credentials - @rc_file = rc_file - - @npmrc_file = T.let( - rc_file&.name&.end_with?(".npmrc") ? rc_file : nil, - T.nilable(DependencyFile) - ) - end - - sig { returns(T.nilable(Registry)) } - def registry - @registry ||= T.let( - locked_registry || configured_registry || first_registry_with_dependency_details, - T.nilable(Registry) - ) - end - - sig { returns(T::Hash[String, String]) } - def auth_headers - auth_header_for(auth_token) - end - - sig { returns(String) } - def dependency_url - "#{registry_url}/#{escaped_dependency_name}" - end - - sig { params(version: Version).returns(String) } - def tarball_url(version) - version_without_build_metadata = version.to_s.gsub(/\+.*/, "") - - # Dependency name needs to be unescaped since tarball URLs don't always work with escaped slashes - "#{registry_url}/#{dependency&.name}/-/#{scopeless_name}-#{version_without_build_metadata}.tgz" - end - - sig { params(registry: String).returns(T::Boolean) } - def self.central_registry?(registry) - CENTRAL_REGISTRIES.any? do |r| - r.include?(registry) - end - end - - sig { params(dependency_name: String).returns(T.nilable(String)) } - def registry_from_rc(dependency_name) - explicit_registry_from_rc(dependency_name) || global_registry - end - - private - - sig { returns(T.nilable(Dependency)) } - attr_reader :dependency - - sig { returns(T::Array[Credential]) } - attr_reader :credentials - - sig { returns(T.nilable(DependencyFile)) } - attr_reader :rc_file - - sig { returns(T.nilable(DependencyFile)) } - attr_reader :npmrc_file - - sig { params(dependency_name: T.nilable(String)).returns(T.nilable(String)) } - def explicit_registry_from_rc(dependency_name) - if dependency_name&.start_with?("@") && dependency_name.include?("/") - scope = dependency_name.split("/").first - scoped_registry(scope) || configured_global_registry - else - configured_global_registry - end - end - - sig { returns(T.nilable(Registry)) } - def first_registry_with_dependency_details - @first_registry_with_dependency_details ||= T.let( - known_registries.find do |details| - url = "#{details['registry'].gsub(%r{/+$}, '')}/#{escaped_dependency_name}" - url = "https://#{url}" unless url.start_with?("http") - response = Dependabot::RegistryClient.get( - url: url, - headers: auth_header_for(details["token"]) - ) - response.status < 400 && JSON.parse(response.body) - rescue Excon::Error::Timeout, - Excon::Error::Socket, - JSON::ParserError - nil - rescue URI::InvalidURIError => e - raise DependencyFileNotResolvable, e.message - end&.fetch("registry"), - T.nilable(Registry) - ) - - @first_registry_with_dependency_details ||= global_registry.to_s.sub(%r{/+$}, "").sub(%r{^.*?//}, "") - end - - sig { returns(T.nilable(String)) } - def registry_url - url = - if registry&.start_with?("http") - registry - else - protocol = - if registry_source_url - registry_source_url&.split("://")&.first - else - "https" - end - - "#{protocol}://#{registry}" - end - - url&.gsub(%r{/+$}, "") - end - - sig { params(token: T.nilable(String)).returns(T::Hash[String, String]) } - def auth_header_for(token) - return {} unless token - - if token.include?(":") - encoded_token = Base64.encode64(token).delete("\n") - { "Authorization" => "Basic #{encoded_token}" } - elsif Base64.decode64(token).ascii_only? && - Base64.decode64(token).include?(":") - { "Authorization" => "Basic #{token.delete("\n")}" } - else - { "Authorization" => "Bearer #{token}" } - end - end - - sig { returns(T.nilable(String)) } - def auth_token - known_registries - .find { |cred| cred["registry"] == registry } - &.fetch("token", nil) - end - - sig { returns(T.nilable(Registry)) } - def locked_registry - return unless registry_source_url - - lockfile_registry = - T.must(registry_source_url) - .gsub("https://", "") - .gsub("http://", "") - detailed_registry = - known_registries - .find { |h| h["registry"].include?(lockfile_registry) } - &.fetch("registry") - - detailed_registry || lockfile_registry - end - - sig { returns(T.nilable(String)) } - def configured_registry - configured_registry_url = explicit_registry_from_rc(dependency&.name) - return unless configured_registry_url - - normalize_configured_registry(configured_registry_url) - end - - sig { returns(T::Array[T::Hash[String, T.untyped]]) } - def known_registries - @known_registries ||= T.let( - begin - registries = [] - registries += credentials - .select { |cred| cred["type"] == "npm_registry" && cred["registry"] } - .tap { |arr| arr.each { |c| c["token"] ||= nil } } - registries += npmrc_registries - - unique_registries(registries) - end, - T.nilable(T::Array[T::Hash[String, T.untyped]]) - ) - end - - sig { returns(T::Array[T::Hash[String, T.untyped]]) } - def npmrc_registries - return [] unless npmrc_file - - registries = [] - T.must(npmrc_file).content&.scan(NPM_AUTH_TOKEN_REGEX) do - next if Regexp.last_match&.[](:registry)&.include?("${") - - registry = T.must(Regexp.last_match)[:registry] - token = T.must(Regexp.last_match)[:token]&.strip - - registries << { - "type" => "npm_registry", - "registry" => registry&.gsub(/\s+/, "%20"), - "token" => token - } - end - - registries += npmrc_global_registries - end - - sig { params(registries: T::Array[T::Hash[String, T.untyped]]).returns(T::Array[T::Hash[String, T.untyped]]) } - def unique_registries(registries) - registries.uniq.reject do |registry| - next if registry["token"] - - # Reject this entry if an identical one with a token exists - registries.any? do |r| - r["token"] && r["registry"] == registry["registry"] - end - end - end - - sig { returns(T.nilable(String)) } - def global_registry - return @global_registry if defined? @global_registry - - @global_registry ||= T.let(configured_global_registry || "https://registry.npmjs.org", T.nilable(String)) - end - - sig { returns(T.nilable(String)) } - def configured_global_registry - return @configured_global_registry if defined? @configured_global_registry - - @configured_global_registry = T.let( - npmrc_file && npmrc_global_registries.first&.fetch("url"), - T.nilable(String) - ) - return @configured_global_registry if @configured_global_registry - - replaces_base = credentials.find { |cred| cred["type"] == "npm_registry" && cred.replaces_base? } - if replaces_base - registry = replaces_base["registry"] - registry = "https://#{registry}" unless registry&.start_with?("http") - return @configured_global_registry = registry - end - - @configured_global_registry = nil - end - - sig { returns(T::Array[T::Hash[String, T.nilable(String)]]) } - def npmrc_global_registries - return [] unless npmrc_file - - global_rc_registries(T.must(npmrc_file), syntax: NPM_GLOBAL_REGISTRY_REGEX) - end - - sig { params(scope: T.nilable(String)).returns(T.nilable(String)) } - def scoped_registry(scope) - scoped_rc_registry(npmrc_file, syntax: NPM_SCOPED_REGISTRY_REGEX, scope: scope) - end - - sig do - params(file: DependencyFile, syntax: RegistrySyntax) - .returns(T::Array[T::Hash[String, T.nilable(String)]]) - end - def global_rc_registries(file, syntax:) - registries = [] - - file.content&.scan(syntax) do - next if Regexp.last_match&.[](:registry)&.include?("${") - - url = T.must(T.must(Regexp.last_match)[:registry]).strip - registry = normalize_configured_registry(url) - registries << { - "type" => "npm_registry", - "registry" => registry, - "url" => url, - "token" => nil - } - end - - registries - end - - sig do - params(file: T.nilable(DependencyFile), syntax: RegistrySyntax, scope: T.nilable(String)) - .returns(T.nilable(String)) - end - def scoped_rc_registry(file, syntax:, scope:) - file&.content.to_s.scan(syntax) do - next if Regexp.last_match&.[](:registry)&.include?("${") || Regexp.last_match&.[](:scope) != scope - - return T.must(T.must(Regexp.last_match)[:registry]).strip - end - - nil - end - - # npm registries expect slashes to be escaped - sig { returns(T.nilable(String)) } - def escaped_dependency_name - return unless dependency - - T.must(dependency).name.gsub("/", "%2F") - end - - sig { returns(T.nilable(String)) } - def scopeless_name - return unless dependency - - T.must(dependency).name.split("/").last - end - - sig { returns(T.nilable(String)) } - def registry_source_url - return unless dependency - - sources = T.must(dependency).requirements - .map { |r| r.fetch(:source) }.uniq.compact - .sort_by { |source| self.class.central_registry?(source[:url]) ? 1 : 0 } - - sources.find { |s| s[:type] == "registry" }&.fetch(:url) - end - - sig { params(url: String).returns(String) } - def normalize_configured_registry(url) - url.sub(%r{/+$}, "") - .sub(%r{^.*?//}, "") - .gsub(/\s+/, "%20") - end - end - end - end -end diff --git a/javascript/lib/dependabot/javascript/version.rb b/javascript/lib/dependabot/javascript/version.rb deleted file mode 100644 index 0473dbc978..0000000000 --- a/javascript/lib/dependabot/javascript/version.rb +++ /dev/null @@ -1,135 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -require "dependabot/version" -require "dependabot/utils" -require "sorbet-runtime" - -# JavaScript pre-release versions use 1.0.1-rc1 syntax, which Gem::Version -# converts into 1.0.1.pre.rc1. We override the `to_s` method to stop that -# alteration. -# -# See https://semver.org/ for details of node's version syntax. - -module Dependabot - module Javascript - class Version < Dependabot::Version - extend T::Sig - - sig { returns(T.nilable(String)) } - attr_reader :build_info - - # These are possible npm versioning tags that can be used in place of a version. - # See https://docs.npmjs.com/cli/v10/commands/npm-dist-tag#purpose for more details. - VERSION_TAGS = T.let([ - "alpha", # Alpha version, early testing phase - "beta", # Beta version, more stable than alpha - "canary", # Canary version, often used for cutting-edge builds - "dev", # Development version, ongoing development - "experimental", # Experimental version, unstable and new features - "latest", # Latest stable version, used by npm to identify the current version of a package - "legacy", # Legacy version, older version maintained for compatibility - "next", # Next version, used by some projects to identify the upcoming version - "nightly", # Nightly build, daily builds often including latest changes - "rc", # Release candidate, potential final version - "release", # General release version - "stable" # Stable version, thoroughly tested and stable - ].freeze.map(&:freeze), T::Array[String]) - - VERSION_PATTERN = T.let(Gem::Version::VERSION_PATTERN + '(\+[0-9a-zA-Z\-.]+)?', String) - ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z/ - - sig { override.params(version: VersionParameter).returns(T::Boolean) } - def self.correct?(version) - version = version.gsub(/^v/, "") if version.is_a?(String) - - return false if version.nil? - - version.to_s.match?(ANCHORED_VERSION_PATTERN) - end - - sig { params(version: VersionParameter).returns(VersionParameter) } - def self.semver_for(version) - # The next two lines are to guard against improperly formatted - # versions in a lockfile, such as an empty string or additional - # characters. NPM/yarn fixes these when running an update, so we can - # safely ignore these versions. - return if version == "" - return unless correct?(version) - - version - end - - sig { override.params(version: VersionParameter).void } - def initialize(version) - version = clean_version(version) - - @version_string = T.let(version.to_s, String) - - @build_info = T.let(nil, T.nilable(String)) - - version, @build_info = version.to_s.split("+") if version.to_s.include?("+") - - super(T.must(version)) - end - - sig { params(version: VersionParameter).returns(VersionParameter) } - def clean_version(version) - # Check if version is a string before attempting to match - if version.is_a?(String) - # Matches @ followed by x.y.z (digits separated by dots) - if (match = version.match(/@(\d+\.\d+\.\d+)/)) - version = match[1] # Just "4.5.3" - - # Extract version in case the output contains Corepack verbose data - elsif version.include?("Corepack") - version = T.must(T.must(version.tr("\n", " ").match(/(\d+\.\d+\.\d+)/))[-1]) - end - version = version&.gsub(/^v/, "") - end - - version - end - - sig { override.params(version: VersionParameter).returns(Dependabot::Javascript::Version) } - def self.new(version) - T.cast(super, Dependabot::Javascript::Version) - end - - sig { returns(Integer) } - def major - @major ||= T.let(segments[0].to_i, T.nilable(Integer)) - end - - sig { returns(Integer) } - def minor - @minor ||= T.let(segments[1].to_i, T.nilable(Integer)) - end - - sig { returns(Integer) } - def patch - @patch ||= T.let(segments[2].to_i, T.nilable(Integer)) - end - - sig { params(other: Dependabot::Javascript::Version).returns(T::Boolean) } - def backwards_compatible_with?(other) - case major - when 0 - self == other - else - major == other.major && minor >= other.minor - end - end - - sig { override.returns(String) } - def to_s - @version_string - end - - sig { override.returns(String) } - def inspect - "#<#{self.class} #{@version_string}>" - end - end - end -end diff --git a/javascript/lib/dependabot/javascript/version_selector.rb b/javascript/lib/dependabot/javascript/version_selector.rb deleted file mode 100644 index a0ffd083fa..0000000000 --- a/javascript/lib/dependabot/javascript/version_selector.rb +++ /dev/null @@ -1,58 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -module Dependabot - module Javascript - class VersionSelector - extend T::Sig - extend T::Helpers - - # For limited testing, allowing only specific versions defined in engines in package.json - # such as "20.8.7", "8.1.2", "8.21.2", - NODE_ENGINE_SUPPORTED_REGEX = /^\d+(?:\.\d+)*$/ - - # Sets up engine versions from the given manifest JSON. - # - # @param manifest_json [Hash] The manifest JSON containing version information. - # @param name [String] The engine name to match. - # @return [Hash] A hash with selected versions, if found. - sig do - params( - manifest_json: T::Hash[String, T.untyped], - name: String, - dependabot_versions: T.nilable(T::Array[Dependabot::Version]) - ) - .returns(T::Hash[Symbol, T.untyped]) - end - def setup(manifest_json, name, dependabot_versions = nil) - engine_versions = manifest_json["engines"] - - # Return an empty hash if no engine versions are specified - return {} if engine_versions.nil? - - versions = {} - - if Dependabot::Experiments.enabled?(:enable_engine_version_detection) - engine_versions.each do |engine, value| - next unless engine.to_s.match(name) - - versions[name] = ConstraintHelper.find_highest_version_from_constraint_expression( - value, dependabot_versions - ) - end - else - versions = engine_versions.select do |engine, value| - engine.to_s.match(name) && valid_extracted_version?(value) - end - end - - versions - end - - sig { params(version: String).returns(T::Boolean) } - def valid_extracted_version?(version) - version.match?(NODE_ENGINE_SUPPORTED_REGEX) - end - end - end -end From e518a42496b57be186361c739b3b194423f20a9a Mon Sep 17 00:00:00 2001 From: Kamil Bukum Date: Fri, 7 Feb 2025 19:48:03 -0800 Subject: [PATCH 08/15] fix specs and move shared ones into shared folder --- .../javascript/{ => shared}/constraint_helper_spec.rb | 2 +- .../{ => shared}/file_updater/npmrc_builder_spec.rb | 2 +- .../file_updater/package_json_preparer_spec.rb | 2 +- .../file_updater/package_json_updater_spec.rb | 2 +- .../javascript/{ => shared}/language_spec.rb | 10 +++++----- .../{ => shared}/package_manager_detector_spec.rb | 2 +- .../javascript/{ => shared}/registry_helper_spec.rb | 2 +- .../javascript/{ => shared}/requirement_spec.rb | 4 ++-- .../dependabot/javascript/{ => shared}/version_spec.rb | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) rename javascript/spec/dependabot/javascript/{ => shared}/constraint_helper_spec.rb (99%) rename javascript/spec/dependabot/javascript/{ => shared}/file_updater/npmrc_builder_spec.rb (98%) rename javascript/spec/dependabot/javascript/{ => shared}/file_updater/package_json_preparer_spec.rb (84%) rename javascript/spec/dependabot/javascript/{ => shared}/file_updater/package_json_updater_spec.rb (99%) rename javascript/spec/dependabot/javascript/{ => shared}/language_spec.rb (77%) rename javascript/spec/dependabot/javascript/{ => shared}/package_manager_detector_spec.rb (98%) rename javascript/spec/dependabot/javascript/{ => shared}/registry_helper_spec.rb (98%) rename javascript/spec/dependabot/javascript/{ => shared}/requirement_spec.rb (98%) rename javascript/spec/dependabot/javascript/{ => shared}/version_spec.rb (99%) diff --git a/javascript/spec/dependabot/javascript/constraint_helper_spec.rb b/javascript/spec/dependabot/javascript/shared/constraint_helper_spec.rb similarity index 99% rename from javascript/spec/dependabot/javascript/constraint_helper_spec.rb rename to javascript/spec/dependabot/javascript/shared/constraint_helper_spec.rb index e279f5c9a1..3d34a74dc4 100644 --- a/javascript/spec/dependabot/javascript/constraint_helper_spec.rb +++ b/javascript/spec/dependabot/javascript/shared/constraint_helper_spec.rb @@ -4,7 +4,7 @@ require "spec_helper" require "dependabot/bun" -RSpec.describe Dependabot::Javascript::ConstraintHelper do +RSpec.describe Dependabot::Javascript::Shared::ConstraintHelper do let(:helper) { described_class } let(:version_regex) { /^#{helper::VERSION}$/o } let(:dependabot_versions) do diff --git a/javascript/spec/dependabot/javascript/file_updater/npmrc_builder_spec.rb b/javascript/spec/dependabot/javascript/shared/file_updater/npmrc_builder_spec.rb similarity index 98% rename from javascript/spec/dependabot/javascript/file_updater/npmrc_builder_spec.rb rename to javascript/spec/dependabot/javascript/shared/file_updater/npmrc_builder_spec.rb index 87ee0cd8c7..dba96cd6d0 100644 --- a/javascript/spec/dependabot/javascript/file_updater/npmrc_builder_spec.rb +++ b/javascript/spec/dependabot/javascript/shared/file_updater/npmrc_builder_spec.rb @@ -4,7 +4,7 @@ require "spec_helper" require "dependabot/bun" -RSpec.describe Dependabot::Javascript::FileUpdater::NpmrcBuilder do +RSpec.describe Dependabot::Javascript::Shared::FileUpdater::NpmrcBuilder do let(:npmrc_builder) do described_class.new( dependency_files: dependency_files, diff --git a/javascript/spec/dependabot/javascript/file_updater/package_json_preparer_spec.rb b/javascript/spec/dependabot/javascript/shared/file_updater/package_json_preparer_spec.rb similarity index 84% rename from javascript/spec/dependabot/javascript/file_updater/package_json_preparer_spec.rb rename to javascript/spec/dependabot/javascript/shared/file_updater/package_json_preparer_spec.rb index 7dcad7524c..e27a54c7aa 100644 --- a/javascript/spec/dependabot/javascript/file_updater/package_json_preparer_spec.rb +++ b/javascript/spec/dependabot/javascript/shared/file_updater/package_json_preparer_spec.rb @@ -4,7 +4,7 @@ require "spec_helper" require "dependabot/bun" -RSpec.describe Dependabot::Javascript::FileUpdater::PackageJsonPreparer do +RSpec.describe Dependabot::Javascript::Shared::FileUpdater::PackageJsonPreparer do describe "#prepared_content" do it "does not craash when finding null dependencies" do original_content = fixture("projects", "javascript", "null_deps", "package.json") diff --git a/javascript/spec/dependabot/javascript/file_updater/package_json_updater_spec.rb b/javascript/spec/dependabot/javascript/shared/file_updater/package_json_updater_spec.rb similarity index 99% rename from javascript/spec/dependabot/javascript/file_updater/package_json_updater_spec.rb rename to javascript/spec/dependabot/javascript/shared/file_updater/package_json_updater_spec.rb index 3a6c2b1b93..0ee3c13bad 100644 --- a/javascript/spec/dependabot/javascript/file_updater/package_json_updater_spec.rb +++ b/javascript/spec/dependabot/javascript/shared/file_updater/package_json_updater_spec.rb @@ -4,7 +4,7 @@ require "spec_helper" require "dependabot/bun" -RSpec.describe Dependabot::Javascript::FileUpdater::PackageJsonUpdater do +RSpec.describe Dependabot::Javascript::Shared::FileUpdater::PackageJsonUpdater do let(:package_json_updater) do described_class.new( package_json: package_json, diff --git a/javascript/spec/dependabot/javascript/language_spec.rb b/javascript/spec/dependabot/javascript/shared/language_spec.rb similarity index 77% rename from javascript/spec/dependabot/javascript/language_spec.rb rename to javascript/spec/dependabot/javascript/shared/language_spec.rb index 63dbd895e4..f098d7f981 100644 --- a/javascript/spec/dependabot/javascript/language_spec.rb +++ b/javascript/spec/dependabot/javascript/shared/language_spec.rb @@ -4,7 +4,7 @@ require "spec_helper" require "dependabot/bun" -RSpec.describe Dependabot::Javascript::Language do +RSpec.describe Dependabot::Javascript::Shared::Language do let(:language) do described_class.new( raw_version: raw_version, @@ -20,19 +20,19 @@ end it "sets the name correctly" do - expect(language.name).to eq(Dependabot::Javascript::Language::NAME) + expect(language.name).to eq(Dependabot::Javascript::Shared::Language::NAME) end it "sets the deprecated_versions correctly" do - expect(language.deprecated_versions).to eq(Dependabot::Javascript::Language::DEPRECATED_VERSIONS) + expect(language.deprecated_versions).to eq(Dependabot::Javascript::Shared::Language::DEPRECATED_VERSIONS) end it "sets the supported_versions correctly" do - expect(language.supported_versions).to eq(Dependabot::Javascript::Language::SUPPORTED_VERSIONS) + expect(language.supported_versions).to eq(Dependabot::Javascript::Shared::Language::SUPPORTED_VERSIONS) end context "when a requirement is provided" do - let(:requirement) { Dependabot::Javascript::Requirement.new([">= 16.0.0", "< 17.0.0"]) } + let(:requirement) { Dependabot::Javascript::Shared::Requirement.new([">= 16.0.0", "< 17.0.0"]) } it "sets the requirement correctly" do expect(language.requirement).to eq(requirement) diff --git a/javascript/spec/dependabot/javascript/package_manager_detector_spec.rb b/javascript/spec/dependabot/javascript/shared/package_manager_detector_spec.rb similarity index 98% rename from javascript/spec/dependabot/javascript/package_manager_detector_spec.rb rename to javascript/spec/dependabot/javascript/shared/package_manager_detector_spec.rb index 83b433b033..ba52d75863 100644 --- a/javascript/spec/dependabot/javascript/package_manager_detector_spec.rb +++ b/javascript/spec/dependabot/javascript/shared/package_manager_detector_spec.rb @@ -4,7 +4,7 @@ require "spec_helper" require "dependabot/bun" -RSpec.describe Dependabot::Javascript::PackageManagerDetector do +RSpec.describe Dependabot::Javascript::Shared::PackageManagerDetector do let(:npm_lockfile) do instance_double( Dependabot::DependencyFile, diff --git a/javascript/spec/dependabot/javascript/registry_helper_spec.rb b/javascript/spec/dependabot/javascript/shared/registry_helper_spec.rb similarity index 98% rename from javascript/spec/dependabot/javascript/registry_helper_spec.rb rename to javascript/spec/dependabot/javascript/shared/registry_helper_spec.rb index 728d204ac2..20574361cc 100644 --- a/javascript/spec/dependabot/javascript/registry_helper_spec.rb +++ b/javascript/spec/dependabot/javascript/shared/registry_helper_spec.rb @@ -4,7 +4,7 @@ require "spec_helper" require "dependabot/bun" -RSpec.describe Dependabot::Javascript::RegistryHelper do +RSpec.describe Dependabot::Javascript::Shared::RegistryHelper do let(:npmrc_file) do Dependabot::DependencyFile.new( name: ".npmrc", diff --git a/javascript/spec/dependabot/javascript/requirement_spec.rb b/javascript/spec/dependabot/javascript/shared/requirement_spec.rb similarity index 98% rename from javascript/spec/dependabot/javascript/requirement_spec.rb rename to javascript/spec/dependabot/javascript/shared/requirement_spec.rb index 923cf7d287..8d00012fc5 100644 --- a/javascript/spec/dependabot/javascript/requirement_spec.rb +++ b/javascript/spec/dependabot/javascript/shared/requirement_spec.rb @@ -4,7 +4,7 @@ require "spec_helper" require "dependabot/bun" -RSpec.describe Dependabot::Javascript::Requirement do +RSpec.describe Dependabot::Javascript::Shared::Requirement do subject(:requirement) { described_class.new(requirement_string) } let(:requirement_string) { ">=1.0.0" } @@ -331,7 +331,7 @@ context "with a Javascript::Version" do let(:version) do - Dependabot::Javascript::Version.new(version_string) + Dependabot::Javascript::Shared::Version.new(version_string) end context "when dealing with the current version" do diff --git a/javascript/spec/dependabot/javascript/version_spec.rb b/javascript/spec/dependabot/javascript/shared/version_spec.rb similarity index 99% rename from javascript/spec/dependabot/javascript/version_spec.rb rename to javascript/spec/dependabot/javascript/shared/version_spec.rb index 1774384ea5..5276494965 100644 --- a/javascript/spec/dependabot/javascript/version_spec.rb +++ b/javascript/spec/dependabot/javascript/shared/version_spec.rb @@ -4,7 +4,7 @@ require "spec_helper" require "dependabot/bun" -RSpec.describe Dependabot::Javascript::Version do +RSpec.describe Dependabot::Javascript::Shared::Version do subject(:version) { described_class.new(version_string) } let(:version_string) { "1.0.0" } From 8c6746b7edb468654df9bf4898bfbe877989a1bc Mon Sep 17 00:00:00 2001 From: Kamil Bukum Date: Fri, 7 Feb 2025 19:50:49 -0800 Subject: [PATCH 09/15] removed unused lockfile parser --- .../bun/file_parser/lockfile_parser.rb | 48 ------------------- 1 file changed, 48 deletions(-) delete mode 100644 javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb diff --git a/javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb b/javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb deleted file mode 100644 index 620ac8f05a..0000000000 --- a/javascript/lib/dependabot/bun/file_parser/lockfile_parser.rb +++ /dev/null @@ -1,48 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -module Dependabot - module Javascript - module Bun - class FileParser - class LockfileParser < Dependabot::Javascript::Shared::FileParser::LockfileParser - extend T::Sig - - sig { override.returns(Dependabot::FileParsers::Base::DependencySet) } - def parse_set - dependency_set = Dependabot::FileParsers::Base::DependencySet.new - - bun_locks.each do |file| - dependency_set += lockfile_for(file).dependencies - end - - dependency_set - end - - private - - sig { override.params(file: DependencyFile).returns(BunLock) } - def lockfile_for(file) - @lockfiles ||= T.let({}, T.nilable(T::Hash[String, BunLock])) - @lockfiles[file.name] ||= case file.name - when *bun_locks.map(&:name) - Bun::FileParser::BunLock.new(file) - else - raise "Unexpected lockfile: #{file.name}" - end - end - - sig { returns(T::Array[DependencyFile]) } - def bun_locks - @bun_locks ||= T.let(select_files_by_extension("bun.lock"), T.nilable(T::Array[DependencyFile])) - end - - sig { override.returns(T.class_of(Version)) } - def version_class - Version - end - end - end - end - end -end From 4fedcabba2b6e9b77a10032c1de81a4262f2cd3f Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Mon, 10 Feb 2025 11:46:56 +0000 Subject: [PATCH 10/15] Use dependency injection for DependencyFilesFilterer --- .../dependabot/javascript/bun/file_updater.rb | 5 +++++ .../bun/update_checker/version_resolver.rb | 3 ++- .../shared/dependency_files_filterer.rb | 17 ++++++++++++++--- .../javascript/shared/file_updater.rb | 10 +++++++--- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/javascript/lib/dependabot/javascript/bun/file_updater.rb b/javascript/lib/dependabot/javascript/bun/file_updater.rb index 8efb0eff9f..025d8804d2 100644 --- a/javascript/lib/dependabot/javascript/bun/file_updater.rb +++ b/javascript/lib/dependabot/javascript/bun/file_updater.rb @@ -15,6 +15,11 @@ def self.updated_files_regex private + sig { override.returns(T.class_of(FileParser::LockfileParser)) } + def lockfile_parser_class + FileParser::LockfileParser + end + sig { returns(T::Array[Dependabot::DependencyFile]) } def bun_locks @bun_locks ||= T.let( diff --git a/javascript/lib/dependabot/javascript/bun/update_checker/version_resolver.rb b/javascript/lib/dependabot/javascript/bun/update_checker/version_resolver.rb index 69d84934a5..601d0550e8 100644 --- a/javascript/lib/dependabot/javascript/bun/update_checker/version_resolver.rb +++ b/javascript/lib/dependabot/javascript/bun/update_checker/version_resolver.rb @@ -481,7 +481,8 @@ def paths_requiring_update_check @paths_requiring_update_check ||= Dependabot::Javascript::Shared::DependencyFilesFilterer.new( dependency_files: dependency_files, - updated_dependencies: [dependency] + updated_dependencies: [dependency], + lockfile_parser_class: FileParser::LockfileParser ).paths_requiring_update_check end diff --git a/javascript/lib/dependabot/javascript/shared/dependency_files_filterer.rb b/javascript/lib/dependabot/javascript/shared/dependency_files_filterer.rb index 6441f2ffb0..16f3fb1fcb 100644 --- a/javascript/lib/dependabot/javascript/shared/dependency_files_filterer.rb +++ b/javascript/lib/dependabot/javascript/shared/dependency_files_filterer.rb @@ -8,10 +8,18 @@ module Shared class DependencyFilesFilterer extend T::Sig - sig { params(dependency_files: T::Array[DependencyFile], updated_dependencies: T::Array[Dependency]).void } - def initialize(dependency_files:, updated_dependencies:) + sig do + params( + dependency_files: T::Array[DependencyFile], + updated_dependencies: T::Array[Dependency], + lockfile_parser_class: T.class_of(FileParser::LockfileParser) + ) + .void + end + def initialize(dependency_files:, updated_dependencies:, lockfile_parser_class:) @dependency_files = dependency_files @updated_dependencies = updated_dependencies + @lockfile_parser_class = lockfile_parser_class end sig { returns(T::Array[String]) } @@ -47,6 +55,9 @@ def package_files_requiring_update sig { returns(T::Array[Dependency]) } attr_reader :updated_dependencies + sig { returns(T.class_of(FileParser::LockfileParser)) } + attr_reader :lockfile_parser_class + sig { returns(T::Array[String]) } def fetch_paths_requiring_update_check # if only a root lockfile exists, it tracks all dependencies @@ -127,7 +138,7 @@ def updated_dependencies_in_lockfile?(lockfile) def lockfile_dependencies(lockfile) @lockfile_dependencies ||= T.let({}, T.nilable(T::Hash[String, T::Array[Dependency]])) @lockfile_dependencies[lockfile.name] ||= - NpmAndYarn::FileParser::LockfileParser.new( + lockfile_parser_class.new( dependency_files: [lockfile] ).parse end diff --git a/javascript/lib/dependabot/javascript/shared/file_updater.rb b/javascript/lib/dependabot/javascript/shared/file_updater.rb index cbacf120ba..2b0cc73471 100644 --- a/javascript/lib/dependabot/javascript/shared/file_updater.rb +++ b/javascript/lib/dependabot/javascript/shared/file_updater.rb @@ -111,12 +111,13 @@ def pnp_updater def filtered_dependency_files @filtered_dependency_files ||= T.let( if dependencies.any?(&:top_level?) - DependencyFilesFilterer.new( + Shared::DependencyFilesFilterer.new( dependency_files: dependency_files, - updated_dependencies: dependencies + updated_dependencies: dependencies, + lockfile_parser_class: lockfile_parser_class ).files_requiring_update else - SubDependencyFilesFilterer.new( + Shared::SubDependencyFilesFilterer.new( dependency_files: dependency_files, updated_dependencies: dependencies ).files_requiring_update @@ -124,6 +125,9 @@ def filtered_dependency_files ) end + sig { abstract.returns(T.class_of(FileParser::LockfileParser)) } + def lockfile_parser_class; end + sig { override.void } def check_required_files raise DependencyFileNotFound.new(nil, "package.json not found.") unless get_original_file("package.json") From 1bfa64eacc25d03589034edb0104e0545385474d Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Mon, 10 Feb 2025 11:52:48 +0000 Subject: [PATCH 11/15] Remove the FileFetcherHelper as we have swithed to class inheritance --- .../dependabot/javascript/bun/file_fetcher.rb | 1 - .../javascript/shared/file_fetcher_helper.rb | 264 ------------------ 2 files changed, 265 deletions(-) delete mode 100644 javascript/lib/dependabot/javascript/shared/file_fetcher_helper.rb diff --git a/javascript/lib/dependabot/javascript/bun/file_fetcher.rb b/javascript/lib/dependabot/javascript/bun/file_fetcher.rb index e4c48379ce..979f8d8d7f 100644 --- a/javascript/lib/dependabot/javascript/bun/file_fetcher.rb +++ b/javascript/lib/dependabot/javascript/bun/file_fetcher.rb @@ -5,7 +5,6 @@ module Dependabot module Javascript module Bun class FileFetcher < Shared::FileFetcher - include Shared::FileFetcherHelper extend T::Sig extend T::Helpers diff --git a/javascript/lib/dependabot/javascript/shared/file_fetcher_helper.rb b/javascript/lib/dependabot/javascript/shared/file_fetcher_helper.rb deleted file mode 100644 index 3c39839d25..0000000000 --- a/javascript/lib/dependabot/javascript/shared/file_fetcher_helper.rb +++ /dev/null @@ -1,264 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -module Dependabot - module Javascript - module Shared - module FileFetcherHelper - include Kernel - extend T::Sig - - PATH_DEPENDENCY_STARTS = T.let(%w(file: / ./ ../ ~/).freeze, [String, String, String, String, String]) - PATH_DEPENDENCY_CLEAN_REGEX = /^file:|^link:/ - DEPENDENCY_TYPES = T.let(%w(dependencies devDependencies optionalDependencies).freeze, T::Array[String]) - - sig { params(instance: FileFetcher).returns(T::Array[DependencyFile]) } - def workspace_package_jsons(instance) - @workspace_package_jsons ||= T.let(fetch_workspace_package_jsons(instance), - T.nilable(T::Array[DependencyFile])) - end - - sig do - params(instance: FileFetcher, fetched_files: T::Array[DependencyFile]) - .returns(T::Array[DependencyFile]) - end - def path_dependencies(instance, fetched_files) - package_json_files = T.let([], T::Array[DependencyFile]) - - path_dependency_details(instance, fetched_files).each do |name, path| - # This happens with relative paths in the package-lock. Skipping it since it results - # in /package.json which is outside of the project directory. - next if path == "file:" - - path = path.gsub(PATH_DEPENDENCY_CLEAN_REGEX, "") - raise PathDependenciesNotReachable, "#{name} at #{path}" if path.start_with?("/") - - filename = path - filename = File.join(filename, MANIFEST_FILENAME) - cleaned_name = Pathname.new(filename).cleanpath.to_path - next if fetched_files.map(&:name).include?(cleaned_name) - - file = instance.fetch_file(filename, fetch_submodules: true) - package_json_files << file - end - - if package_json_files.any? - package_json_files += - path_dependencies(instance, fetched_files + package_json_files) - end - - package_json_files.tap { |fs| fs.each { |f| f.support_file = true } } - end - - sig do - params(instance: FileFetcher, fetched_files: T::Array[DependencyFile]) - .returns(T::Array[[String, String]]) - end - def path_dependency_details(instance, fetched_files) - package_json_path_deps = T.let([], T::Array[[String, String]]) - - fetched_files.each do |file| - package_json_path_deps += - path_dependency_details_from_manifest(instance, file) - end - - [ - *package_json_path_deps - ].uniq - end - - # rubocop:disable Metrics/PerceivedComplexity - # rubocop:disable Metrics/AbcSize - sig do - params(instance: FileFetcher, - file: DependencyFile).returns(T::Array[[String, String]]) - end - def path_dependency_details_from_manifest(instance, file) - return [] unless file.name.end_with?(MANIFEST_FILENAME) - - current_dir = file.name.rpartition("/").first - current_dir = nil if current_dir == "" - - current_depth = File.join(instance.directory, file.name).split("/").count { |path| !path.empty? } - path_to_directory = "../" * current_depth - - dep_types = DEPENDENCY_TYPES # TODO: Is this needed? - parsed_manifest = JSON.parse(T.must(file.content)) - dependency_objects = parsed_manifest.values_at(*dep_types).compact - # Fetch yarn "file:" path "resolutions" so the lockfile can be resolved - resolution_objects = parsed_manifest.values_at("resolutions").compact - manifest_objects = dependency_objects + resolution_objects - - raise Dependabot::DependencyFileNotParseable, file.path unless manifest_objects.all?(Hash) - - resolution_deps = resolution_objects.flat_map(&:to_a) - .map do |path, value| - # skip dependencies that contain invalid values such as inline comments, null, etc. - - unless value.is_a?(String) - Dependabot.logger.warn("File fetcher: Skipping dependency \"#{path}\" " \ - "with value: \"#{value}\"") - - next - end - - convert_dependency_path_to_name(path, value) - end - - path_starts = PATH_DEPENDENCY_STARTS - (dependency_objects.flat_map(&:to_a) + resolution_deps) - .select { |_, v| v.is_a?(String) && v.start_with?(*path_starts) } - .map do |name, path| - path = path.gsub(PATH_DEPENDENCY_CLEAN_REGEX, "") - raise PathDependenciesNotReachable, "#{name} at #{path}" if path.start_with?("/", - "#{path_to_directory}..") - - path = File.join(current_dir, path) unless current_dir.nil? - [name, Pathname.new(path).cleanpath.to_path] - end - rescue JSON::ParserError - raise Dependabot::DependencyFileNotParseable, file.path - end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/PerceivedComplexity - - # Re-write the glob name to the targeted dependency name (which is used - # in the lockfile), for example "parent-package/**/sub-dep/target-dep" > - # "target-dep" - sig { params(path: String, value: String).returns([String, String]) } - def convert_dependency_path_to_name(path, value) - # Picking the last two parts that might include a scope - parts = path.split("/").last(2) - parts.shift if parts.count == 2 && !T.must(parts.first).start_with?("@") - [parts.join("/"), value] - end - - sig { params(instance: FileFetcher).returns(T::Array[DependencyFile]) } - def fetch_workspace_package_jsons(instance) - parsed_manifest = parsed_package_json(instance) - return [] unless parsed_manifest["workspaces"] - - workspace_paths(instance, parsed_manifest["workspaces"]).filter_map do |workspace| - fetch_package_json_if_present(instance, workspace) - end - end - - sig do - params(instance: FileFetcher, - workspace_object: T.untyped).returns(T::Array[String]) - end - def workspace_paths(instance, workspace_object) - paths_array = - if workspace_object.is_a?(Hash) - workspace_object.values_at("packages", "nohoist").flatten.compact - elsif workspace_object.is_a?(Array) then workspace_object - else - [] # Invalid lerna.json, which must not be in use - end - - paths_array.flat_map { |path| recursive_find_directories(instance, path) } - end - - sig { params(instance: FileFetcher, glob: String).returns(T::Array[String]) } - def find_directories(instance, glob) - return [glob] unless glob.include?("*") - - unglobbed_path = - glob.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*") - .split("*") - .first&.gsub(%r{(?<=/)[^/]*$}, "") || "." - - dir = instance.directory.gsub(%r{(^/|/$)}, "") - - paths = - instance.fetch_repo_contents(dir: unglobbed_path, raise_errors: false) - .select { |file| file.type == "dir" } - .map { |f| f.path.gsub(%r{^/?#{Regexp.escape(dir)}/?}, "") } - - matching_paths(glob, paths) - end - - sig { params(glob: String, paths: T::Array[String]).returns(T::Array[String]) } - def matching_paths(glob, paths) - glob = glob.gsub(%r{^\./}, "").gsub(/!\(.*?\)/, "*") - glob = "#{glob}/*" if glob.end_with?("**") - - paths.select { |filename| File.fnmatch?(glob, filename, File::FNM_PATHNAME) } - end - - sig do - params(instance: FileFetcher, glob: String, - prefix: String).returns(T::Array[String]) - end - def recursive_find_directories(instance, glob, prefix = "") - return [prefix + glob] unless glob.include?("*") - - glob = glob.gsub(%r{^\./}, "") - glob_parts = glob.split("/") - - current_glob = glob_parts.first - paths = find_directories(instance, prefix + T.must(current_glob)) - next_parts = current_glob == "**" ? glob_parts : glob_parts.drop(1) - return paths if next_parts.empty? - - paths += paths.flat_map do |expanded_path| - recursive_find_directories(instance, next_parts.join("/"), "#{expanded_path}/") - end - - matching_paths(prefix + glob, paths) - end - - sig do - params(instance: FileFetcher, workspace: String).returns(T.nilable(DependencyFile)) - end - def fetch_package_json_if_present(instance, workspace) - file = File.join(workspace, MANIFEST_FILENAME) - - begin - instance.fetch_file(file) - rescue Dependabot::DependencyFileNotFound - # Not all paths matched by a workspace glob may contain a package.json - # file. Ignore if that's the case - nil - end - end - - sig { params(instance: FileFetcher).returns(DependencyFile) } - def package_json(instance) - @package_json ||= T.let(instance.fetch_file(Javascript::MANIFEST_FILENAME), T.nilable(DependencyFile)) - end - - sig { params(instance: FileFetcher).returns(T.untyped) } - def parsed_package_json(instance) - manifest_file = package_json(instance) - parsed = JSON.parse(T.must(manifest_file.content)) - raise Dependabot::DependencyFileNotParseable, manifest_file.path unless parsed.is_a?(Hash) - - parsed - rescue JSON::ParserError - raise Dependabot::DependencyFileNotParseable, T.must(manifest_file).path - end - - sig do - params(instance: FileFetcher, filename: String).returns(T.nilable(DependencyFile)) - end - def fetch_file_with_support(instance, filename) - instance.fetch_file(filename).tap { |f| f.support_file = true } - rescue Dependabot::DependencyFileNotFound - nil - end - - sig do - params(instance: FileFetcher, filename: String).returns(T.nilable(DependencyFile)) - end - def fetch_file_from_parent_directories(instance, filename) - (1..instance.directory.split("/").count).each do |i| - file = fetch_file_with_support(instance, ("../" * i) + filename) - return file if file - end - nil - end - end - end - end -end From 4e040089c390067d1ae960bbb54b848f389d7122 Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Mon, 10 Feb 2025 12:10:28 +0000 Subject: [PATCH 12/15] Apply suggestions from code review --- .../lib/dependabot/javascript/bun/file_parser/bun_lock.rb | 2 +- .../dependabot/javascript/bun/file_parser/lockfile_parser.rb | 2 +- javascript/lib/dependabot/javascript/bun/update_checker.rb | 2 +- .../bun/update_checker/conflicting_dependency_resolver.rb | 2 -- javascript/lib/dependabot/javascript/shared/native_helpers.rb | 2 +- javascript/lib/dependabot/javascript/shared/version.rb | 4 ---- 6 files changed, 4 insertions(+), 10 deletions(-) diff --git a/javascript/lib/dependabot/javascript/bun/file_parser/bun_lock.rb b/javascript/lib/dependabot/javascript/bun/file_parser/bun_lock.rb index 6f5da8fb8a..6b01dbbb18 100644 --- a/javascript/lib/dependabot/javascript/bun/file_parser/bun_lock.rb +++ b/javascript/lib/dependabot/javascript/bun/file_parser/bun_lock.rb @@ -5,7 +5,7 @@ module Dependabot module Javascript module Bun class FileParser - class BunLock < Dependabot::Javascript::Shared::FileParser::Lockfile + class BunLock < Shared::FileParser::Lockfile extend T::Sig sig { params(dependency_file: DependencyFile).void } diff --git a/javascript/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb b/javascript/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb index 201f36fd01..42cf30ced9 100644 --- a/javascript/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb +++ b/javascript/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb @@ -5,7 +5,7 @@ module Dependabot module Javascript module Bun class FileParser - class LockfileParser < Dependabot::Javascript::Shared::FileParser::LockfileParser + class LockfileParser < Shared::FileParser::LockfileParser extend T::Sig DEFAULT_LOCKFILES = %w(bun.lock).freeze diff --git a/javascript/lib/dependabot/javascript/bun/update_checker.rb b/javascript/lib/dependabot/javascript/bun/update_checker.rb index b279fbcd44..633aa571e4 100644 --- a/javascript/lib/dependabot/javascript/bun/update_checker.rb +++ b/javascript/lib/dependabot/javascript/bun/update_checker.rb @@ -189,7 +189,7 @@ def conflicting_updated_dependencies updated_deps << build_updated_dependency( dependency: Dependency.new( name: dependency_name, - package_manager: "npm_and_yarn", + package_manager: "bun", requirements: requirements ), version: update["target_version"], diff --git a/javascript/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb b/javascript/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb index 1341f441a7..648ec535ce 100644 --- a/javascript/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb +++ b/javascript/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb @@ -1,8 +1,6 @@ # typed: true # frozen_string_literal: true -require "dependabot/bun/update_checker/dependency_files_builder" - module Dependabot module Javascript module Bun diff --git a/javascript/lib/dependabot/javascript/shared/native_helpers.rb b/javascript/lib/dependabot/javascript/shared/native_helpers.rb index 56179a9169..d85db9c44f 100644 --- a/javascript/lib/dependabot/javascript/shared/native_helpers.rb +++ b/javascript/lib/dependabot/javascript/shared/native_helpers.rb @@ -11,7 +11,7 @@ def self.helper_path def self.native_helpers_root helpers_root = ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", nil) - return File.join(helpers_root, "npm_and_yarn") unless helpers_root.nil? + return File.join(helpers_root, "javascript", "shared") unless helpers_root.nil? File.join(__dir__, "../../../helpers") end diff --git a/javascript/lib/dependabot/javascript/shared/version.rb b/javascript/lib/dependabot/javascript/shared/version.rb index bd9fd221fe..4ef3a66275 100644 --- a/javascript/lib/dependabot/javascript/shared/version.rb +++ b/javascript/lib/dependabot/javascript/shared/version.rb @@ -1,10 +1,6 @@ # typed: strict # frozen_string_literal: true -require "dependabot/version" -require "dependabot/utils" -require "sorbet-runtime" - # JavaScript pre-release versions use 1.0.1-rc1 syntax, which Gem::Version # converts into 1.0.1.pre.rc1. We override the `to_s` method to stop that # alteration. From 5ca80220afdd8ee786b95b4c96d9932c9e4894f1 Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Mon, 10 Feb 2025 12:12:04 +0000 Subject: [PATCH 13/15] Move register utils to the bun file --- javascript/lib/dependabot/bun.rb | 2 ++ javascript/lib/dependabot/javascript/bun/requirement.rb | 5 ----- javascript/lib/dependabot/javascript/bun/version.rb | 3 --- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/javascript/lib/dependabot/bun.rb b/javascript/lib/dependabot/bun.rb index 24b69522d3..ef798f0679 100644 --- a/javascript/lib/dependabot/bun.rb +++ b/javascript/lib/dependabot/bun.rb @@ -44,3 +44,5 @@ module Bun Dependabot::FileParsers.register("bun", Dependabot::Javascript::Bun::FileParser) Dependabot::FileUpdaters.register("bun", Dependabot::Javascript::Bun::FileUpdater) Dependabot::UpdateCheckers.register("bun", Dependabot::Javascript::Bun::UpdateChecker) +Dependabot::Utils.register_requirement_class("bun", Dependabot::Javascript::Bun::Requirement) +Dependabot::Utils.register_version_class("bun", Dependabot::Javascript::Bun::Version) diff --git a/javascript/lib/dependabot/javascript/bun/requirement.rb b/javascript/lib/dependabot/javascript/bun/requirement.rb index 7ee08b1264..909e363787 100644 --- a/javascript/lib/dependabot/javascript/bun/requirement.rb +++ b/javascript/lib/dependabot/javascript/bun/requirement.rb @@ -9,8 +9,3 @@ class Requirement < Dependabot::Javascript::Shared::Requirement end end end - -Dependabot::Utils.register_requirement_class( - "bun", - Dependabot::Javascript::Bun::Requirement -) diff --git a/javascript/lib/dependabot/javascript/bun/version.rb b/javascript/lib/dependabot/javascript/bun/version.rb index 7f94439eae..665d353541 100644 --- a/javascript/lib/dependabot/javascript/bun/version.rb +++ b/javascript/lib/dependabot/javascript/bun/version.rb @@ -9,6 +9,3 @@ class Version < Dependabot::Javascript::Shared::Version end end end - -Dependabot::Utils - .register_version_class("bun", Dependabot::Javascript::Bun::Version) From 6277deaa2b46e9c70cc371ae4253ae455f2f77bf Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Mon, 10 Feb 2025 15:14:05 +0000 Subject: [PATCH 14/15] Update the javascript gem --- Dockerfile.updater-core | 2 +- Gemfile | 1 + Gemfile.lock | 8 ++++++++ Rakefile | 2 +- ...pendabot-bun.gemspec => dependabot-javascript.gemspec} | 6 +++--- javascript/lib/dependabot/bun.rb | 2 +- omnibus/dependabot-omnibus.gemspec | 2 +- updater/Gemfile | 1 + updater/Gemfile.lock | 8 ++++++++ updater/lib/dependabot/setup.rb | 1 + 10 files changed, 26 insertions(+), 7 deletions(-) rename javascript/{dependabot-bun.gemspec => dependabot-javascript.gemspec} (84%) diff --git a/Dockerfile.updater-core b/Dockerfile.updater-core index cb28e5e575..2a1f99c760 100644 --- a/Dockerfile.updater-core +++ b/Dockerfile.updater-core @@ -94,7 +94,7 @@ COPY --chown=dependabot:dependabot github_actions/.bundle github_actions/dependa COPY --chown=dependabot:dependabot go_modules/.bundle go_modules/dependabot-go_modules.gemspec go_modules/ COPY --chown=dependabot:dependabot gradle/.bundle gradle/dependabot-gradle.gemspec gradle/ COPY --chown=dependabot:dependabot hex/.bundle hex/dependabot-hex.gemspec hex/ -COPY --chown=dependabot:dependabot javascript/.bundle javascript/dependabot-bun.gemspec javascript/ +COPY --chown=dependabot:dependabot javascript/.bundle javascript/dependabot-javascript.gemspec javascript/ COPY --chown=dependabot:dependabot maven/.bundle maven/dependabot-maven.gemspec maven/ COPY --chown=dependabot:dependabot npm_and_yarn/.bundle npm_and_yarn/dependabot-npm_and_yarn.gemspec npm_and_yarn/ COPY --chown=dependabot:dependabot nuget/.bundle nuget/dependabot-nuget.gemspec nuget/ diff --git a/Gemfile b/Gemfile index 62ce388a34..b7a8d4992f 100644 --- a/Gemfile +++ b/Gemfile @@ -15,6 +15,7 @@ gem "dependabot-git_submodules", path: "git_submodules" gem "dependabot-go_modules", path: "go_modules" gem "dependabot-gradle", path: "gradle" gem "dependabot-hex", path: "hex" +gem "dependabot-javascript", path: "javascript" gem "dependabot-maven", path: "maven" gem "dependabot-npm_and_yarn", path: "npm_and_yarn" gem "dependabot-nuget", path: "nuget" diff --git a/Gemfile.lock b/Gemfile.lock index d7c70ade66..0c1b7f368a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -97,6 +97,13 @@ PATH dependabot-hex (0.296.0) dependabot-common (= 0.296.0) +PATH + remote: javascript + specs: + dependabot-javascript (0.296.0) + dependabot-common (= 0.296.0) + zeitwerk (~> 2.7) + PATH remote: maven specs: @@ -391,6 +398,7 @@ DEPENDENCIES dependabot-go_modules! dependabot-gradle! dependabot-hex! + dependabot-javascript! dependabot-maven! dependabot-npm_and_yarn! dependabot-nuget! diff --git a/Rakefile b/Rakefile index fae9b87756..37288ee8bc 100644 --- a/Rakefile +++ b/Rakefile @@ -35,7 +35,7 @@ GEMSPECS = %w( swift/dependabot-swift.gemspec devcontainers/dependabot-devcontainers.gemspec dotnet_sdk/dependabot-dotnet_sdk.gemspec - javascript/dependabot-bun.gemspec + javascript/dependabot-javascript.gemspec ).freeze def run_command(command) diff --git a/javascript/dependabot-bun.gemspec b/javascript/dependabot-javascript.gemspec similarity index 84% rename from javascript/dependabot-bun.gemspec rename to javascript/dependabot-javascript.gemspec index b1e83a823e..16fe7e17be 100644 --- a/javascript/dependabot-bun.gemspec +++ b/javascript/dependabot-javascript.gemspec @@ -4,9 +4,9 @@ Gem::Specification.new do |spec| common_gemspec = Bundler.load_gemspec_uncached("../common/dependabot-common.gemspec") - spec.name = "dependabot-bun" - spec.summary = "Provides Dependabot support for Bun" - spec.description = "Dependabot-bun provides support for bumping Javascript libraries using bun via " \ + spec.name = "dependabot-javascript" + spec.summary = "Provides Dependabot support for javascript" + spec.description = "Dependabot-javascript provides support for bumping Javascript libraries using javascript via " \ "Dependabot. " \ "If you want support for multiple package managers, you probably want the meta-gem " \ "dependabot-omnibus." diff --git a/javascript/lib/dependabot/bun.rb b/javascript/lib/dependabot/bun.rb index ef798f0679..9369b4eda8 100644 --- a/javascript/lib/dependabot/bun.rb +++ b/javascript/lib/dependabot/bun.rb @@ -13,7 +13,7 @@ loader.ignore(File.join(__dir__, "../../../common/lib/dependabot/clients/codecommit.rb")) loader.push_dir(File.join(__dir__, "..")) -loader.ignore("#{__dir__}/../script", "#{__dir__}/../spec", "#{__dir__}/../dependabot-bun.gemspec") +loader.ignore("#{__dir__}/../script", "#{__dir__}/../spec", "#{__dir__}/../dependabot-javascript.gemspec") loader.on_load do |_file| require "json" diff --git a/omnibus/dependabot-omnibus.gemspec b/omnibus/dependabot-omnibus.gemspec index 8fb865f710..c3c4028560 100644 --- a/omnibus/dependabot-omnibus.gemspec +++ b/omnibus/dependabot-omnibus.gemspec @@ -26,7 +26,6 @@ Gem::Specification.new do |spec| spec.require_path = "lib" spec.files = ["lib/dependabot/omnibus.rb"] - spec.add_dependency "dependabot-bun", Dependabot::VERSION spec.add_dependency "dependabot-bundler", Dependabot::VERSION spec.add_dependency "dependabot-cargo", Dependabot::VERSION spec.add_dependency "dependabot-common", Dependabot::VERSION @@ -40,6 +39,7 @@ Gem::Specification.new do |spec| spec.add_dependency "dependabot-go_modules", Dependabot::VERSION spec.add_dependency "dependabot-gradle", Dependabot::VERSION spec.add_dependency "dependabot-hex", Dependabot::VERSION + spec.add_dependency "dependabot-javascript", Dependabot::VERSION spec.add_dependency "dependabot-maven", Dependabot::VERSION spec.add_dependency "dependabot-npm_and_yarn", Dependabot::VERSION spec.add_dependency "dependabot-nuget", Dependabot::VERSION diff --git a/updater/Gemfile b/updater/Gemfile index 7b81bbe76d..83b06c296b 100644 --- a/updater/Gemfile +++ b/updater/Gemfile @@ -15,6 +15,7 @@ gem "dependabot-git_submodules", path: "../git_submodules" gem "dependabot-go_modules", path: "../go_modules" gem "dependabot-gradle", path: "../gradle" gem "dependabot-hex", path: "../hex" +gem "dependabot-javascript", path: "../javascript" gem "dependabot-maven", path: "../maven" gem "dependabot-npm_and_yarn", path: "../npm_and_yarn" gem "dependabot-nuget", path: "../nuget" diff --git a/updater/Gemfile.lock b/updater/Gemfile.lock index b21b953f3b..eb7737722a 100644 --- a/updater/Gemfile.lock +++ b/updater/Gemfile.lock @@ -97,6 +97,13 @@ PATH dependabot-hex (0.296.0) dependabot-common (= 0.296.0) +PATH + remote: ../javascript + specs: + dependabot-javascript (0.296.0) + dependabot-common (= 0.296.0) + zeitwerk (~> 2.7) + PATH remote: ../maven specs: @@ -425,6 +432,7 @@ DEPENDENCIES dependabot-go_modules! dependabot-gradle! dependabot-hex! + dependabot-javascript! dependabot-maven! dependabot-npm_and_yarn! dependabot-nuget! diff --git a/updater/lib/dependabot/setup.rb b/updater/lib/dependabot/setup.rb index 24367425a3..81b6fa9ed7 100644 --- a/updater/lib/dependabot/setup.rb +++ b/updater/lib/dependabot/setup.rb @@ -77,3 +77,4 @@ require "dependabot/silent" require "dependabot/swift" require "dependabot/devcontainers" +require "dependabot/javascript" From 16546efc984e91be556677fc677223468acb8a0b Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Tue, 11 Feb 2025 10:36:37 +0000 Subject: [PATCH 15/15] Add the javascript MetadataFinder --- .../lib/dependabot/metadata_finders/base.rb | 2 +- javascript/lib/dependabot/bun.rb | 1 + .../javascript/shared/metadata_finder.rb | 209 + .../spec/dependabot/javascript/bun_spec.rb | 84 + .../javascript/shared/metadata_finder_spec.rb | 475 + .../gemfury_response_etag.json | 119 + .../fixtures/npm_responses/etag-1.0.0.json | 40 + .../spec/fixtures/npm_responses/etag.json | 965 ++ .../npm_responses/npm_response_bitbucket.json | 896 ++ .../npm_responses/npm_response_no_source.json | 896 ++ .../npm_response_string_link.json | 893 ++ .../npm_response_string_shorthand.json | 893 ++ .../npm_responses/react-dom-with-dir.json | 105 + .../fixtures/npm_responses/react-dom.json | 10285 ++++++++++++++++ 14 files changed, 15862 insertions(+), 1 deletion(-) create mode 100644 javascript/lib/dependabot/javascript/shared/metadata_finder.rb create mode 100644 javascript/spec/dependabot/javascript/bun_spec.rb create mode 100644 javascript/spec/dependabot/javascript/shared/metadata_finder_spec.rb create mode 100644 javascript/spec/fixtures/gemfury_responses/gemfury_response_etag.json create mode 100644 javascript/spec/fixtures/npm_responses/etag-1.0.0.json create mode 100644 javascript/spec/fixtures/npm_responses/etag.json create mode 100644 javascript/spec/fixtures/npm_responses/npm_response_bitbucket.json create mode 100644 javascript/spec/fixtures/npm_responses/npm_response_no_source.json create mode 100644 javascript/spec/fixtures/npm_responses/npm_response_string_link.json create mode 100644 javascript/spec/fixtures/npm_responses/npm_response_string_shorthand.json create mode 100644 javascript/spec/fixtures/npm_responses/react-dom-with-dir.json create mode 100644 javascript/spec/fixtures/npm_responses/react-dom.json diff --git a/common/lib/dependabot/metadata_finders/base.rb b/common/lib/dependabot/metadata_finders/base.rb index 1fd8d9cd54..7bf7e4bf44 100644 --- a/common/lib/dependabot/metadata_finders/base.rb +++ b/common/lib/dependabot/metadata_finders/base.rb @@ -15,7 +15,7 @@ class Base require "dependabot/metadata_finders/base/release_finder" require "dependabot/metadata_finders/base/commits_finder" - PACKAGE_MANAGERS_WITH_RELIABLE_DIRECTORIES = T.let(%w(npm_and_yarn pub).freeze, T::Array[String]) + PACKAGE_MANAGERS_WITH_RELIABLE_DIRECTORIES = T.let(%w(bun npm_and_yarn pub).freeze, T::Array[String]) sig { returns(Dependabot::Dependency) } attr_reader :dependency diff --git a/javascript/lib/dependabot/bun.rb b/javascript/lib/dependabot/bun.rb index 9369b4eda8..948aca8770 100644 --- a/javascript/lib/dependabot/bun.rb +++ b/javascript/lib/dependabot/bun.rb @@ -44,5 +44,6 @@ module Bun Dependabot::FileParsers.register("bun", Dependabot::Javascript::Bun::FileParser) Dependabot::FileUpdaters.register("bun", Dependabot::Javascript::Bun::FileUpdater) Dependabot::UpdateCheckers.register("bun", Dependabot::Javascript::Bun::UpdateChecker) +Dependabot::MetadataFinders.register("bun", Dependabot::Javascript::Shared::MetadataFinder) Dependabot::Utils.register_requirement_class("bun", Dependabot::Javascript::Bun::Requirement) Dependabot::Utils.register_version_class("bun", Dependabot::Javascript::Bun::Version) diff --git a/javascript/lib/dependabot/javascript/shared/metadata_finder.rb b/javascript/lib/dependabot/javascript/shared/metadata_finder.rb new file mode 100644 index 0000000000..f4b5cc3f20 --- /dev/null +++ b/javascript/lib/dependabot/javascript/shared/metadata_finder.rb @@ -0,0 +1,209 @@ +# typed: true +# frozen_string_literal: true + +require "excon" +require "time" + +module Dependabot + module Javascript + module Shared + class MetadataFinder < Dependabot::MetadataFinders::Base + def homepage_url + # Attempt to use version_listing first, as fetching the entire listing + # array can be slow (if it's large) + return latest_version_listing["homepage"] if latest_version_listing["homepage"] + + listing = all_version_listings.find { |_, l| l["homepage"] } + listing&.last&.fetch("homepage", nil) || super + end + + def maintainer_changes + return unless npm_releaser + return unless npm_listing.dig("time", dependency.version) + return if previous_releasers.include?(npm_releaser) + + "This version was pushed to npm by " \ + "[#{npm_releaser}](https://www.npmjs.com/~#{npm_releaser}), a new " \ + "releaser for #{dependency.name} since your current version." + end + + private + + def look_up_source + return find_source_from_registry if new_source.nil? + + source_type = new_source[:type] || new_source.fetch("type") + + case source_type + when "git" then find_source_from_git_url + when "registry" then find_source_from_registry + else raise "Unexpected source type: #{source_type}" + end + end + + def npm_releaser + all_version_listings + .find { |v, _| v == dependency.version } + &.last&.fetch("_npmUser", nil)&.fetch("name", nil) + end + + def previous_releasers + times = npm_listing.fetch("time") + + cutoff = + if dependency.previous_version && times[dependency.previous_version] + Time.parse(times[dependency.previous_version]) + elsif times[dependency.version] + Time.parse(times[dependency.version]) - 1 + end + return unless cutoff + + all_version_listings + .reject { |v, _| Time.parse(times[v]) > cutoff } + .filter_map { |_, d| d.fetch("_npmUser", nil)&.fetch("name", nil) } + end + + def find_source_from_registry + # Attempt to use version_listing first, as fetching the entire listing + # array can be slow (if it's large) + potential_sources = + [ + get_source(latest_version_listing["repository"]), + get_source(latest_version_listing["homepage"]), + get_source(latest_version_listing["bugs"]) + ].compact + + return potential_sources.first if potential_sources.any? + + potential_sources = + all_version_listings.flat_map do |_, listing| + [ + get_source(listing["repository"]), + get_source(listing["homepage"]), + get_source(listing["bugs"]) + ] + end.compact + + potential_sources.first + end + + def new_source + sources = dependency.requirements + .map { |r| r.fetch(:source) }.uniq.compact + .sort_by do |source| + UpdateChecker::RegistryFinder.central_registry?(source[:url]) ? 1 : 0 + end + + sources.first + end + + def get_source(details) + potential_url = get_url(details) + return unless potential_url + + potential_source = Source.from_url(potential_url) + return unless potential_source + + potential_source.directory = get_directory(details) + potential_source + end + + def get_url(details) + url = + case details + when String then details + when Hash then details.fetch("url", nil) + end + return url unless url&.match?(%r{^[\w.-]+/[\w.-]+$}) + + "https://github.com/" + url + end + + def get_directory(details) + # Only return a directory if it is explicitly specified + return unless details.is_a?(Hash) + + details.fetch("directory", nil) + end + + def find_source_from_git_url + url = new_source[:url] || new_source.fetch("url") + Source.from_url(url) + end + + def latest_version_listing + return @latest_version_listing if defined?(@latest_version_listing) + + response = Dependabot::RegistryClient.get(url: "#{dependency_url}/latest", headers: registry_auth_headers) + return @latest_version_listing = JSON.parse(response.body) if response.status == 200 + + @latest_version_listing = {} + rescue JSON::ParserError, Excon::Error::Timeout + @latest_version_listing = {} + end + + def all_version_listings + return [] if npm_listing["versions"].nil? + + npm_listing["versions"] + .reject { |_, details| details["deprecated"] } + .sort_by { |version, _| Version.new(version) } + .reverse + end + + def npm_listing + return @npm_listing unless @npm_listing.nil? + + response = Dependabot::RegistryClient.get(url: dependency_url, headers: registry_auth_headers) + return @npm_listing = {} if response.status >= 500 + + begin + @npm_listing = JSON.parse(response.body) + rescue JSON::ParserError + raise unless non_standard_registry? + + @npm_listing = {} + end + rescue Excon::Error::Timeout + @npm_listing = {} + end + + def dependency_url + registry_url = + if new_source.nil? then "https://registry.npmjs.org" + else + new_source.fetch(:url) + end + + # NPM registries expect slashes to be escaped + escaped_dependency_name = dependency.name.gsub("/", "%2F") + "#{registry_url}/#{escaped_dependency_name}" + end + + def registry_auth_headers + return {} unless auth_token + + { "Authorization" => "Bearer #{auth_token}" } + end + + def dependency_registry + if new_source.nil? then "registry.npmjs.org" + else + new_source.fetch(:url).gsub("https://", "").gsub("http://", "") + end + end + + def auth_token + credentials + .select { |cred| cred["type"] == "npm_registry" } + .find { |cred| cred["registry"] == dependency_registry } + &.fetch("token", nil) + end + + def non_standard_registry? + dependency_registry != "registry.npmjs.org" + end + end + end + end +end diff --git a/javascript/spec/dependabot/javascript/bun_spec.rb b/javascript/spec/dependabot/javascript/bun_spec.rb new file mode 100644 index 0000000000..4446dafb5b --- /dev/null +++ b/javascript/spec/dependabot/javascript/bun_spec.rb @@ -0,0 +1,84 @@ +# typed: strict +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/bun" + +RSpec.describe Dependabot::Javascript::Bun do + extend T::Sig + + describe "ecosystem setup" do + it "has the correct ecosystem name" do + expect(described_class::ECOSYSTEM).to eq("bun") + end + end + + describe "dependency registrations" do + it "registers the bun ecosystem with required components" do + fetcher = T.let( + Dependabot::FileFetchers.for_package_manager("bun"), + T.class_of(Dependabot::Javascript::Bun::FileFetcher) + ) + expect(fetcher).to eq(Dependabot::Javascript::Bun::FileFetcher) + + parser = T.let( + Dependabot::FileParsers.for_package_manager("bun"), + T.class_of(Dependabot::Javascript::Bun::FileParser) + ) + expect(parser).to eq(Dependabot::Javascript::Bun::FileParser) + + updater = T.let( + Dependabot::FileUpdaters.for_package_manager("bun"), + T.class_of(Dependabot::Javascript::Bun::FileUpdater) + ) + expect(updater).to eq(Dependabot::Javascript::Bun::FileUpdater) + + checker = T.let( + Dependabot::UpdateCheckers.for_package_manager("bun"), + T.class_of(Dependabot::Javascript::Bun::UpdateChecker) + ) + expect(checker).to eq(Dependabot::Javascript::Bun::UpdateChecker) + + finder = T.let( + Dependabot::MetadataFinders.for_package_manager("bun"), + T.class_of(Dependabot::Javascript::Shared::MetadataFinder) + ) + expect(finder).to eq(Dependabot::Javascript::Shared::MetadataFinder) + end + + it "registers the correct requirement and version classes" do + requirement_class = T.let( + Dependabot::Utils.requirement_class_for_package_manager("bun"), + T.class_of(Dependabot::Javascript::Bun::Requirement) + ) + expect(requirement_class).to eq(Dependabot::Javascript::Bun::Requirement) + + version_class = T.let( + Dependabot::Utils.version_class_for_package_manager("bun"), + T.class_of(Dependabot::Javascript::Bun::Version) + ) + expect(version_class).to eq(Dependabot::Javascript::Bun::Version) + end + end + + describe "pull request labeling" do + it "registers the correct label details" do + label_details = T.let( + Dependabot::PullRequestCreator::Labeler + .label_details_for_package_manager("bun"), + T::Hash[Symbol, String] + ) + expect(label_details).to eq(name: "javascript", colour: "168700") + end + end + + describe "production dependency check" do + it "considers all dependencies as production dependencies" do + checker = T.let( + Dependabot::Dependency.production_check_for_package_manager("bun"), + T.proc.params(arg0: T.untyped).returns(T::Boolean) + ) + expect(checker.call(nil)).to be true + end + end +end diff --git a/javascript/spec/dependabot/javascript/shared/metadata_finder_spec.rb b/javascript/spec/dependabot/javascript/shared/metadata_finder_spec.rb new file mode 100644 index 0000000000..2feb7c2da2 --- /dev/null +++ b/javascript/spec/dependabot/javascript/shared/metadata_finder_spec.rb @@ -0,0 +1,475 @@ +# typed: strict +# frozen_string_literal: true + +# require "octokit" +require "spec_helper" +require "dependabot/bun" +require_common_spec "metadata_finders/shared_examples_for_metadata_finders" + +RSpec.describe Dependabot::Javascript::Shared::MetadataFinder do + extend T::Sig + + subject(:finder) do + T.let(described_class.new(dependency: dependency, credentials: credentials), + described_class) + end + + let(:dependency_name) { T.let("etag", String) } + let(:credentials) do + T.let([Dependabot::Credential.new({ + "type" => "git_source", + "host" => "github.com", + "username" => "x-access-token", + "password" => "token" + })], T::Array[Dependabot::Credential]) + end + let(:dependency) do + T.let(Dependabot::Dependency.new( + name: dependency_name, + version: "1.6.0", + requirements: [ + { file: "package.json", requirement: "^1.0", groups: [], source: nil } + ], + package_manager: "bun" + ), Dependabot::Dependency) + end + + it_behaves_like "a dependency metadata finder" + + describe "#source_url" do + subject(:source_url) { finder.source_url } + + let(:npm_url) { "https://registry.npmjs.org/etag" } + + before do + stub_request(:get, npm_url + "/latest") + .to_return(status: 200, body: npm_latest_version_response) + stub_request(:get, npm_url) + .to_return(status: 200, body: npm_all_versions_response) + stub_request(:get, "https://example.come/status").to_return( + status: 200, + body: "Not GHES", + headers: {} + ) + stub_request(:get, "https://jshttp/status").to_return(status: 404) + end + + context "when dealing with a git dependency" do + let(:npm_all_versions_response) { nil } + let(:npm_latest_version_response) { nil } + let(:dependency) do + Dependabot::Dependency.new( + name: dependency_name, + version: "d5ac0584ee9ae7bd9288220a39780f155b9ad4c8", + requirements: [{ + file: "package.json", + requirement: nil, + groups: [], + source: { + type: "git", + url: "https://github.com/jshttp/etag", + branch: nil, + ref: "master" + } + }], + package_manager: "bun" + ) + end + + it { is_expected.to eq("https://github.com/jshttp/etag") } + + it "doesn't hit npm" do + source_url + expect(WebMock).not_to have_requested(:get, npm_url) + end + end + + context "when there is a github link in the npm response" do + let(:npm_latest_version_response) do + fixture("npm_responses", "etag-1.0.0.json") + end + let(:npm_all_versions_response) do + fixture("npm_responses", "etag.json") + end + + it { is_expected.to eq("https://github.com/jshttp/etag") } + + it "caches the call to npm" do + 2.times { source_url } + expect(WebMock) + .to have_requested(:get, npm_url + "/latest").once + expect(WebMock) + .not_to have_requested(:get, npm_url) + end + + context "with a monorepo that specifies a directory" do + let(:npm_latest_version_response) do + fixture("npm_responses", "react-dom-with-dir.json") + end + let(:npm_all_versions_response) do + fixture("npm_responses", "react-dom.json") + end + + it "includes details of the directory" do + expect(source_url).to eq( + "https://github.com/facebook/react/tree/HEAD/packages/react-dom" + ) + end + end + end + + context "when there is a bitbucket link in the npm response" do + let(:npm_latest_version_response) { nil } + let(:npm_all_versions_response) do + fixture("npm_responses", "npm_response_bitbucket.json") + end + + it { is_expected.to eq("https://bitbucket.org/jshttp/etag") } + + it "caches the call to npm" do + 2.times { source_url } + expect(WebMock) + .to have_requested(:get, npm_url + "/latest").once + expect(WebMock) + .to have_requested(:get, npm_url).once + end + end + + context "when there's a link without the expected structure" do + let(:npm_latest_version_response) { nil } + let(:npm_all_versions_response) do + fixture("npm_responses", "npm_response_string_link.json") + end + + it { is_expected.to eq("https://github.com/jshttp/etag") } + + it "caches the call to npm" do + 2.times { source_url } + expect(WebMock).to have_requested(:get, npm_url).once + end + end + + context "when there's a link using GitHub shorthand" do + let(:npm_latest_version_response) { nil } + let(:npm_all_versions_response) do + fixture("npm_responses", "npm_response_string_shorthand.json") + end + + it { is_expected.to eq("https://github.com/jshttp/etag") } + + it "caches the call to npm" do + 2.times { source_url } + expect(WebMock).to have_requested(:get, npm_url).once + end + end + + context "when there isn't a source link in the npm response" do + let(:npm_latest_version_response) { nil } + let(:npm_all_versions_response) do + fixture("npm_responses", "npm_response_no_source.json") + end + + it { is_expected.to be_nil } + + it "caches the call to npm" do + 2.times { source_url } + expect(WebMock).to have_requested(:get, npm_url).once + end + end + + context "when the npm link resolves to a redirect" do + let(:redirect_url) { "https://registry.npmjs.org/eTag" } + let(:npm_latest_version_response) { nil } + let(:npm_all_versions_response) { fixture("npm_responses", "etag.json") } + + before do + stub_request(:get, npm_url) + .to_return(status: 302, headers: { "Location" => redirect_url }) + stub_request(:get, redirect_url) + .to_return(status: 200, body: npm_all_versions_response) + end + + it { is_expected.to eq("https://github.com/jshttp/etag") } + end + + context "when the npm link 404s" do + before do + stub_request(:get, npm_url).to_return(status: 404) + stub_request(:get, npm_url + "/latest").to_return(status: 404) + stub_request(:get, npm_url + "/latest").to_return(status: 404) + end + + let(:npm_latest_version_response) { nil } + let(:npm_all_versions_response) { fixture("npm_responses", "etag.json") } + + # Not an ideal error, but this should never happen + specify { expect { finder.source_url }.to raise_error(JSON::ParserError) } + end + + context "when dealing with a scoped package name" do + before do + stub_request(:get, "https://registry.npmjs.org/@etag%2Fetag/latest") + .to_return(status: 200, body: npm_latest_version_response) + stub_request(:get, "https://registry.npmjs.org/@etag%2Fetag") + .to_return(status: 200, body: npm_all_versions_response) + end + + let(:dependency_name) { "@etag/etag" } + let(:npm_latest_version_response) { nil } + let(:npm_all_versions_response) { fixture("npm_responses", "etag.json") } + + it "requests the escaped name" do + finder.source_url + + expect(WebMock) + .to have_requested(:get, "https://registry.npmjs.org/@etag%2Fetag") + end + + context "when registry is private" do + before do + stub_request(:get, "https://registry.npmjs.org/@etag%2Fetag") + .to_return(status: 404, body: '{"error":"Not found"}') + stub_request(:get, "https://registry.npmjs.org/@etag%2Fetag") + .with(headers: { "Authorization" => "Bearer secret_token" }) + .to_return(status: 200, body: npm_all_versions_response) + end + + context "with credentials" do + let(:credentials) do + [ + Dependabot::Credential.new({ + "type" => "git_source", + "host" => "github.com", + "username" => "x-access-token", + "password" => "token" + }), + Dependabot::Credential.new({ + "type" => "npm_registry", + "registry" => "registry.npmjs.org", + "token" => "secret_token" + }) + ] + end + + it { is_expected.to eq("https://github.com/jshttp/etag") } + end + + context "without credentials" do + before do + stub_request(:get, "https://registry.npmjs.org/@etag%2Fetag") + .with(headers: { "Authorization" => "Bearer secret_token" }) + .to_return(status: 404) + end + + it { is_expected.to be_nil } + end + end + + context "when dependency is hosted on gemfury" do + before do + body = fixture("gemfury_responses", "gemfury_response_etag.json") + stub_request(:get, "https://npm.fury.io/dependabot/@etag%2Fetag") + .to_return(status: 404, body: '{"error":"Not found"}') + stub_request( + :get, "https://npm.fury.io/dependabot/@etag%2Fetag/latest" + ).to_return(status: 404, body: '{"error":"Not found"}') + stub_request( + :get, "https://npm.fury.io/dependabot/@etag%2Fetag/latest" + ).to_return(status: 404, body: '{"error":"Not found"}') + stub_request(:get, "https://npm.fury.io/dependabot/@etag%2Fetag") + .with(headers: { "Authorization" => "Bearer secret_token" }) + .to_return(status: 200, body: body) + end + + let(:dependency) do + Dependabot::Dependency.new( + name: dependency_name, + version: "1.0", + requirements: [{ + file: "package.json", + requirement: "^1.0", + groups: [], + source: { + type: "registry", + url: "https://npm.fury.io/dependabot" + } + }], + package_manager: "bun" + ) + end + + context "with credentials" do + let(:credentials) do + [ + Dependabot::Credential.new({ + "type" => "git_source", + "host" => "github.com", + "username" => "x-access-token", + "password" => "token" + }), + Dependabot::Credential.new({ + "type" => "npm_registry", + "registry" => "npm.fury.io/dependabot", + "token" => "secret_token" + }) + ] + end + + it { is_expected.to eq("https://github.com/jshttp/etag") } + end + + context "without credentials" do + before do + stub_request( + :get, "https://registry.npmjs.org/@etag%2Fetag/latest" + ).with(headers: { "Authorization" => "Bearer secret_token" }) + .to_return(status: 404) + stub_request( + :get, "https://registry.npmjs.org/@etag%2Fetag/latest" + ).with(headers: { "Authorization" => "Bearer secret_token" }) + .to_return(status: 404) + stub_request(:get, "https://registry.npmjs.org/@etag%2Fetag") + .with(headers: { "Authorization" => "Bearer secret_token" }) + .to_return(status: 404) + end + + it { is_expected.to be_nil } + end + end + end + + context "when multiple sources are detected" do + let(:npm_latest_version_response) { nil } + let(:npm_all_versions_response) { nil } + let(:dependency_name) { "@etag/etag" } + + let(:credentials) do + [ + Dependabot::Credential.new({ + "type" => "git_source", + "host" => "github.com", + "username" => "x-access-token", + "password" => "token" + }), + Dependabot::Credential.new({ + "type" => "npm_registry", + "registry" => "npm.fury.io/dependabot", + "token" => "secret_token" + }) + ] + end + + let(:dependency) do + Dependabot::Dependency.new( + name: dependency_name, + version: "1.0", + requirements: [ + { + file: "package.json", + requirement: "^1.0", + groups: [], + source: { + type: "registry", + url: "https://registry.npmjs.org" + } + }, + { + file: "package.json", + requirement: "^1.0", + groups: [], + source: { + type: "registry", + url: "https://npm.fury.io/dependabot" + } + } + ], + package_manager: "bun" + ) + end + + before do + stub_request( + :get, "https://npm.fury.io/dependabot/@etag%2Fetag/latest" + ).to_return(status: 404, body: '{"error":"Not found"}').times(2) + + stub_request(:get, "https://npm.fury.io/dependabot/@etag%2Fetag") + .with(headers: { "Authorization" => "Bearer secret_token" }) + .to_return( + status: 200, + body: fixture("gemfury_responses", "gemfury_response_etag.json") + ) + end + + it "prefers to fetch metadata from the private registry" do + expect(source_url).to eq("https://github.com/jshttp/etag") + end + end + end + + describe "#homepage_url" do + subject(:homepage_url) { finder.homepage_url } + + let(:npm_url) { "https://registry.npmjs.org/etag" } + + before do + stub_request(:get, npm_url + "/latest") + .to_return(status: 200, body: npm_latest_version_response) + stub_request(:get, npm_url) + .to_return(status: 200, body: npm_all_versions_response) + end + + context "when there is a homepage link in the npm response" do + let(:npm_all_versions_response) do + fixture("npm_responses", "npm_response_no_source.json") + end + let(:npm_latest_version_response) { nil } + + it "returns the specified homepage" do + expect(homepage_url).to eq("https://example.come/jshttp/etag") + end + end + end + + describe "#maintainer_changes" do + subject(:maintainer_changes) { finder.maintainer_changes } + + let(:npm_url) { "https://registry.npmjs.org/etag" } + let(:npm_all_versions_response) do + fixture("npm_responses", "etag.json") + end + + before do + stub_request(:get, npm_url) + .to_return(status: 200, body: npm_all_versions_response) + end + + context "when the user that pushed this version has pushed before" do + it { is_expected.to be_nil } + end + + context "when the user that pushed this version hasn't pushed before" do + let(:dependency) do + Dependabot::Dependency.new( + name: dependency_name, + version: "1.6.0", + previous_version: "1.0.0", + requirements: [{ + file: "package.json", + requirement: "^1.0", + groups: [], + source: nil + }], + package_manager: "bun" + ) + end + + it "gives details of the new releaser" do + expect(maintainer_changes).to eq( + "This version was pushed to npm by " \ + "[dougwilson](https://www.npmjs.com/~dougwilson), a new releaser " \ + "for etag since your current version." + ) + end + end + end +end diff --git a/javascript/spec/fixtures/gemfury_responses/gemfury_response_etag.json b/javascript/spec/fixtures/gemfury_responses/gemfury_response_etag.json new file mode 100644 index 0000000000..1552f36f0b --- /dev/null +++ b/javascript/spec/fixtures/gemfury_responses/gemfury_response_etag.json @@ -0,0 +1,119 @@ +{ + "_id": "etag", + "name": "etag", + "dist-tags": { + "latest": "1.8.1" + }, + "versions": { + "1.8.1": { + "name": "etag", + "description": "Create simple HTTP ETags", + "version": "1.8.1", + "contributors": [ + "Douglas Christopher Wilson ", + "David Björklund " + ], + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "license": "MIT", + "keywords": [ + "etag", + "http", + "res" + ], + "repository": "jshttp/etag", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "2.1.4", + "eslint": "3.19.0", + "eslint-config-standard": "10.2.1", + "eslint-plugin-import": "2.7.0", + "eslint-plugin-markdown": "1.0.0-beta.6", + "eslint-plugin-node": "5.1.1", + "eslint-plugin-promise": "3.5.0", + "eslint-plugin-standard": "3.0.1", + "istanbul": "0.4.5", + "mocha": "1.21.5", + "safe-buffer": "5.1.1", + "seedrandom": "2.4.3" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "README.md", + "index.js" + ], + "engines": { + "node": ">= 0.6" + }, + "scripts": { + "bench": "node benchmark/index.js", + "lint": "eslint --plugin markdown --ext js,md .", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "_id": "etag@1.8.1", + "dist": { + "shasum": "6a21748bc0860b08f7c68f6a82605e9d626f1267", + "tarball": "fury-repo:/~/d/etag/etag-1.8.1" + } + }, + "1.8.0": { + "name": "etag", + "description": "Create simple HTTP ETags", + "version": "1.8.0", + "contributors": [ + "Douglas Christopher Wilson ", + "David Björklund " + ], + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "license": "MIT", + "keywords": [ + "etag", + "http", + "res" + ], + "repository": "jshttp/etag", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "2.1.4", + "eslint": "3.19.0", + "eslint-config-standard": "10.2.1", + "eslint-plugin-import": "2.7.0", + "eslint-plugin-markdown": "1.0.0-beta.6", + "eslint-plugin-node": "5.1.1", + "eslint-plugin-promise": "3.5.0", + "eslint-plugin-standard": "3.0.1", + "istanbul": "0.4.5", + "mocha": "1.21.5", + "safe-buffer": "5.1.1", + "seedrandom": "2.4.3" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "README.md", + "index.js" + ], + "engines": { + "node": ">= 0.6" + }, + "scripts": { + "bench": "node benchmark/index.js", + "lint": "eslint --plugin markdown --ext js,md .", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "_id": "etag@1.8.0", + "dist": { + "shasum": "6a21748bc0860b08f7c68f6a82605e9d626f1267", + "tarball": "fury-repo:/~/d/etag/etag-1.8.0" + } + } + } +} diff --git a/javascript/spec/fixtures/npm_responses/etag-1.0.0.json b/javascript/spec/fixtures/npm_responses/etag-1.0.0.json new file mode 100644 index 0000000000..0a8d6fa47a --- /dev/null +++ b/javascript/spec/fixtures/npm_responses/etag-1.0.0.json @@ -0,0 +1,40 @@ +{ + "_from": ".", + "_id": "etag@1.0.0", + "_npmUser": { + "email": "david.bjorklund@gmail.com", + "name": "kesla" + }, + "_npmVersion": "1.4.3", + "author": { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + }, + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "description": "Small thingy to create an etag from a String or Buffer", + "directories": {}, + "dist": { + "shasum": "db9f9610cc2bf22036d713012dff4aa7e0c96162", + "tarball": "https://registry.npmjs.org/etag/-/etag-1.0.0.tgz" + }, + "homepage": "https://github.com/jshttp/etag", + "license": "MIT", + "main": "etag.js", + "maintainers": [ + { + "email": "david.bjorklund@gmail.com", + "name": "kesla" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "git://github.com/jshttp/etag.git" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "version": "1.0.0" +} diff --git a/javascript/spec/fixtures/npm_responses/etag.json b/javascript/spec/fixtures/npm_responses/etag.json new file mode 100644 index 0000000000..021423155c --- /dev/null +++ b/javascript/spec/fixtures/npm_responses/etag.json @@ -0,0 +1,965 @@ +{ + "_attachments": {}, + "_id": "etag", + "_rev": "27-f99109879afdc9fa5a0757c26b02e51e", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Bjarklund" + } + ], + "description": "Create simple ETags", + "dist-tags": { + "latest": "1.7.0", + "stable": "1.5.1" + }, + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "readme": "# etag\n\n[![NPM Version][npm-image]][npm-url]\n[![NPM Downloads][downloads-image]][downloads-url]\n[![Node.js Version][node-version-image]][node-version-url]\n[![Build Status][travis-image]][travis-url]\n[![Test Coverage][coveralls-image]][coveralls-url]\n\nCreate simple ETags\n\n## Installation\n\n```sh\n$ npm install etag\n```\n\n## API\n\n```js\nvar etag = require('etag')\n```\n\n### etag(entity, [options])\n\nGenerate a strong ETag for the given entity. This should be the complete\nbody of the entity. Strings, `Buffer`s, and `fs.Stats` are accepted. By\ndefault, a strong ETag is generated except for `fs.Stats`, which will\ngenerate a weak ETag (this can be overwritten by `options.weak`).\n\n```js\nres.setHeader('ETag', etag(body))\n```\n\n#### Options\n\n`etag` accepts these properties in the options object.\n\n##### weak\n\nSpecifies if the generated ETag will include the weak validator mark (that\nis, the leading `W/`). The actual entity tag is the same. The default value\nis `false`, unless the `entity` is `fs.Stats`, in which case it is `true`.\n\n## Testing\n\n```sh\n$ npm test\n```\n\n## Benchmark\n\n```bash\n$ npm run-script bench\n\n> etag@1.6.0 bench nodejs-etag\n> node benchmark/index.js\n\n http_parser@1.0\n node@0.10.33\n v8@3.14.5.9\n ares@1.9.0-DEV\n uv@0.10.29\n zlib@1.2.3\n modules@11\n openssl@1.0.1j\n\n> node benchmark/body0-100b.js\n\n 100B body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 289,198 ops/sec ±1.09% (190 runs sampled)\n* buffer - weak x 287,838 ops/sec ±0.91% (189 runs sampled)\n* string - strong x 284,586 ops/sec ±1.05% (192 runs sampled)\n* string - weak x 287,439 ops/sec ±0.82% (192 runs sampled)\n\n> node benchmark/body1-1kb.js\n\n 1KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 212,423 ops/sec ±0.75% (193 runs sampled)\n* buffer - weak x 211,871 ops/sec ±0.74% (194 runs sampled)\n string - strong x 205,291 ops/sec ±0.86% (194 runs sampled)\n string - weak x 208,463 ops/sec ±0.79% (192 runs sampled)\n\n> node benchmark/body2-5kb.js\n\n 5KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 92,901 ops/sec ±0.58% (195 runs sampled)\n* buffer - weak x 93,045 ops/sec ±0.65% (192 runs sampled)\n string - strong x 89,621 ops/sec ±0.68% (194 runs sampled)\n string - weak x 90,070 ops/sec ±0.70% (196 runs sampled)\n\n> node benchmark/body3-10kb.js\n\n 10KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 54,220 ops/sec ±0.85% (192 runs sampled)\n* buffer - weak x 54,069 ops/sec ±0.83% (191 runs sampled)\n string - strong x 53,078 ops/sec ±0.53% (194 runs sampled)\n string - weak x 53,849 ops/sec ±0.47% (197 runs sampled)\n\n> node benchmark/body4-100kb.js\n\n 100KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 6,673 ops/sec ±0.15% (197 runs sampled)\n* buffer - weak x 6,716 ops/sec ±0.12% (198 runs sampled)\n string - strong x 6,357 ops/sec ±0.14% (197 runs sampled)\n string - weak x 6,344 ops/sec ±0.21% (197 runs sampled)\n\n> node benchmark/stats.js\n\n stats\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* real - strong x 1,671,989 ops/sec ±0.13% (197 runs sampled)\n* real - weak x 1,681,297 ops/sec ±0.12% (198 runs sampled)\n fake - strong x 927,063 ops/sec ±0.14% (198 runs sampled)\n fake - weak x 914,461 ops/sec ±0.41% (191 runs sampled)\n```\n\n## License\n\n[MIT](LICENSE)\n\n[npm-image]: https://img.shields.io/npm/v/etag.svg\n[npm-url]: https://npmjs.org/package/etag\n[node-version-image]: https://img.shields.io/node/v/etag.svg\n[node-version-url]: http://nodejs.org/download/\n[travis-image]: https://img.shields.io/travis/jshttp/etag/master.svg\n[travis-url]: https://travis-ci.org/jshttp/etag\n[coveralls-image]: https://img.shields.io/coveralls/jshttp/etag/master.svg\n[coveralls-url]: https://coveralls.io/r/jshttp/etag?branch=master\n[downloads-image]: https://img.shields.io/npm/dm/etag.svg\n[downloads-url]: https://npmjs.org/package/etag\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "time": { + "1.0.0": "2014-05-18T11:14:58.281Z", + "1.0.1": "2014-08-24T23:28:33.196Z", + "1.1.0": "2014-08-25T02:07:34.113Z", + "1.2.0": "2014-08-25T04:09:33.706Z", + "1.2.1": "2014-08-30T03:58:39.314Z", + "1.3.0": "2014-08-30T04:25:31.815Z", + "1.3.1": "2014-09-14T16:54:11.346Z", + "1.4.0": "2014-09-21T18:47:20.760Z", + "1.5.0": "2014-10-14T04:48:49.796Z", + "1.5.1": "2014-11-20T07:06:45.217Z", + "1.6.0": "2015-05-11T02:11:35.427Z", + "1.7.0": "2015-06-09T03:38:54.614Z", + "2.0.0": "2016-06-09T03:38:54.614Z", + "created": "2014-05-18T11:14:58.281Z", + "modified": "2015-06-09T03:38:54.614Z" + }, + "users": { + "blitzprog": true, + "program247365": true, + "simplyianm": true + }, + "versions": { + "1.0.0": { + "_from": ".", + "_id": "etag@1.0.0", + "_npmUser": { + "email": "david.bjorklund@gmail.com", + "name": "kesla" + }, + "_npmVersion": "1.4.3", + "author": { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + }, + "bugs": { + "url": "https://github.com/kesla/etag/issues" + }, + "description": "Small thingy to create an etag from a String or Buffer", + "directories": {}, + "dist": { + "shasum": "db9f9610cc2bf22036d713012dff4aa7e0c96162", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.0.0.tgz" + }, + "homepage": "https://github.com/kesla/etag", + "license": "MIT", + "main": "etag.js", + "maintainers": [ + { + "email": "david.bjorklund@gmail.com", + "name": "kesla" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "git://github.com/kesla/etag.git" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "version": "1.0.0" + }, + "1.0.1": { + "_from": ".", + "_id": "etag@1.0.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "2aa41de474ffc45669f25c9fedacd64fea4f6ff7", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "description": "Create simple ETags", + "devDependencies": { + "istanbul": "0.3.0", + "mocha": "~1.21.4" + }, + "directories": {}, + "dist": { + "shasum": "2aa41de474ffc45669f25c9fedacd64fea4f6ff7", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.0.1.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "d28ee6654ff51e83f3025a73677a30ec0fe04fc8", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "david.bjorklund@gmail.com", + "name": "kesla" + }, + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.0.1" + }, + "1.1.0": { + "_from": ".", + "_id": "etag@1.1.0", + "_npmVersion": "1.4.21", + "_shasum": "e44af7bbabe2f998d9fc3bee00db0d34b29c13a3", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "2.1.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "istanbul": "0.3.0", + "mocha": "~1.21.4" + }, + "directories": {}, + "dist": { + "shasum": "e44af7bbabe2f998d9fc3bee00db0d34b29c13a3", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.1.0.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "415cebe1d2a87510ffbd102816733e2c9934d0c7", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.1.0" + }, + "1.2.0": { + "_from": ".", + "_id": "etag@1.2.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "fa537a8e1b1e29aa53480b69cfceb906a47aca8a", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "2.1.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "istanbul": "0.3.0", + "mocha": "~1.21.4" + }, + "directories": {}, + "dist": { + "shasum": "fa537a8e1b1e29aa53480b69cfceb906a47aca8a", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.2.0.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "0d38a87d4217882b31e4a34786069281631a5cb2", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.2.0" + }, + "1.2.1": { + "_from": ".", + "_id": "etag@1.2.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "74b16ea7511ce0041b944852eca2a95b9afefba3", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "buffer-crc32": "0.2.3" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.0", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "74b16ea7511ce0041b944852eca2a95b9afefba3", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.2.1.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "4b9369db7434104ee7c449f288af03c6abe7bad3", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.2.1" + }, + "1.3.0": { + "_from": ".", + "_id": "etag@1.3.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "c837debfbfe0baf7eb8e2f0bbb3d1d9cc3229697", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "buffer-crc32": "0.2.3" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.0", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "c837debfbfe0baf7eb8e2f0bbb3d1d9cc3229697", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.3.0.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "b1531685ee679ca8fe329202e56d429d3c02eac0", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.3.0" + }, + "1.3.1": { + "_from": ".", + "_id": "etag@1.3.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "e51925728688a32dc4eea1cfa9ab4f734d055567", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.0.0" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "e51925728688a32dc4eea1cfa9ab4f734d055567", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.3.1.tgz" + }, + "engines": { + "node": ">= 0.8" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "88a83fdccc15065893cfad6e2c7af8a379941f16", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.3.1" + }, + "1.4.0": { + "_from": ".", + "_id": "etag@1.4.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "3050991615857707c04119d075ba2088e0701225", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.0.0" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "3050991615857707c04119d075ba2088e0701225", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.4.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "6b701773b06a102947cc063286e7e3f3bceae27b", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.4.0" + }, + "1.5.0": { + "_from": ".", + "_id": "etag@1.5.0", + "_npmUser": { + "email": "doug@somethingdoug.com" + }, + "_npmVersion": "1.4.21", + "_shasum": "8ca0f7a30b4b7305f034e8902fb8ec3c321491e4", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.0.0" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "8ca0f7a30b4b7305f034e8902fb8ec3c321491e4", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.5.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "d42734af50250c05893f02cb900e1c71efffbc45", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.5.0" + }, + "1.5.1": { + "_from": ".", + "_id": "etag@1.5.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "54c50de04ee42695562925ac566588291be7e9ea", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.2.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "54c50de04ee42695562925ac566588291be7e9ea", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.5.1.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "27335e2265388109e50a9f037452081dc8a8260f", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.5.1" + }, + "1.6.0": { + "_from": ".", + "_id": "etag@1.6.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.28", + "_shasum": "8bcb2c6af1254c481dfc8b997c906ef4e442c207", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.2.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.9", + "mocha": "~1.21.4", + "seedrandom": "2.3.11" + }, + "directories": {}, + "dist": { + "shasum": "8bcb2c6af1254c481dfc8b997c906ef4e442c207", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.6.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "README.md", + "index.js" + ], + "gitHead": "76a8f5250b02e85bc1c9b1b049d83b853a87df44", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.6.0" + }, + "1.7.0": { + "_from": ".", + "_id": "etag@1.7.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.28", + "_shasum": "03d30b5f67dd6e632d2945d30d6652731a34d5d8", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.14", + "mocha": "~1.21.4", + "seedrandom": "2.3.11" + }, + "directories": {}, + "dist": { + "shasum": "03d30b5f67dd6e632d2945d30d6652731a34d5d8", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.7.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "README.md", + "index.js" + ], + "gitHead": "a511f5c8c930fd9546dbd88acb080f96bc788cfc", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.7.0" + }, + "2.0.0": { + "_from": ".", + "_id": "etag@2.0.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.28", + "_shasum": "03d30b5f67dd6e632d2945d30d6652731a34d5d8", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "deprecated": "this is an obsolete implementation of a pre-0.16 installer.", + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.14", + "mocha": "~1.21.4", + "seedrandom": "2.3.11" + }, + "directories": {}, + "dist": { + "shasum": "03d30b5f67dd6e632d2945d30d6652731a34d5d8", + "tarball": "http://registry.npmjs.org/etag/-/etag-2.0.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "README.md", + "index.js" + ], + "gitHead": "a511f5c8c930fd9546dbd88acb080f96bc788cfc", + "homepage": "https://github.com/totally-deprecated/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/totally-deprecated/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "2.0.0" + } + } +} diff --git a/javascript/spec/fixtures/npm_responses/npm_response_bitbucket.json b/javascript/spec/fixtures/npm_responses/npm_response_bitbucket.json new file mode 100644 index 0000000000..919053be83 --- /dev/null +++ b/javascript/spec/fixtures/npm_responses/npm_response_bitbucket.json @@ -0,0 +1,896 @@ +{ + "_attachments": {}, + "_id": "etag", + "_rev": "27-f99109879afdc9fa5a0757c26b02e51e", + "bugs": { + "url": "https://bitbucket.org/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Bjarklund" + } + ], + "description": "Create simple ETags", + "dist-tags": { + "latest": "1.7.0" + }, + "homepage": "https://bitbucket.org/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "readme": "# etag\n\n[![NPM Version][npm-image]][npm-url]\n[![NPM Downloads][downloads-image]][downloads-url]\n[![Node.js Version][node-version-image]][node-version-url]\n[![Build Status][travis-image]][travis-url]\n[![Test Coverage][coveralls-image]][coveralls-url]\n\nCreate simple ETags\n\n## Installation\n\n```sh\n$ npm install etag\n```\n\n## API\n\n```js\nvar etag = require('etag')\n```\n\n### etag(entity, [options])\n\nGenerate a strong ETag for the given entity. This should be the complete\nbody of the entity. Strings, `Buffer`s, and `fs.Stats` are accepted. By\ndefault, a strong ETag is generated except for `fs.Stats`, which will\ngenerate a weak ETag (this can be overwritten by `options.weak`).\n\n```js\nres.setHeader('ETag', etag(body))\n```\n\n#### Options\n\n`etag` accepts these properties in the options object.\n\n##### weak\n\nSpecifies if the generated ETag will include the weak validator mark (that\nis, the leading `W/`). The actual entity tag is the same. The default value\nis `false`, unless the `entity` is `fs.Stats`, in which case it is `true`.\n\n## Testing\n\n```sh\n$ npm test\n```\n\n## Benchmark\n\n```bash\n$ npm run-script bench\n\n> etag@1.6.0 bench nodejs-etag\n> node benchmark/index.js\n\n http_parser@1.0\n node@0.10.33\n v8@3.14.5.9\n ares@1.9.0-DEV\n uv@0.10.29\n zlib@1.2.3\n modules@11\n openssl@1.0.1j\n\n> node benchmark/body0-100b.js\n\n 100B body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 289,198 ops/sec ±1.09% (190 runs sampled)\n* buffer - weak x 287,838 ops/sec ±0.91% (189 runs sampled)\n* string - strong x 284,586 ops/sec ±1.05% (192 runs sampled)\n* string - weak x 287,439 ops/sec ±0.82% (192 runs sampled)\n\n> node benchmark/body1-1kb.js\n\n 1KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 212,423 ops/sec ±0.75% (193 runs sampled)\n* buffer - weak x 211,871 ops/sec ±0.74% (194 runs sampled)\n string - strong x 205,291 ops/sec ±0.86% (194 runs sampled)\n string - weak x 208,463 ops/sec ±0.79% (192 runs sampled)\n\n> node benchmark/body2-5kb.js\n\n 5KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 92,901 ops/sec ±0.58% (195 runs sampled)\n* buffer - weak x 93,045 ops/sec ±0.65% (192 runs sampled)\n string - strong x 89,621 ops/sec ±0.68% (194 runs sampled)\n string - weak x 90,070 ops/sec ±0.70% (196 runs sampled)\n\n> node benchmark/body3-10kb.js\n\n 10KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 54,220 ops/sec ±0.85% (192 runs sampled)\n* buffer - weak x 54,069 ops/sec ±0.83% (191 runs sampled)\n string - strong x 53,078 ops/sec ±0.53% (194 runs sampled)\n string - weak x 53,849 ops/sec ±0.47% (197 runs sampled)\n\n> node benchmark/body4-100kb.js\n\n 100KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 6,673 ops/sec ±0.15% (197 runs sampled)\n* buffer - weak x 6,716 ops/sec ±0.12% (198 runs sampled)\n string - strong x 6,357 ops/sec ±0.14% (197 runs sampled)\n string - weak x 6,344 ops/sec ±0.21% (197 runs sampled)\n\n> node benchmark/stats.js\n\n stats\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* real - strong x 1,671,989 ops/sec ±0.13% (197 runs sampled)\n* real - weak x 1,681,297 ops/sec ±0.12% (198 runs sampled)\n fake - strong x 927,063 ops/sec ±0.14% (198 runs sampled)\n fake - weak x 914,461 ops/sec ±0.41% (191 runs sampled)\n```\n\n## License\n\n[MIT](LICENSE)\n\n[npm-image]: https://img.shields.io/npm/v/etag.svg\n[npm-url]: https://npmjs.org/package/etag\n[node-version-image]: https://img.shields.io/node/v/etag.svg\n[node-version-url]: http://nodejs.org/download/\n[travis-image]: https://img.shields.io/travis/jshttp/etag/master.svg\n[travis-url]: https://travis-ci.org/jshttp/etag\n[coveralls-image]: https://img.shields.io/coveralls/jshttp/etag/master.svg\n[coveralls-url]: https://coveralls.io/r/jshttp/etag?branch=master\n[downloads-image]: https://img.shields.io/npm/dm/etag.svg\n[downloads-url]: https://npmjs.org/package/etag\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "https://bitbucket.org/jshttp/etag" + }, + "time": { + "1.0.0": "2014-05-18T11:14:58.281Z", + "1.0.1": "2014-08-24T23:28:33.196Z", + "1.1.0": "2014-08-25T02:07:34.113Z", + "1.2.0": "2014-08-25T04:09:33.706Z", + "1.2.1": "2014-08-30T03:58:39.314Z", + "1.3.0": "2014-08-30T04:25:31.815Z", + "1.3.1": "2014-09-14T16:54:11.346Z", + "1.4.0": "2014-09-21T18:47:20.760Z", + "1.5.0": "2014-10-14T04:48:49.796Z", + "1.5.1": "2014-11-20T07:06:45.217Z", + "1.6.0": "2015-05-11T02:11:35.427Z", + "1.7.0": "2015-06-09T03:38:54.614Z", + "created": "2014-05-18T11:14:58.281Z", + "modified": "2015-06-09T03:38:54.614Z" + }, + "users": { + "blitzprog": true, + "program247365": true, + "simplyianm": true + }, + "versions": { + "1.0.0": { + "_from": ".", + "_id": "etag@1.0.0", + "_npmUser": { + "email": "david.bjorklund@gmail.com", + "name": "kesla" + }, + "_npmVersion": "1.4.3", + "author": { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + }, + "bugs": { + "url": "https://bitbucket.org/kesla/etag/issues" + }, + "description": "Small thingy to create an etag from a String or Buffer", + "directories": {}, + "dist": { + "shasum": "db9f9610cc2bf22036d713012dff4aa7e0c96162", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.0.0.tgz" + }, + "homepage": "https://bitbucket.org/kesla/etag", + "license": "MIT", + "main": "etag.js", + "maintainers": [ + { + "email": "david.bjorklund@gmail.com", + "name": "kesla" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "git://bitbucket.org/kesla/etag.git" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "version": "1.0.0" + }, + "1.0.1": { + "_from": ".", + "_id": "etag@1.0.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "2aa41de474ffc45669f25c9fedacd64fea4f6ff7", + "bugs": { + "url": "https://bitbucket.org/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "description": "Create simple ETags", + "devDependencies": { + "istanbul": "0.3.0", + "mocha": "~1.21.4" + }, + "directories": {}, + "dist": { + "shasum": "2aa41de474ffc45669f25c9fedacd64fea4f6ff7", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.0.1.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "d28ee6654ff51e83f3025a73677a30ec0fe04fc8", + "homepage": "https://bitbucket.org/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "david.bjorklund@gmail.com", + "name": "kesla" + }, + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://bitbucket.org/jshttp/etag" + }, + "scripts": { + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.0.1" + }, + "1.1.0": { + "_from": ".", + "_id": "etag@1.1.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "e44af7bbabe2f998d9fc3bee00db0d34b29c13a3", + "bugs": { + "url": "https://bitbucket.org/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "2.1.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "istanbul": "0.3.0", + "mocha": "~1.21.4" + }, + "directories": {}, + "dist": { + "shasum": "e44af7bbabe2f998d9fc3bee00db0d34b29c13a3", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.1.0.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "415cebe1d2a87510ffbd102816733e2c9934d0c7", + "homepage": "https://bitbucket.org/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://bitbucket.org/jshttp/etag" + }, + "scripts": { + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.1.0" + }, + "1.2.0": { + "_from": ".", + "_id": "etag@1.2.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "fa537a8e1b1e29aa53480b69cfceb906a47aca8a", + "bugs": { + "url": "https://bitbucket.org/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "2.1.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "istanbul": "0.3.0", + "mocha": "~1.21.4" + }, + "directories": {}, + "dist": { + "shasum": "fa537a8e1b1e29aa53480b69cfceb906a47aca8a", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.2.0.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "0d38a87d4217882b31e4a34786069281631a5cb2", + "homepage": "https://bitbucket.org/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://bitbucket.org/jshttp/etag" + }, + "scripts": { + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.2.0" + }, + "1.2.1": { + "_from": ".", + "_id": "etag@1.2.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "74b16ea7511ce0041b944852eca2a95b9afefba3", + "bugs": { + "url": "https://bitbucket.org/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "buffer-crc32": "0.2.3" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.0", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "74b16ea7511ce0041b944852eca2a95b9afefba3", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.2.1.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "4b9369db7434104ee7c449f288af03c6abe7bad3", + "homepage": "https://bitbucket.org/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://bitbucket.org/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.2.1" + }, + "1.3.0": { + "_from": ".", + "_id": "etag@1.3.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "c837debfbfe0baf7eb8e2f0bbb3d1d9cc3229697", + "bugs": { + "url": "https://bitbucket.org/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "buffer-crc32": "0.2.3" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.0", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "c837debfbfe0baf7eb8e2f0bbb3d1d9cc3229697", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.3.0.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "b1531685ee679ca8fe329202e56d429d3c02eac0", + "homepage": "https://bitbucket.org/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://bitbucket.org/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.3.0" + }, + "1.3.1": { + "_from": ".", + "_id": "etag@1.3.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "e51925728688a32dc4eea1cfa9ab4f734d055567", + "bugs": { + "url": "https://bitbucket.org/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.0.0" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "e51925728688a32dc4eea1cfa9ab4f734d055567", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.3.1.tgz" + }, + "engines": { + "node": ">= 0.8" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "88a83fdccc15065893cfad6e2c7af8a379941f16", + "homepage": "https://bitbucket.org/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://bitbucket.org/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.3.1" + }, + "1.4.0": { + "_from": ".", + "_id": "etag@1.4.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "3050991615857707c04119d075ba2088e0701225", + "bugs": { + "url": "https://bitbucket.org/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.0.0" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "3050991615857707c04119d075ba2088e0701225", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.4.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "6b701773b06a102947cc063286e7e3f3bceae27b", + "homepage": "https://bitbucket.org/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://bitbucket.org/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.4.0" + }, + "1.5.0": { + "_from": ".", + "_id": "etag@1.5.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "8ca0f7a30b4b7305f034e8902fb8ec3c321491e4", + "bugs": { + "url": "https://bitbucket.org/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.0.0" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "8ca0f7a30b4b7305f034e8902fb8ec3c321491e4", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.5.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "d42734af50250c05893f02cb900e1c71efffbc45", + "homepage": "https://bitbucket.org/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://bitbucket.org/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.5.0" + }, + "1.5.1": { + "_from": ".", + "_id": "etag@1.5.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "54c50de04ee42695562925ac566588291be7e9ea", + "bugs": { + "url": "https://bitbucket.org/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.2.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "54c50de04ee42695562925ac566588291be7e9ea", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.5.1.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "27335e2265388109e50a9f037452081dc8a8260f", + "homepage": "https://bitbucket.org/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://bitbucket.org/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.5.1" + }, + "1.6.0": { + "_from": ".", + "_id": "etag@1.6.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.28", + "_shasum": "8bcb2c6af1254c481dfc8b997c906ef4e442c207", + "bugs": { + "url": "https://bitbucket.org/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.2.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.9", + "mocha": "~1.21.4", + "seedrandom": "2.3.11" + }, + "directories": {}, + "dist": { + "shasum": "8bcb2c6af1254c481dfc8b997c906ef4e442c207", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.6.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "README.md", + "index.js" + ], + "gitHead": "76a8f5250b02e85bc1c9b1b049d83b853a87df44", + "homepage": "https://bitbucket.org/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://bitbucket.org/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.6.0" + }, + "1.7.0": { + "_from": ".", + "_id": "etag@1.7.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.28", + "_shasum": "03d30b5f67dd6e632d2945d30d6652731a34d5d8", + "bugs": { + "url": "https://bitbucket.org/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.14", + "mocha": "~1.21.4", + "seedrandom": "2.3.11" + }, + "directories": {}, + "dist": { + "shasum": "03d30b5f67dd6e632d2945d30d6652731a34d5d8", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.7.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "README.md", + "index.js" + ], + "gitHead": "a511f5c8c930fd9546dbd88acb080f96bc788cfc", + "homepage": "https://bitbucket.org/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://bitbucket.org/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.7.0" + } + } +} diff --git a/javascript/spec/fixtures/npm_responses/npm_response_no_source.json b/javascript/spec/fixtures/npm_responses/npm_response_no_source.json new file mode 100644 index 0000000000..8419ade143 --- /dev/null +++ b/javascript/spec/fixtures/npm_responses/npm_response_no_source.json @@ -0,0 +1,896 @@ +{ + "_attachments": {}, + "_id": "etag", + "_rev": "27-f99109879afdc9fa5a0757c26b02e51e", + "bugs": { + "url": "https://example.come/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Bjarklund" + } + ], + "description": "Create simple ETags", + "dist-tags": { + "latest": "1.7.0" + }, + "homepage": "https://example.come/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "readme": "# etag\n\n[![NPM Version][npm-image]][npm-url]\n[![NPM Downloads][downloads-image]][downloads-url]\n[![Node.js Version][node-version-image]][node-version-url]\n[![Build Status][travis-image]][travis-url]\n[![Test Coverage][coveralls-image]][coveralls-url]\n\nCreate simple ETags\n\n## Installation\n\n```sh\n$ npm install etag\n```\n\n## API\n\n```js\nvar etag = require('etag')\n```\n\n### etag(entity, [options])\n\nGenerate a strong ETag for the given entity. This should be the complete\nbody of the entity. Strings, `Buffer`s, and `fs.Stats` are accepted. By\ndefault, a strong ETag is generated except for `fs.Stats`, which will\ngenerate a weak ETag (this can be overwritten by `options.weak`).\n\n```js\nres.setHeader('ETag', etag(body))\n```\n\n#### Options\n\n`etag` accepts these properties in the options object.\n\n##### weak\n\nSpecifies if the generated ETag will include the weak validator mark (that\nis, the leading `W/`). The actual entity tag is the same. The default value\nis `false`, unless the `entity` is `fs.Stats`, in which case it is `true`.\n\n## Testing\n\n```sh\n$ npm test\n```\n\n## Benchmark\n\n```bash\n$ npm run-script bench\n\n> etag@1.6.0 bench nodejs-etag\n> node benchmark/index.js\n\n http_parser@1.0\n node@0.10.33\n v8@3.14.5.9\n ares@1.9.0-DEV\n uv@0.10.29\n zlib@1.2.3\n modules@11\n openssl@1.0.1j\n\n> node benchmark/body0-100b.js\n\n 100B body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 289,198 ops/sec ±1.09% (190 runs sampled)\n* buffer - weak x 287,838 ops/sec ±0.91% (189 runs sampled)\n* string - strong x 284,586 ops/sec ±1.05% (192 runs sampled)\n* string - weak x 287,439 ops/sec ±0.82% (192 runs sampled)\n\n> node benchmark/body1-1kb.js\n\n 1KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 212,423 ops/sec ±0.75% (193 runs sampled)\n* buffer - weak x 211,871 ops/sec ±0.74% (194 runs sampled)\n string - strong x 205,291 ops/sec ±0.86% (194 runs sampled)\n string - weak x 208,463 ops/sec ±0.79% (192 runs sampled)\n\n> node benchmark/body2-5kb.js\n\n 5KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 92,901 ops/sec ±0.58% (195 runs sampled)\n* buffer - weak x 93,045 ops/sec ±0.65% (192 runs sampled)\n string - strong x 89,621 ops/sec ±0.68% (194 runs sampled)\n string - weak x 90,070 ops/sec ±0.70% (196 runs sampled)\n\n> node benchmark/body3-10kb.js\n\n 10KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 54,220 ops/sec ±0.85% (192 runs sampled)\n* buffer - weak x 54,069 ops/sec ±0.83% (191 runs sampled)\n string - strong x 53,078 ops/sec ±0.53% (194 runs sampled)\n string - weak x 53,849 ops/sec ±0.47% (197 runs sampled)\n\n> node benchmark/body4-100kb.js\n\n 100KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 6,673 ops/sec ±0.15% (197 runs sampled)\n* buffer - weak x 6,716 ops/sec ±0.12% (198 runs sampled)\n string - strong x 6,357 ops/sec ±0.14% (197 runs sampled)\n string - weak x 6,344 ops/sec ±0.21% (197 runs sampled)\n\n> node benchmark/stats.js\n\n stats\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* real - strong x 1,671,989 ops/sec ±0.13% (197 runs sampled)\n* real - weak x 1,681,297 ops/sec ±0.12% (198 runs sampled)\n fake - strong x 927,063 ops/sec ±0.14% (198 runs sampled)\n fake - weak x 914,461 ops/sec ±0.41% (191 runs sampled)\n```\n\n## License\n\n[MIT](LICENSE)\n\n[npm-image]: https://img.shields.io/npm/v/etag.svg\n[npm-url]: https://npmjs.org/package/etag\n[node-version-image]: https://img.shields.io/node/v/etag.svg\n[node-version-url]: http://nodejs.org/download/\n[travis-image]: https://img.shields.io/travis/jshttp/etag/master.svg\n[travis-url]: https://travis-ci.org/jshttp/etag\n[coveralls-image]: https://img.shields.io/coveralls/jshttp/etag/master.svg\n[coveralls-url]: https://coveralls.io/r/jshttp/etag?branch=master\n[downloads-image]: https://img.shields.io/npm/dm/etag.svg\n[downloads-url]: https://npmjs.org/package/etag\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "https://example.come/jshttp/etag" + }, + "time": { + "1.0.0": "2014-05-18T11:14:58.281Z", + "1.0.1": "2014-08-24T23:28:33.196Z", + "1.1.0": "2014-08-25T02:07:34.113Z", + "1.2.0": "2014-08-25T04:09:33.706Z", + "1.2.1": "2014-08-30T03:58:39.314Z", + "1.3.0": "2014-08-30T04:25:31.815Z", + "1.3.1": "2014-09-14T16:54:11.346Z", + "1.4.0": "2014-09-21T18:47:20.760Z", + "1.5.0": "2014-10-14T04:48:49.796Z", + "1.5.1": "2014-11-20T07:06:45.217Z", + "1.6.0": "2015-05-11T02:11:35.427Z", + "1.7.0": "2015-06-09T03:38:54.614Z", + "created": "2014-05-18T11:14:58.281Z", + "modified": "2015-06-09T03:38:54.614Z" + }, + "users": { + "blitzprog": true, + "program247365": true, + "simplyianm": true + }, + "versions": { + "1.0.0": { + "_from": ".", + "_id": "etag@1.0.0", + "_npmUser": { + "email": "david.bjorklund@gmail.com", + "name": "kesla" + }, + "_npmVersion": "1.4.3", + "author": { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + }, + "bugs": { + "url": "https://example.come/kesla/etag/issues" + }, + "description": "Small thingy to create an etag from a String or Buffer", + "directories": {}, + "dist": { + "shasum": "db9f9610cc2bf22036d713012dff4aa7e0c96162", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.0.0.tgz" + }, + "homepage": "https://example.come/kesla/etag", + "license": "MIT", + "main": "etag.js", + "maintainers": [ + { + "email": "david.bjorklund@gmail.com", + "name": "kesla" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "git://example.come/kesla/etag.git" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "version": "1.0.0" + }, + "1.0.1": { + "_from": ".", + "_id": "etag@1.0.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "2aa41de474ffc45669f25c9fedacd64fea4f6ff7", + "bugs": { + "url": "https://example.come/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "description": "Create simple ETags", + "devDependencies": { + "istanbul": "0.3.0", + "mocha": "~1.21.4" + }, + "directories": {}, + "dist": { + "shasum": "2aa41de474ffc45669f25c9fedacd64fea4f6ff7", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.0.1.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "d28ee6654ff51e83f3025a73677a30ec0fe04fc8", + "homepage": "https://example.come/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "david.bjorklund@gmail.com", + "name": "kesla" + }, + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://example.come/jshttp/etag" + }, + "scripts": { + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.0.1" + }, + "1.1.0": { + "_from": ".", + "_id": "etag@1.1.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "e44af7bbabe2f998d9fc3bee00db0d34b29c13a3", + "bugs": { + "url": "https://example.come/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "2.1.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "istanbul": "0.3.0", + "mocha": "~1.21.4" + }, + "directories": {}, + "dist": { + "shasum": "e44af7bbabe2f998d9fc3bee00db0d34b29c13a3", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.1.0.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "415cebe1d2a87510ffbd102816733e2c9934d0c7", + "homepage": "https://example.come/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://example.come/jshttp/etag" + }, + "scripts": { + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.1.0" + }, + "1.2.0": { + "_from": ".", + "_id": "etag@1.2.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "fa537a8e1b1e29aa53480b69cfceb906a47aca8a", + "bugs": { + "url": "https://example.come/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "2.1.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "istanbul": "0.3.0", + "mocha": "~1.21.4" + }, + "directories": {}, + "dist": { + "shasum": "fa537a8e1b1e29aa53480b69cfceb906a47aca8a", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.2.0.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "0d38a87d4217882b31e4a34786069281631a5cb2", + "homepage": "https://example.come/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://example.come/jshttp/etag" + }, + "scripts": { + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.2.0" + }, + "1.2.1": { + "_from": ".", + "_id": "etag@1.2.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "74b16ea7511ce0041b944852eca2a95b9afefba3", + "bugs": { + "url": "https://example.come/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "buffer-crc32": "0.2.3" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.0", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "74b16ea7511ce0041b944852eca2a95b9afefba3", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.2.1.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "4b9369db7434104ee7c449f288af03c6abe7bad3", + "homepage": "https://example.come/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://example.come/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.2.1" + }, + "1.3.0": { + "_from": ".", + "_id": "etag@1.3.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "c837debfbfe0baf7eb8e2f0bbb3d1d9cc3229697", + "bugs": { + "url": "https://example.come/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "buffer-crc32": "0.2.3" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.0", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "c837debfbfe0baf7eb8e2f0bbb3d1d9cc3229697", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.3.0.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "b1531685ee679ca8fe329202e56d429d3c02eac0", + "homepage": "https://example.come/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://example.come/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.3.0" + }, + "1.3.1": { + "_from": ".", + "_id": "etag@1.3.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "e51925728688a32dc4eea1cfa9ab4f734d055567", + "bugs": { + "url": "https://example.come/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.0.0" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "e51925728688a32dc4eea1cfa9ab4f734d055567", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.3.1.tgz" + }, + "engines": { + "node": ">= 0.8" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "88a83fdccc15065893cfad6e2c7af8a379941f16", + "homepage": "https://example.come/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://example.come/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.3.1" + }, + "1.4.0": { + "_from": ".", + "_id": "etag@1.4.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "3050991615857707c04119d075ba2088e0701225", + "bugs": { + "url": "https://example.come/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.0.0" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "3050991615857707c04119d075ba2088e0701225", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.4.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "6b701773b06a102947cc063286e7e3f3bceae27b", + "homepage": "https://example.come/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://example.come/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.4.0" + }, + "1.5.0": { + "_from": ".", + "_id": "etag@1.5.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "8ca0f7a30b4b7305f034e8902fb8ec3c321491e4", + "bugs": { + "url": "https://example.come/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.0.0" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "8ca0f7a30b4b7305f034e8902fb8ec3c321491e4", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.5.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "d42734af50250c05893f02cb900e1c71efffbc45", + "homepage": "https://example.come/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://example.come/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.5.0" + }, + "1.5.1": { + "_from": ".", + "_id": "etag@1.5.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "54c50de04ee42695562925ac566588291be7e9ea", + "bugs": { + "url": "https://example.come/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.2.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "54c50de04ee42695562925ac566588291be7e9ea", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.5.1.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "27335e2265388109e50a9f037452081dc8a8260f", + "homepage": "https://example.come/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://example.come/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.5.1" + }, + "1.6.0": { + "_from": ".", + "_id": "etag@1.6.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.28", + "_shasum": "8bcb2c6af1254c481dfc8b997c906ef4e442c207", + "bugs": { + "url": "https://example.come/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.2.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.9", + "mocha": "~1.21.4", + "seedrandom": "2.3.11" + }, + "directories": {}, + "dist": { + "shasum": "8bcb2c6af1254c481dfc8b997c906ef4e442c207", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.6.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "README.md", + "index.js" + ], + "gitHead": "76a8f5250b02e85bc1c9b1b049d83b853a87df44", + "homepage": "https://example.come/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://example.come/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.6.0" + }, + "1.7.0": { + "_from": ".", + "_id": "etag@1.7.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.28", + "_shasum": "03d30b5f67dd6e632d2945d30d6652731a34d5d8", + "bugs": { + "url": "https://example.come/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.14", + "mocha": "~1.21.4", + "seedrandom": "2.3.11" + }, + "directories": {}, + "dist": { + "shasum": "03d30b5f67dd6e632d2945d30d6652731a34d5d8", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.7.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "README.md", + "index.js" + ], + "gitHead": "a511f5c8c930fd9546dbd88acb080f96bc788cfc", + "homepage": "https://example.come/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://example.come/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.7.0" + } + } +} diff --git a/javascript/spec/fixtures/npm_responses/npm_response_string_link.json b/javascript/spec/fixtures/npm_responses/npm_response_string_link.json new file mode 100644 index 0000000000..add45b7523 --- /dev/null +++ b/javascript/spec/fixtures/npm_responses/npm_response_string_link.json @@ -0,0 +1,893 @@ +{ + "_attachments": {}, + "_id": "etag", + "_rev": "27-f99109879afdc9fa5a0757c26b02e51e", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Bjarklund" + } + ], + "description": "Create simple ETags", + "dist-tags": { + "latest": "1.7.0" + }, + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "readme": "# etag\n\n[![NPM Version][npm-image]][npm-url]\n[![NPM Downloads][downloads-image]][downloads-url]\n[![Node.js Version][node-version-image]][node-version-url]\n[![Build Status][travis-image]][travis-url]\n[![Test Coverage][coveralls-image]][coveralls-url]\n\nCreate simple ETags\n\n## Installation\n\n```sh\n$ npm install etag\n```\n\n## API\n\n```js\nvar etag = require('etag')\n```\n\n### etag(entity, [options])\n\nGenerate a strong ETag for the given entity. This should be the complete\nbody of the entity. Strings, `Buffer`s, and `fs.Stats` are accepted. By\ndefault, a strong ETag is generated except for `fs.Stats`, which will\ngenerate a weak ETag (this can be overwritten by `options.weak`).\n\n```js\nres.setHeader('ETag', etag(body))\n```\n\n#### Options\n\n`etag` accepts these properties in the options object.\n\n##### weak\n\nSpecifies if the generated ETag will include the weak validator mark (that\nis, the leading `W/`). The actual entity tag is the same. The default value\nis `false`, unless the `entity` is `fs.Stats`, in which case it is `true`.\n\n## Testing\n\n```sh\n$ npm test\n```\n\n## Benchmark\n\n```bash\n$ npm run-script bench\n\n> etag@1.6.0 bench nodejs-etag\n> node benchmark/index.js\n\n http_parser@1.0\n node@0.10.33\n v8@3.14.5.9\n ares@1.9.0-DEV\n uv@0.10.29\n zlib@1.2.3\n modules@11\n openssl@1.0.1j\n\n> node benchmark/body0-100b.js\n\n 100B body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 289,198 ops/sec ±1.09% (190 runs sampled)\n* buffer - weak x 287,838 ops/sec ±0.91% (189 runs sampled)\n* string - strong x 284,586 ops/sec ±1.05% (192 runs sampled)\n* string - weak x 287,439 ops/sec ±0.82% (192 runs sampled)\n\n> node benchmark/body1-1kb.js\n\n 1KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 212,423 ops/sec ±0.75% (193 runs sampled)\n* buffer - weak x 211,871 ops/sec ±0.74% (194 runs sampled)\n string - strong x 205,291 ops/sec ±0.86% (194 runs sampled)\n string - weak x 208,463 ops/sec ±0.79% (192 runs sampled)\n\n> node benchmark/body2-5kb.js\n\n 5KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 92,901 ops/sec ±0.58% (195 runs sampled)\n* buffer - weak x 93,045 ops/sec ±0.65% (192 runs sampled)\n string - strong x 89,621 ops/sec ±0.68% (194 runs sampled)\n string - weak x 90,070 ops/sec ±0.70% (196 runs sampled)\n\n> node benchmark/body3-10kb.js\n\n 10KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 54,220 ops/sec ±0.85% (192 runs sampled)\n* buffer - weak x 54,069 ops/sec ±0.83% (191 runs sampled)\n string - strong x 53,078 ops/sec ±0.53% (194 runs sampled)\n string - weak x 53,849 ops/sec ±0.47% (197 runs sampled)\n\n> node benchmark/body4-100kb.js\n\n 100KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 6,673 ops/sec ±0.15% (197 runs sampled)\n* buffer - weak x 6,716 ops/sec ±0.12% (198 runs sampled)\n string - strong x 6,357 ops/sec ±0.14% (197 runs sampled)\n string - weak x 6,344 ops/sec ±0.21% (197 runs sampled)\n\n> node benchmark/stats.js\n\n stats\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* real - strong x 1,671,989 ops/sec ±0.13% (197 runs sampled)\n* real - weak x 1,681,297 ops/sec ±0.12% (198 runs sampled)\n fake - strong x 927,063 ops/sec ±0.14% (198 runs sampled)\n fake - weak x 914,461 ops/sec ±0.41% (191 runs sampled)\n```\n\n## License\n\n[MIT](LICENSE)\n\n[npm-image]: https://img.shields.io/npm/v/etag.svg\n[npm-url]: https://npmjs.org/package/etag\n[node-version-image]: https://img.shields.io/node/v/etag.svg\n[node-version-url]: http://nodejs.org/download/\n[travis-image]: https://img.shields.io/travis/jshttp/etag/master.svg\n[travis-url]: https://travis-ci.org/jshttp/etag\n[coveralls-image]: https://img.shields.io/coveralls/jshttp/etag/master.svg\n[coveralls-url]: https://coveralls.io/r/jshttp/etag?branch=master\n[downloads-image]: https://img.shields.io/npm/dm/etag.svg\n[downloads-url]: https://npmjs.org/package/etag\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "time": { + "1.0.0": "2014-05-18T11:14:58.281Z", + "1.0.1": "2014-08-24T23:28:33.196Z", + "1.1.0": "2014-08-25T02:07:34.113Z", + "1.2.0": "2014-08-25T04:09:33.706Z", + "1.2.1": "2014-08-30T03:58:39.314Z", + "1.3.0": "2014-08-30T04:25:31.815Z", + "1.3.1": "2014-09-14T16:54:11.346Z", + "1.4.0": "2014-09-21T18:47:20.760Z", + "1.5.0": "2014-10-14T04:48:49.796Z", + "1.5.1": "2014-11-20T07:06:45.217Z", + "1.6.0": "2015-05-11T02:11:35.427Z", + "1.7.0": "2015-06-09T03:38:54.614Z", + "created": "2014-05-18T11:14:58.281Z", + "modified": "2015-06-09T03:38:54.614Z" + }, + "users": { + "blitzprog": true, + "program247365": true, + "simplyianm": true + }, + "versions": { + "1.0.0": { + "_from": ".", + "_id": "etag@1.0.0", + "_npmUser": { + "email": "david.bjorklund@gmail.com", + "name": "kesla" + }, + "_npmVersion": "1.4.3", + "author": { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + }, + "bugs": { + "url": "https://github.com/kesla/etag/issues" + }, + "description": "Small thingy to create an etag from a String or Buffer", + "directories": {}, + "dist": { + "shasum": "db9f9610cc2bf22036d713012dff4aa7e0c96162", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.0.0.tgz" + }, + "homepage": "https://github.com/kesla/etag", + "license": "MIT", + "main": "etag.js", + "maintainers": [ + { + "email": "david.bjorklund@gmail.com", + "name": "kesla" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "git://github.com/kesla/etag.git" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "version": "1.0.0" + }, + "1.0.1": { + "_from": ".", + "_id": "etag@1.0.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "2aa41de474ffc45669f25c9fedacd64fea4f6ff7", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "description": "Create simple ETags", + "devDependencies": { + "istanbul": "0.3.0", + "mocha": "~1.21.4" + }, + "directories": {}, + "dist": { + "shasum": "2aa41de474ffc45669f25c9fedacd64fea4f6ff7", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.0.1.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "d28ee6654ff51e83f3025a73677a30ec0fe04fc8", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "david.bjorklund@gmail.com", + "name": "kesla" + }, + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.0.1" + }, + "1.1.0": { + "_from": ".", + "_id": "etag@1.1.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "e44af7bbabe2f998d9fc3bee00db0d34b29c13a3", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "2.1.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "istanbul": "0.3.0", + "mocha": "~1.21.4" + }, + "directories": {}, + "dist": { + "shasum": "e44af7bbabe2f998d9fc3bee00db0d34b29c13a3", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.1.0.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "415cebe1d2a87510ffbd102816733e2c9934d0c7", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.1.0" + }, + "1.2.0": { + "_from": ".", + "_id": "etag@1.2.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "fa537a8e1b1e29aa53480b69cfceb906a47aca8a", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "2.1.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "istanbul": "0.3.0", + "mocha": "~1.21.4" + }, + "directories": {}, + "dist": { + "shasum": "fa537a8e1b1e29aa53480b69cfceb906a47aca8a", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.2.0.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "0d38a87d4217882b31e4a34786069281631a5cb2", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.2.0" + }, + "1.2.1": { + "_from": ".", + "_id": "etag@1.2.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "74b16ea7511ce0041b944852eca2a95b9afefba3", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "buffer-crc32": "0.2.3" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.0", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "74b16ea7511ce0041b944852eca2a95b9afefba3", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.2.1.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "4b9369db7434104ee7c449f288af03c6abe7bad3", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.2.1" + }, + "1.3.0": { + "_from": ".", + "_id": "etag@1.3.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "c837debfbfe0baf7eb8e2f0bbb3d1d9cc3229697", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "buffer-crc32": "0.2.3" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.0", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "c837debfbfe0baf7eb8e2f0bbb3d1d9cc3229697", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.3.0.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "b1531685ee679ca8fe329202e56d429d3c02eac0", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.3.0" + }, + "1.3.1": { + "_from": ".", + "_id": "etag@1.3.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "e51925728688a32dc4eea1cfa9ab4f734d055567", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.0.0" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "e51925728688a32dc4eea1cfa9ab4f734d055567", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.3.1.tgz" + }, + "engines": { + "node": ">= 0.8" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "88a83fdccc15065893cfad6e2c7af8a379941f16", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.3.1" + }, + "1.4.0": { + "_from": ".", + "_id": "etag@1.4.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "3050991615857707c04119d075ba2088e0701225", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.0.0" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "3050991615857707c04119d075ba2088e0701225", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.4.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "6b701773b06a102947cc063286e7e3f3bceae27b", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.4.0" + }, + "1.5.0": { + "_from": ".", + "_id": "etag@1.5.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "8ca0f7a30b4b7305f034e8902fb8ec3c321491e4", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.0.0" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "8ca0f7a30b4b7305f034e8902fb8ec3c321491e4", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.5.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "d42734af50250c05893f02cb900e1c71efffbc45", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.5.0" + }, + "1.5.1": { + "_from": ".", + "_id": "etag@1.5.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "54c50de04ee42695562925ac566588291be7e9ea", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.2.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "54c50de04ee42695562925ac566588291be7e9ea", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.5.1.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "27335e2265388109e50a9f037452081dc8a8260f", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.5.1" + }, + "1.6.0": { + "_from": ".", + "_id": "etag@1.6.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.28", + "_shasum": "8bcb2c6af1254c481dfc8b997c906ef4e442c207", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.2.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.9", + "mocha": "~1.21.4", + "seedrandom": "2.3.11" + }, + "directories": {}, + "dist": { + "shasum": "8bcb2c6af1254c481dfc8b997c906ef4e442c207", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.6.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "README.md", + "index.js" + ], + "gitHead": "76a8f5250b02e85bc1c9b1b049d83b853a87df44", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "https://github.com/jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.6.0" + }, + "1.7.0": { + "_from": ".", + "_id": "etag@1.7.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.28", + "_shasum": "03d30b5f67dd6e632d2945d30d6652731a34d5d8", + "bugs": { + "url": "https://github.com/jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.14", + "mocha": "~1.21.4", + "seedrandom": "2.3.11" + }, + "directories": {}, + "dist": { + "shasum": "03d30b5f67dd6e632d2945d30d6652731a34d5d8", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.7.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "README.md", + "index.js" + ], + "gitHead": "a511f5c8c930fd9546dbd88acb080f96bc788cfc", + "homepage": "https://github.com/jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": "https://github.com/jshttp/etag", + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.7.0" + } + } +} diff --git a/javascript/spec/fixtures/npm_responses/npm_response_string_shorthand.json b/javascript/spec/fixtures/npm_responses/npm_response_string_shorthand.json new file mode 100644 index 0000000000..8c109fc55e --- /dev/null +++ b/javascript/spec/fixtures/npm_responses/npm_response_string_shorthand.json @@ -0,0 +1,893 @@ +{ + "_attachments": {}, + "_id": "etag", + "_rev": "27-f99109879afdc9fa5a0757c26b02e51e", + "bugs": { + "url": "jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Bjarklund" + } + ], + "description": "Create simple ETags", + "dist-tags": { + "latest": "1.7.0" + }, + "homepage": "jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "readme": "# etag\n\n[![NPM Version][npm-image]][npm-url]\n[![NPM Downloads][downloads-image]][downloads-url]\n[![Node.js Version][node-version-image]][node-version-url]\n[![Build Status][travis-image]][travis-url]\n[![Test Coverage][coveralls-image]][coveralls-url]\n\nCreate simple ETags\n\n## Installation\n\n```sh\n$ npm install etag\n```\n\n## API\n\n```js\nvar etag = require('etag')\n```\n\n### etag(entity, [options])\n\nGenerate a strong ETag for the given entity. This should be the complete\nbody of the entity. Strings, `Buffer`s, and `fs.Stats` are accepted. By\ndefault, a strong ETag is generated except for `fs.Stats`, which will\ngenerate a weak ETag (this can be overwritten by `options.weak`).\n\n```js\nres.setHeader('ETag', etag(body))\n```\n\n#### Options\n\n`etag` accepts these properties in the options object.\n\n##### weak\n\nSpecifies if the generated ETag will include the weak validator mark (that\nis, the leading `W/`). The actual entity tag is the same. The default value\nis `false`, unless the `entity` is `fs.Stats`, in which case it is `true`.\n\n## Testing\n\n```sh\n$ npm test\n```\n\n## Benchmark\n\n```bash\n$ npm run-script bench\n\n> etag@1.6.0 bench nodejs-etag\n> node benchmark/index.js\n\n http_parser@1.0\n node@0.10.33\n v8@3.14.5.9\n ares@1.9.0-DEV\n uv@0.10.29\n zlib@1.2.3\n modules@11\n openssl@1.0.1j\n\n> node benchmark/body0-100b.js\n\n 100B body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 289,198 ops/sec ±1.09% (190 runs sampled)\n* buffer - weak x 287,838 ops/sec ±0.91% (189 runs sampled)\n* string - strong x 284,586 ops/sec ±1.05% (192 runs sampled)\n* string - weak x 287,439 ops/sec ±0.82% (192 runs sampled)\n\n> node benchmark/body1-1kb.js\n\n 1KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 212,423 ops/sec ±0.75% (193 runs sampled)\n* buffer - weak x 211,871 ops/sec ±0.74% (194 runs sampled)\n string - strong x 205,291 ops/sec ±0.86% (194 runs sampled)\n string - weak x 208,463 ops/sec ±0.79% (192 runs sampled)\n\n> node benchmark/body2-5kb.js\n\n 5KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 92,901 ops/sec ±0.58% (195 runs sampled)\n* buffer - weak x 93,045 ops/sec ±0.65% (192 runs sampled)\n string - strong x 89,621 ops/sec ±0.68% (194 runs sampled)\n string - weak x 90,070 ops/sec ±0.70% (196 runs sampled)\n\n> node benchmark/body3-10kb.js\n\n 10KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 54,220 ops/sec ±0.85% (192 runs sampled)\n* buffer - weak x 54,069 ops/sec ±0.83% (191 runs sampled)\n string - strong x 53,078 ops/sec ±0.53% (194 runs sampled)\n string - weak x 53,849 ops/sec ±0.47% (197 runs sampled)\n\n> node benchmark/body4-100kb.js\n\n 100KB body\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* buffer - strong x 6,673 ops/sec ±0.15% (197 runs sampled)\n* buffer - weak x 6,716 ops/sec ±0.12% (198 runs sampled)\n string - strong x 6,357 ops/sec ±0.14% (197 runs sampled)\n string - weak x 6,344 ops/sec ±0.21% (197 runs sampled)\n\n> node benchmark/stats.js\n\n stats\n\n 1 test completed.\n 2 tests completed.\n 3 tests completed.\n 4 tests completed.\n\n* real - strong x 1,671,989 ops/sec ±0.13% (197 runs sampled)\n* real - weak x 1,681,297 ops/sec ±0.12% (198 runs sampled)\n fake - strong x 927,063 ops/sec ±0.14% (198 runs sampled)\n fake - weak x 914,461 ops/sec ±0.41% (191 runs sampled)\n```\n\n## License\n\n[MIT](LICENSE)\n\n[npm-image]: https://img.shields.io/npm/v/etag.svg\n[npm-url]: https://npmjs.org/package/etag\n[node-version-image]: https://img.shields.io/node/v/etag.svg\n[node-version-url]: http://nodejs.org/download/\n[travis-image]: https://img.shields.io/travis/jshttp/etag/master.svg\n[travis-url]: https://travis-ci.org/jshttp/etag\n[coveralls-image]: https://img.shields.io/coveralls/jshttp/etag/master.svg\n[coveralls-url]: https://coveralls.io/r/jshttp/etag?branch=master\n[downloads-image]: https://img.shields.io/npm/dm/etag.svg\n[downloads-url]: https://npmjs.org/package/etag\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "jshttp/etag" + }, + "time": { + "1.0.0": "2014-05-18T11:14:58.281Z", + "1.0.1": "2014-08-24T23:28:33.196Z", + "1.1.0": "2014-08-25T02:07:34.113Z", + "1.2.0": "2014-08-25T04:09:33.706Z", + "1.2.1": "2014-08-30T03:58:39.314Z", + "1.3.0": "2014-08-30T04:25:31.815Z", + "1.3.1": "2014-09-14T16:54:11.346Z", + "1.4.0": "2014-09-21T18:47:20.760Z", + "1.5.0": "2014-10-14T04:48:49.796Z", + "1.5.1": "2014-11-20T07:06:45.217Z", + "1.6.0": "2015-05-11T02:11:35.427Z", + "1.7.0": "2015-06-09T03:38:54.614Z", + "created": "2014-05-18T11:14:58.281Z", + "modified": "2015-06-09T03:38:54.614Z" + }, + "users": { + "blitzprog": true, + "program247365": true, + "simplyianm": true + }, + "versions": { + "1.0.0": { + "_from": ".", + "_id": "etag@1.0.0", + "_npmUser": { + "email": "david.bjorklund@gmail.com", + "name": "kesla" + }, + "_npmVersion": "1.4.3", + "author": { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + }, + "bugs": { + "url": "https://github.com/kesla/etag/issues" + }, + "description": "Small thingy to create an etag from a String or Buffer", + "directories": {}, + "dist": { + "shasum": "db9f9610cc2bf22036d713012dff4aa7e0c96162", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.0.0.tgz" + }, + "homepage": "https://github.com/kesla/etag", + "license": "MIT", + "main": "etag.js", + "maintainers": [ + { + "email": "david.bjorklund@gmail.com", + "name": "kesla" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "git://github.com/kesla/etag.git" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "version": "1.0.0" + }, + "1.0.1": { + "_from": ".", + "_id": "etag@1.0.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "2aa41de474ffc45669f25c9fedacd64fea4f6ff7", + "bugs": { + "url": "jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "description": "Create simple ETags", + "devDependencies": { + "istanbul": "0.3.0", + "mocha": "~1.21.4" + }, + "directories": {}, + "dist": { + "shasum": "2aa41de474ffc45669f25c9fedacd64fea4f6ff7", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.0.1.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "d28ee6654ff51e83f3025a73677a30ec0fe04fc8", + "homepage": "jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "david.bjorklund@gmail.com", + "name": "kesla" + }, + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "jshttp/etag" + }, + "scripts": { + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.0.1" + }, + "1.1.0": { + "_from": ".", + "_id": "etag@1.1.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "e44af7bbabe2f998d9fc3bee00db0d34b29c13a3", + "bugs": { + "url": "jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "2.1.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "istanbul": "0.3.0", + "mocha": "~1.21.4" + }, + "directories": {}, + "dist": { + "shasum": "e44af7bbabe2f998d9fc3bee00db0d34b29c13a3", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.1.0.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "415cebe1d2a87510ffbd102816733e2c9934d0c7", + "homepage": "jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "jshttp/etag" + }, + "scripts": { + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.1.0" + }, + "1.2.0": { + "_from": ".", + "_id": "etag@1.2.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "fa537a8e1b1e29aa53480b69cfceb906a47aca8a", + "bugs": { + "url": "jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "2.1.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "istanbul": "0.3.0", + "mocha": "~1.21.4" + }, + "directories": {}, + "dist": { + "shasum": "fa537a8e1b1e29aa53480b69cfceb906a47aca8a", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.2.0.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "0d38a87d4217882b31e4a34786069281631a5cb2", + "homepage": "jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "jshttp/etag" + }, + "scripts": { + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.2.0" + }, + "1.2.1": { + "_from": ".", + "_id": "etag@1.2.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "74b16ea7511ce0041b944852eca2a95b9afefba3", + "bugs": { + "url": "jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "buffer-crc32": "0.2.3" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.0", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "74b16ea7511ce0041b944852eca2a95b9afefba3", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.2.1.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "4b9369db7434104ee7c449f288af03c6abe7bad3", + "homepage": "jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.2.1" + }, + "1.3.0": { + "_from": ".", + "_id": "etag@1.3.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "c837debfbfe0baf7eb8e2f0bbb3d1d9cc3229697", + "bugs": { + "url": "jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "buffer-crc32": "0.2.3" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.0", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "c837debfbfe0baf7eb8e2f0bbb3d1d9cc3229697", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.3.0.tgz" + }, + "engines": { + "node": ">= 0.8.0" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "b1531685ee679ca8fe329202e56d429d3c02eac0", + "homepage": "jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.3.0" + }, + "1.3.1": { + "_from": ".", + "_id": "etag@1.3.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "e51925728688a32dc4eea1cfa9ab4f734d055567", + "bugs": { + "url": "jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.0.0" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "e51925728688a32dc4eea1cfa9ab4f734d055567", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.3.1.tgz" + }, + "engines": { + "node": ">= 0.8" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "88a83fdccc15065893cfad6e2c7af8a379941f16", + "homepage": "jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.3.1" + }, + "1.4.0": { + "_from": ".", + "_id": "etag@1.4.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "3050991615857707c04119d075ba2088e0701225", + "bugs": { + "url": "jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.0.0" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "3050991615857707c04119d075ba2088e0701225", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.4.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "6b701773b06a102947cc063286e7e3f3bceae27b", + "homepage": "jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.4.0" + }, + "1.5.0": { + "_from": ".", + "_id": "etag@1.5.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "8ca0f7a30b4b7305f034e8902fb8ec3c321491e4", + "bugs": { + "url": "jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.0.0" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "8ca0f7a30b4b7305f034e8902fb8ec3c321491e4", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.5.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "d42734af50250c05893f02cb900e1c71efffbc45", + "homepage": "jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.5.0" + }, + "1.5.1": { + "_from": ".", + "_id": "etag@1.5.1", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.21", + "_shasum": "54c50de04ee42695562925ac566588291be7e9ea", + "bugs": { + "url": "jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.2.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.2", + "mocha": "~1.21.4", + "seedrandom": "~2.3.6" + }, + "directories": {}, + "dist": { + "shasum": "54c50de04ee42695562925ac566588291be7e9ea", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.5.1.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "index.js" + ], + "gitHead": "27335e2265388109e50a9f037452081dc8a8260f", + "homepage": "jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.5.1" + }, + "1.6.0": { + "_from": ".", + "_id": "etag@1.6.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.28", + "_shasum": "8bcb2c6af1254c481dfc8b997c906ef4e442c207", + "bugs": { + "url": "jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "dependencies": { + "crc": "3.2.1" + }, + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.9", + "mocha": "~1.21.4", + "seedrandom": "2.3.11" + }, + "directories": {}, + "dist": { + "shasum": "8bcb2c6af1254c481dfc8b997c906ef4e442c207", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.6.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "README.md", + "index.js" + ], + "gitHead": "76a8f5250b02e85bc1c9b1b049d83b853a87df44", + "homepage": "jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": { + "type": "git", + "url": "jshttp/etag" + }, + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.6.0" + }, + "1.7.0": { + "_from": ".", + "_id": "etag@1.7.0", + "_npmUser": { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + }, + "_npmVersion": "1.4.28", + "_shasum": "03d30b5f67dd6e632d2945d30d6652731a34d5d8", + "bugs": { + "url": "jshttp/etag/issues" + }, + "contributors": [ + { + "email": "doug@somethingdoug.com", + "name": "Douglas Christopher Wilson" + }, + { + "email": "david.bjorklund@gmail.com", + "name": "David Björklund" + } + ], + "description": "Create simple ETags", + "devDependencies": { + "beautify-benchmark": "0.2.4", + "benchmark": "1.0.0", + "istanbul": "0.3.14", + "mocha": "~1.21.4", + "seedrandom": "2.3.11" + }, + "directories": {}, + "dist": { + "shasum": "03d30b5f67dd6e632d2945d30d6652731a34d5d8", + "tarball": "http://registry.npmjs.org/etag/-/etag-1.7.0.tgz" + }, + "engines": { + "node": ">= 0.6" + }, + "files": [ + "LICENSE", + "HISTORY.md", + "README.md", + "index.js" + ], + "gitHead": "a511f5c8c930fd9546dbd88acb080f96bc788cfc", + "homepage": "jshttp/etag", + "keywords": [ + "etag", + "http", + "res" + ], + "license": "MIT", + "maintainers": [ + { + "email": "doug@somethingdoug.com", + "name": "dougwilson" + } + ], + "name": "etag", + "repository": "jshttp/etag", + "scripts": { + "bench": "node benchmark/index.js", + "test": "mocha --reporter spec --bail --check-leaks test/", + "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", + "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" + }, + "version": "1.7.0" + } + } +} diff --git a/javascript/spec/fixtures/npm_responses/react-dom-with-dir.json b/javascript/spec/fixtures/npm_responses/react-dom-with-dir.json new file mode 100644 index 0000000000..2b369cf195 --- /dev/null +++ b/javascript/spec/fixtures/npm_responses/react-dom-with-dir.json @@ -0,0 +1,105 @@ +{ + "_hasShrinkwrap": false, + "_id": "react-dom@16.8.2", + "_nodeVersion": "8.10.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.8.2_1550170899672_0.6266335928232099" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "6.4.1", + "browser": { + "./server.js": "./server.browser.js", + "./unstable-fizz.js": "./unstable-fizz.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.13.2" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 46, + "integrity": "sha512-cPGfgFfwi+VCZjk73buu14pYkYBR1b/SRMSYqkLDdhSEHnSwcuYTPu6/Bh6ZphJFIk80XLvbSe2azfcRzNF+Xg==", + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcZbsUCRA9TVsSAnZWagAAZrcP/A7008ceiHOp3XFgFIym\n2Q6hio33/Q/4odxm08HPoU/Yowx2nM6kikilpHlx+8SgyzLbSXij45KEVqSN\nDVVtf8tHONANUAZW8baPtBh7E9LotQ1EPvQl4tZdL6qCOsqFcIlQfSxf9Kcy\nS3+oK4TA7RJ/OPCGlDohT8SO6JhmXnevCxTgNFRk9xS8ekbe5x0bX6OltqSw\nbtiQSXJBGV8j8Y3lqInXQeyMrKzI6GjoPS0XXb4iPrp3RSOz/1io6z7OEIST\n3/fGrXUepUglyc52euuUMznROndtZzRHLv8YS3XMzlUeh3Mustl1r2L1y2gX\nLcFGkg8U+4HT7mu+4gqVdrqSI3x3/dIIl+yNrYbjiESuggujA+DNgnPdICBg\nVKliAmcdgTg0Iy0uIQi5zYzz2EcGSyNdGmFtlPCJ0WCBNldSdQ8p1XlfxHO5\nZNoxSB589DwKgzljvXpTlBnSb1YDTT/jwB5Jn06miWTRsjCwqoC3+/NWGK+H\nSD0DKoPoxPRq9Dmjx54C9PmDCNaDEWXsXTryQDGS2t7wo+wKIIp22dA/Jm2s\nzWQHHdaoplmeuY9YleXf4pjyFj64TJimbfQdVuQb1NVWrcMF965Py98x65ET\nh/blJUa2TORzJh9hX1eJEDCMo3Y5hwEZuujwlidJvE6nsXIWLWEw3sm4w3Fz\nIORr\r\n=cYnr\r\n-----END PGP SIGNATURE-----\r\n", + "shasum": "7c8a69545dd554d45d66442230ba04a6a0a3c3d3", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.2.tgz", + "unpackedSize": 4726725 + }, + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "npm@andrewclark.io", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "threepointone@gmail.com", + "name": "threepointone" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "readme": "# `react-dom`\n\nThis package serves as the entry point to the DOM and server renderers for React. It is intended to be paired with the generic React package, which is shipped as `react` to npm.\n\n## Installation\n\n```sh\nnpm install react react-dom\n```\n\n## Usage\n\n### In the browser\n\n```js\nvar React = require('react');\nvar ReactDOM = require('react-dom');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOM.render(, node);\n```\n\n### On the server\n\n```js\nvar React = require('react');\nvar ReactDOMServer = require('react-dom/server');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOMServer.renderToString();\n```\n\n## API\n\n### `react-dom`\n\n- `findDOMNode`\n- `render`\n- `unmountComponentAtNode`\n\n### `react-dom/server`\n\n- `renderToString`\n- `renderToStaticMarkup`\n", + "readmeFilename": "README.md", + "repository": { + "directory": "packages/react-dom", + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.8.2" +} diff --git a/javascript/spec/fixtures/npm_responses/react-dom.json b/javascript/spec/fixtures/npm_responses/react-dom.json new file mode 100644 index 0000000000..5f881c10b3 --- /dev/null +++ b/javascript/spec/fixtures/npm_responses/react-dom.json @@ -0,0 +1,10285 @@ +{ + "_id": "react-dom", + "_rev": "428-718f196ce7884c8cbd92be121aa222a0", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "description": "React package for working with the DOM.", + "dist-tags": { + "canary": "16.6.0-alpha.8af6728", + "latest": "16.6.0", + "next": "16.7.0-alpha.0", + "tmp": "16.3.3" + }, + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "readme": "", + "readmeFilename": "", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "time": { + "0.1.0": "2014-05-06T18:59:36.160Z", + "0.14.0": "2015-10-07T17:29:35.289Z", + "0.14.0-beta1": "2015-07-03T08:47:36.859Z", + "0.14.0-beta2": "2015-07-31T05:32:31.129Z", + "0.14.0-beta3": "2015-08-03T21:33:53.540Z", + "0.14.0-rc1": "2015-09-10T16:02:13.157Z", + "0.14.1": "2015-10-28T21:36:05.173Z", + "0.14.2": "2015-11-02T19:55:14.560Z", + "0.14.3": "2015-11-19T02:26:48.974Z", + "0.14.4": "2015-12-29T22:00:13.038Z", + "0.14.5": "2015-12-29T22:40:03.262Z", + "0.14.6": "2016-01-06T23:52:52.338Z", + "0.14.7": "2016-01-28T19:59:21.186Z", + "0.14.8": "2016-03-29T16:19:51.077Z", + "0.14.9": "2017-04-12T15:45:23.918Z", + "0.15.0-alpha.1": "2016-01-21T04:16:19.791Z", + "15.0.0": "2016-04-07T21:25:44.824Z", + "15.0.0-rc.1": "2016-03-08T01:07:30.431Z", + "15.0.0-rc.2": "2016-03-16T22:19:25.716Z", + "15.0.1": "2016-04-08T18:24:03.871Z", + "15.0.2": "2016-04-30T00:38:21.736Z", + "15.0.2-alpha.1": "2016-04-20T18:17:21.275Z", + "15.0.2-alpha.2": "2016-04-21T22:22:04.168Z", + "15.0.2-alpha.3": "2016-04-25T20:03:54.772Z", + "15.0.2-alpha.4": "2016-04-28T07:12:05.405Z", + "15.0.3-alpha.1": "2016-05-03T19:40:25.441Z", + "15.0.3-alpha.2": "2016-05-10T04:37:56.938Z", + "15.1.0": "2016-05-20T23:06:19.582Z", + "15.1.0-alpha.1": "2016-05-10T05:28:28.727Z", + "15.2.0": "2016-07-01T18:52:18.934Z", + "15.2.0-rc.1": "2016-06-15T01:30:10.969Z", + "15.2.0-rc.2": "2016-07-01T06:32:02.930Z", + "15.2.1": "2016-07-08T22:31:06.338Z", + "15.3.0": "2016-07-29T18:38:13.089Z", + "15.3.0-rc.1": "2016-07-13T18:58:27.198Z", + "15.3.0-rc.2": "2016-07-13T21:02:06.521Z", + "15.3.0-rc.3": "2016-07-21T22:59:10.004Z", + "15.3.1": "2016-08-19T18:50:14.130Z", + "15.3.1-rc.1": "2016-08-12T23:35:19.931Z", + "15.3.1-rc.2": "2016-08-15T22:56:22.608Z", + "15.3.2": "2016-09-19T17:46:57.685Z", + "15.3.2-rc.1": "2016-09-15T23:43:35.377Z", + "15.4.0": "2016-11-16T14:32:55.418Z", + "15.4.0-rc.1": "2016-10-04T22:35:37.609Z", + "15.4.0-rc.2": "2016-10-05T22:50:57.582Z", + "15.4.0-rc.3": "2016-10-14T19:14:23.429Z", + "15.4.0-rc.4": "2016-10-14T22:00:09.527Z", + "15.4.1": "2016-11-23T01:59:08.303Z", + "15.4.2": "2017-01-06T20:16:11.024Z", + "15.5.0": "2017-04-07T21:39:55.712Z", + "15.5.0-rc.1": "2017-04-05T19:04:40.321Z", + "15.5.0-rc.2": "2017-04-06T23:19:15.765Z", + "15.5.1": "2017-04-07T22:40:57.597Z", + "15.5.2": "2017-04-08T01:39:02.809Z", + "15.5.3": "2017-04-08T04:10:06.023Z", + "15.5.4": "2017-04-11T19:30:09.326Z", + "15.6.0": "2017-06-13T17:29:43.818Z", + "15.6.0-rc.1": "2017-06-01T17:47:56.117Z", + "15.6.1": "2017-06-15T00:05:24.745Z", + "15.6.2": "2017-09-26T00:10:29.803Z", + "16.0.0": "2017-09-26T16:00:34.718Z", + "16.0.0-alpha": "2017-01-10T00:27:31.952Z", + "16.0.0-alpha.0": "2017-01-25T20:04:49.683Z", + "16.0.0-alpha.10": "2017-04-20T19:59:54.647Z", + "16.0.0-alpha.11": "2017-04-25T01:55:04.683Z", + "16.0.0-alpha.12": "2017-05-02T21:25:14.084Z", + "16.0.0-alpha.13": "2017-06-09T14:00:54.767Z", + "16.0.0-alpha.2": "2017-02-09T16:16:26.330Z", + "16.0.0-alpha.3": "2017-02-23T23:52:33.985Z", + "16.0.0-alpha.4": "2017-03-13T15:57:52.301Z", + "16.0.0-alpha.5": "2017-03-21T20:02:43.184Z", + "16.0.0-alpha.6": "2017-03-24T22:47:10.931Z", + "16.0.0-alpha.7": "2017-04-06T18:53:51.099Z", + "16.0.0-alpha.8": "2017-04-07T17:49:09.446Z", + "16.0.0-alpha.9": "2017-04-11T21:45:39.308Z", + "16.0.0-beta.1": "2017-07-26T20:03:49.535Z", + "16.0.0-beta.2": "2017-07-27T17:07:08.810Z", + "16.0.0-beta.3": "2017-08-03T23:07:39.169Z", + "16.0.0-beta.4": "2017-08-08T16:09:48.852Z", + "16.0.0-beta.5": "2017-08-08T17:27:35.449Z", + "16.0.0-rc.1": "2017-09-06T23:13:40.178Z", + "16.0.0-rc.2": "2017-09-07T03:36:51.262Z", + "16.0.0-rc.3": "2017-09-14T13:09:40.591Z", + "16.0.1": "2018-08-01T18:47:12.808Z", + "16.1.0": "2017-11-09T15:04:38.346Z", + "16.1.0-beta": "2017-11-02T22:45:12.511Z", + "16.1.0-beta.1": "2017-11-07T14:57:35.815Z", + "16.1.0-rc": "2017-11-09T00:13:15.575Z", + "16.1.1": "2017-11-13T16:13:14.142Z", + "16.1.2": "2018-08-01T18:55:18.353Z", + "16.2.0": "2017-11-28T21:32:29.110Z", + "16.2.1": "2018-08-01T18:57:20.123Z", + "16.3.0": "2018-03-29T20:16:21.022Z", + "16.3.0-alpha.0": "2018-02-02T21:05:53.621Z", + "16.3.0-alpha.1": "2018-02-12T18:49:31.086Z", + "16.3.0-alpha.2": "2018-03-14T20:30:11.164Z", + "16.3.0-alpha.3": "2018-03-22T19:48:55.388Z", + "16.3.0-rc.0": "2018-03-28T02:15:03.439Z", + "16.3.1": "2018-04-04T00:39:16.342Z", + "16.3.2": "2018-04-16T15:32:42.988Z", + "16.3.3": "2018-08-01T18:58:58.451Z", + "16.4.0": "2018-05-24T00:39:50.456Z", + "16.4.0-alpha.0911da3": "2018-02-27T02:14:06.198Z", + "16.4.0-alpha.3174632": "2018-02-24T05:07:52.951Z", + "16.4.0-alpha.7926752": "2018-02-13T00:52:26.068Z", + "16.4.1": "2018-06-13T16:47:20.487Z", + "16.4.2": "2018-08-01T19:01:01.146Z", + "16.5.0": "2018-09-06T16:44:51.243Z", + "16.5.1": "2018-09-13T18:34:20.794Z", + "16.5.2": "2018-09-18T19:31:10.943Z", + "16.6.0": "2018-10-23T23:36:09.900Z", + "16.6.0-alpha.0": "2018-09-17T22:11:39.953Z", + "16.6.0-alpha.400d197": "2018-10-05T01:04:16.036Z", + "16.6.0-alpha.8af6728": "2018-10-10T16:22:25.206Z", + "16.7.0-alpha.0": "2018-10-25T16:13:39.411Z", + "created": "2014-05-06T18:59:36.160Z", + "modified": "2018-10-25T16:13:43.562Z" + }, + "users": { + "abpeinado": true, + "adamduehansen": true, + "agamlarage": true, + "ajaegle": true, + "akarem": true, + "akh-rman": true, + "albertico88": true, + "alek-s": true, + "alexandesigner": true, + "alexdevero": true, + "alexis-nava": true, + "alexjsdev": true, + "alexparish": true, + "alexxnica": true, + "almccann": true, + "ambassador": true, + "ambdxtrch": true, + "amiziara": true, + "andrelamont": true, + "arayzou": true, + "arkanciscan": true, + "asm2hex": true, + "astrogeek": true, + "atulmy": true, + "awayken": true, + "belcour": true, + "blittle": true, + "bogdanvlviv": true, + "borasta": true, + "brandonccx": true, + "brien-crean": true, + "bsnote": true, + "buzzpsych": true, + "cfleschhut": true, + "chenchenalex": true, + "chinawolf_wyp": true, + "chrisakakay": true, + "ciro-maciel": true, + "cl0udw4lk3r": true, + "coderaiser": true, + "codyschindler": true, + "daniele_cammarata": true, + "daniellink": true, + "daskepon": true, + "davidchubbs": true, + "davidjsalazarmoreno": true, + "derrickbeining": true, + "dhanya-kr": true, + "digitalblake": true, + "digitalextremist": true, + "dnsnx": true, + "dskecse": true, + "easimonenko": true, + "endsoul": true, + "enzoferey": true, + "evan2x": true, + "evdokimovm": true, + "fabioppalumbo": true, + "fabrianibrahim": true, + "fearnbuster": true, + "federico-garcia": true, + "ferchoriverar": true, + "flubox": true, + "foto": true, + "freebird": true, + "freelancerumesh": true, + "fsgdez": true, + "fstgeorge": true, + "fugudesign": true, + "gaboesquivel": true, + "gamersdelight": true, + "geofftech": true, + "gpmetheny": true, + "henriesteves": true, + "henrybrown0": true, + "highlanderkev": true, + "huyz": true, + "hyteer": true, + "iceglaive": true, + "jakedalus": true, + "jakedemonaco": true, + "jamal-safwat": true, + "jaredwilli": true, + "joelbandi": true, + "jolyon": true, + "josejaguirre": true, + "josokinas": true, + "kaapex": true, + "kainos90": true, + "karzanosman984": true, + "katsos": true, + "kay.sackey": true, + "kaybeard": true, + "kazem1": true, + "kerwyn": true, + "kevinagin": true, + "kmathmann": true, + "konamgil": true, + "koulmomo": true, + "kratyk": true, + "ksugiura": true, + "kytart": true, + "langri-sha": true, + "leocreatini": true, + "leonardorb": true, + "leor": true, + "linkchef": true, + "lomocc": true, + "luffy84217": true, + "maddas": true, + "majkel": true, + "manojkhannakm": true, + "markhe": true, + "markthethomas": true, + "mathieuancelin": true, + "mattyboy": true, + "mestar": true, + "migkjy": true, + "mikeljames": true, + "mjurincic": true, + "modao": true, + "morogasper": true, + "moxiong": true, + "mswanson1524": true, + "naokie": true, + "nate-river": true, + "natforyou": true, + "nelix": true, + "nguyenvanhoang26041994": true, + "nicolaslevy": true, + "nisimjoseph": true, + "nizar-banna": true, + "npmlincq": true, + "npmrud5g": true, + "ntucker": true, + "oandrelopes": true, + "ongmin": true, + "onufrienko": true, + "orenschwartz": true, + "ostgals": true, + "panlw": true, + "paulkolesnyk": true, + "pddivine": true, + "philipjc": true, + "phixid": true, + "phpjsnerd": true, + "pilusonoka": true, + "pmasa": true, + "princetoad": true, + "pris54": true, + "ptitdam2001": true, + "pupudu": true, + "qddegtya": true, + "qinyuhang": true, + "qqcome110": true, + "raciat": true, + "rapt0p7": true, + "rayng": true, + "razr9": true, + "rethinkflash": true, + "rokeyzki": true, + "romelperez": true, + "rommguy": true, + "rupertong": true, + "rxmth": true, + "rylan_yan": true, + "samar": true, + "saravntbe": true, + "sayrilamar": true, + "scotchulous": true, + "septs": true, + "severen": true, + "shahyar": true, + "shore12358": true, + "sibawite": true, + "sinsaints76": true, + "smtnkc": true, + "sobear": true, + "soutarm": true, + "sshrike": true, + "stefanof": true, + "sternelee": true, + "tcrowe": true, + "tedyhy": true, + "tiggem1993": true, + "timjk": true, + "tmurngon": true, + "tonyetro": true, + "tonyseek": true, + "travm": true, + "tyxla": true, + "ungurys": true, + "urbantumbleweed": true, + "v3rron": true, + "vbv": true, + "vinbhatt": true, + "wayn": true, + "wearevilla": true, + "weisen": true, + "wkaifang": true, + "wlritchie33": true, + "wwayne": true, + "yangzw": true, + "zachkrall": true, + "zhouanbo": true + }, + "versions": { + "0.1.0": { + "_from": ".", + "_id": "react-dom@0.1.0", + "_npmUser": { + "email": "etienne@heliom.ca", + "name": "EtienneLem" + }, + "_npmVersion": "1.4.3", + "author": { + "email": "etienne@heliom.ca", + "name": "Etienne Lemay" + }, + "bugs": { + "url": "https://github.com/EtienneLem/react-dom/issues" + }, + "deprecated": "The old react-dom package has been deprecated. Until React v0.14 is officially released, please installed react-dom with npm install react-dom@latest.", + "description": "DOM is a `React.DOM` wrapper with — subjectively — more sanity and awesome helpers", + "devDependencies": { + "colors": "0.6.2", + "jasmine-node": "2.0.0-beta4", + "react": "0.10.0", + "uglify-js": "2.4.13" + }, + "directories": {}, + "dist": { + "shasum": "6c821d418caed4f8ed39424cf0fad36f03fb35c4", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-0.1.0.tgz" + }, + "homepage": "https://github.com/EtienneLem/react-dom", + "keywords": [ + "react", + "dom" + ], + "license": "MIT", + "main": "react-dom.js", + "maintainers": [ + { + "email": "etienne@heliom.ca", + "name": "EtienneLem" + } + ], + "name": "react-dom", + "repository": { + "type": "git", + "url": "https://github.com/EtienneLem/react-dom.git" + }, + "scripts": { + "test": "jasmine-node ./spec" + }, + "version": "0.1.0" + }, + "0.14.0": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@0.14.0", + "_nodeVersion": "4.1.0", + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.14.3", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "fc2e792ab27d88db009a76ecbcfd5611368a849e", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "react": "^0.14.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "fc2e792ab27d88db009a76ecbcfd5611368a849e", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-0.14.0.tgz" + }, + "homepage": "https://github.com/facebook/react/tree/master/npm-react-dom", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "0.14.0" + }, + "0.14.0-beta1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@0.14.0-beta1", + "_nodeVersion": "0.10.35", + "_npmUser": { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + "_npmVersion": "2.2.0", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "a008dd5eeb41c60a11e77738eb43c00d7ff390b8", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "react": "^0.14.0-beta1" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "a008dd5eeb41c60a11e77738eb43c00d7ff390b8", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-0.14.0-beta1.tgz" + }, + "homepage": "https://github.com/facebook/react/tree/master/npm-react-dom", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "0.14.0-beta1" + }, + "0.14.0-beta2": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@0.14.0-beta2", + "_nodeVersion": "0.10.40", + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.13.2", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "6376465678604bf925158ce0068c5f5524f00d21", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "0.1.0-alpha.4", + "react": "^0.14.0-beta2" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "6376465678604bf925158ce0068c5f5524f00d21", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-0.14.0-beta2.tgz" + }, + "homepage": "https://github.com/facebook/react/tree/master/npm-react-dom", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "0.14.0-beta2" + }, + "0.14.0-beta3": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@0.14.0-beta3", + "_nodeVersion": "0.10.40", + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.13.2", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "548dd048f39c71f1092ed942eb119ba37bf1b7e3", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "0.1.0-alpha.4", + "react": "^0.14.0-beta3" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "548dd048f39c71f1092ed942eb119ba37bf1b7e3", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-0.14.0-beta3.tgz" + }, + "homepage": "https://github.com/facebook/react/tree/master/npm-react-dom", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "0.14.0-beta3" + }, + "0.14.0-rc1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@0.14.0-rc1", + "_nodeVersion": "0.10.35", + "_npmUser": { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + "_npmVersion": "2.2.0", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "84a71d326d9326951bf562cdbea75330bd9be638", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "react": "^0.14.0-rc1" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "84a71d326d9326951bf562cdbea75330bd9be638", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-0.14.0-rc1.tgz" + }, + "homepage": "https://github.com/facebook/react/tree/master/npm-react-dom", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "0.14.0-rc1" + }, + "0.14.1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@0.14.1", + "_nodeVersion": "4.2.0", + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.14.8", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "9c2e416d9703cd8dcdbae41f76caa3b0eb3ad664", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "react": "^0.14.1" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "9c2e416d9703cd8dcdbae41f76caa3b0eb3ad664", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-0.14.1.tgz" + }, + "homepage": "https://github.com/facebook/react/tree/master/npm-react-dom", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "0.14.1" + }, + "0.14.2": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@0.14.2", + "_nodeVersion": "4.2.0", + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.14.9", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "ba979850dca2513f99cf9fb3fb574ad9de3dab84", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "ba979850dca2513f99cf9fb3fb574ad9de3dab84", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-0.14.2.tgz" + }, + "homepage": "https://github.com/facebook/react/tree/master/npm-react-dom", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^0.14.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "0.14.2" + }, + "0.14.3": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@0.14.3", + "_nodeVersion": "4.2.2", + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.14.7", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "b3f0aeeed1a433b20c5458e34439a51916818628", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "b3f0aeeed1a433b20c5458e34439a51916818628", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-0.14.3.tgz" + }, + "homepage": "https://github.com/facebook/react/tree/master/npm-react-dom", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^0.14.3" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "0.14.3" + }, + "0.14.4": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@0.14.4", + "_nodeVersion": "4.1.0", + "_npmUser": { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + "_npmVersion": "2.14.4", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "d0064c711fbc90c744385fd6fba045c62ed93c9d", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "d0064c711fbc90c744385fd6fba045c62ed93c9d", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-0.14.4.tgz" + }, + "homepage": "https://github.com/facebook/react/tree/master/npm-react-dom", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^0.14.4" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "0.14.4" + }, + "0.14.5": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@0.14.5", + "_nodeVersion": "4.1.0", + "_npmUser": { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + "_npmVersion": "2.14.4", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "f0018bee7a941b2236d89ebe64b02adf15512002", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "f0018bee7a941b2236d89ebe64b02adf15512002", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-0.14.5.tgz" + }, + "homepage": "https://github.com/facebook/react/tree/master/npm-react-dom", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^0.14.5" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "0.14.5" + }, + "0.14.6": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@0.14.6", + "_nodeVersion": "4.2.2", + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.14.15", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "46d4e3243884f277e89ce8759131e8a16ac23e65", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "46d4e3243884f277e89ce8759131e8a16ac23e65", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-0.14.6.tgz" + }, + "homepage": "https://github.com/facebook/react/tree/master/npm-react-dom", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^0.14.6" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "0.14.6" + }, + "0.14.7": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@0.14.7", + "_nodeVersion": "4.2.6", + "_npmUser": { + "email": "scott@oceanbase.org", + "name": "graue" + }, + "_npmVersion": "2.14.12", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "973c302110b3a680c9afe69d07c623b9c0952d82", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "973c302110b3a680c9afe69d07c623b9c0952d82", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-0.14.7.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^0.14.7" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "0.14.7" + }, + "0.14.8": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@0.14.8", + "_nodeVersion": "4.2.1", + "_npmOperationalInternal": { + "host": "packages-16-east.internal.npmjs.com", + "tmp": "tmp/react-dom-0.14.8.tgz_1459268390053_0.8757442627102137" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "2.14.7", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "0f1c547514263f771bd31814a739e5306575069e", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "0f1c547514263f771bd31814a739e5306575069e", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-0.14.8.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^0.14.8" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "0.14.8" + }, + "0.14.9": { + "_from": "packages/react-dom.tgz", + "_id": "react-dom@0.14.9", + "_nodeVersion": "4.7.3", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/react-dom-0.14.9.tgz_1492011923294_0.06204423378221691" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "2.15.11", + "_resolved": "file:packages/react-dom.tgz", + "_shasum": "05064a3dcf0fb1880a3b2bfc9d58c55d8d9f6293", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "05064a3dcf0fb1880a3b2bfc9d58c55d8d9f6293", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-0.14.9.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^0.14.9" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "0.14.9" + }, + "0.15.0-alpha.1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@0.15.0-alpha.1", + "_nodeVersion": "4.2.2", + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.14.15", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "0104a450f753a834946b99ead6ed6170f5da25b6", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "0104a450f753a834946b99ead6ed6170f5da25b6", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-0.15.0-alpha.1.tgz" + }, + "homepage": "https://github.com/facebook/react/tree/master/npm-react-dom", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^0.15.0-alpha.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "0.15.0-alpha.1" + }, + "15.0.0": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.0.0", + "_nodeVersion": "4.4.0", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.0.0.tgz_1460064344208_0.7453324194066226" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.15.3", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "49a91a663d8ff9f699eb4997f0027ead8abe0d44", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "49a91a663d8ff9f699eb4997f0027ead8abe0d44", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.0.0.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.0.0" + }, + "15.0.0-rc.1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.0.0-rc.1", + "_nodeVersion": "4.3.0", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.0.0-rc.1.tgz_1457399248218_0.8456978918984532" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.14.12", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "3d4edb346860eafae6d91ec906a4c230119962c3", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "3d4edb346860eafae6d91ec906a4c230119962c3", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.0.0-rc.1.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.0.0-rc.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.0.0-rc.1" + }, + "15.0.0-rc.2": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.0.0-rc.2", + "_nodeVersion": "5.1.1", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.0.0-rc.2.tgz_1458166763454_0.7732546178158373" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "3.8.1", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "abb4fa8963dc9f9c4d1bcb1674389711caa3d398", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "abb4fa8963dc9f9c4d1bcb1674389711caa3d398", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.0.0-rc.2.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.0.0-rc.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.0.0-rc.2" + }, + "15.0.1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.0.1", + "_nodeVersion": "4.4.2", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.0.1.tgz_1460139843335_0.14761509280651808" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.15.0", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "33c67f557b43ab27a6ba3ec3200321681921c48d", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "33c67f557b43ab27a6ba3ec3200321681921c48d", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.0.1.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.0.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.0.1" + }, + "15.0.2": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.0.2", + "_nodeVersion": "4.4.3", + "_npmOperationalInternal": { + "host": "packages-16-east.internal.npmjs.com", + "tmp": "tmp/react-dom-15.0.2.tgz_1461976699566_0.6056196035351604" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.15.1", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "f8a20ac1bc2da9eb494fce3b44c3ec19d0951e27", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "f8a20ac1bc2da9eb494fce3b44c3ec19d0951e27", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.0.2.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.0.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.0.2" + }, + "15.0.2-alpha.1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.0.2-alpha.1", + "_nodeVersion": "4.4.2", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.0.2-alpha.1.tgz_1461176240821_0.19873908162117004" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.15.3", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "872a03e463beb41b62d5970ee67e791da1c35d5d", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "872a03e463beb41b62d5970ee67e791da1c35d5d", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.0.2-alpha.1.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.0.2-alpha.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.0.2-alpha.1" + }, + "15.0.2-alpha.2": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.0.2-alpha.2", + "_nodeVersion": "4.1.0", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.0.2-alpha.2.tgz_1461277323635_0.5002606643829495" + }, + "_npmUser": { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + "_npmVersion": "2.14.19", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "cdfe435d05b6bacc1e6f9abde356bc0b20098ce4", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "cdfe435d05b6bacc1e6f9abde356bc0b20098ce4", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.0.2-alpha.2.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.0.2-alpha.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.0.2-alpha.2" + }, + "15.0.2-alpha.3": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.0.2-alpha.3", + "_nodeVersion": "4.4.2", + "_npmOperationalInternal": { + "host": "packages-16-east.internal.npmjs.com", + "tmp": "tmp/react-dom-15.0.2-alpha.3.tgz_1461614631521_0.2638078455347568" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.15.3", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "37335f91b914b5ab603bd8ba8e5f1e3141a8bd3e", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "37335f91b914b5ab603bd8ba8e5f1e3141a8bd3e", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.0.2-alpha.3.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.0.2-alpha.3" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.0.2-alpha.3" + }, + "15.0.2-alpha.4": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.0.2-alpha.4", + "_nodeVersion": "4.4.2", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.0.2-alpha.4.tgz_1461827524935_0.9299324578605592" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.15.3", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "9f90af3b43a885b7e26acf68ba571a4915ec6f75", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "9f90af3b43a885b7e26acf68ba571a4915ec6f75", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.0.2-alpha.4.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.0.2-alpha.4" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.0.2-alpha.4" + }, + "15.0.3-alpha.1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.0.3-alpha.1", + "_nodeVersion": "4.4.3", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.0.3-alpha.1.tgz_1462304424938_0.958863329840824" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.15.1", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "d4ddac83991b9e01884e613605b4e999f5b6a31f", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "d4ddac83991b9e01884e613605b4e999f5b6a31f", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.0.3-alpha.1.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.0.3-alpha.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.0.3-alpha.1" + }, + "15.0.3-alpha.2": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.0.3-alpha.2", + "_nodeVersion": "4.4.3", + "_npmOperationalInternal": { + "host": "packages-16-east.internal.npmjs.com", + "tmp": "tmp/react-dom-15.0.3-alpha.2.tgz_1462855075711_0.8020870140753686" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.15.1", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "df4e216dd9f9bc10365697baf7aad1b585b1b579", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "df4e216dd9f9bc10365697baf7aad1b585b1b579", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.0.3-alpha.2.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.0.3-alpha.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.0.3-alpha.2" + }, + "15.1.0": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.1.0", + "_nodeVersion": "4.4.3", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.1.0.tgz_1463785579139_0.1863514157012105" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.15.1", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "d0c2b24c8b47a41a2b9ec766662d4e686f353153", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "d0c2b24c8b47a41a2b9ec766662d4e686f353153", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.1.0.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.1.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.1.0" + }, + "15.1.0-alpha.1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.1.0-alpha.1", + "_nodeVersion": "4.4.3", + "_npmOperationalInternal": { + "host": "packages-16-east.internal.npmjs.com", + "tmp": "tmp/react-dom-15.1.0-alpha.1.tgz_1462858107556_0.014126174617558718" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.15.1", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "eea28251915d5c6479c12dd51b0b775da3abdfab", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "eea28251915d5c6479c12dd51b0b775da3abdfab", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.1.0-alpha.1.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.1.0-alpha.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.1.0-alpha.1" + }, + "15.2.0": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.2.0", + "_nodeVersion": "4.4.5", + "_npmOperationalInternal": { + "host": "packages-16-east.internal.npmjs.com", + "tmp": "tmp/react-dom-15.2.0.tgz_1467399135416_0.5253081950359046" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.15.5", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "cc9b9a2f51e460d6a727c7b23058fe94f42c874c", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "cc9b9a2f51e460d6a727c7b23058fe94f42c874c", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.2.0.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.2.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.2.0" + }, + "15.2.0-rc.1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.2.0-rc.1", + "_nodeVersion": "4.4.5", + "_npmOperationalInternal": { + "host": "packages-16-east.internal.npmjs.com", + "tmp": "tmp/react-dom-15.2.0-rc.1.tgz_1465954205984_0.23762806924059987" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.15.5", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "e0d6cf5fa044c4cbcf041f0b901775ca28298764", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "e0d6cf5fa044c4cbcf041f0b901775ca28298764", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.2.0-rc.1.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.2.0-rc.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.2.0-rc.1" + }, + "15.2.0-rc.2": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.2.0-rc.2", + "_nodeVersion": "4.4.5", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.2.0-rc.2.tgz_1467354722454_0.136842803331092" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.15.5", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "cb4f4b72b0186d2195285043578666c0e69c9e6c", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "cb4f4b72b0186d2195285043578666c0e69c9e6c", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.2.0-rc.2.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.2.0-rc.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.2.0-rc.2" + }, + "15.2.1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.2.1", + "_nodeVersion": "4.4.5", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.2.1.tgz_1468017065864_0.8788268391508609" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.15.5", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "dc942a5268f955e180158c9d4d2609fec9388ff0", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "dc942a5268f955e180158c9d4d2609fec9388ff0", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.2.1.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.2.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.2.1" + }, + "15.3.0": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.3.0", + "_nodeVersion": "6.2.0", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.3.0.tgz_1469817492868_0.942254323977977" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "3.9.6", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "24a6a3df67969656b8a8225fd0d7fcb939d28bdd", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "24a6a3df67969656b8a8225fd0d7fcb939d28bdd", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.3.0.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.3.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.3.0" + }, + "15.3.0-rc.1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.3.0-rc.1", + "_nodeVersion": "6.2.0", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.3.0-rc.1.tgz_1468436306652_0.3014015706721693" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "3.9.6", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "e2f43cc147725ff45fbe5639cb2ebbf137bfe84e", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "e2f43cc147725ff45fbe5639cb2ebbf137bfe84e", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.3.0-rc.1.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.3.0-rc.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.3.0-rc.1" + }, + "15.3.0-rc.2": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.3.0-rc.2", + "_nodeVersion": "6.2.0", + "_npmOperationalInternal": { + "host": "packages-16-east.internal.npmjs.com", + "tmp": "tmp/react-dom-15.3.0-rc.2.tgz_1468443723532_0.40137659502215683" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "3.9.6", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "c8caac9560b881abd4a2593980e7dfbde2b1b8c6", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "c8caac9560b881abd4a2593980e7dfbde2b1b8c6", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.3.0-rc.2.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.3.0-rc.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.3.0-rc.2" + }, + "15.3.0-rc.3": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.3.0-rc.3", + "_nodeVersion": "6.2.0", + "_npmOperationalInternal": { + "host": "packages-16-east.internal.npmjs.com", + "tmp": "tmp/react-dom-15.3.0-rc.3.tgz_1469141947218_0.42605880298651755" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "3.9.6", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "29864a63b14ecf38ef5740f2d6156866d9fe1987", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "29864a63b14ecf38ef5740f2d6156866d9fe1987", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.3.0-rc.3.tgz" + }, + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.3.0-rc.3" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.3.0-rc.3" + }, + "15.3.1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.3.1", + "_nodeVersion": "4.4.5", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.3.1.tgz_1471632613890_0.979504783404991" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.15.5", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "6d42cd2b64c8c5e0b693f3ffaec301e6e627e24e", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "6d42cd2b64c8c5e0b693f3ffaec301e6e627e24e", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.3.1.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.3.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.3.1" + }, + "15.3.1-rc.1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.3.1-rc.1", + "_nodeVersion": "4.4.5", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.3.1-rc.1.tgz_1471044919680_0.46068282099440694" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.15.5", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "f6b3809b3e2a9a10a62175a82c0c2cc8fb06916d", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "f6b3809b3e2a9a10a62175a82c0c2cc8fb06916d", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.3.1-rc.1.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.3.1-rc.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.3.1-rc.1" + }, + "15.3.1-rc.2": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.3.1-rc.2", + "_nodeVersion": "4.4.5", + "_npmOperationalInternal": { + "host": "packages-16-east.internal.npmjs.com", + "tmp": "tmp/react-dom-15.3.1-rc.2.tgz_1471301779244_0.6211496945470572" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "2.15.5", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "3771828474cbf1ca4f2dab5065fa1c61315c4913", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "3771828474cbf1ca4f2dab5065fa1c61315c4913", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.3.1-rc.2.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.3.1-rc.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.3.1-rc.2" + }, + "15.3.2": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.3.2", + "_nodeVersion": "6.5.0", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.3.2.tgz_1474307217444_0.7416605225298554" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "3.10.3", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "c46b0aa5380d7b838e7a59c4a7beff2ed315531f", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "c46b0aa5380d7b838e7a59c4a7beff2ed315531f", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.3.2.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.3.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.3.2" + }, + "15.3.2-rc.1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.3.2-rc.1", + "_nodeVersion": "6.5.0", + "_npmOperationalInternal": { + "host": "packages-16-east.internal.npmjs.com", + "tmp": "tmp/react-dom-15.3.2-rc.1.tgz_1473983012960_0.35608154558576643" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "3.10.3", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "ef51d9353982b6702086a7b45f551851c57618f9", + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": {}, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "ef51d9353982b6702086a7b45f551851c57618f9", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.3.2-rc.1.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.3.2-rc.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.3.2-rc.1" + }, + "15.4.0": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.4.0", + "_nodeVersion": "6.9.1", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/react-dom-15.4.0.tgz_1479306774757_0.8015152509324253" + }, + "_npmUser": { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + "_npmVersion": "3.10.8", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "6a97a69000966570db48c746bc4b7b0ca50d1534", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.1", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "6a97a69000966570db48c746bc4b7b0ca50d1534", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.4.0.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.4.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.4.0" + }, + "15.4.0-rc.1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.4.0-rc.1", + "_nodeVersion": "6.7.0", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.4.0-rc.1.tgz_1475620537380_0.3098792328964919" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "3.10.3", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "b19c1a16290d435bfdd3dade2c0642b245f59b24", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.1", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "b19c1a16290d435bfdd3dade2c0642b245f59b24", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.4.0-rc.1.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.4.0-rc.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.4.0-rc.1" + }, + "15.4.0-rc.2": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.4.0-rc.2", + "_nodeVersion": "6.7.0", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.4.0-rc.2.tgz_1475707857341_0.00034499261528253555" + }, + "_npmUser": { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + "_npmVersion": "3.10.3", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "a9dd33a076d0db4ab77101a4b3add4b8378b29de", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.1", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "a9dd33a076d0db4ab77101a4b3add4b8378b29de", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.4.0-rc.2.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.4.0-rc.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.4.0-rc.2" + }, + "15.4.0-rc.3": { + "_from": "packages/react-dom.tgz", + "_id": "react-dom@15.4.0-rc.3", + "_nodeVersion": "6.3.1", + "_npmOperationalInternal": { + "host": "packages-16-east.internal.npmjs.com", + "tmp": "tmp/react-dom-15.4.0-rc.3.tgz_1476472461910_0.6667447746731341" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "3.10.3", + "_resolved": "file:packages/react-dom.tgz", + "_shasum": "01f71014a907bb66dd1a103a5e33b06665755327", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.1", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "01f71014a907bb66dd1a103a5e33b06665755327", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.4.0-rc.3.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.4.0-rc.3" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.4.0-rc.3" + }, + "15.4.0-rc.4": { + "_from": ".", + "_id": "react-dom@15.4.0-rc.4", + "_nodeVersion": "6.6.0", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.4.0-rc.4.tgz_1476482409291_0.7534566151443869" + }, + "_npmUser": { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + "_npmVersion": "3.10.3", + "_shasum": "e95bbe99d47aeefd60bad9a0502ca926283d960a", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.1", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "e95bbe99d47aeefd60bad9a0502ca926283d960a", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.4.0-rc.4.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.4.0-rc.4" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.4.0-rc.4" + }, + "15.4.1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.4.1", + "_nodeVersion": "6.9.0", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/react-dom-15.4.1.tgz_1479866345994_0.5994439113419503" + }, + "_npmUser": { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + "_npmVersion": "3.10.8", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "d54c913261aaedb17adc20410d029dcc18a1344a", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.1", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "d54c913261aaedb17adc20410d029dcc18a1344a", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.4.1.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.4.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.4.1" + }, + "15.4.2": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.4.2", + "_nodeVersion": "6.3.1", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.4.2.tgz_1483733768418_0.5142030897550285" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "3.10.8", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "015363f05b0a1fd52ae9efdd3a0060d90695208f", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.1", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "015363f05b0a1fd52ae9efdd3a0060d90695208f", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-15.4.2.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "scott@oceanbase.org", + "name": "graue" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.4.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.4.2" + }, + "15.5.0": { + "_from": "packages/react-dom.tgz", + "_id": "react-dom@15.5.0", + "_nodeVersion": "6.10.0", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.5.0.tgz_1491601195390_0.9668477815575898" + }, + "_npmUser": { + "email": "acdlite@me.com", + "name": "acdlite" + }, + "_npmVersion": "3.10.10", + "_resolved": "file:packages/react-dom.tgz", + "_shasum": "86a8d6dcde388473815039de3840706e1f28f697", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "~15.5.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "86a8d6dcde388473815039de3840706e1f28f697", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-15.5.0.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js", + "test-utils.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.5.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.5.0" + }, + "15.5.0-rc.1": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.5.0-rc.1", + "_nodeVersion": "6.10.0", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/react-dom-15.5.0-rc.1.tgz_1491419078084_0.3999989372678101" + }, + "_npmUser": { + "email": "acdlite@me.com", + "name": "acdlite" + }, + "_npmVersion": "3.10.10", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "eceb19edad0b809843ef149417d78600bdacc4a0", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "eceb19edad0b809843ef149417d78600bdacc4a0", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-15.5.0-rc.1.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.5.0-rc.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.5.0-rc.1" + }, + "15.5.0-rc.2": { + "_from": "packages/react-dom.tgz", + "_id": "react-dom@15.5.0-rc.2", + "_nodeVersion": "6.10.0", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.5.0-rc.2.tgz_1491520755489_0.6455270848236978" + }, + "_npmUser": { + "email": "acdlite@me.com", + "name": "acdlite" + }, + "_npmVersion": "3.10.10", + "_resolved": "file:packages/react-dom.tgz", + "_shasum": "d484e85619ee2dacaa910896a5e81c0364190f92", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "~15.5.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "d484e85619ee2dacaa910896a5e81c0364190f92", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-15.5.0-rc.2.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js", + "test-utils.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.5.0-rc.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.5.0-rc.2" + }, + "15.5.1": { + "_from": "packages/react-dom.tgz", + "_id": "react-dom@15.5.1", + "_nodeVersion": "6.10.0", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/react-dom-15.5.1.tgz_1491604855258_0.716976499883458" + }, + "_npmUser": { + "email": "acdlite@me.com", + "name": "acdlite" + }, + "_npmVersion": "3.10.10", + "_resolved": "file:packages/react-dom.tgz", + "_shasum": "05a75129171a5d4937cc206d4189a518149bee36", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "~15.5.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "05a75129171a5d4937cc206d4189a518149bee36", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-15.5.1.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js", + "test-utils.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.5.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.5.1" + }, + "15.5.2": { + "_from": "packages/react-dom.tgz", + "_id": "react-dom@15.5.2", + "_nodeVersion": "6.10.0", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/react-dom-15.5.2.tgz_1491615540604_0.583563131513074" + }, + "_npmUser": { + "email": "acdlite@me.com", + "name": "acdlite" + }, + "_npmVersion": "3.10.10", + "_resolved": "file:packages/react-dom.tgz", + "_shasum": "9248038cf37427ff675efdefbb78924c229e085d", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "~15.5.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "9248038cf37427ff675efdefbb78924c229e085d", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-15.5.2.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js", + "test-utils.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.5.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.5.2" + }, + "15.5.3": { + "_from": "packages/react-dom.tgz", + "_id": "react-dom@15.5.3", + "_nodeVersion": "6.10.0", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.5.3.tgz_1491624605683_0.37772830948233604" + }, + "_npmUser": { + "email": "acdlite@me.com", + "name": "acdlite" + }, + "_npmVersion": "3.10.10", + "_resolved": "file:packages/react-dom.tgz", + "_shasum": "2ee127ce942df55da53111ae303316e68072b5c5", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "~15.5.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "2ee127ce942df55da53111ae303316e68072b5c5", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-15.5.3.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js", + "test-utils.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.5.3" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.5.3" + }, + "15.5.4": { + "_from": "packages/react-dom.tgz", + "_id": "react-dom@15.5.4", + "_nodeVersion": "7.8.0", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-15.5.4.tgz_1491939006776_0.3741262429393828" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "4.2.0", + "_resolved": "file:packages/react-dom.tgz", + "_shasum": "ba0c28786fd52ed7e4f2135fe0288d462aef93da", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "~15.5.7" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "ba0c28786fd52ed7e4f2135fe0288d462aef93da", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-15.5.4.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js", + "test-utils.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.5.4" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.5.4" + }, + "15.6.0": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@15.6.0", + "_nodeVersion": "6.10.2", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-15.6.0.tgz_1497374983564_0.14919925550930202" + }, + "_npmUser": { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + "_npmVersion": "3.10.10", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "8bc23cb0c80e706355b76ca9f8ce47cf7bdfb6d1", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "~15.5.7" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "8bc23cb0c80e706355b76ca9f8ce47cf7bdfb6d1", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.0.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js", + "test-utils.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.6.0-rc.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.6.0" + }, + "15.6.0-rc.1": { + "_from": "packages/react-dom.tgz", + "_id": "react-dom@15.6.0-rc.1", + "_nodeVersion": "6.10.2", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-15.6.0-rc.1.tgz_1496339275986_0.707226385595277" + }, + "_npmUser": { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + "_npmVersion": "3.10.10", + "_resolved": "file:packages/react-dom.tgz", + "_shasum": "d23aeef1d6ae5732b190c5a9a8a3dc38640c5b50", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "~15.5.7" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "d23aeef1d6ae5732b190c5a9a8a3dc38640c5b50", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.0-rc.1.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js", + "test-utils.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.6.0-rc.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.6.0-rc.1" + }, + "15.6.1": { + "_from": "packages/react-dom.tgz", + "_id": "react-dom@15.6.1", + "_nodeVersion": "6.2.1", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-15.6.1.tgz_1497485123117_0.07075862702913582" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "3.9.3", + "_resolved": "file:packages/react-dom.tgz", + "_shasum": "2cb0ed4191038e53c209eb3a79a23e2a4cf99470", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.10" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "2cb0ed4191038e53c209eb3a79a23e2a4cf99470", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.1.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js", + "test-utils.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.6.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.6.1" + }, + "15.6.2": { + "_from": "packages/react-dom.tgz", + "_id": "react-dom@15.6.2", + "_nodeVersion": "6.9.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-15.6.2.tgz_1506384629718_0.17134208767674863" + }, + "_npmUser": { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + "_npmVersion": "3.10.8", + "_resolved": "file:packages/react-dom.tgz", + "_shasum": "41cfadf693b757faf2708443a1d1fd5a02bef730", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.10" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "41cfadf693b757faf2708443a1d1fd5a02bef730", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz" + }, + "files": [ + "LICENSE", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js", + "test-utils.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "npm2@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^15.6.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "15.6.2" + }, + "16.0.0": { + "_from": ".", + "_id": "react-dom@16.0.0", + "_nodeVersion": "6.10.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-16.0.0.tgz_1506441634332_0.6775156231597066" + }, + "_npmUser": { + "email": "acdlite@me.com", + "name": "acdlite" + }, + "_npmVersion": "3.10.10", + "_shasum": "9cc3079c3dcd70d4c6e01b84aab2a7e34c303f58", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "deprecated": "This version of react-dom/server contains a minor vulnerability. Please update react-dom to 16.0.1 or 16.4.2+. Learn more: https://fb.me/cve-2018-6341", + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "9cc3079c3dcd70d4c6e01b84aab2a7e34c303f58", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0.tgz" + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-beta.5" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0" + }, + "16.0.0-alpha": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@16.0.0-alpha", + "_nodeVersion": "6.9.1", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-16.0.0-alpha.tgz_1484008051724_0.10438701952807605" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "3.10.8", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "51c7e7c0d34342a29614e3a91f3905625fc134af", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.4", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "51c7e7c0d34342a29614e3a91f3905625fc134af", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-alpha.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-alpha" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-alpha" + }, + "16.0.0-alpha.0": { + "_from": "packages/react-dom.tgz", + "_id": "react-dom@16.0.0-alpha.0", + "_nodeVersion": "6.9.1", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-16.0.0-alpha.0.tgz_1485374689453_0.008099786471575499" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "3.10.8", + "_resolved": "file:packages/react-dom.tgz", + "_shasum": "80ea996b10d0f49cf280eacc9c7b1738c6de29aa", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.4", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "80ea996b10d0f49cf280eacc9c7b1738c6de29aa", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-alpha.0.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-alpha.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-alpha.0" + }, + "16.0.0-alpha.10": { + "_from": ".", + "_id": "react-dom@16.0.0-alpha.10", + "_nodeVersion": "6.9.1", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/react-dom-16.0.0-alpha.10.tgz_1492718392432_0.6055275576654822" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "3.10.8", + "_shasum": "bf1baf39f065ca3cfd696cd23a59b760442d9152", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.6" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "bf1baf39f065ca3cfd696cd23a59b760442d9152", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-alpha.10.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "index.js", + "server.js", + "test-utils.js", + "cjs/", + "umd/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-alpha.10" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-alpha.10" + }, + "16.0.0-alpha.11": { + "_from": ".", + "_id": "react-dom@16.0.0-alpha.11", + "_nodeVersion": "6.9.1", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/react-dom-16.0.0-alpha.11.tgz_1493085302151_0.8701744617428631" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "3.10.8", + "_shasum": "f35df79a5542448a4e256d71f1519c9a5ed723f5", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.6" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "f35df79a5542448a4e256d71f1519c9a5ed723f5", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-alpha.11.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "index.js", + "server.js", + "test-utils.js", + "cjs/", + "umd/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-alpha.11" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-alpha.11" + }, + "16.0.0-alpha.12": { + "_from": ".", + "_id": "react-dom@16.0.0-alpha.12", + "_nodeVersion": "6.10.0", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-16.0.0-alpha.12.tgz_1493760313848_0.7026757823769003" + }, + "_npmUser": { + "email": "acdlite@me.com", + "name": "acdlite" + }, + "_npmVersion": "3.10.10", + "_shasum": "c9894ccfd9600ef87952e05340903180b013171e", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.6" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "c9894ccfd9600ef87952e05340903180b013171e", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-alpha.12.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "index.js", + "server.js", + "test-utils.js", + "cjs/", + "umd/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-alpha.12" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-alpha.12" + }, + "16.0.0-alpha.13": { + "_from": ".", + "_id": "react-dom@16.0.0-alpha.13", + "_nodeVersion": "7.7.2", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-16.0.0-alpha.13.tgz_1497016853281_0.10224363603629172" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "4.1.2", + "_shasum": "c2b39aaf8645f1d664619fb49a1fbb9f60719c23", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.6" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "c2b39aaf8645f1d664619fb49a1fbb9f60719c23", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-alpha.13.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "index.js", + "server.js", + "test-utils.js", + "cjs/", + "umd/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-alpha.13" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-alpha.13" + }, + "16.0.0-alpha.2": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@16.0.0-alpha.2", + "_nodeVersion": "6.3.1", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/react-dom-16.0.0-alpha.2.tgz_1486656985673_0.4703085436485708" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "3.10.3", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "b2c916b0b0d8b34567967bdb945f55827a571c29", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "b2c916b0b0d8b34567967bdb945f55827a571c29", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-alpha.2.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-alpha.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-alpha.2" + }, + "16.0.0-alpha.3": { + "_from": "build/packages/react-dom.tgz", + "_id": "react-dom@16.0.0-alpha.3", + "_nodeVersion": "6.10.0", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-16.0.0-alpha.3.tgz_1487893953751_0.648889419157058" + }, + "_npmUser": { + "email": "acdlite@me.com", + "name": "acdlite" + }, + "_npmVersion": "3.10.10", + "_resolved": "file:build/packages/react-dom.tgz", + "_shasum": "9cf304ea4bcdafe026f29ec2e71373316ec597ab", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "9cf304ea4bcdafe026f29ec2e71373316ec597ab", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-alpha.3.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-alpha.3" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-alpha.3" + }, + "16.0.0-alpha.4": { + "_from": "packages/react-dom.tgz", + "_id": "react-dom@16.0.0-alpha.4", + "_nodeVersion": "6.9.0", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/react-dom-16.0.0-alpha.4.tgz_1489420671644_0.07928068120963871" + }, + "_npmUser": { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + "_npmVersion": "3.10.8", + "_resolved": "file:packages/react-dom.tgz", + "_shasum": "feb5782eea58d945afcb0bed110deda9df75818a", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "feb5782eea58d945afcb0bed110deda9df75818a", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-alpha.4.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-alpha.4" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-alpha.4" + }, + "16.0.0-alpha.5": { + "_from": "packages/react-dom.tgz", + "_id": "react-dom@16.0.0-alpha.5", + "_nodeVersion": "6.9.1", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/react-dom-16.0.0-alpha.5.tgz_1490126560758_0.15045873820781708" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "3.10.8", + "_resolved": "file:packages/react-dom.tgz", + "_shasum": "2e1b8efc3ba1c3000210ad364a6b385f1306655c", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "2e1b8efc3ba1c3000210ad364a6b385f1306655c", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-alpha.5.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-alpha.5" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-alpha.5" + }, + "16.0.0-alpha.6": { + "_from": "packages/react-dom.tgz", + "_id": "react-dom@16.0.0-alpha.6", + "_nodeVersion": "6.9.1", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/react-dom-16.0.0-alpha.6.tgz_1490395627521_0.5598961692303419" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "3.10.8", + "_resolved": "file:packages/react-dom.tgz", + "_shasum": "a70fa5dd62d7cc11c6a01868b45de95dc2095c90", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "a70fa5dd62d7cc11c6a01868b45de95dc2095c90", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-alpha.6.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "dist/", + "lib/", + "index.js", + "server.js" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-alpha.6" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-alpha.6" + }, + "16.0.0-alpha.7": { + "_from": ".", + "_id": "react-dom@16.0.0-alpha.7", + "_nodeVersion": "6.0.0", + "_npmOperationalInternal": { + "host": "packages-12-west.internal.npmjs.com", + "tmp": "tmp/react-dom-16.0.0-alpha.7.tgz_1491504829111_0.5904473909176886" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "3.8.6", + "_shasum": "28c421402680b3409fae12320e64f7d64ae74483", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "28c421402680b3409fae12320e64f7d64ae74483", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-alpha.7.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "index.js", + "server.js", + "cjs/", + "umd/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-alpha.7" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-alpha.7" + }, + "16.0.0-alpha.8": { + "_from": ".", + "_id": "react-dom@16.0.0-alpha.8", + "_nodeVersion": "6.0.0", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/react-dom-16.0.0-alpha.8.tgz_1491587348726_0.2399351738858968" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "3.8.6", + "_shasum": "a46ab177f08fff1f3601b06ebd7867d2b97bc306", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "a46ab177f08fff1f3601b06ebd7867d2b97bc306", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-alpha.8.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "index.js", + "server.js", + "cjs/", + "umd/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-alpha.8" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-alpha.8" + }, + "16.0.0-alpha.9": { + "_from": ".", + "_id": "react-dom@16.0.0-alpha.9", + "_nodeVersion": "6.9.1", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/react-dom-16.0.0-alpha.9.tgz_1491947137266_0.6918263349216431" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "3.10.8", + "_shasum": "8630250db53956b974220ddc11ce3e3cc4efff2b", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.6" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "8630250db53956b974220ddc11ce3e3cc4efff2b", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-alpha.9.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "index.js", + "server.js", + "cjs/", + "umd/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "sebastian@calyptus.eu", + "name": "sebmarkbage" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + }, + { + "email": "tomocchino@gmail.com", + "name": "tomocchino" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-alpha.9" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-alpha.9" + }, + "16.0.0-beta.1": { + "_id": "react-dom@16.0.0-beta.1", + "_nodeVersion": "8.2.1", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-16.0.0-beta.1.tgz_1501099429302_0.924584420165047" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "5.3.0", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.6" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "integrity": "sha512-ebPdkHAPQbrUmQSKYXki1WYqdy+lJa9ryEFQacn7wENk6DvpO8q978bVdprMUg/N6XXXrTXtrlNMo9RuJ/wdvg==", + "shasum": "be3e958578dae850884f7abd7ba559f9dfb39815", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-beta.1.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "index.js", + "server.js", + "node-stream.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-beta.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-beta.1" + }, + "16.0.0-beta.2": { + "_from": ".", + "_id": "react-dom@16.0.0-beta.2", + "_nodeVersion": "8.1.2", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-16.0.0-beta.2.tgz_1501175225745_0.3876317834947258" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "4.6.1", + "_shasum": "55352e02d93112f06cce7f83ca378a1beae465d9", + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.6" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "55352e02d93112f06cce7f83ca378a1beae465d9", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-beta.2.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "index.js", + "server.js", + "node-stream.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-beta.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-beta.2" + }, + "16.0.0-beta.3": { + "_id": "react-dom@16.0.0-beta.3", + "_nodeVersion": "8.2.1", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-16.0.0-beta.3.tgz_1501801659011_0.26547439326532185" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "5.3.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.6" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "integrity": "sha512-tk0fJqKXQTr2uFyDcSmlIygUHQjpLQiQGaU8gMUPDYUwzy5XRE7AXuA2dk+il2+76I3vLI1YgvCELYjOGaZoHQ==", + "shasum": "6b662e8db127d14565b98799c13532044c7768b9", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-beta.3.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-beta.3" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-beta.3" + }, + "16.0.0-beta.4": { + "_id": "react-dom@16.0.0-beta.4", + "_nodeVersion": "8.2.1", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-16.0.0-beta.4.tgz_1502208588639_0.7636195805389434" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "5.3.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.6" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "integrity": "sha512-tJ95rbPQwGTvXNdjRzvKWS22aEl8hSPuyeF+TabV9+XEsZDoXnt66qr0KWWjVzG5MpserGK2J9liejc+w+gTzw==", + "shasum": "ce7fc85f26811c56af6de0055abadc32dfb86c89", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-beta.4.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-beta.4" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-beta.4" + }, + "16.0.0-beta.5": { + "_id": "react-dom@16.0.0-beta.5", + "_nodeVersion": "8.2.1", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-16.0.0-beta.5.tgz_1502213255191_0.7491100074257702" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "5.3.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.6" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "integrity": "sha512-tOaXMe9m8f7GCUKS0xS/CZ5mkruIr5r3uiD/wTS2oadKaTU/wMX3dxo/Fxu/2Rrz/tm3xTBMEjIg81UkZTl23w==", + "shasum": "c9c2e5c059576f77b55e8c3959535d63cb1227e5", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-beta.5.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-beta.5" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-beta.5" + }, + "16.0.0-rc.1": { + "_from": ".", + "_id": "react-dom@16.0.0-rc.1", + "_nodeVersion": "6.11.2", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-16.0.0-rc.1.tgz_1504739619977_0.5365656756330281" + }, + "_npmUser": { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + "_npmVersion": "3.10.10", + "_shasum": "04bdb5fc0cc165d5299c70acb03872838ac6d8d9", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.6" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "04bdb5fc0cc165d5299c70acb03872838ac6d8d9", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-rc.1.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "ben@benalpert.com", + "name": "spicyj" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-beta.5" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-rc.1" + }, + "16.0.0-rc.2": { + "_from": ".", + "_id": "react-dom@16.0.0-rc.2", + "_nodeVersion": "6.9.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-16.0.0-rc.2.tgz_1504755410867_0.4404754228889942" + }, + "_npmUser": { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + "_npmVersion": "3.10.8", + "_shasum": "f94dc1c350acd67284233558996a8293d7aa99c7", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.6" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "f94dc1c350acd67284233558996a8293d7aa99c7", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-rc.2.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "npm2@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-beta.5" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-rc.2" + }, + "16.0.0-rc.3": { + "_from": ".", + "_id": "react-dom@16.0.0-rc.3", + "_nodeVersion": "8.1.2", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-16.0.0-rc.3.tgz_1505394579016_0.14666322246193886" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "4.6.1", + "_shasum": "bd4e4d2abd464df5149a062b45fe796bbb804c2c", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.9", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0", + "prop-types": "^15.5.6" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "shasum": "bd4e4d2abd464df5149a062b45fe796bbb804c2c", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0-rc.3.tgz" + }, + "files": [ + "LICENSE", + "PATENTS", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "BSD-3-Clause", + "main": "index.js", + "maintainers": [ + { + "email": "npm2@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0-beta.5" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.0-rc.3" + }, + "16.0.1": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.0.1", + "_nodeVersion": "8.9.4", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.0.1_1533149232613_0.3875040801457095" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "5.6.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 23, + "integrity": "sha512-gGJNmuS0VpkJsNStpzplcgc4iuHJ2X8rjiiaY/5YfHrsAd2cw1JkMXD6Z1kBOed8rDUNrRYrnDYptnhFghFFhA==", + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJbYgAxCRA9TVsSAnZWagAAF5QP/j8CSSiFj3V5jZtrRSJV\nQscQZ/Tei0GvpvkWf2Ih0xBrZEpjExJz8ozXgtFjbC6tyB/R01yDv48/rl3s\ntXrn0BltQ1SZT9SHwCtna6AHKRDGmZwFuxO6ZpHq0w9DDNdCOL2UbbfmngaG\nPD23iCULuZ1fYmR2gNlvJhdR7davRNP20RSFRJpgVphEoPior1HrUgREM2P4\ncPwdruJdGuKVZNDTZzVoeild9F8SdgK6oV3IxSmZGkzfcFHNg4eliGUGXNvZ\nCLiaPOxkT4o4qi8oE06GzMNtO6K4zniqg1og3sDR1jdv2hlA18YUXRQpcGM/\nBMg/VInOIz6oQ0YOwv0EFbqqA5B4/bzGJJXrdrlpmNadRpQgLhz1ZVZ7IFNk\nis6ZUzX0W0P42zJMN/dev3ryQOtOlc8qiATj6VsvrCJ1l6QsszZmTn2+rKG9\nH7mN2rRGdzf7ar50VbTgNS7OxBCYMt0o1HIe89RKdOlvpSMvLh7ay//4kO7A\nVqoE69sf+ljKzGU0lRizK2UGyYm6nwUNYkT1sG/KFJcfaI7QzZCNRwQIgsr2\n6S/Hpo4tTBxT40/PM4xF/2VcGiJN9zXvAxDTy14FZ3EWS5r5AZbEhnpvlpVO\nFQk8GUuprNe5zqon6BkCF8f8Iycngh3NcYfFdsvrmRofoRd933plml0bD96m\nR+se\r\n=O90c\r\n-----END PGP SIGNATURE-----\r\n", + "shasum": "bd2fc18d2bb81645cb807bb27a03a14d8b796e6d", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.1.tgz", + "unpackedSize": 2100602 + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://facebook.github.io/react/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.0.1" + }, + "16.1.0": { + "_id": "react-dom@16.1.0", + "_nodeVersion": "8.6.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-16.1.0.tgz_1510239876599_0.8195754773914814" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "5.5.1", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "deprecated": "This version of react-dom/server contains a minor vulnerability. Please update react-dom to 16.1.2 or 16.4.2+. Learn more: https://fb.me/cve-2018-6341", + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "integrity": "sha512-i9in5qW3H2PDinUPD9bnQK7tLAD8LhjYQ+fXi3nJOvVnxOO3ErHq6RNEnKY7pbjTPt155e74q7al8eBUuyLtew==", + "shasum": "ab6fd2a285096f388aeba51919a573d06c9bdde4", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.1.0.tgz" + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.1.0" + }, + "16.1.0-beta": { + "_id": "react-dom@16.1.0-beta", + "_nodeVersion": "8.4.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-16.1.0-beta.tgz_1509662712401_0.24209285038523376" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "5.3.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "integrity": "sha512-/RjFzNDN3W2msyJTNavYRBCncYkrgIY4Z6N/vnVBzSH7SlLV8GnbA7wgkLp1RTLWRGfo7s5T47hXVpWlSPb+kQ==", + "shasum": "15a1f9d021fc19d7dfa23e627f89994d7e7c7514", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.1.0-beta.tgz" + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.1.0-beta" + }, + "16.1.0-beta.1": { + "_id": "react-dom@16.1.0-beta.1", + "_nodeVersion": "8.6.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-16.1.0-beta.1.tgz_1510066654360_0.7693996331654489" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "5.5.1", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "integrity": "sha512-s+skjMlsY55mrpIWT5OV7G6Ys/1qrjOYdenBikw7a3Vfajb2r3IP/sOt51f31FznxhuTXUWXpgJzp4R9XdCtOA==", + "shasum": "74fce0fc487eaea9136169e95ebc8b8294f4949b", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.1.0-beta.1.tgz" + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.1.0-beta.1" + }, + "16.1.0-rc": { + "_id": "react-dom@16.1.0-rc", + "_nodeVersion": "8.9.1", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-16.1.0-rc.tgz_1510186394105_0.9208172427024692" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "5.5.1", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "integrity": "sha512-3YgV4JI2gNWaRnbft2siwQ7ptb7EVtCQQu2R1pCeZX/Rdjeo91AsZbs7KjrYIwQ2U8W4MSxFWyAripYnprzfJQ==", + "shasum": "691d10e13e41c50f7f145a6f2aba11e7cfa5be99", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.1.0-rc.tgz" + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.1.0-rc" + }, + "16.1.1": { + "_id": "react-dom@16.1.1", + "_nodeVersion": "8.6.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-16.1.1.tgz_1510589592557_0.7087671686895192" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "5.5.1", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "deprecated": "This version of react-dom/server contains a minor vulnerability. Please update react-dom to 16.1.2 or 16.4.2+. Learn more: https://fb.me/cve-2018-6341", + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "integrity": "sha512-q06jiwST8SEPAMIEkAsu7BgynEZtqF87VrTc70XsW7nxVhWEu2Y4MF5UfxxHQO/mNtQHQWP0YcFxmwm9oMrMaQ==", + "shasum": "b2e331b6d752faf1a2d31399969399a41d8d45f8", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.1.1.tgz" + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.1.1" + }, + "16.1.2": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.1.2", + "_nodeVersion": "8.9.4", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.1.2_1533149718204_0.01916830763880184" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "5.6.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 25, + "integrity": "sha512-e2iKeys/pJcIzszO+9EYxnTus6JI3pftzhXGG8K9B9co2SljknimfuY1/VlAG9nDBipDAc8jkIuuhlz+EiGL+g==", + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJbYgIWCRA9TVsSAnZWagAAaQsP+gN5z1NLMgm8EGfVLLhL\nb0wjm2Wklo40nI7okMYVHVz7PmiKWm1LvPC0cywEAlEYCgqH0K1du816wo7N\n+04m6vJc37Q2DC7k8jw7q5ouZ1aW6K+j8f6eI8EsE3ve3ADG/C3l5hK9QIRi\nK6Od3gM1kbPyhjMbioWvAUbMpakK952FEo3JByGb+Ang4djNcMJxSRb6Q/WY\n/rAySfyKiyt2cxCxRmJygFW/sQxvz5QQPm945Do5q+QGZqZ3qsBspFokc8e+\nVmps4ZeM0+I+Bmgv5TX3YBVOS6zfTWJ6XuKzZfJZFKYifYdjd6jmqaIdObZR\nAa+vkoLMrGMqNvbOF7RYKOONh4dWkTeHp6xpkHEDfc9FjE+Es1jGeWIaJ+Si\nEnDqoj1ktRcaYV37UHTLdOqX3HTBduG9VdtxvF6NUKFJLcs6J/AxGUryj9VQ\ncZJSnLh1lHe6VTZaDQyd3i/KgMk/wetIZRzR5qW4QTV2KJ7HkOX/t2OnDBJp\nPn7oFRoxrNoVsEDvh/r335CPgFqdKjCdNZzfzB4qH//SkuFfoznBmrA1XDBr\nu2ck4jvVudCLiQFLqabsGGgcuka+3L2eTM4KN5G0GXVeAuaen6vIA6IkHilM\nII2+vQ1Jy65+CYIYlcjdj3RxFVQg8fDOoG9Zd7AuuF6l8s4WGHlej4iMEakJ\nbkuD\r\n=iGk+\r\n-----END PGP SIGNATURE-----\r\n", + "shasum": "c4011fa5183fcbdf34344472935ef920d8dc7179", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.1.2.tgz", + "unpackedSize": 1808376 + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "readme": "# `react-dom`\n\nThis package serves as the entry point of the DOM-related rendering paths. It is intended to be paired with the isomorphic React, which will be shipped as `react` to npm.\n\n## Installation\n\n```sh\nnpm install react react-dom\n```\n\n## Usage\n\n### In the browser\n\n```js\nvar React = require('react');\nvar ReactDOM = require('react-dom');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOM.render(, node);\n```\n\n### On the server\n\n```js\nvar React = require('react');\nvar ReactDOMServer = require('react-dom/server');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOMServer.renderToString();\n```\n\n## API\n\n### `react-dom`\n\n- `findDOMNode`\n- `render`\n- `unmountComponentAtNode`\n\n### `react-dom/server`\n\n- `renderToString`\n- `renderToStaticMarkup`\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.1.2" + }, + "16.2.0": { + "_id": "react-dom@16.2.0", + "_nodeVersion": "8.4.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-16.2.0.tgz_1511904748974_0.6541443960741162" + }, + "_npmUser": { + "email": "clement.hoang24@gmail.com", + "name": "clemmy" + }, + "_npmVersion": "5.3.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "deprecated": "This version of react-dom/server contains a minor vulnerability. Please update react-dom to 16.2.1 or 16.4.2+. Learn more: https://fb.me/cve-2018-6341", + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "integrity": "sha512-zpGAdwHVn9K0091d+hr+R0qrjoJ84cIBFL2uU60KvWBPfZ7LPSrfqviTxGHWN0sjPZb2hxWzMexwrvJdKePvjg==", + "shasum": "69003178601c0ca19b709b33a83369fe6124c044", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.2.0.tgz" + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "clement.hoang24@gmail.com", + "name": "clemmy" + }, + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.2.0" + }, + "16.2.1": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.2.1", + "_nodeVersion": "8.9.4", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.2.1_1533149839932_0.9716707774474977" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "5.6.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 27, + "integrity": "sha512-0ujGgYnpX0GlaAjUfwU7ddy0DjuzPmTHHi2SlPolGv7hAyUpK7XA7WZcxit5ZcU7cW5QU1HJjlS3eMn42tSfYQ==", + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJbYgKQCRA9TVsSAnZWagAAih8P/jv8nSlwsj6vBZh3LANm\n9tkcFIQ8PPpHmeitaDqoF3HtNq7+oMmWqGp08JYjB37v+bEheexysWLAEjy4\ndbMFIK92XT9hpUcVPojnogoHpMqkau4iuVQTUKu65tQjgQ5WaRpn5kkDgb1h\nnJ65Ncn99SuFIzj02mwLeviX/VMvCtkWsgQTJoB9jf3CABWVXBFgLgZij7DZ\nXDJzZ3ktbVLhpV4gK5cKg2FIygNGRfkY6EfxkKx+iK/LGmVk8DhsE47WE8zJ\nACMDGxl1h9tLE9+9oHB5ZT/Pb+mWRAICvnL6Q/B4Y1isJvWGEjULivB8KiaP\nftdtrtbRsdl6ynRWFpU4MN2WsK5CTHQe5de/6WMF/ocA7RQqOxVsdkrMK21Z\n8nwN/6uy+Tn6uY0dhUPQ5KIoWhULQp1ESZ9rwztmdiuUv/7t0iK3Y3G4ENPJ\nB2w2B7BfXsKmnfZmpKGLslr6rVsiSLKhb4itAw24mC8VNaAV5doopoZT4A82\nAqgX87T+oPV2pFYWxLdoimInth2TsyLxoWY9jOyevm3GRX/K7E7fuO0e+ec6\n5moK3puLRMCSEi0IGX9tn/3XNJa8U2YypqNcyzSanQm2Q0RXOV9K+GNAEji1\nn8t9lJYQlJQKLN2MJtg6IjnPTiKcNuY/+VI7Ol684kueNEFNpGsP1ewqpBF4\n83R2\r\n=QCtJ\r\n-----END PGP SIGNATURE-----\r\n", + "shasum": "5cfb32f66267ece7b3850466bf3b219d4911fc1a", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.2.1.tgz", + "unpackedSize": 1863665 + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "readme": "# `react-dom`\n\nThis package serves as the entry point of the DOM-related rendering paths. It is intended to be paired with the isomorphic React, which will be shipped as `react` to npm.\n\n## Installation\n\n```sh\nnpm install react react-dom\n```\n\n## Usage\n\n### In the browser\n\n```js\nvar React = require('react');\nvar ReactDOM = require('react-dom');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOM.render(, node);\n```\n\n### On the server\n\n```js\nvar React = require('react');\nvar ReactDOMServer = require('react-dom/server');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOMServer.renderToString();\n```\n\n## API\n\n### `react-dom`\n\n- `findDOMNode`\n- `render`\n- `unmountComponentAtNode`\n\n### `react-dom/server`\n\n- `renderToString`\n- `renderToStaticMarkup`\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.2.1" + }, + "16.3.0": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.3.0", + "_nodeVersion": "8.4.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.3.0_1522354580935_0.5787352268274351" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "5.6.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "deprecated": "This version of react-dom/server contains a minor vulnerability. Please update react-dom to 16.3.3 or 16.4.2+. Learn more: https://fb.me/cve-2018-6341", + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 27, + "integrity": "sha512-xT/FxawAurL6AV8YtAP7LkdDJFFX2vvv17AqFLQRF81ZtWLXkV/0dcAaiFIy0lmoQEFT931TU9aaH+5dBUxTcw==", + "shasum": "b318e52184188ecb5c3e81117420cca40618643e", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-16.3.0.tgz", + "unpackedSize": 2005802 + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "clement.hoang24@gmail.com", + "name": "clemmy" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.3.0" + }, + "16.3.0-alpha.0": { + "_id": "react-dom@16.3.0-alpha.0", + "_nodeVersion": "8.4.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom-16.3.0-alpha.0.tgz_1517605553404_0.6313903259579092" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "5.3.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "integrity": "sha512-ubSErSbLySqvD+BQuEl5Rvp6u/d13DrmMVQkmBbMwAAr4WvXuFMAfth3ZTlL7wpzK04bNWuGRj0qXnm4VRl7Uw==", + "shasum": "e3ff7cf8f26639072c0c41ce78eb9674da886c60", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.3.0-alpha.0.tgz" + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "clement.hoang24@gmail.com", + "name": "clemmy" + }, + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.3.0-alpha.0" + }, + "16.3.0-alpha.1": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.3.0-alpha.1", + "_nodeVersion": "8.4.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.3.0-alpha.1_1518461370862_0.21064586528920426" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "5.3.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 27, + "integrity": "sha512-1HYvdyslO5D0d6C1mGqzyz7V+wzjLcUcj14a7DEEseV3eEn6fPvq4wAGHguG+QbXHkelrrkxh2sgLr/vFA0UYA==", + "shasum": "5a87746c59f1c8cd4b6d1e1c961bcb3218af88e3", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-16.3.0-alpha.1.tgz", + "unpackedSize": 1941224 + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "clement.hoang24@gmail.com", + "name": "clemmy" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0 || 16.3.0-alpha.1" + }, + "readme": "# `react-dom`\n\nThis package serves as the entry point of the DOM-related rendering paths. It is intended to be paired with the isomorphic React, which will be shipped as `react` to npm.\n\n## Installation\n\n```sh\nnpm install react react-dom\n```\n\n## Usage\n\n### In the browser\n\n```js\nvar React = require('react');\nvar ReactDOM = require('react-dom');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOM.render(, node);\n```\n\n### On the server\n\n```js\nvar React = require('react');\nvar ReactDOMServer = require('react-dom/server');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOMServer.renderToString();\n```\n\n## API\n\n### `react-dom`\n\n- `findDOMNode`\n- `render`\n- `unmountComponentAtNode`\n\n### `react-dom/server`\n\n- `renderToString`\n- `renderToStaticMarkup`\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.3.0-alpha.1" + }, + "16.3.0-alpha.2": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.3.0-alpha.2", + "_nodeVersion": "8.4.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.3.0-alpha.2_1521059410951_0.7007911252845114" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "5.6.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 27, + "integrity": "sha512-T8zKRXRDbs34byvtVrnW2rE0xGm4xoEY0Ag6FQXVD68jbSeHCgf+DF+cjNqCO+ldz9d9D/YAuMOlsvYYWqBtMA==", + "shasum": "a970b6185684941e89a568c09321d22643457cb6", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-16.3.0-alpha.2.tgz", + "unpackedSize": 1973919 + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "clement.hoang24@gmail.com", + "name": "clemmy" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0 || 16.3.0-alpha.2" + }, + "readme": "# `react-dom`\n\nThis package serves as the entry point of the DOM-related rendering paths. It is intended to be paired with the isomorphic React, which will be shipped as `react` to npm.\n\n## Installation\n\n```sh\nnpm install react react-dom\n```\n\n## Usage\n\n### In the browser\n\n```js\nvar React = require('react');\nvar ReactDOM = require('react-dom');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOM.render(, node);\n```\n\n### On the server\n\n```js\nvar React = require('react');\nvar ReactDOMServer = require('react-dom/server');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOMServer.renderToString();\n```\n\n## API\n\n### `react-dom`\n\n- `findDOMNode`\n- `render`\n- `unmountComponentAtNode`\n\n### `react-dom/server`\n\n- `renderToString`\n- `renderToStaticMarkup`\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.3.0-alpha.2" + }, + "16.3.0-alpha.3": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.3.0-alpha.3", + "_nodeVersion": "8.4.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.3.0-alpha.3_1521748135209_0.08844130403408434" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "5.6.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 27, + "integrity": "sha512-qitn+6WjB+SQyaWoiP1UExOtIQZYB070IQet5Afayhz89m/jRLh2ljESZvEZoZPpNV6La/u+q0OEYXovMdbbCA==", + "shasum": "edfc7a78263c54bcd7cc396b68163b0aed46be58", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-16.3.0-alpha.3.tgz", + "unpackedSize": 1990198 + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "clement.hoang24@gmail.com", + "name": "clemmy" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0 || 16.3.0-alpha.3" + }, + "readme": "# `react-dom`\n\nThis package serves as the entry point of the DOM-related rendering paths. It is intended to be paired with the isomorphic React, which will be shipped as `react` to npm.\n\n## Installation\n\n```sh\nnpm install react react-dom\n```\n\n## Usage\n\n### In the browser\n\n```js\nvar React = require('react');\nvar ReactDOM = require('react-dom');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOM.render(, node);\n```\n\n### On the server\n\n```js\nvar React = require('react');\nvar ReactDOMServer = require('react-dom/server');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOMServer.renderToString();\n```\n\n## API\n\n### `react-dom`\n\n- `findDOMNode`\n- `render`\n- `unmountComponentAtNode`\n\n### `react-dom/server`\n\n- `renderToString`\n- `renderToStaticMarkup`\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.3.0-alpha.3" + }, + "16.3.0-rc.0": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.3.0-rc.0", + "_nodeVersion": "8.4.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.3.0-rc.0_1522203303302_0.9109638524641006" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "5.6.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 27, + "integrity": "sha512-+iyeHHeVPKI5VIYQLVRxkEgRG01FnLLlGOvkdAvv2c29AaXBKbRu4UBJ8boRkjASLnacxGR9jmpVZZgBtGz1Zw==", + "shasum": "18282bcc98496197877b9365467362caf74b8e45", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-16.3.0-rc.0.tgz", + "unpackedSize": 2003136 + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "clement.hoang24@gmail.com", + "name": "clemmy" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0 || 16.3.0-rc.0" + }, + "readme": "# `react-dom`\n\nThis package serves as the entry point of the DOM-related rendering paths. It is intended to be paired with the isomorphic React, which will be shipped as `react` to npm.\n\n## Installation\n\n```sh\nnpm install react react-dom\n```\n\n## Usage\n\n### In the browser\n\n```js\nvar React = require('react');\nvar ReactDOM = require('react-dom');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOM.render(, node);\n```\n\n### On the server\n\n```js\nvar React = require('react');\nvar ReactDOMServer = require('react-dom/server');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOMServer.renderToString();\n```\n\n## API\n\n### `react-dom`\n\n- `findDOMNode`\n- `render`\n- `unmountComponentAtNode`\n\n### `react-dom/server`\n\n- `renderToString`\n- `renderToStaticMarkup`\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.3.0-rc.0" + }, + "16.3.1": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.3.1", + "_nodeVersion": "8.6.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.3.1_1522802356174_0.4281071102651548" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "5.6.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "deprecated": "This version of react-dom/server contains a minor vulnerability. Please update react-dom to 16.3.3 or 16.4.2+. Learn more: https://fb.me/cve-2018-6341", + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 27, + "integrity": "sha512-2Infg89vzahq8nfVi1GkjPqq0vrBvf0f3T0+dTtyjq4f6HKOqKixAK25Vr593O3QTx4kw/vmUtAJwerlevNWOA==", + "shasum": "6a3c90a4fb62f915bdbcf6204422d93a7d4ca573", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-16.3.1.tgz", + "unpackedSize": 2015658 + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "clement.hoang24@gmail.com", + "name": "clemmy" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.3.1" + }, + "16.3.2": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.3.2", + "_nodeVersion": "8.6.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.3.2_1523892762824_0.9372514927019884" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "5.6.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "deprecated": "This version of react-dom/server contains a minor vulnerability. Please update react-dom to 16.3.3 or 16.4.2+. Learn more: https://fb.me/cve-2018-6341", + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 27, + "integrity": "sha512-MMPko3zYncNrz/7gG17wJWUREZDvskZHXOwbttzl0F0L3wDmToyuETuo/r8Y5yvDejwYcRyWI1lvVBjLJWFwKA==", + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJa1MIbCRA9TVsSAnZWagAACkAP/1M/2+5bhCsGK4PDgnYd\nSMopg4hj5UvMGr4P/3YzAHbGo1p91/czstYkVFTCFxi2hTnXTarzuRh9vnox\nXFSrNC+jhzMgwdasghVvyoGxP+NMFicvfekfi6S3OC/8PYiC99gHJOfqH7lO\nO+G0wFkdXY7UYsx1dwiqaTVm//VFS9AvtP+lC9vwp58GbbdiQAAtCHNWVcQp\n6m2QuWMgjMRWKaZonG8WboE6UCOckr50Gfc/0a5zDJaBU+gpj7yLgIiIAT/w\nA39X3KSRWfa35vIEeH6v+mNqF8n9hVPo4l1SqQe6UajCGzWhd8EmcFKWFNYf\nsZM+dolrdI3RnKH/Z+Lr630jiWk8gm3JsLnFAL4JWSpUlVlUXu4D7GBLkANd\n8l/dTvayxksK/MYo97B8ewabtQDWkeBJPigccc6VtivD6VztBoF4LQiMSVzP\nbZmIDOy/cb4q00OFALvL+ZELYKX80cO1x/HmF5ULlDcy95IeV2kcJJq+SI6a\nXi3zTLbWNcgQoUvHJwR/xg75Yz8BpN614SDLhCy3YsrXfv5LdCDLf6LB2nAA\n4SAbCTuvL0FqB6AADfN8kKrrTpnVICSBATBWeuXZTDB5xu8kY0i7BkTiolaG\nhCoDR5dzqlCr4FUj4/OGcWwpxQRhgi8gFu4KeNV4a1WGB/6Az3J0Noqb/uXV\n9ORw\r\n=UEJp\r\n-----END PGP SIGNATURE-----\r\n", + "shasum": "cb90f107e09536d683d84ed5d4888e9640e0e4df", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.3.2.tgz", + "unpackedSize": 2020779 + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "clement.hoang24@gmail.com", + "name": "clemmy" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.3.2" + }, + "16.3.3": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.3.3", + "_nodeVersion": "8.9.4", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.3.3_1533149938317_0.430968982900233" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "5.6.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 27, + "integrity": "sha512-ALCp7ZbSGkqRDtQoZozKVNgwXMxbxf/IGOUMC2A0yF6JHeZrS8e2cOotPT87Vf4b7PKCuUVKU4/RDEXxToA/yA==", + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJbYgLyCRA9TVsSAnZWagAA0P0P/ib8EQHdDKcMF84tipyp\nEXwYXys9LAI7MKUKC1mV7c6yaxrkCNXlNIfDFmFJlZN7gNiUHvcFuXnbZ8uu\nm403lOsh+orsQPO0aRtkolqE8EdZVamjZNdUEQwwfmhXosxjVZ48VMIzth2s\nZ9s5HJJ4KI1Qh+h+cwNBu7obm2w+cU1+jqtcPQgwuIHhpZPFyBYQl7I/hL5n\nC1LeesvfcesjgEBYKqvANiDWP/IFV2oAZ8We6ecynGRxSGr9Op+F23W4tjr+\noZfIzj6nVrdyRDJ+uyY+/bfHEbm/9LmSRqKVPl3lyf59ICEEFziFh6kmvRmK\nDH2dfXJ/GiaZdvivtNHa/po3uF6Skz6GupzNFawQ885q09e5PepCJ7lG4S3E\nNcaftjpEujB6N14sdTHaqBJHU6F7ehxLPSQ1up2/ZI0EkMHy79svJMBjBq6G\nbxV2cxjcNNvJqIorqSIrNy0xKfWrhHxS0OtWjxrnYZsTdedaTywIVWtUUGNB\nUdzGlPeG2JZ8VjwUMlBD7pMj9TrxiPZpIkuxtyBsGv5xR5EDRTvDy/F06qo1\nhsZrh8K8+7Bvsf3WmVoEvjScEIJDDiNyPEfRihZOdj+jZwNN9QlRqil7FdUz\n+yoRWpAiLpmJiotkdzXlofItPNDx8fAWtv6mm18TvthfezS3Zcu3b4TB9oV0\nIeHl\r\n=qmS/\r\n-----END PGP SIGNATURE-----\r\n", + "shasum": "af4c2aef9f6a66251a46da50253c860a67ae66d9", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.3.3.tgz", + "unpackedSize": 2020935 + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "readme": "# `react-dom`\n\nThis package serves as the entry point of the DOM-related rendering paths. It is intended to be paired with the isomorphic React, which will be shipped as `react` to npm.\n\n## Installation\n\n```sh\nnpm install react react-dom\n```\n\n## Usage\n\n### In the browser\n\n```js\nvar React = require('react');\nvar ReactDOM = require('react-dom');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOM.render(, node);\n```\n\n### On the server\n\n```js\nvar React = require('react');\nvar ReactDOMServer = require('react-dom/server');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOMServer.renderToString();\n```\n\n## API\n\n### `react-dom`\n\n- `findDOMNode`\n- `render`\n- `unmountComponentAtNode`\n\n### `react-dom/server`\n\n- `renderToString`\n- `renderToStaticMarkup`\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.3.3" + }, + "16.4.0": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.4.0", + "_nodeVersion": "8.9.1", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.4.0_1527122390382_0.4414275802610099" + }, + "_npmUser": { + "email": "acdlite@me.com", + "name": "acdlite" + }, + "_npmVersion": "5.5.1", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "deprecated": "This version of react-dom/server contains a minor vulnerability. Please update react-dom to 16.4.2+. Learn more: https://fb.me/cve-2018-6341", + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 27, + "integrity": "sha512-bbLd+HYpBEnYoNyxDe9XpSG2t9wypMohwQPvKw8Hov3nF7SJiJIgK56b46zHpBUpHb06a1iEuw7G3rbrsnNL6w==", + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJbBgnXCRA9TVsSAnZWagAALREP/1Z/PVf+FmgHOEU2lzxS\n7pCFMTo+n2pDHg2l+SyEV4O0qLXbYEXBJ8aeXlrjAUFHTAKUvocMQYfC0oBt\nzM9m7S/qEc0bhL/WbRVFO0nEe+5hsK3oN7Oz+rT2DlcEyV6Ek6844LpRTt23\nW6F65XbdEuWV5BeiTt3w4QWHvBgQpdRvpCW9Gr9JF3jBnAMswKBa6QB6IK18\nz/rX1Ss4CRRz7zkGDUEnqcZq5gg/kpZ51f+hUsYnZt5Jw7U5KktEL3+2khBb\nk8CZPju1pCOr/O3zdtr/UWX1H41BE61L5NuOlKj3Q0659zXTJElj8hyU26gE\npmhYwH6WbAXbybvvZawdw3jXFi87334zdrqBCCSs3Qcg/FGuVTTYEpmCMAsT\nbUz6yKkSalv2MJxSvPrhiYutrbEh4w4J892CPF/EBBb4D6nTo5pRoDrMQgyv\ngBDGcpD2NmtJKzmlt9iPvW9ugGesAUBKlBnbB4wwWKGFfKn0mF+WgEq2LvRA\nXF0cu6zb+qVbRv0uiWq7LAUBeJCqn+KKAyEYITmSr9ourjemKQMaRPcgHrZs\nwtKoqhr2KZQ7+nvskmd+ulL1IacXj7R/YCBlorGcjyEzzyKG1/MWWyDIutZ5\nvoa6pBZXYdk10Cq5m7O/dzaVV2rshzY5sVa+HGSs82F6T3QCZX66hWnLq5jD\neZ+J\r\n=3PP0\r\n-----END PGP SIGNATURE-----\r\n", + "shasum": "099f067dd5827ce36a29eaf9a6cdc7cbf6216b1e", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.4.0.tgz", + "unpackedSize": 2042338 + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "clement.hoang24@gmail.com", + "name": "clemmy" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.4.0" + }, + "16.4.0-alpha.0911da3": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.4.0-alpha.0911da3", + "_nodeVersion": "8.9.1", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.4.0-alpha.0911da3_1519697646086_0.3255505555520246" + }, + "_npmUser": { + "email": "acdlite@me.com", + "name": "acdlite" + }, + "_npmVersion": "5.5.1", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 27, + "integrity": "sha512-i5c2iluZSgViNqRrVI5k2UIEf211ILW1tBNB0Xo9QRiBdTWqcHKmFzFG76r9NYmW6RTLjZaSKZCL2Xxpacjhbw==", + "shasum": "4f50568a3f44fade1da7d0747b1308d46281210c", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-16.4.0-alpha.0911da3.tgz", + "unpackedSize": 2022772 + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "clement.hoang24@gmail.com", + "name": "clemmy" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0 || 16.4.0-alpha.0911da3" + }, + "readme": "# `react-dom`\n\nThis package serves as the entry point of the DOM-related rendering paths. It is intended to be paired with the isomorphic React, which will be shipped as `react` to npm.\n\n## Installation\n\n```sh\nnpm install react react-dom\n```\n\n## Usage\n\n### In the browser\n\n```js\nvar React = require('react');\nvar ReactDOM = require('react-dom');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOM.render(, node);\n```\n\n### On the server\n\n```js\nvar React = require('react');\nvar ReactDOMServer = require('react-dom/server');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOMServer.renderToString();\n```\n\n## API\n\n### `react-dom`\n\n- `findDOMNode`\n- `render`\n- `unmountComponentAtNode`\n\n### `react-dom/server`\n\n- `renderToString`\n- `renderToStaticMarkup`\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.4.0-alpha.0911da3" + }, + "16.4.0-alpha.3174632": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.4.0-alpha.3174632", + "_nodeVersion": "8.9.1", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.4.0-alpha.3174632_1519448872800_0.004975957281870169" + }, + "_npmUser": { + "email": "acdlite@me.com", + "name": "acdlite" + }, + "_npmVersion": "5.5.1", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 27, + "integrity": "sha512-BVK2V2xsBNDq5KoRL36n5L3Xr+oNq7LLh2uW58c/Zmo1+YJp/FZTj9mHGLp5Df2qo73yoN2H0FrpYXR7TANHwQ==", + "shasum": "f0464b7ae72b612d0870620db7246a4735a70093", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-16.4.0-alpha.3174632.tgz", + "unpackedSize": 2015926 + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "clement.hoang24@gmail.com", + "name": "clemmy" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0 || 16.4.0-alpha.3174632" + }, + "readme": "# `react-dom`\n\nThis package serves as the entry point of the DOM-related rendering paths. It is intended to be paired with the isomorphic React, which will be shipped as `react` to npm.\n\n## Installation\n\n```sh\nnpm install react react-dom\n```\n\n## Usage\n\n### In the browser\n\n```js\nvar React = require('react');\nvar ReactDOM = require('react-dom');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOM.render(, node);\n```\n\n### On the server\n\n```js\nvar React = require('react');\nvar ReactDOMServer = require('react-dom/server');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOMServer.renderToString();\n```\n\n## API\n\n### `react-dom`\n\n- `findDOMNode`\n- `render`\n- `unmountComponentAtNode`\n\n### `react-dom/server`\n\n- `renderToString`\n- `renderToStaticMarkup`\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.4.0-alpha.3174632" + }, + "16.4.0-alpha.7926752": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.4.0-alpha.7926752", + "_nodeVersion": "8.9.1", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.4.0-alpha.7926752_1518483145933_0.8430273304257574" + }, + "_npmUser": { + "email": "acdlite@me.com", + "name": "acdlite" + }, + "_npmVersion": "5.5.1", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 27, + "integrity": "sha512-tl9WJ5epIw4S3tjN55Jl3hUrxgplLJB6VlIoLSdZCc8awmQM2fn/7t/Wc3x/wUMtGzqCln/Rye3y7+7jJbtZdw==", + "shasum": "e825504a975456ce2fa989fabdfdf2f30c7d7c3b", + "tarball": "http://registry.npmjs.org/react-dom/-/react-dom-16.4.0-alpha.7926752.tgz", + "unpackedSize": 2045573 + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "clement.hoang24@gmail.com", + "name": "clemmy" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0 || 16.4.0-alpha.7926752" + }, + "readme": "# `react-dom`\n\nThis package serves as the entry point of the DOM-related rendering paths. It is intended to be paired with the isomorphic React, which will be shipped as `react` to npm.\n\n## Installation\n\n```sh\nnpm install react react-dom\n```\n\n## Usage\n\n### In the browser\n\n```js\nvar React = require('react');\nvar ReactDOM = require('react-dom');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOM.render(, node);\n```\n\n### On the server\n\n```js\nvar React = require('react');\nvar ReactDOMServer = require('react-dom/server');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOMServer.renderToString();\n```\n\n## API\n\n### `react-dom`\n\n- `findDOMNode`\n- `render`\n- `unmountComponentAtNode`\n\n### `react-dom/server`\n\n- `renderToString`\n- `renderToStaticMarkup`\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.4.0-alpha.7926752" + }, + "16.4.1": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.4.1", + "_nodeVersion": "8.9.4", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.4.1_1528908440278_0.8937087583701679" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "5.6.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "deprecated": "This version of react-dom/server contains a minor vulnerability. Please update react-dom to 16.4.2+. Learn more: https://fb.me/cve-2018-6341", + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 28, + "integrity": "sha512-1Gin+wghF/7gl4Cqcvr1DxFX2Osz7ugxSwl6gBqCMpdrxHjIFUS7GYxrFftZ9Ln44FHw0JxCFD9YtZsrbR5/4A==", + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJbIUqYCRA9TVsSAnZWagAAwkgP/ipXGtoGg1pa9bES0VY4\nxCrPbiey/xtlfx89cb0FxM1SV6ni0I523cB1jKUMoXfj73ZpTV3mTLu3sCPi\nowO06smQHvNTk/71tDAn3fdSJVyfvxtJXZuVP04kUsOE+WbF5GPuxbWQWaUQ\njSZaDU0TrzRtMORoQh4UQiGcSiYec3cz/MVSQICwAP2jKm7M4hVHd25vt4pZ\nXp0DiznFzamB6x9dIXXtlz0dgMv8SeCenZigPASFPjkPIUEeXLPZKXIQhjUo\nXoM31eJJmHXWRXVAp/mMKoVIFwBBaslNMtrQugSRaFuIdOy7dz8tBpcJpTm6\nFbKEfrzHjNUEwK1HfVbrSk1dQbCcoz2PEceLjae4jCPiPcsIkqhdyOpObV49\nS5HNUdwCIuXLvMnsnoqeXA/H2hyAYp05TTdrTBfhXasErgVVdnYTuJtis1y0\nZphXRVd5TfK3WOUyXlrTlsuiEjrstPAmBDmWvNKDpi4HSkOEh0GgpqohCRd+\n71qG3sENCYYwt/hS4O4OTSPVu9W43gFf20Yc6Q+8ki6ByerBVEzmuhI3isgw\n58tfaIxSv52HaO/H/6yHWbrp9QtsgFETZ6BKHocQ97d4R5iF20EWsiUg3xto\n+1tWFhdRWzrXjRbtS4Wn+iau0R+GBrt5CCRPj8iAPx1Bs+0824MRr1ZuibNP\nDfO7\r\n=iIFN\r\n-----END PGP SIGNATURE-----\r\n", + "shasum": "7f8b0223b3a5fbe205116c56deb85de32685dad6", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.4.1.tgz", + "unpackedSize": 2152519 + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "clement.hoang24@gmail.com", + "name": "clemmy" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.4.1" + }, + "16.4.2": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.4.2", + "_nodeVersion": "8.9.4", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.4.2_1533150060999_0.5273489181516489" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "5.6.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 28, + "integrity": "sha512-Usl73nQqzvmJN+89r97zmeUpQDKDlh58eX6Hbs/ERdDHzeBzWy+ENk7fsGQ+5KxArV1iOFPT46/VneklK9zoWw==", + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJbYgNtCRA9TVsSAnZWagAAHjgP/A9UQGYC1BxeN6bR8KBq\nY1Mxlsrp6MJkSlNsYX8WU0qXupNJY+cvffgj8RyTWeRcNwuAYgK6f0gLjDo5\npgQXYAjb3Xzkm0ExvvWaVJjb1weLpMbMfHXCKvh21NxSJC3YN2rn6gk+cGBW\nWL2/RuchQd6xzxmgqs5yVV98npl3OmhU/V4BdB/R0QMiS7QHpD/6+ZUUErux\nceVCv71a0OYRWlvDUDU4mfAV+nDOxU+EvepsVVrClhK5qcl2WAuXbGR5gcSR\nSQJC10tv9sX/jVt9lwZQtx4jpv1qEGHPCCYovUxCVyxgxI4RLxQLPgoo2lB7\n4dkDy+MSWuszLPSKaTg/Q+G60oPXHcsCaKI5ox/9Zlz23wScizTuS4G5I3Cn\n6OGY5KJiJ1eVW6HxXC5ss/bDcFoK61GU5ftxtrG/zYL6vy9bezmAQpye+tzd\nRVbOuowwg7cB96HqYWc6v2huwGYtedllTIbWaLb3Sh8EBp6rtd9HGdq4E7H9\n26zlTdcCxvI/802LNd3T1VeyhEtgJcoOD5a853hCjcKnkiQqaHFhAaUttjug\ntWXrstWdNtGN8x3qbCKQ5+CXx5Climzjy5y3eRbf12VeWbtA1/sCWJQapMxm\n5fnUeaRev6/nhVWaRB631/p+TvkTQBWBWxHuv4LZdr0zO8ABu3hRy/4Qckf/\niOm/\r\n=00NE\r\n-----END PGP SIGNATURE-----\r\n", + "shasum": "4afed569689f2c561d2b8da0b819669c38a0bda4", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.4.2.tgz", + "unpackedSize": 2153432 + }, + "files": [ + "LICENSE", + "README.md", + "index.js", + "server.js", + "server.browser.js", + "server.node.js", + "test-utils.js", + "unstable-native-dependencies.js", + "cjs/", + "umd/" + ], + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.4.2" + }, + "16.5.0": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.5.0", + "_nodeVersion": "9.11.2", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.5.0_1536252283000_0.2998909120909927" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "6.2.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "schedule": "^0.3.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 29, + "integrity": "sha512-qgsQdjFH54pQ1AGLCBKsqjPxib4Pnp+cOsNxGPlkHn5YnsSt43sBvHSif6FheY7NMMS6HPeSJOxXf6ECanjacA==", + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJbkVmDCRA9TVsSAnZWagAAOTMP/09620DjaIQlRA5KK1wM\n90dTnNwIjZ3AEuk2X2sxag2LkIyF0Df9kp3VdlXDx2Gya9z8zwAZzv8qd63P\n6+HcsxRg0++uvD1qQMGHtmKf9yP75mCNRLafek+WvhHsGg2aj2jT32ix4tcg\n4xriKRxWL9NW97fswkbwIU/eT+sZ/gzY1yby8nItki03YrckNlEDeyk/SJCN\nW2PNl1EqnefWwaK8E3Oa2XQc4PjtAEz7PT0mkurX/VeiutTqDMfdLXEBQ9hN\nOqzBwmGX+/9lZaggWCjskDefr5MnhhJEkrQFg2m1knAj8LzZA2owZaWbgfNG\n9F4prv9Pn42inzLUdYgPiiT7iuYQG0eG4OworzfUwjSPI6URv4H4YS9YNMpt\nS5GWw8j2BuBXtbnfEJD7ZcQXx/QpjsZDV+ki0mJ80ADAo1YpJvcQMpwQQmIm\nC69SEcSoX5/+l84IML52p9z6YXQiifjmrNumcZnuACN84xJ/j+XLVbSETP1u\n6X1jKPquD27fYlp2+wbjm5sT2bWFcWGceMtc74JJpy6urPm/52izoikF+rGv\nJ2viJ/pdmpnbfrhpV6qYfSzOs+/4jjHbPzy32mu+00wjrCWug194Jvnc7ll5\nn65ZMIbEwtfuW1kD7QtQiwq8mraaXN5xgWhd+ycg9bgcfdU6vcF3rUX7heV3\ndNFz\r\n=UFDm\r\n-----END PGP SIGNATURE-----\r\n", + "shasum": "57704e5718669374b182a17ea79a6d24922cb27d", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.5.0.tgz", + "unpackedSize": 2231523 + }, + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.5.0" + }, + "16.5.1": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.5.1", + "_nodeVersion": "8.10.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.5.1_1536863660581_0.22496431481620904" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "5.6.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "schedule": "^0.4.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 29, + "integrity": "sha512-l4L9GtX7ezgnDIIr6AaNvGBM4BiK0fSs4/V8bdsu9X6xqrtHr+jp6auT0hbHpN7bH9WRvDBZceWQ9WJ3lGCIvQ==", + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJbmq2zCRA9TVsSAnZWagAA/IIP/1wJ5e5zimT6rDcrGKEk\nZ8hgrQQYlbaq9q/aMPwC7012ol3XExgIDjcHFGXjAeazYj/ypqp/8t0tQGWt\netlHbfO4cd3dO/GMTu3dWWp7stG1Wndy6zPPlOA2TnDZaDx2qiPc+c49vCRO\n2l1zPHcI0eBFHya/Gw8GF97qC9pcWEjb+2qbpHJKRnUmjO3cl99XcIdCo/nW\nyouYDrMKiJtRhdm9KEJ+CotK+xYFXD56MCZgmWEJ/tmW+lnSI3CHLFQvpctg\nvYrJedH2x/7ujuIff6ebzDJuWqfcvpbuWG36E374X65H3o7n6aX/LNM+F4dG\n0hDPvy9FasRYhjziOkMyUWT9ElVDQKHZNIJhhm/w+f8ItHoiM81Ri/WO66Nf\nH0S9VhnRSEayfd17tod7yoEkRvPguAW13oeM110NzL7+RX5Y/CYAqLngrIHE\nsM2dPCh0Ac9uQJMxF3ELtdawzJ8jSqPTQMkKF8zA6ODY9fPrcqSwhuSNllwo\n74+G8Ote3iCWuumB8y7ya7uloHx91cQOrP1LsMEhTxrv2a4hkhmTKopruib5\nouvHxPP+e1IQTLd+hLMrcbryow7aR8l8eanaxJB5x8Adu61pWZW8RBOI5mxC\npT+8bTrtI/+j54XnBEfvK1fJmm/ydpqF5nUUrct1ZxpNCB6fXfD9eTmSvMx8\no6gj\r\n=3wTf\r\n-----END PGP SIGNATURE-----\r\n", + "shasum": "29d0c5a01ed3b6b4c14309aa91af6ec4eb4f292c", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.5.1.tgz", + "unpackedSize": 2253533 + }, + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.5.1" + }, + "16.5.2": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.5.2", + "_nodeVersion": "10.10.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.5.2_1537299070703_0.30813369741667573" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "6.4.1", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "schedule": "^0.5.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 30, + "integrity": "sha512-RC8LDw8feuZOHVgzEf7f+cxBr/DnKdqp56VU0lAs1f4UfKc4cU8wU4fTq/mgnvynLQo8OtlPC19NUFh/zjZPuA==", + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJboVJ/CRA9TVsSAnZWagAAZT0P/2JyMJJfxUdMiBWiUyVd\nY76Lg164D48ZsYQIrz+jtp3sfOZQ5sklWV4tDfhZrw6tED/eGZgiwevHoHeZ\nXLHJoe3ZUYX4wtkJZAm7HL4LIf02TnL+FE64dIMGvehdVAxW08EL36PBgWFs\n4F6vpVeI/QD2Ctx/blTJcRxlBQPYOEvkuO8jD3FGBVWKVbaAsksAPIb+GQtS\nafC+PL5AnGYyNf8qlrecoPVTky+cDV1jHRcOKXz9V7e5pcopN136NyDnEI/w\n4G7K06yeFVlwdvCYY3HBjQtFYg+UsbmYl1Sl9SCsS87c5k+IYwAx5O1jpVGV\n+2ThslBFKRxtvZEKo0D/is7ROtZkX+JZNYgrnikm/+ink+eCZOOfXqzQKRRg\naBlDaYoTUOwftrVbybcddPQZC4Yzc83Y0Thkq+p+mpJOnQOr6NjOUOY4H4tx\n6gyDlC1iYqDVbsuuamc9luFX3r3Q42uojEla9vvNbKl/PqYvJs/AOJsfVlwY\nppQBeOMHCCRzrx+C38U6VftQkuLEwTBqVkf+0OyQD/HcFfo7mZbFGnZDtcLD\nQbt7MzGImblUCOCrL+VdKsqN2BWLJ6ksmZHvLE+K48EKKHqZtaSWLbIIyR0A\n1/qFmvxhaQsEDUSP75rwL/jvmeAhDOw8YRBMuWPNtenUIIyb5fvIP2dvN1Om\nlgeK\r\n=OtMk\r\n-----END PGP SIGNATURE-----\r\n", + "shasum": "b69ee47aa20bab5327b2b9d7c1fe2a30f2cfa9d7", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.5.2.tgz", + "unpackedSize": 2351067 + }, + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.5.2" + }, + "16.6.0": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.6.0", + "_nodeVersion": "8.10.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.6.0_1540337769628_0.9475086702849651" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "6.4.1", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.10.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 30, + "integrity": "sha512-Stm2D9dXEUUAQdvpvhvFj/DEXwC2PAL/RwEMhoN4dvvD2ikTlJegEXf97xryg88VIAU22ZAP7n842l+9BTz6+w==", + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJbz7BqCRA9TVsSAnZWagAARf0QAJppnsYcu4/JHgE7XZj2\n71e0+p1fn1oDRl2L2YTxcf4jXy0WlXU2YixPSy4HEsJ7eOy4qJYHiL0/RLS4\nMkmyyNe1fwX7blYFGCCmXaJwaQwxiIzvd9z9y2f3hM4T7QfEvP6isNIEyzJR\no8iAu9bNZ5yXvqPVtaw77RijmToqxzXdiYPkZvpbOuHCsR2tKKfMmDcdCOIT\nGppmtmMNu2x8FUVfh9NtrTcNK44ZXjmhOh4M7egsIK8ZxT5LfakvG0Y2gIN3\n60eG+pDzMeaxKJBgDWMywfy1YFd5REwLw7ZhkaxY++n4Do9zBzCeP4FZoTzV\nkJF93ecwrIdirDivF8x7acDrDR570X8+bSsnPnKvYgUNWXv+nWklznkuWSZ7\nalwZoZpzHAt/PZWGDYfPSjPYA8RLJ16T19kWwMxty8BH7TlsHLz0Mff9YEO4\nkdYsA/4BMALiz/CDZyZWXOuQzWavWvvam+ebnU1n1Lkud1i3HgrLjPFVadRW\ngIfk8sDSHuv61IdwYu5V0g25MiqWsRb6uTJgkOLo8sTy1OePmKMdhqhP3UWU\nalO6bkDDQkT1K+x3ZrTei8kE0Dxh8SoWoDmC1oAWyHpqSJGD5znBHRKsty2R\nZDjM9jX+AFl3fXJeJD7EXkU1IztTZnnu+o6e+ewIAn5M22UwV0B1TgrI7GMn\nYSaO\r\n=0/In\r\n-----END PGP SIGNATURE-----\r\n", + "shasum": "6375b8391e019a632a89a0988bce85f0cc87a92f", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.0.tgz", + "unpackedSize": 2440257 + }, + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.6.0" + }, + "16.6.0-alpha.0": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.6.0-alpha.0", + "_nodeVersion": "10.10.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.6.0-alpha.0_1537222291588_0.008345044295471782" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "6.4.1", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "schedule": "^0.5.0-alpha.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 30, + "integrity": "sha512-A4IKCabPFqJeyA7hiVHa5AP47WGnHfkI4syHmGx1bjdn4Om3VsVEdX6FNMoVNhoZPSKfnGrL7btQaMh5kuIA6Q==", + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJboCacCRA9TVsSAnZWagAA8rYQAIdF5XYG0Vk7HiOOWmcS\nX/B/tLg/jnKWpu8VBZJB0+WNP0IUVZ8Wf13a7KT/BvkEFn21IODNAUSvMKWs\nbr2egW8O1v443m6zhQpKF0R4E77ZFKdHrijYGJ/C+QjFKV4JmHWPZ3P8vuzE\nW14cUxJ2bFo+E0w8M0LgmkEJ96jXd73WhY9pZCWczu08O3RHqlQRqVoifck1\nFwqJAu9EE3swEFZ5UwMgPBtTzO9nQbvsBHVQsLh6MVWSsDcKx0ou1OCaRUN8\nzoJMefAMyxvMRbInRmHmZC1da5dWhbusTQYAXTRACiqgxxvkw0R3rAX67ePR\n7OgeKc7sDja3qvYnVrQpq/UGzWGpT2oLQ01MWNcDIPC1O5ZFMGv/gPSTO2c2\n6Y7i86XiukD3pBv/IiK3a6Ur2ZDqxvxP+Zs6DUN1QXOGZ/kQ4vPp50N1MHQL\nGKuhtGJShkvRgCcafQnYrELhQ/BAeVq5B17ztcsSve1Jc1QU57cP87An2849\ni6tI5Fa2T4jnBTJXPxIOADE2K1p3eFML+eg8iu+ZDbC07UY8WUvKg08q3UQP\nREB6yIBE9PRIsCxJ0O+lNtZ5hI3tBtEMslV6tA/riVWg+cPyRMg1wOb9tdL2\nHw9OqLlkh5fjfBYdA4JGnmS/rTCrwOVZlHLBq/pSGJ6Nex48WmlBlOp37ecw\nQ4kY\r\n=nvEU\r\n-----END PGP SIGNATURE-----\r\n", + "shasum": "2f4219aebf60f33fd66ff4d9c7f4b85f98a224e7", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.0-alpha.0.tgz", + "unpackedSize": 2351369 + }, + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0 || 16.6.0-alpha.0" + }, + "readme": "# `react-dom`\n\nThis package serves as the entry point to the DOM and server renderers for React. It is intended to be paired with the generic React package, which is shipped as `react` to npm.\n\n## Installation\n\n```sh\nnpm install react react-dom\n```\n\n## Usage\n\n### In the browser\n\n```js\nvar React = require('react');\nvar ReactDOM = require('react-dom');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOM.render(, node);\n```\n\n### On the server\n\n```js\nvar React = require('react');\nvar ReactDOMServer = require('react-dom/server');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOMServer.renderToString();\n```\n\n## API\n\n### `react-dom`\n\n- `findDOMNode`\n- `render`\n- `unmountComponentAtNode`\n\n### `react-dom/server`\n\n- `renderToString`\n- `renderToStaticMarkup`\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.6.0-alpha.0" + }, + "16.6.0-alpha.400d197": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.6.0-alpha.400d197", + "_nodeVersion": "8.11.4", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.6.0-alpha.400d197_1538701448481_0.6915321021568843" + }, + "_npmUser": { + "email": "acdlite@me.com", + "name": "acdlite" + }, + "_npmVersion": "5.6.0", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.10.0-alpha.400d197" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 30, + "integrity": "sha512-IoirMkzkxloRiKDvFuQFvPjDqA+6pmNR1jlv44yyZlyUk5ReYQo8+4tV6BbT6oc24xLK4ZyvnKohF7uhb3Pb5w==", + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJbtriQCRA9TVsSAnZWagAAp8IP+wfTYLpVCiwA9K/rif4y\n6MiYKSjgokaLUyEytgolik3kHWxQ+OnyLQ+mJn9EikU0ZNN756Oxv9BSdVaq\nFSDESfQq6dlgL0eRPmUrAxwOpQvZ7ag6aVBnxD1xnVG0Njf97cyd9q5ElF5E\nr9XP8gbKeNcvvzxweiJ2cTUSKq4adCU/foLtZMHlL7xr5sZPIl+kcamxrGLb\nUd1/5xPrwfJmQpbDMKWXmjvwdkzvWzkygtuo6bOs9JEezZmeAVJf4b3e9OjA\nZ3ACrVeMTqWoFSdPk3Vz2mrKaaQQ+Dlkb0ZZjkV+JIKYnMyLwpo4Sd0GXZre\nj+rzefEQwGsdfuMd9Ze3z/Qg1b02oIJ9gDjE3FkrmIqWtWA2rBVTY8FakxtQ\njtIIgvHrC9d84XX6NQKrtqtaKzks8jGvZewYcwymevupN6R2dfDTcf2dlw+1\nQalLWXb+jRSsjmJ9KjugblhmyIzCvwM/uz0DQs04y1Bsap+p8+iKmD+3ZcP8\n26TuyxZXrNwt3TcS2RPpnldMIpNBgLPdhjlSB0XOjURQmV4wpLniX3S86iB6\noDpjttTttGwMUHycprf9jW30ynOzw/reNV00n8G0w/FBqIym+EoytPAQYrZy\n7j8hM0ZZhI3AQ0M3oQ6IOcBBCt9qus4dEAIOC1+4lSSgYdY2aViHPB4a/po+\nS3IS\r\n=WQsU\r\n-----END PGP SIGNATURE-----\r\n", + "shasum": "232a07ee19cc05e600440bf3c64d34b86b1a4957", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.0-alpha.400d197.tgz", + "unpackedSize": 2477643 + }, + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0 || 16.6.0-alpha.400d197" + }, + "readme": "# `react-dom`\n\nThis package serves as the entry point to the DOM and server renderers for React. It is intended to be paired with the generic React package, which is shipped as `react` to npm.\n\n## Installation\n\n```sh\nnpm install react react-dom\n```\n\n## Usage\n\n### In the browser\n\n```js\nvar React = require('react');\nvar ReactDOM = require('react-dom');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOM.render(, node);\n```\n\n### On the server\n\n```js\nvar React = require('react');\nvar ReactDOMServer = require('react-dom/server');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOMServer.renderToString();\n```\n\n## API\n\n### `react-dom`\n\n- `findDOMNode`\n- `render`\n- `unmountComponentAtNode`\n\n### `react-dom/server`\n\n- `renderToString`\n- `renderToStaticMarkup`\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.6.0-alpha.400d197" + }, + "16.6.0-alpha.8af6728": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.6.0-alpha.8af6728", + "_nodeVersion": "8.10.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.6.0-alpha.8af6728_1539188545093_0.001332694814434765" + }, + "_npmUser": { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + "_npmVersion": "6.4.1", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.10.0-alpha.8af6728" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 30, + "integrity": "sha512-/45amCtd+yJD2Ee8mC4Z6KrupY24XydGzmGpkYlG5gIy7oTf+8g6+X+0DigDtvt6e4FOd8EJajf9YX++CW5A+g==", + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJbvidBCRA9TVsSAnZWagAAVxcP/jIJJgr+Vuq+IORzmSgs\nmfgJYOyYg2LTh01SK1AAarMp8WF/Z+fz9QMVbJ9vQES+SxINX8arhUcNcNKa\nxtAq0UZSND3CJa8KEb1l18T5oWQmp8AM0dtLlG5mh0HCuRHKDVNwnXdEkPK+\nSr8Y7K8aABGwXoWOMtqA+DETYnHTsA6kJCbG9G4OIoj8yPH4m2uhR50r9FX+\nTQjGFWD6JTSRiBJzj//k4BRtLMDaMeYf27O+a0CBWgysq/i3gOD3w36uFHUJ\nwBHPbISdm8FNxQS0aK+Y5Zbdl0Xrtt6KqX50CcLudd1zjFe98a+Dd1M9LfBj\n17frnS5JCCWTd/gxl32sDjJurlnSzmvPrNoiRuk5iTmjZM12XRVvSsMiiYl2\neQQ1ZcRqM4x77tRIdKVbAzW3bw6NrHGIc+kQDK34s5EG+08eAw8VhYzyxE9F\nu4yxwhnAD/OncS47BUvJlo6ztMo6i3dsWbD+2Rel7ZvANgAkXeD8JgAuNcZj\nWqANC5nRzWizozFQr1ayfzDeSr8gdT9791v1achwZPfgBwuijNMpylLotu4F\nSriCLdk/xIiSgLntkbiedt0hnUlV0tk9AqaGWPAPrMynNBra0vLw23FRpW/a\n0EiYmMXYDp/8HNATG4cFvLRkX/U71es9LX+Wzd0b1w/baC1vjPtD2LFUGA0o\nZYpH\r\n=Dfzu\r\n-----END PGP SIGNATURE-----\r\n", + "shasum": "0bc2020dcf0a594fe06e41379a9346d1982f587b", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.0-alpha.8af6728.tgz", + "unpackedSize": 2374532 + }, + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0 || 16.6.0-alpha.8af6728" + }, + "readme": "# `react-dom`\n\nThis package serves as the entry point to the DOM and server renderers for React. It is intended to be paired with the generic React package, which is shipped as `react` to npm.\n\n## Installation\n\n```sh\nnpm install react react-dom\n```\n\n## Usage\n\n### In the browser\n\n```js\nvar React = require('react');\nvar ReactDOM = require('react-dom');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOM.render(, node);\n```\n\n### On the server\n\n```js\nvar React = require('react');\nvar ReactDOMServer = require('react-dom/server');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOMServer.renderToString();\n```\n\n## API\n\n### `react-dom`\n\n- `findDOMNode`\n- `render`\n- `unmountComponentAtNode`\n\n### `react-dom/server`\n\n- `renderToString`\n- `renderToStaticMarkup`\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.6.0-alpha.8af6728" + }, + "16.7.0-alpha.0": { + "_hasShrinkwrap": false, + "_id": "react-dom@16.7.0-alpha.0", + "_nodeVersion": "10.12.0", + "_npmOperationalInternal": { + "host": "s3://npm-registry-packages", + "tmp": "tmp/react-dom_16.7.0-alpha.0_1540484019205_0.9521091249614908" + }, + "_npmUser": { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + "_npmVersion": "6.4.1", + "browser": { + "./server.js": "./server.browser.js" + }, + "browserify": { + "transform": [ + "loose-envify" + ] + }, + "bugs": { + "url": "https://github.com/facebook/react/issues" + }, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.11.0-alpha.0" + }, + "description": "React package for working with the DOM.", + "directories": {}, + "dist": { + "fileCount": 30, + "integrity": "sha512-/XUn1ldxmoV2B7ov0rWT5LMZaaHMlF9GGLkUsmPRxmWTJwRDOuAPXidSaSlmR/VOhDSI1s+v3+KzFqhhDFJxYA==", + "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJb0eu0CRA9TVsSAnZWagAAIMIQAJDGrvlHzAra1aSVE+PL\npHN8fgfYREc7lEOP51YwUh32JOSYgRsO8EaebbJZuWJGyfOtK22mIkGGb48e\nY8PQJrP5039QZz3GuPIXDD4ZDTpm6glJ7vOB6K/kaJ+gcFXheeEnq9riDpz5\nqn20Y+wlMEt46Xy0TN+mT6QPdS+0C8hBw6gPUQjC4Mohu3Oy1S1kGP366QdG\n/0ExtARxRq9ACuiaO/mH2irYl2Gjcc/p2D0E2bpYBZ2s85DLa0rkOTJO6cl5\nu04l/zuFA+XjJFD8VrsWjgaXQ9/uR6YtCtCcTKQvIAeEIVu5o7/Mem8uMclL\n+USL74PncGoVno1tuZUHvfbrTY7rUdTgk7A1J+7ojw4QzMHE6dhjAEIHEi/e\nDBc/Aju/l1TBXc3XB6KEHI8InLkHvzmEHNw5xD71cB+nspwGQtit5q/udgjF\nkBNUdCPP0UQTASMW9iV3YiffhGeLXaZ7wvpJEm91KIxcfwUDEG3GQKnDoPb4\n7r3zLfgcmYKS6BiAon+Oxy/vKMcDwL/e/ObDmdgfCtN8NQTcYNDvMJIzAQ7l\nZhOca3eHb1yvi0YjLbcDF9mZQwP+WfHLi193YbnFTNJ/A4MdrgVdR6CATBBf\nXfGaOxLs4MylHPBDszNx3zDgT7hGQX976WL+mVxsUa3Iezya/5hyMmhR6+M6\nTWJz\r\n=OiQL\r\n-----END PGP SIGNATURE-----\r\n", + "shasum": "8379158d4c76d63c989f325f45dfa5762582584f", + "tarball": "https://registry.npmjs.org/react-dom/-/react-dom-16.7.0-alpha.0.tgz", + "unpackedSize": 2549836 + }, + "homepage": "https://reactjs.org/", + "keywords": [ + "react" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "email": "acdlite@me.com", + "name": "acdlite" + }, + { + "email": "briandavidvaughn@gmail.com", + "name": "brianvaughn" + }, + { + "email": "opensource+npm@fb.com", + "name": "fb" + }, + { + "email": "flarnie.npm@gmail.com", + "name": "flarnie" + }, + { + "email": "dan.abramov@gmail.com", + "name": "gaearon" + }, + { + "email": "npm@sophiebits.com", + "name": "sophiebits" + }, + { + "email": "dg@domgan.com", + "name": "trueadm" + }, + { + "email": "paul@oshannessy.com", + "name": "zpao" + } + ], + "name": "react-dom", + "peerDependencies": { + "react": "^16.0.0 || 16.7.0-alpha.0" + }, + "readme": "# `react-dom`\n\nThis package serves as the entry point to the DOM and server renderers for React. It is intended to be paired with the generic React package, which is shipped as `react` to npm.\n\n## Installation\n\n```sh\nnpm install react react-dom\n```\n\n## Usage\n\n### In the browser\n\n```js\nvar React = require('react');\nvar ReactDOM = require('react-dom');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOM.render(, node);\n```\n\n### On the server\n\n```js\nvar React = require('react');\nvar ReactDOMServer = require('react-dom/server');\n\nclass MyComponent extends React.Component {\n render() {\n return
Hello World
;\n }\n}\n\nReactDOMServer.renderToString();\n```\n\n## API\n\n### `react-dom`\n\n- `findDOMNode`\n- `render`\n- `unmountComponentAtNode`\n\n### `react-dom/server`\n\n- `renderToString`\n- `renderToStaticMarkup`\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/facebook/react.git" + }, + "scripts": { + "start": "node server.js" + }, + "version": "16.7.0-alpha.0" + } + } +}