Skip to content

Commit 6cb976f

Browse files
committed
Handle dealiasing of manifests
1 parent 0db8db8 commit 6cb976f

3 files changed

Lines changed: 147 additions & 0 deletions

File tree

npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,9 @@ def manifest_dependencies
240240
next if requirement.start_with?("workspace:", "catalog:")
241241

242242
requirement = "*" if requirement == ""
243+
244+
name, requirement = dealias_package(name, requirement) if dealias_packages?
245+
243246
dep = build_dependency(
244247
file: file, type: type, name: name, requirement: requirement
245248
)
@@ -388,6 +391,64 @@ def aliased_package_name?(name)
388391
name.include?("@#{NpmPackageManager::NAME}:")
389392
end
390393

394+
sig { returns(T::Boolean) }
395+
def dealias_packages?
396+
options.fetch(:dealias_packages, false) == true
397+
end
398+
399+
# Resolves an aliased manifest entry to its real package name and requirement.
400+
# Yarn-style: "my-fetch-factory@npm:fetch-factory": "0.0.2"
401+
# npm-style: "my-fetch-factory": "npm:fetch-factory@0.0.2"
402+
sig { params(name: String, requirement: String).returns([String, String]) }
403+
def dealias_package(name, requirement)
404+
if aliased_package_name?(name)
405+
real_name = extract_real_name_from_alias_key(name)
406+
name = real_name if real_name
407+
elsif alias_package?(requirement)
408+
parsed = parse_alias_package_requirement(requirement)
409+
if parsed
410+
name = T.must(parsed[:name])
411+
requirement = T.must(parsed[:requirement])
412+
end
413+
end
414+
415+
[name, requirement]
416+
end
417+
418+
# npm-style: "npm:fetch-factory@0.0.2" → { name: "fetch-factory", requirement: "0.0.2" }
419+
# npm-style: "npm:@scope/pkg@^1.0.0" → { name: "@scope/pkg", requirement: "^1.0.0" }
420+
sig { params(requirement: String).returns(T.nilable(T::Hash[Symbol, String])) }
421+
def parse_alias_package_requirement(requirement)
422+
return nil unless requirement.start_with?("#{NpmPackageManager::NAME}:")
423+
424+
rest = requirement.delete_prefix("#{NpmPackageManager::NAME}:")
425+
426+
if rest.start_with?("@")
427+
second_at = rest.index("@", 1)
428+
if second_at
429+
{ name: rest[0...second_at], requirement: rest[(second_at + 1)..] }
430+
else
431+
{ name: rest, requirement: "*" }
432+
end
433+
else
434+
at_index = rest.index("@")
435+
if at_index
436+
{ name: rest[0...at_index], requirement: rest[(at_index + 1)..] }
437+
else
438+
{ name: rest, requirement: "*" }
439+
end
440+
end
441+
end
442+
443+
# Yarn-style: "my-fetch-factory@npm:fetch-factory" → "fetch-factory"
444+
sig { params(name: String).returns(T.nilable(String)) }
445+
def extract_real_name_from_alias_key(name)
446+
match = name.match(/@#{NpmPackageManager::NAME}:(.+)$/o)
447+
return nil unless match
448+
449+
match[1]
450+
end
451+
391452
sig { returns(T::Array[String]) }
392453
def workspace_package_names
393454
@workspace_package_names ||= T.let(

npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser_spec.rb

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,26 @@
11521152
end
11531153
end
11541154

1155+
context "with an npm aliased dependency" do
1156+
let(:files) { project_dependency_files("grapher/npm_with_alias") }
1157+
1158+
it "doesn't include the aliased dependency" do
1159+
expect(top_level_dependencies.map(&:name)).to include("etag")
1160+
expect(top_level_dependencies.map(&:name)).not_to include("is-number")
1161+
expect(top_level_dependencies.map(&:name)).not_to include("my-is-number")
1162+
end
1163+
end
1164+
1165+
context "with a pnpm aliased dependency" do
1166+
let(:files) { project_dependency_files("pnpm/aliased_dependency") }
1167+
1168+
it "doesn't include the aliased dependency" do
1169+
expect(top_level_dependencies.map(&:name)).to include("etag")
1170+
expect(top_level_dependencies.map(&:name)).not_to include("fetch-factory")
1171+
expect(top_level_dependencies.map(&:name)).not_to include("my-fetch-factory")
1172+
end
1173+
end
1174+
11551175
context "with an aliased dependency name (only supported by yarn)" do
11561176
let(:files) { project_dependency_files("yarn/aliased_dependency_name") }
11571177

@@ -1196,6 +1216,64 @@
11961216
end
11971217
end
11981218

1219+
context "with an npm aliased dependency and dealias_packages enabled" do
1220+
let(:files) { project_dependency_files("grapher/npm_with_alias") }
1221+
let(:parser) do
1222+
described_class.new(
1223+
dependency_files: files,
1224+
source: source,
1225+
credentials: credentials,
1226+
options: { dealias_packages: true }
1227+
)
1228+
end
1229+
1230+
it "includes the real aliased package (is-number)" do
1231+
expect(top_level_dependencies.map(&:name)).to include("is-number")
1232+
expect(top_level_dependencies.map(&:name)).to include("etag")
1233+
expect(top_level_dependencies.map(&:name)).not_to include("my-is-number")
1234+
end
1235+
end
1236+
1237+
context "with a pnpm aliased dependency and dealias_packages enabled" do
1238+
let(:files) { project_dependency_files("pnpm/aliased_dependency") }
1239+
let(:parser) do
1240+
described_class.new(
1241+
dependency_files: files,
1242+
source: source,
1243+
credentials: credentials,
1244+
options: { dealias_packages: true }
1245+
)
1246+
end
1247+
1248+
it "includes the real aliased package (fetch-factory)" do
1249+
expect(top_level_dependencies.map(&:name)).to include("fetch-factory")
1250+
expect(top_level_dependencies.map(&:name)).not_to include("my-fetch-factory")
1251+
end
1252+
end
1253+
1254+
context "with an aliased dependency, dealias_packages enabled, and no lockfile" do
1255+
let(:files) { project_dependency_files("npm8/aliased_dependency_no_lockfile") }
1256+
let(:parser) do
1257+
described_class.new(
1258+
dependency_files: files,
1259+
source: source,
1260+
credentials: credentials,
1261+
options: { dealias_packages: true }
1262+
)
1263+
end
1264+
1265+
it "includes the real aliased package with the requirement as version" do
1266+
dep = top_level_dependencies.find { |d| d.name == "is-number" }
1267+
expect(dep).to_not be_nil
1268+
expect(dep.version).to be_nil
1269+
expect(dep.requirements.first[:requirement]).to eq("^7.0.0")
1270+
end
1271+
1272+
it "still includes non-aliased packages" do
1273+
expect(top_level_dependencies.map(&:name)).to include("etag")
1274+
end
1275+
end
1276+
11991277
context "with a git dependency" do
12001278
let(:files) { project_dependency_files("npm6_and_yarn/git_dependency") }
12011279

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "alias-no-lockfile-test",
3+
"version": "1.0.0",
4+
"dependencies": {
5+
"my-is-number": "npm:is-number@^7.0.0",
6+
"etag": "^1.8.1"
7+
}
8+
}

0 commit comments

Comments
 (0)