diff --git a/common/lib/dependabot/file_parsers/base.rb b/common/lib/dependabot/file_parsers/base.rb index 5dddd226abb..8e169f415e9 100644 --- a/common/lib/dependabot/file_parsers/base.rb +++ b/common/lib/dependabot/file_parsers/base.rb @@ -81,6 +81,14 @@ def run_in_parsed_context(_command) raise Dependabot::NotImplemented, "No run_parsed_context utility method is provided for this ecosystem." end + # Enables alias extraction so that aliased packages are parsed using the + # real package name rather than being skipped. Must be called before #parse. + # This is a no-op for ecosystems that don't support aliases. + sig { void } + def dealias_packages! + options[:dealias_packages] = true + end + private sig { abstract.void } diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/dependency_grapher.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/dependency_grapher.rb index 1491ac8e035..d4a2e28ff53 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/dependency_grapher.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/dependency_grapher.rb @@ -2,7 +2,6 @@ # frozen_string_literal: true require "json" -require "yaml" require "sorbet-runtime" require "dependabot/dependency_graphers" @@ -17,6 +16,9 @@ class DependencyGrapher < Dependabot::DependencyGraphers::Base extend T::Sig require_relative "dependency_grapher/lockfile_generator" + require_relative "dependency_grapher/npm_relationship_resolver" + require_relative "dependency_grapher/yarn_relationship_resolver" + require_relative "dependency_grapher/pnpm_relationship_resolver" sig { override.returns(Dependabot::DependencyFile) } def relevant_dependency_file @@ -44,7 +46,7 @@ def resolved_dependencies resolved[purl] = Dependabot::DependencyGraphers::ResolvedDependency.new( package_url: purl, - direct: version_dep.top_level?, + direct: version_dep.top_level? || !version_dep.metadata[:alias].nil?, runtime: version_dep.production?, dependencies: subdependency_purls_for(version_dep) ) @@ -54,6 +56,10 @@ def resolved_dependencies sig { override.void } def prepare! + # Enable alias extraction for graph jobs so aliased packages appear + # in the dependency graph for security scanning. + file_parser.dealias_packages! + if lockfile.nil? Dependabot.logger.info("No lockfile found, generating ephemeral lockfile for dependency graphing") generate_ephemeral_lockfile! @@ -104,11 +110,14 @@ def parsed_package_json sig { returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) } def lockfiles_hash - { - npm: dependency_files.find { |f| f.name.end_with?(NpmPackageManager::LOCKFILE_NAME) }, - yarn: dependency_files.find { |f| f.name.end_with?(YarnPackageManager::LOCKFILE_NAME) }, - pnpm: dependency_files.find { |f| f.name.end_with?(PNPMPackageManager::LOCKFILE_NAME) } - } + @lockfiles_hash ||= T.let( + { + npm: dependency_files.find { |f| f.name.end_with?(NpmPackageManager::LOCKFILE_NAME) }, + yarn: dependency_files.find { |f| f.name.end_with?(YarnPackageManager::LOCKFILE_NAME) }, + pnpm: dependency_files.find { |f| f.name.end_with?(PNPMPackageManager::LOCKFILE_NAME) } + }, + T.nilable(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) + ) end sig { returns(String) } @@ -155,6 +164,7 @@ def inject_ephemeral_lockfile(ephemeral_lockfile) # Clear our cached lockfile reference so it picks up the new one remove_instance_variable(:@lockfile) if instance_variable_defined?(:@lockfile) + remove_instance_variable(:@lockfiles_hash) if instance_variable_defined?(:@lockfiles_hash) # Clear the FileParser's memoized lockfile references so it will # find the newly injected lockfile when parse is called @@ -231,238 +241,27 @@ def package_relationships fetch_package_relationships, T.nilable(T::Hash[String, T::Array[String]]) ) + rescue StandardError => e + errored_fetching_subdependencies! + @subdependency_error = T.let(e, T.nilable(StandardError)) + Dependabot.logger.error("Error fetching subdependencies: #{e.message}") + @package_relationships = {} end sig { returns(T::Hash[String, T::Array[String]]) } def fetch_package_relationships case detected_package_manager when NpmPackageManager::NAME - fetch_npm_lock_relationships + NpmRelationshipResolver.new(T.must(lockfiles_hash[:npm])).relationships when YarnPackageManager::NAME - fetch_yarn_lock_relationships + YarnRelationshipResolver.new(T.must(lockfiles_hash[:yarn])).relationships when PNPMPackageManager::NAME - fetch_pnpm_lock_relationships + PnpmRelationshipResolver.new(T.must(lockfiles_hash[:pnpm])).relationships else {} end end - sig { returns(T.nilable(Dependabot::DependencyFile)) } - def npm_lockfile - return @npm_lockfile if defined?(@npm_lockfile) - - @npm_lockfile = T.let( - dependency_files.find { |f| f.name.end_with?(NpmPackageManager::LOCKFILE_NAME) }, - T.nilable(Dependabot::DependencyFile) - ) - end - - sig { returns(T.nilable(Dependabot::DependencyFile)) } - def yarn_lockfile - return @yarn_lockfile if defined?(@yarn_lockfile) - - @yarn_lockfile = T.let( - dependency_files.find { |f| f.name.end_with?(YarnPackageManager::LOCKFILE_NAME) }, - T.nilable(Dependabot::DependencyFile) - ) - end - - sig { returns(T.nilable(Dependabot::DependencyFile)) } - def pnpm_lockfile - return @pnpm_lockfile if defined?(@pnpm_lockfile) - - @pnpm_lockfile = T.let( - dependency_files.find { |f| f.name.end_with?(PNPMPackageManager::LOCKFILE_NAME) }, - T.nilable(Dependabot::DependencyFile) - ) - end - - sig { returns(T::Hash[String, T::Array[String]]) } - def fetch_npm_lock_relationships - parsed = JSON.parse(T.must(T.must(npm_lockfile).content)) - packages = parsed.fetch("packages", {}) - - # v3/v2 lockfiles use a flat "packages" section - if packages.is_a?(Hash) && !packages.empty? - return packages.each_with_object({}) do |(path, details), rels| - next if path.empty? # skip root package entry - next unless details.is_a?(Hash) - - children = details.fetch("dependencies", {}).keys - next if children.empty? - - package_name = path.split("node_modules/").last - version = details["version"] - next if version.nil? || version.to_s.empty? - - resolved = resolve_npm_v3_children(packages, path, children) - rels["#{package_name}@#{version}"] = resolved unless resolved.empty? - end - end - - # if packages isn't present, attempt a v1 fallback - fetch_npm_v1_lock_relationships(parsed) - end - - sig do - params( - packages: T::Hash[String, T.untyped], - parent_path: String, - children: T::Array[String] - ).returns(T::Array[String]) - end - def resolve_npm_v3_children(packages, parent_path, children) - children.filter_map do |child_name| - child_details = resolve_npm_child(packages, parent_path, child_name) - next unless child_details - - child_version = child_details["version"] - next if child_version.nil? || child_version.to_s.empty? - - "#{child_name}@#{child_version}" - end - end - - # Walks up the node_modules tree to resolve a child dependency, - # matching Node.js module resolution behavior. - sig do - params( - packages: T::Hash[String, T.untyped], - parent_path: String, - child_name: String - ).returns(T.nilable(T::Hash[String, T.untyped])) - end - def resolve_npm_child(packages, parent_path, child_name) - # First check directly nested under parent - candidate = "#{parent_path}/node_modules/#{child_name}" - return packages[candidate] if packages.key?(candidate) - - # Walk up the tree: strip trailing node_modules/pkg segments - segments = parent_path.split("node_modules/") - segments.pop # remove the current package segment - - while segments.any? - candidate = "#{segments.join('node_modules/')}node_modules/#{child_name}" - return packages[candidate] if packages.key?(candidate) - - segments.pop - end - - # Top-level fallback - packages["node_modules/#{child_name}"] - end - - sig { params(parsed: T::Hash[String, T.untyped]).returns(T::Hash[String, T::Array[String]]) } - def fetch_npm_v1_lock_relationships(parsed) - dependencies = parsed.fetch("dependencies", {}) - return {} unless dependencies.is_a?(Hash) - - dependencies.each_with_object({}) do |(name, details), rels| - next unless details.is_a?(Hash) - - nested = details.fetch("dependencies", nil) - next unless nested.is_a?(Hash) - - version = details["version"] - next if version.nil? || version.to_s.empty? - - children = resolve_npm_v1_children(nested) - rels["#{name}@#{version}"] = children unless children.empty? - rels.merge!(fetch_npm_v1_lock_relationships(details)) - end - end - - sig { params(nested: T::Hash[String, T.untyped]).returns(T::Array[String]) } - def resolve_npm_v1_children(nested) - nested.filter_map do |child_name, child_details| - next unless child_details.is_a?(Hash) - - child_version = child_details["version"] - next if child_version.nil? || child_version.to_s.empty? - - "#{child_name}@#{child_version}" - end - end - - sig { returns(T::Hash[String, T::Array[String]]) } - def fetch_yarn_lock_relationships - parsed = FileParser::YarnLock.new(T.must(yarn_lockfile)).parsed - - parsed.each_with_object({}) do |(req, details), rels| - next unless details.is_a?(Hash) - - version = details["version"] - parent_name = T.must(req.split(/(?<=\w)\@/).first) - children = details.fetch("dependencies", {}) - - next if children.nil? || children.empty? - - key = "#{parent_name}@#{version}" - resolved_children = resolve_yarn_children(children, parsed) - - rels[key] ||= [] - rels[key].concat(resolved_children).uniq! - end - end - - sig { params(children: T::Hash[String, String], parsed: T::Hash[String, T.untyped]).returns(T::Array[String]) } - def resolve_yarn_children(children, parsed) - children.filter_map do |child_name, child_req| - version = resolve_yarn_child_version(child_name, child_req, parsed) - "#{child_name}@#{version}" if version - end - end - - sig { params(child_name: String, child_req: String, parsed: T::Hash[String, T.untyped]).returns(T.nilable(String)) } - def resolve_yarn_child_version(child_name, child_req, parsed) - # Try exact key first - child_entry = parsed["#{child_name}@#{child_req}"] - return child_entry["version"] if child_entry && child_entry["version"] - - # Yarn groups multiple requirements into single keys like "foo@^1.0.0, foo@^1.2.0" - target_req = "#{child_name}@#{child_req}" - grouped_match = parsed.find { |k, _| k.split(", ").include?(target_req) } - return grouped_match.last["version"] if grouped_match && grouped_match.last["version"] - - # Fallback: find by name only if there's exactly one candidate - candidates = parsed.select { |k, _| k.split(/(?<=\w)\@/).first == child_name } - candidate = candidates.first - candidate.last["version"] if candidates.size == 1 && candidate - end - - sig { returns(T::Hash[String, T::Array[String]]) } - def fetch_pnpm_lock_relationships - parsed = YAML.safe_load(T.must(T.must(pnpm_lockfile).content)) || {} - - # v9+ uses "snapshots" for resolved dependency details; v6 uses "packages" - entries = parsed.fetch("snapshots", nil) || parsed.fetch("packages", {}) - - entries.each_with_object({}) do |(key, details), rels| - next unless details.is_a?(Hash) - - # Keys are "/name@version" (v6) or "name@version" (v9) - name_version = key.sub(%r{^/}, "") - children = details.fetch("dependencies", {}) - - next if children.nil? || children.empty? - - # Strip any pnpm suffix metadata (e.g., parenthesized peer dep info) - name_version = name_version.sub(/\(.*\)$/, "") - - # pnpm dependencies are already resolved: {"name": "version"} - # Strip any peer metadata suffixes like "7.49.0(react@18.2.0)" - resolved_children = children.filter_map do |child_name, child_version| - clean_version = child_version.to_s.sub(/\(.*\)$/, "") - next if clean_version.empty? - - "#{child_name}@#{clean_version}" - end - - rels[name_version] ||= [] - rels[name_version].concat(resolved_children).uniq! - end - end - sig { override.params(_dependency: Dependabot::Dependency).returns(String) } def purl_pkg_for(_dependency) "npm" diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/dependency_grapher/npm_relationship_resolver.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/dependency_grapher/npm_relationship_resolver.rb new file mode 100644 index 00000000000..d938f334966 --- /dev/null +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/dependency_grapher/npm_relationship_resolver.rb @@ -0,0 +1,134 @@ +# typed: strict +# frozen_string_literal: true + +require "json" +require "sorbet-runtime" + +module Dependabot + module NpmAndYarn + class DependencyGrapher < Dependabot::DependencyGraphers::Base + class NpmRelationshipResolver + extend T::Sig + + sig { params(lockfile: Dependabot::DependencyFile).void } + def initialize(lockfile) + @lockfile = lockfile + end + + sig { returns(T::Hash[String, T::Array[String]]) } + def relationships + parsed = JSON.parse(T.must(@lockfile.content)) + packages = parsed.fetch("packages", {}) + + # v3/v2 lockfiles use a flat "packages" section + return build_v3_relationships(packages) if packages.is_a?(Hash) && !packages.empty? + + # if packages isn't present, attempt a v1 fallback + build_v1_relationships(parsed) + end + + private + + sig { params(packages: T::Hash[String, T.untyped]).returns(T::Hash[String, T::Array[String]]) } + def build_v3_relationships(packages) + packages.each_with_object({}) do |(path, details), rels| + next if path.empty? # skip root package entry + next unless details.is_a?(Hash) + + children = details.fetch("dependencies", {}).keys + next if children.empty? + + package_name = details["name"] || path.split("node_modules/").last + version = details["version"] + next if version.nil? || version.to_s.empty? + + resolved = resolve_v3_children(packages, path, children) + rels["#{package_name}@#{version}"] = resolved unless resolved.empty? + end + end + + sig do + params( + packages: T::Hash[String, T.untyped], + parent_path: String, + children: T::Array[String] + ).returns(T::Array[String]) + end + def resolve_v3_children(packages, parent_path, children) + children.filter_map do |child_name| + child_details = resolve_child(packages, parent_path, child_name) + next unless child_details + + child_version = child_details["version"] + next if child_version.nil? || child_version.to_s.empty? + + # Use the "name" field for aliased packages (real name vs path alias) + real_name = child_details["name"] || child_name + "#{real_name}@#{child_version}" + end + end + + # Walks up the node_modules tree to resolve a child dependency, + # matching Node.js module resolution behavior. + sig do + params( + packages: T::Hash[String, T.untyped], + parent_path: String, + child_name: String + ).returns(T.nilable(T::Hash[String, T.untyped])) + end + def resolve_child(packages, parent_path, child_name) + # First check directly nested under parent + candidate = "#{parent_path}/node_modules/#{child_name}" + return packages[candidate] if packages.key?(candidate) + + # Walk up the tree: strip trailing node_modules/pkg segments + segments = parent_path.split("node_modules/") + segments.pop # remove the current package segment + + while segments.any? + candidate = "#{segments.join('node_modules/')}node_modules/#{child_name}" + return packages[candidate] if packages.key?(candidate) + + segments.pop + end + + # Top-level fallback + packages["node_modules/#{child_name}"] + end + + sig { params(parsed: T::Hash[String, T.untyped]).returns(T::Hash[String, T::Array[String]]) } + def build_v1_relationships(parsed) + dependencies = parsed.fetch("dependencies", {}) + return {} unless dependencies.is_a?(Hash) + + dependencies.each_with_object({}) do |(name, details), rels| + next unless details.is_a?(Hash) + + nested = details.fetch("dependencies", nil) + next unless nested.is_a?(Hash) + + version = details["version"] + next if version.nil? || version.to_s.empty? + + children = resolve_v1_children(nested) + rels["#{name}@#{version}"] = children unless children.empty? + rels.merge!(build_v1_relationships(details)) + end + end + + sig { params(nested: T::Hash[String, T.untyped]).returns(T::Array[String]) } + def resolve_v1_children(nested) + nested.filter_map do |child_name, child_details| + next unless child_details.is_a?(Hash) + + child_version = child_details["version"] + next if child_version.nil? || child_version.to_s.empty? + + "#{child_name}@#{child_version}" + end + end + end + end + end +end diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/dependency_grapher/pnpm_relationship_resolver.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/dependency_grapher/pnpm_relationship_resolver.rb new file mode 100644 index 00000000000..0cbdb7394d3 --- /dev/null +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/dependency_grapher/pnpm_relationship_resolver.rb @@ -0,0 +1,53 @@ +# typed: strict +# frozen_string_literal: true + +require "yaml" +require "sorbet-runtime" + +module Dependabot + module NpmAndYarn + class DependencyGrapher < Dependabot::DependencyGraphers::Base + class PnpmRelationshipResolver + extend T::Sig + + sig { params(lockfile: Dependabot::DependencyFile).void } + def initialize(lockfile) + @lockfile = lockfile + end + + sig { returns(T::Hash[String, T::Array[String]]) } + def relationships + parsed = YAML.safe_load(T.must(@lockfile.content)) || {} + + # v9+ uses "snapshots" for resolved dependency details; v6 uses "packages" + entries = parsed.fetch("snapshots", nil) || parsed.fetch("packages", {}) + + entries.each_with_object({}) do |(key, details), rels| + next unless details.is_a?(Hash) + + # Keys are "/name@version" (v6) or "name@version" (v9) + name_version = key.sub(%r{^/}, "") + children = details.fetch("dependencies", {}) + + next if children.nil? || children.empty? + + # Strip any pnpm suffix metadata (e.g., parenthesized peer dep info) + name_version = name_version.sub(/\(.*\)$/, "") + + # pnpm dependencies are already resolved: {"name": "version"} + # Strip any peer metadata suffixes like "7.49.0(react@18.2.0)" + resolved_children = children.filter_map do |child_name, child_version| + clean_version = child_version.to_s.sub(/\(.*\)$/, "") + next if clean_version.empty? + + "#{child_name}@#{clean_version}" + end + + rels[name_version] ||= [] + rels[name_version].concat(resolved_children).uniq! + end + end + end + end + end +end diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/dependency_grapher/yarn_relationship_resolver.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/dependency_grapher/yarn_relationship_resolver.rb new file mode 100644 index 00000000000..17196d4b5a1 --- /dev/null +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/dependency_grapher/yarn_relationship_resolver.rb @@ -0,0 +1,68 @@ +# typed: strict +# frozen_string_literal: true + +require "sorbet-runtime" +require "dependabot/npm_and_yarn/file_parser/yarn_lock" + +module Dependabot + module NpmAndYarn + class DependencyGrapher < Dependabot::DependencyGraphers::Base + class YarnRelationshipResolver + extend T::Sig + + sig { params(lockfile: Dependabot::DependencyFile).void } + def initialize(lockfile) + @lockfile = lockfile + end + + sig { returns(T::Hash[String, T::Array[String]]) } + def relationships + parsed = FileParser::YarnLock.new(@lockfile).parsed + + parsed.each_with_object({}) do |(req, details), rels| + next unless details.is_a?(Hash) + + version = details["version"] + parent_name = T.must(req.split(/(?<=\w)\@/).first) + children = details.fetch("dependencies", {}) + + next if children.nil? || children.empty? + + key = "#{parent_name}@#{version}" + resolved_children = resolve_children(children, parsed) + + rels[key] ||= [] + rels[key].concat(resolved_children).uniq! + end + end + + private + + sig { params(children: T::Hash[String, String], parsed: T::Hash[String, T.untyped]).returns(T::Array[String]) } + def resolve_children(children, parsed) + children.filter_map do |child_name, child_req| + version = resolve_child_version(child_name, child_req, parsed) + "#{child_name}@#{version}" if version + end + end + + sig { params(child_name: String, child_req: String, parsed: T::Hash[String, T.untyped]).returns(T.nilable(String)) } + def resolve_child_version(child_name, child_req, parsed) + # Try exact key first + child_entry = parsed["#{child_name}@#{child_req}"] + return child_entry["version"] if child_entry && child_entry["version"] + + # Yarn groups multiple requirements into single keys like "foo@^1.0.0, foo@^1.2.0" + target_req = "#{child_name}@#{child_req}" + grouped_match = parsed.find { |k, _| k.split(", ").include?(target_req) } + return grouped_match.last["version"] if grouped_match && grouped_match.last["version"] + + # Fallback: find by name only if there's exactly one candidate + candidates = parsed.select { |k, _| k.split(/(?<=\w)\@/).first == child_name } + candidate = candidates.first + candidate.last["version"] if candidates.size == 1 && candidate + end + end + end + end +end diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb index 1c67bb9c89d..3b587ead7fc 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb @@ -240,6 +240,9 @@ def manifest_dependencies next if requirement.start_with?("workspace:", "catalog:") requirement = "*" if requirement == "" + + name, requirement = dealias_package(name, requirement) if dealias_packages? + dep = build_dependency( file: file, type: type, name: name, requirement: requirement ) @@ -278,7 +281,8 @@ def workspace_catalog_dependencies def lockfile_parser @lockfile_parser ||= T.let( LockfileParser.new( - dependency_files: dependency_files + dependency_files: dependency_files, + dealias_packages: dealias_packages? ), T.nilable(Dependabot::NpmAndYarn::FileParser::LockfileParser) ) @@ -387,6 +391,64 @@ def aliased_package_name?(name) name.include?("@#{NpmPackageManager::NAME}:") end + sig { returns(T::Boolean) } + def dealias_packages? + options.fetch(:dealias_packages, false) == true + end + + # Resolves an aliased manifest entry to its real package name and requirement. + # Yarn-style: "my-fetch-factory@npm:fetch-factory": "0.0.2" + # npm-style: "my-fetch-factory": "npm:fetch-factory@0.0.2" + sig { params(name: String, requirement: String).returns([String, String]) } + def dealias_package(name, requirement) + if aliased_package_name?(name) + real_name = extract_real_name_from_alias_key(name) + name = real_name if real_name + elsif alias_package?(requirement) + parsed = parse_alias_package_requirement(requirement) + if parsed + name = T.must(parsed[:name]) + requirement = T.must(parsed[:requirement]) + end + end + + [name, requirement] + end + + # npm-style: "npm:fetch-factory@0.0.2" → { name: "fetch-factory", requirement: "0.0.2" } + # npm-style: "npm:@scope/pkg@^1.0.0" → { name: "@scope/pkg", requirement: "^1.0.0" } + sig { params(requirement: String).returns(T.nilable(T::Hash[Symbol, String])) } + def parse_alias_package_requirement(requirement) + return nil unless requirement.start_with?("#{NpmPackageManager::NAME}:") + + rest = requirement.delete_prefix("#{NpmPackageManager::NAME}:") + + if rest.start_with?("@") + second_at = rest.index("@", 1) + if second_at + { name: rest[0...second_at], requirement: rest[(second_at + 1)..] } + else + { name: rest, requirement: "*" } + end + else + at_index = rest.index("@") + if at_index + { name: rest[0...at_index], requirement: rest[(at_index + 1)..] } + else + { name: rest, requirement: "*" } + end + end + end + + # Yarn-style: "my-fetch-factory@npm:fetch-factory" → "fetch-factory" + sig { params(name: String).returns(T.nilable(String)) } + def extract_real_name_from_alias_key(name) + match = name.match(/@#{NpmPackageManager::NAME}:(.+)$/o) + return nil unless match + + match[1] + end + sig { returns(T::Array[String]) } def workspace_package_names @workspace_package_names ||= T.let( diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/json_lock.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/json_lock.rb index e0c8219c71a..e1c53aa80f9 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/json_lock.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/json_lock.rb @@ -12,9 +12,10 @@ class FileParser < Dependabot::FileParsers::Base class JsonLock extend T::Sig - sig { params(dependency_file: DependencyFile).void } - def initialize(dependency_file) + sig { params(dependency_file: DependencyFile, dealias_packages: T::Boolean).void } + def initialize(dependency_file, dealias_packages: false) @dependency_file = dependency_file + @dealias_packages = dealias_packages end sig { returns(T::Hash[String, T.untyped]) } @@ -64,7 +65,7 @@ def recursively_fetch_dependencies(object_with_dependencies) version = Version.semver_for(details["version"]) next unless version - package_name = name.split("node_modules/").last + package_name = package_name_for(name, details) version = version.to_s dependency_args = { @@ -74,6 +75,11 @@ def recursively_fetch_dependencies(object_with_dependencies) requirements: [] } + if aliased_package?(name, details) + alias_name = name.split("node_modules/").last + dependency_args[:metadata] = { alias: alias_name } + end + if details["bundled"] dependency_args[:subdependency_metadata] = [{ npm_bundled: details["bundled"] }] @@ -91,6 +97,29 @@ def recursively_fetch_dependencies(object_with_dependencies) dependency_set end + sig { params(package_path: String, details: T::Hash[String, T.untyped]).returns(String) } + def package_name_for(package_path, details) + package_name = T.must(package_path.split("node_modules/").last) + return package_name unless dealias_packages? + + real_package_name = details["name"] + return package_name unless real_package_name.is_a?(String) + return package_name if real_package_name == package_name + + real_package_name + end + + sig { params(package_path: String, details: T::Hash[String, T.untyped]).returns(T::Boolean) } + def aliased_package?(package_path, details) + return false unless dealias_packages? + + real_package_name = details["name"] + return false unless real_package_name.is_a?(String) + + package_name = T.must(package_path.split("node_modules/").last) + real_package_name != package_name + end + sig { params(manifest_name: String, dependency_name: String).returns(String) } def node_modules_path(manifest_name, dependency_name) return "node_modules/#{dependency_name}" if manifest_name == "package.json" @@ -98,6 +127,11 @@ def node_modules_path(manifest_name, dependency_name) workspace_path = manifest_name.gsub("/package.json", "") File.join(workspace_path, "node_modules", dependency_name) end + + sig { returns(T::Boolean) } + def dealias_packages? + @dealias_packages + end end end end diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/lockfile_parser.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/lockfile_parser.rb index 16effc32ae6..ba0864274e9 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/lockfile_parser.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/lockfile_parser.rb @@ -20,9 +20,10 @@ class LockfileParser LockFile = T.type_alias { T.any(JsonLock, YarnLock, PnpmLock) } - sig { params(dependency_files: T::Array[DependencyFile]).void } - def initialize(dependency_files:) + sig { params(dependency_files: T::Array[DependencyFile], dealias_packages: T::Boolean).void } + def initialize(dependency_files:, dealias_packages: false) @dependency_files = dependency_files + @dealias_packages = dealias_packages end sig { returns(Dependabot::FileParsers::Base::DependencySet) } @@ -81,11 +82,11 @@ def lockfile_for(file) @lockfiles ||= T.let({}, T.nilable(T::Hash[String, LockFile])) @lockfiles[file.name] ||= case file.name when *package_locks.map(&:name), *shrinkwraps.map(&:name) - JsonLock.new(file) + JsonLock.new(file, dealias_packages: @dealias_packages) when *yarn_locks.map(&:name) - YarnLock.new(file) + YarnLock.new(file, dealias_packages: @dealias_packages) when *pnpm_locks.map(&:name) - PnpmLock.new(file) + PnpmLock.new(file, dealias_packages: @dealias_packages) else raise "Unexpected lockfile: #{file.name}" end diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/pnpm_lock.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/pnpm_lock.rb index 1552b7cfa97..de160345e1a 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/pnpm_lock.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/pnpm_lock.rb @@ -15,9 +15,10 @@ class FileParser < Dependabot::FileParsers::Base class PnpmLock extend T::Sig - sig { params(dependency_file: Dependabot::DependencyFile).void } - def initialize(dependency_file) + sig { params(dependency_file: Dependabot::DependencyFile, dealias_packages: T::Boolean).void } + def initialize(dependency_file, dealias_packages: false) @dependency_file = dependency_file + @dealias_packages = dealias_packages end sig { returns(T::Array[T::Hash[String, T.untyped]]) } @@ -43,6 +44,7 @@ def parsed # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/PerceivedComplexity sig { returns(Dependabot::FileParsers::Base::DependencySet) } def dependencies dependency_set = Dependabot::FileParsers::Base::DependencySet.new @@ -52,7 +54,7 @@ def dependencies dependencies_without_specifiers = T.let([], T::Array[T::Hash[Symbol, T.untyped]]) parsed.each do |details| - next if details["aliased"] + next if details["aliased"] && !dealias_packages? name = T.cast(details["name"], String) version = T.cast(details["version"], T.nilable(String)) @@ -64,6 +66,9 @@ def dependencies requirements: [] } + # Tag aliased packages with metadata so the grapher can identify them as direct + dependency_args[:metadata] = { alias: name } if details["aliased"] + # Add metadata for subdependencies if marked as a dev dependency. dependency_args[:subdependency_metadata] = [{ production: !details["dev"] }] if details["dev"] @@ -82,7 +87,8 @@ def dependencies version: dependency_args[:version], package_manager: dependency_args[:package_manager], requirements: dependency_args[:requirements], - subdependency_metadata: dependency_args[:subdependency_metadata] + subdependency_metadata: dependency_args[:subdependency_metadata], + metadata: dependency_args[:metadata] ) end @@ -92,12 +98,14 @@ def dependencies version: dependency_args[:version], package_manager: dependency_args[:package_manager], requirements: dependency_args[:requirements], - subdependency_metadata: dependency_args[:subdependency_metadata] + subdependency_metadata: dependency_args[:subdependency_metadata], + metadata: dependency_args[:metadata] ) end dependency_set end + # rubocop:enable Metrics/PerceivedComplexity # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/MethodLength @@ -120,6 +128,13 @@ def details(dependency_name, requirement, _manifest_name) details_candidates.find { |info| info["specifiers"]&.include?(requirement) } end end + + private + + sig { returns(T::Boolean) } + def dealias_packages? + @dealias_packages + end end end end diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/yarn_lock.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/yarn_lock.rb index e1b46716836..cafaa6f1552 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/yarn_lock.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/yarn_lock.rb @@ -12,9 +12,10 @@ class FileParser < Dependabot::FileParsers::Base class YarnLock extend T::Sig - sig { params(dependency_file: Dependabot::DependencyFile).void } - def initialize(dependency_file) + sig { params(dependency_file: Dependabot::DependencyFile, dealias_packages: T::Boolean).void } + def initialize(dependency_file, dealias_packages: false) @dependency_file = dependency_file + @dealias_packages = dealias_packages end sig { returns(T::Hash[String, T::Hash[String, T.untyped]]) } @@ -50,16 +51,33 @@ def dependencies reqs.split(", ").each do |req| version = Version.semver_for(details["version"]) next unless version - next if alias_package?(req) next if workspace_package?(req) next if req == "__metadata" - dependency_set << Dependency.new( - name: T.must(req.split(/(?<=\w)\@/).first), - version: version.to_s, - package_manager: "npm_and_yarn", - requirements: [] - ) + if alias_package?(req) + # Skip unless we are dealiasing packages + next unless dealias_packages? + + real_name = extract_real_name_from_yarn_alias(req) + next unless real_name + + alias_name = T.must(req.split(/(?<=\w)\@npm:/).first) + + dependency_set << Dependency.new( + name: real_name, + version: version.to_s, + package_manager: "npm_and_yarn", + requirements: [], + metadata: { alias: alias_name } + ) + else + dependency_set << Dependency.new( + name: T.must(req.split(/(?<=\w)\@/).first), + version: version.to_s, + package_manager: "npm_and_yarn", + requirements: [] + ) + end end end @@ -92,11 +110,34 @@ def details(dependency_name, requirement, _manifest_name) private + sig { returns(T::Boolean) } + def dealias_packages? + @dealias_packages + end + sig { params(requirement: String).returns(T::Boolean) } def alias_package?(requirement) requirement.match?(/@npm:(.+@(?!npm))/) end + # Examples: + # - "my-fetch-factory@npm:fetch-factory@^0.0.1" → "fetch-factory" + # - "my-pkg@npm:@scope/real-pkg@^1.0.0" → "@scope/real-pkg" + sig { params(requirement: String).returns(T.nilable(String)) } + def extract_real_name_from_yarn_alias(requirement) + match = requirement.match(/@npm:(.+)$/) + return nil unless match + + rest = T.must(match[1]) + if rest.start_with?("@") + second_at = rest.index("@", 1) + second_at ? rest[0...second_at] : rest + else + at_index = rest.index("@") + at_index ? rest[0...at_index] : rest + end + end + sig { params(requirement: String).returns(T::Boolean) } def workspace_package?(requirement) requirement.include?("@workspace:") diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/dependency_grapher_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/dependency_grapher_spec.rb index e5dcac1a63a..d8c130eb0de 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/dependency_grapher_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/dependency_grapher_spec.rb @@ -98,6 +98,121 @@ end end + context "with an aliased dependency" do + let(:dependency_files) { project_dependency_files("grapher/npm_with_alias") } + + it "includes the real aliased package in resolved dependencies" do + resolved_dependencies = grapher.resolved_dependencies + + # The aliased package (is-number aliased as my-is-number) should appear + # under its real name + is_number = resolved_dependencies["pkg:npm/is-number@7.0.0"] + expect(is_number).not_to be_nil + expect(is_number.package_url).to eq("pkg:npm/is-number@7.0.0") + expect(is_number.direct).to be(true) + expect(is_number.runtime).to be(true) + end + + it "includes non-aliased dependencies normally" do + resolved_dependencies = grapher.resolved_dependencies + + etag = resolved_dependencies["pkg:npm/etag@1.8.1"] + expect(etag).not_to be_nil + expect(etag.direct).to be(true) + end + end + + context "with an aliased dependency that creates multiple versions of the same package" do + let(:dependency_files) { project_dependency_files("grapher/npm_with_alias_multiversion") } + + it "marks the direct version as direct" do + resolved_dependencies = grapher.resolved_dependencies + + is_number7 = resolved_dependencies["pkg:npm/is-number@7.0.0"] + expect(is_number7).not_to be_nil + expect(is_number7.direct).to be(true) + expect(is_number7.dependencies).to eq([]) + end + + it "marks the aliased version as direct" do + resolved_dependencies = grapher.resolved_dependencies + + # The aliased version appears under its real name and is still direct + # because the manifest explicitly declares it (via the alias) + is_number3 = resolved_dependencies["pkg:npm/is-number@3.0.0"] + expect(is_number3).not_to be_nil + expect(is_number3.direct).to be(true) + expect(is_number3.dependencies).to include("pkg:npm/kind-of@3.2.2") + end + + it "marks transitive versions as not direct" do + resolved_dependencies = grapher.resolved_dependencies + + # is-number@2.1.0 is brought in transitively by fill-range + is_number2 = resolved_dependencies["pkg:npm/is-number@2.1.0"] + expect(is_number2).not_to be_nil + expect(is_number2.direct).to be(false) + + # is-number@4.0.0 is brought in transitively by randomatic + is_number4 = resolved_dependencies["pkg:npm/is-number@4.0.0"] + expect(is_number4).not_to be_nil + expect(is_number4.direct).to be(false) + end + + it "resolves subdependencies of the aliased version correctly" do + resolved_dependencies = grapher.resolved_dependencies + + kind_of = resolved_dependencies["pkg:npm/kind-of@3.2.2"] + expect(kind_of).not_to be_nil + expect(kind_of.direct).to be(false) + expect(kind_of.dependencies).to include("pkg:npm/is-buffer@1.1.6") + end + end + + context "with a yarn aliased dependency" do + let(:dependency_files) { project_dependency_files("grapher/yarn_with_alias") } + + it "includes the real aliased package in resolved dependencies" do + resolved_dependencies = grapher.resolved_dependencies + + fetch_factory = resolved_dependencies["pkg:npm/fetch-factory@0.0.1"] + expect(fetch_factory).not_to be_nil + expect(fetch_factory.package_url).to eq("pkg:npm/fetch-factory@0.0.1") + expect(fetch_factory.direct).to be(true) + expect(fetch_factory.runtime).to be(true) + end + + it "includes non-aliased dependencies normally" do + resolved_dependencies = grapher.resolved_dependencies + + etag = resolved_dependencies["pkg:npm/etag@1.8.1"] + expect(etag).not_to be_nil + expect(etag.direct).to be(true) + end + end + + context "with a pnpm aliased dependency" do + let(:dependency_files) { project_dependency_files("grapher/pnpm_with_alias") } + + it "includes the real aliased package in resolved dependencies" do + resolved_dependencies = grapher.resolved_dependencies + + fetch_factory = resolved_dependencies["pkg:npm/fetch-factory@0.0.2"] + expect(fetch_factory).not_to be_nil + expect(fetch_factory.package_url).to eq("pkg:npm/fetch-factory@0.0.2") + expect(fetch_factory.direct).to be(true) + expect(fetch_factory.runtime).to be(true) + end + + it "includes non-aliased dependencies normally" do + resolved_dependencies = grapher.resolved_dependencies + + etag = resolved_dependencies["pkg:npm/etag@1.8.1"] + expect(etag).not_to be_nil + expect(etag.direct).to be(true) + end + end + context "with a lockfile containing subdependencies" do let(:dependency_files) { project_dependency_files("grapher/npm_with_subdeps") } @@ -757,7 +872,7 @@ corrupt_lockfile = Dependabot::DependencyFile.new( name: "package-lock.json", content: "not valid json {{{", directory: "/" ) - grapher.instance_variable_set(:@npm_lockfile, corrupt_lockfile) + grapher.send(:lockfiles_hash)[:npm] = corrupt_lockfile end it "sets the errored_fetching_subdependencies flag" do @@ -792,7 +907,7 @@ corrupt_lockfile = Dependabot::DependencyFile.new( name: "yarn.lock", content: "\x00\x01 invalid", directory: "/" ) - grapher.instance_variable_set(:@yarn_lockfile, corrupt_lockfile) + grapher.send(:lockfiles_hash)[:yarn] = corrupt_lockfile end it "sets the errored_fetching_subdependencies flag" do @@ -827,7 +942,7 @@ corrupt_lockfile = Dependabot::DependencyFile.new( name: "pnpm-lock.yaml", content: ": :\n invalid: [yaml", directory: "/" ) - grapher.instance_variable_set(:@pnpm_lockfile, corrupt_lockfile) + grapher.send(:lockfiles_hash)[:pnpm] = corrupt_lockfile end it "sets the errored_fetching_subdependencies flag" do diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser/lockfile_parser_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser/lockfile_parser_spec.rb index 7b36891c136..5ca7b6fd20c 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser/lockfile_parser_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser/lockfile_parser_spec.rb @@ -35,6 +35,17 @@ expect(dependencies.count).to eq(10) expect(dependencies.map(&:name)).not_to include("my-fetch-factory") end + + context "with dealias_packages enabled" do + subject(:lockfile_parser) do + described_class.new(dependency_files: dependency_files, dealias_packages: true) + end + + it "includes the real aliased package" do + expect(dependencies.map(&:name)).to include("fetch-factory") + expect(dependencies.count).to eq(11) + end + end end context "when there are multiple dependencies" do @@ -137,6 +148,17 @@ expect(dependencies.count).to eq(10) expect(dependencies.map(&:name)).not_to include("my-fetch-factory") end + + context "with dealias_packages enabled" do + subject(:lockfile_parser) do + described_class.new(dependency_files: dependency_files, dealias_packages: true) + end + + it "includes the real aliased package" do + expect(dependencies.map(&:name)).to include("fetch-factory") + expect(dependencies.count).to eq(11) + end + end end context "when there are multiple dependencies" do @@ -280,6 +302,26 @@ expect(bad_names).to be_empty end end + + context "when there is an aliased dependency" do + let(:dependency_files) { project_dependency_files("grapher/npm_with_alias") } + + it "excludes the real package name by default" do + expect(dependencies.map(&:name)).not_to include("is-number") + expect(dependencies.map(&:name)).to include("my-is-number") + end + + context "with dealias_packages enabled" do + subject(:lockfile_parser) do + described_class.new(dependency_files: dependency_files, dealias_packages: true) + end + + it "includes the real aliased package" do + expect(dependencies.map(&:name)).to include("is-number") + expect(dependencies.map(&:name)).not_to include("my-is-number") + end + end + end end context "when dealing with npm shrinkwraps" do diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser_spec.rb index 7084df3812d..14eda6c873f 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser_spec.rb @@ -1152,6 +1152,26 @@ end end + context "with an npm aliased dependency" do + let(:files) { project_dependency_files("grapher/npm_with_alias") } + + it "doesn't include the aliased dependency" do + expect(top_level_dependencies.map(&:name)).to include("etag") + expect(top_level_dependencies.map(&:name)).not_to include("is-number") + expect(top_level_dependencies.map(&:name)).not_to include("my-is-number") + end + end + + context "with a pnpm aliased dependency" do + let(:files) { project_dependency_files("pnpm/aliased_dependency") } + + it "doesn't include the aliased dependency" do + expect(top_level_dependencies.map(&:name)).to include("etag") + expect(top_level_dependencies.map(&:name)).not_to include("fetch-factory") + expect(top_level_dependencies.map(&:name)).not_to include("my-fetch-factory") + end + end + context "with an aliased dependency name (only supported by yarn)" do let(:files) { project_dependency_files("yarn/aliased_dependency_name") } @@ -1162,6 +1182,98 @@ end end + context "with an aliased dependency and dealias_packages enabled" do + let(:files) { project_dependency_files("yarn/aliased_dependency") } + let(:parser) do + described_class.new( + dependency_files: files, + source: source, + credentials: credentials, + options: { dealias_packages: true } + ) + end + + it "includes the real aliased package (fetch-factory)" do + expect(top_level_dependencies.map(&:name)).to include("fetch-factory") + expect(top_level_dependencies.map(&:name)).to include("etag") + end + end + + context "with an aliased dependency name (yarn-style) and dealias_packages enabled" do + let(:files) { project_dependency_files("yarn/aliased_dependency_name") } + let(:parser) do + described_class.new( + dependency_files: files, + source: source, + credentials: credentials, + options: { dealias_packages: true } + ) + end + + it "includes the real aliased package (fetch-factory)" do + expect(top_level_dependencies.map(&:name)).to include("fetch-factory") + expect(top_level_dependencies.map(&:name)).to include("etag") + end + end + + context "with an npm aliased dependency and dealias_packages enabled" do + let(:files) { project_dependency_files("grapher/npm_with_alias") } + let(:parser) do + described_class.new( + dependency_files: files, + source: source, + credentials: credentials, + options: { dealias_packages: true } + ) + end + + it "includes the real aliased package (is-number)" do + expect(top_level_dependencies.map(&:name)).to include("is-number") + expect(top_level_dependencies.map(&:name)).to include("etag") + expect(top_level_dependencies.map(&:name)).not_to include("my-is-number") + end + end + + context "with a pnpm aliased dependency and dealias_packages enabled" do + let(:files) { project_dependency_files("pnpm/aliased_dependency") } + let(:parser) do + described_class.new( + dependency_files: files, + source: source, + credentials: credentials, + options: { dealias_packages: true } + ) + end + + it "includes the real aliased package (fetch-factory)" do + expect(top_level_dependencies.map(&:name)).to include("fetch-factory") + expect(top_level_dependencies.map(&:name)).not_to include("my-fetch-factory") + end + end + + context "with an aliased dependency, dealias_packages enabled, and no lockfile" do + let(:files) { project_dependency_files("npm8/aliased_dependency_no_lockfile") } + let(:parser) do + described_class.new( + dependency_files: files, + source: source, + credentials: credentials, + options: { dealias_packages: true } + ) + end + + it "includes the real aliased package with the requirement as version" do + dep = top_level_dependencies.find { |d| d.name == "is-number" } + expect(dep).not_to be_nil + expect(dep.version).to be_nil + expect(dep.requirements.first[:requirement]).to eq("^7.0.0") + end + + it "still includes non-aliased packages" do + expect(top_level_dependencies.map(&:name)).to include("etag") + end + end + context "with a git dependency" do let(:files) { project_dependency_files("npm6_and_yarn/git_dependency") } diff --git a/npm_and_yarn/spec/fixtures/projects/grapher/npm_with_alias/package-lock.json b/npm_and_yarn/spec/fixtures/projects/grapher/npm_with_alias/package-lock.json new file mode 100644 index 00000000000..bd19517d992 --- /dev/null +++ b/npm_and_yarn/spec/fixtures/projects/grapher/npm_with_alias/package-lock.json @@ -0,0 +1,33 @@ +{ + "name": "grapher-npm-with-alias", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "grapher-npm-with-alias", + "version": "1.0.0", + "dependencies": { + "my-is-number": "npm:is-number@7.0.0", + "etag": "^1.8.1" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/my-is-number": { + "name": "is-number", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + } + } +} diff --git a/npm_and_yarn/spec/fixtures/projects/grapher/npm_with_alias/package.json b/npm_and_yarn/spec/fixtures/projects/grapher/npm_with_alias/package.json new file mode 100644 index 00000000000..c03cc2ebda0 --- /dev/null +++ b/npm_and_yarn/spec/fixtures/projects/grapher/npm_with_alias/package.json @@ -0,0 +1,9 @@ +{ + "name": "grapher-npm-with-alias", + "version": "1.0.0", + "description": "Test project with an aliased dependency", + "dependencies": { + "my-is-number": "npm:is-number@7.0.0", + "etag": "^1.8.1" + } +} diff --git a/npm_and_yarn/spec/fixtures/projects/grapher/npm_with_alias_multiversion/package-lock.json b/npm_and_yarn/spec/fixtures/projects/grapher/npm_with_alias_multiversion/package-lock.json new file mode 100644 index 00000000000..21074e7bb03 --- /dev/null +++ b/npm_and_yarn/spec/fixtures/projects/grapher/npm_with_alias_multiversion/package-lock.json @@ -0,0 +1,451 @@ +{ + "name": "alias-three-versions", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "alias-three-versions", + "version": "1.0.0", + "dependencies": { + "is-number": "^7.0.0", + "is-number-legacy": "npm:is-number@^3.0.0", + "micromatch": "2.3.11" + } + }, + "node_modules/arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha512-dtXTVMkh6VkEEA7OhXnN1Ecb8aAGFdZ1LFxtOCoqj4qkyOJMt7+qs6Ahdy6p/NQCPYsRSXXivhSB/J5E9jmYKA==", + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha512-G2n5bG5fSUCpnsXz4+8FUkYsGPkNfLn9YvS66U5qbTIXI2Ynnlo4Bi42bWv+omKUCqz+ejzfClwne0alJWJPhg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha512-xU7bpz2ytJl1bH9cgIurjpg/n8Gohy9GTw81heDYLJQ4RU60dlyJsa+atVF2pI0yMMvKxI9HkKwjePCj5XI1hw==", + "license": "MIT", + "dependencies": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha512-hxx03P2dJxss6ceIeri9cmYOT4SRs3Zk3afZwWpOsRqLqprhTR8u++SlC+sFGsQr7WGFPdMF7Gjc1njDLDK6UA==", + "license": "MIT", + "dependencies": { + "is-posix-bracket": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha512-AFASGfIlnIbkKPQwX1yHaDjFvh/1gyKJODme52V6IORh69uEYgZp0o9C+qsIGNVEiuuhQU0CSSl++Rlegg1qvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha512-1FOj1LOwn42TMrruOHGt18HemVnbwAmAak7krWk+wa93KXxGbK+2jpezm+ytJYDaBX0/SPLZFHKM7m+tKobWGg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha512-BTCqyBaWBTsauvnHiE8i562+EdJj+oUpkqWp2R1iCoR8f6oo8STRu3of7WJJ0TqWtxN50a5YFpzYK4Jj9esYfQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "license": "MIT", + "dependencies": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range/node_modules/is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha512-QUzH43Gfb9+5yckcrSA0VBDwEtDUchrk4F6tfJZQuNzDJbEDB9cZNzSfXGQ1jqmdDY/kl41lUOWM9syA8z8jlg==", + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", + "license": "MIT", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha512-ab1S1g1EbO7YzauaJLkgLp7DZVAqj9M/dvKlTt8DkXA2tiOIcSMrlVI2J1RZyB5iJVccEscjGn+kpOG9788MHA==", + "license": "MIT", + "dependencies": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha512-JDYOvfxio/t42HKdxkAYaCiBN7oYiuxykOxKxdaUW5Qn0zaYN3gRQWolrwdnf0shM9/EP0ebuuTmyoXNr1cC5w==", + "license": "ISC", + "dependencies": { + "is-glob": "^2.0.0" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, + "node_modules/is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha512-9YclgOGtN/f8zx0Pr4FQYMdibBiTaH3sn52vjYip4ZSf6C4/6RfTEZ+MR4GvKhCxdPh21Bg42/WL55f6KSnKpg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha512-0EygVC5qPvIyb+gSz7zdD5/AAoS6Qrx1e//6N4yv4oNm30kqvdmG66oZFWVlQHUWe5OjP08FuTw2IdT0EOTcYA==", + "license": "MIT", + "dependencies": { + "is-primitive": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-legacy": { + "name": "is-number", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha512-Yu68oeXJ7LeWNmZ3Zov/xg/oDBnBK2RNxwYY1ilNJX+tKKZqgPK+qOn/Gs9jEu66KDY9Netf5XLKNGzas/vPfQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha512-N3w1tFaRfk3UrPfqeRyD+GYDASU3W5VinKhlORy8EWVf/sIdDL9GAcew85XmktCfH+ngG7SRXEVDoO18WMdB/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "license": "MIT", + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha512-LnU2XFEk9xxSJ6rfgAry/ty5qwUTyHYOBU0g4R6tIw5ljwgGIBmiKhRWLw5NpMOnrgUNcDJ4WMp8rl3sYVHLNA==", + "license": "MIT", + "dependencies": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "license": "MIT", + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha512-UiAM5mhmIuKLsOvrL+B0U2d1hXHF3bFYWIuH1LMpuV2EJEHG1Ntz06PgLEHjm6VFd87NpH8rastvPoyv6UW2fA==", + "license": "MIT", + "dependencies": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha512-FC5TeK0AwXzq3tUBFtH74naWkPQCEWs4K+xMxWZBlKDWu0bVHXGZa+KKqxKidd7xwhdZ19ZNuF2uO1M/r196HA==", + "license": "MIT", + "dependencies": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha512-s/46sYeylUfHNjI+sA/78FAHlmIuKqI9wNnzEOGehAlUUYeObv5C2mOinXBjyUyWmJ2SfcS2/ydApH4hTF4WXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "license": "MIT", + "dependencies": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/randomatic/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/randomatic/node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "license": "MIT", + "dependencies": { + "is-equal-shallow": "^0.1.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "license": "ISC" + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + } + } +} diff --git a/npm_and_yarn/spec/fixtures/projects/grapher/npm_with_alias_multiversion/package.json b/npm_and_yarn/spec/fixtures/projects/grapher/npm_with_alias_multiversion/package.json new file mode 100644 index 00000000000..f4f4cee4dbd --- /dev/null +++ b/npm_and_yarn/spec/fixtures/projects/grapher/npm_with_alias_multiversion/package.json @@ -0,0 +1,9 @@ +{ + "name": "alias-three-versions", + "version": "1.0.0", + "dependencies": { + "is-number": "^7.0.0", + "is-number-legacy": "npm:is-number@^3.0.0", + "micromatch": "2.3.11" + } +} diff --git a/npm_and_yarn/spec/fixtures/projects/grapher/pnpm_with_alias/package.json b/npm_and_yarn/spec/fixtures/projects/grapher/pnpm_with_alias/package.json new file mode 100644 index 00000000000..614f9728ac9 --- /dev/null +++ b/npm_and_yarn/spec/fixtures/projects/grapher/pnpm_with_alias/package.json @@ -0,0 +1,10 @@ +{ + "name": "grapher-pnpm-with-alias", + "version": "1.0.0", + "dependencies": { + "my-fetch-factory": "npm:fetch-factory@0.0.2" + }, + "devDependencies": { + "etag": "^1.0.0" + } +} diff --git a/npm_and_yarn/spec/fixtures/projects/grapher/pnpm_with_alias/pnpm-lock.yaml b/npm_and_yarn/spec/fixtures/projects/grapher/pnpm_with_alias/pnpm-lock.yaml new file mode 100644 index 00000000000..1920c7ad537 --- /dev/null +++ b/npm_and_yarn/spec/fixtures/projects/grapher/pnpm_with_alias/pnpm-lock.yaml @@ -0,0 +1,74 @@ +lockfileVersion: '6.0' + +dependencies: + my-fetch-factory: + specifier: npm:fetch-factory@0.0.2 + version: /fetch-factory@0.0.2 + +devDependencies: + etag: + specifier: ^1.0.0 + version: 1.8.1 + +packages: + + /encoding@0.1.12: + resolution: {integrity: sha512-bl1LAgiQc4ZWr++pNYUdRe/alecaHFeHxIJ/pNciqGdKXghaTCOwKkbKp6ye7pKZGu/GcaSXFk8PBVhgs+dJdA==} + dependencies: + iconv-lite: 0.4.23 + dev: false + + /es6-promise@3.3.1: + resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} + dev: false + + /etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + dev: true + + /fetch-factory@0.0.2: + resolution: {integrity: sha512-4G9zr4/vs97MlQRMTqr6HzCVCCnzUy6VdfJldIyweecAdrRJs3DjV1duugymtHexXMne48i+pRArN+zTsBpXSw==} + dependencies: + es6-promise: 3.3.1 + isomorphic-fetch: 2.2.1 + lodash: 3.10.1 + dev: false + + /iconv-lite@0.4.23: + resolution: {integrity: sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: false + + /is-stream@1.1.0: + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} + dev: false + + /isomorphic-fetch@2.2.1: + resolution: {integrity: sha512-9c4TNAKYXM5PRyVcwUZrF3W09nQ+sO7+jydgs4ZGW9dhsLG2VOlISJABombdQqQRXCwuYG3sYV/puGf5rp0qmA==} + dependencies: + node-fetch: 1.7.3 + whatwg-fetch: 2.0.4 + dev: false + + /lodash@3.10.1: + resolution: {integrity: sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ==} + dev: false + + /node-fetch@1.7.3: + resolution: {integrity: sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==} + dependencies: + encoding: 0.1.12 + is-stream: 1.1.0 + dev: false + + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: false + + /whatwg-fetch@2.0.4: + resolution: {integrity: sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==} + dev: false diff --git a/npm_and_yarn/spec/fixtures/projects/grapher/yarn_with_alias/package.json b/npm_and_yarn/spec/fixtures/projects/grapher/yarn_with_alias/package.json new file mode 100644 index 00000000000..086b1ed55ec --- /dev/null +++ b/npm_and_yarn/spec/fixtures/projects/grapher/yarn_with_alias/package.json @@ -0,0 +1,8 @@ +{ + "name": "grapher-yarn-with-alias", + "version": "1.0.0", + "dependencies": { + "my-fetch-factory": "npm:fetch-factory@0.0.1", + "etag": "^1.0.0" + } +} diff --git a/npm_and_yarn/spec/fixtures/projects/grapher/yarn_with_alias/yarn.lock b/npm_and_yarn/spec/fixtures/projects/grapher/yarn_with_alias/yarn.lock new file mode 100644 index 00000000000..e3521072ead --- /dev/null +++ b/npm_and_yarn/spec/fixtures/projects/grapher/yarn_with_alias/yarn.lock @@ -0,0 +1,61 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +encoding@^0.1.11: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + dependencies: + iconv-lite "~0.4.13" + +es6-promise@^3.0.2: + version "3.3.1" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" + +etag@^1.0.0: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + +iconv-lite@~0.4.13: + version "0.4.23" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" + dependencies: + safer-buffer ">= 2.1.2 < 3" + +is-stream@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + +isomorphic-fetch@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + +lodash@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" + +"my-fetch-factory@npm:fetch-factory@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/fetch-factory/-/fetch-factory-0.0.1.tgz#e0076059bdb31e3147c75b3b8c04133ba8c7e071" + dependencies: + es6-promise "^3.0.2" + isomorphic-fetch "^2.1.1" + lodash "^3.10.1" + +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + +whatwg-fetch@>=0.10.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" diff --git a/npm_and_yarn/spec/fixtures/projects/npm8/aliased_dependency_no_lockfile/package.json b/npm_and_yarn/spec/fixtures/projects/npm8/aliased_dependency_no_lockfile/package.json new file mode 100644 index 00000000000..1b1dcd10f8d --- /dev/null +++ b/npm_and_yarn/spec/fixtures/projects/npm8/aliased_dependency_no_lockfile/package.json @@ -0,0 +1,8 @@ +{ + "name": "alias-no-lockfile-test", + "version": "1.0.0", + "dependencies": { + "my-is-number": "npm:is-number@^7.0.0", + "etag": "^1.8.1" + } +}