Skip to content

Commit 1142d67

Browse files
authored
Merge branch 'main' into dependabot/pip/uv/helpers/uv-0.8.6
2 parents 03432e0 + 044c763 commit 1142d67

File tree

20 files changed

+457
-217
lines changed

20 files changed

+457
-217
lines changed

NEW_ECOSYSTEMS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ Your ecosystem implementation requires updates to numerous supporting files thro
148148
- [updater/GemFile.lock](https://github.com/dependabot/dependabot-core/blob/main/updater/Gemfile.lock)
149149
- [dependabot-omnibus.gemspec](https://github.com/dependabot/dependabot-core/blob/main/omnibus/dependabot-omnibus.gemspec)
150150
- [omnibus.rb](https://github.com/dependabot/dependabot-core/blob/main/omnibus/lib/dependabot/omnibus.rb)
151+
- [Rakefile](https://github.com/dependabot/dependabot-core/blob/main/Rakefile)
151152
- **Gemfile**: Update if your ecosystem introduces new Ruby dependencies
152153
- [GemFile](https://github.com/dependabot/dependabot-core/blob/main/Gemfile)
153154
- [GemFile.lock](https://github.com/dependabot/dependabot-core/blob/main/Gemfile.lock)

bundler/lib/dependabot/bundler/file_parser.rb

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def gemfile_dependencies
122122
parsed_gemfile.each do |dep|
123123
next unless gemfile_declaration_finder.gemfile_includes_dependency?(dep)
124124

125-
dependencies <<
125+
dep =
126126
Dependency.new(
127127
name: dep.fetch("name"),
128128
version: dependency_version(dep.fetch("name"))&.to_s,
@@ -134,14 +134,17 @@ def gemfile_dependencies
134134
}],
135135
package_manager: "bundler"
136136
)
137+
138+
file.dependencies << dep
139+
dependencies << dep
137140
end
138141
end
139142

140143
@gemfile_dependencies = dependencies
141144
end
142145

143146
sig { returns(DependencySet) }
144-
def gemspec_dependencies # rubocop:disable Metrics/PerceivedComplexity
147+
def gemspec_dependencies # rubocop:disable Metrics/PerceivedComplexity,Metrics/AbcSize
145148
@gemspec_dependencies = T.let(@gemspec_dependencies, T.nilable(DependencySet))
146149
return @gemspec_dependencies if @gemspec_dependencies
147150

@@ -156,7 +159,7 @@ def gemspec_dependencies # rubocop:disable Metrics/PerceivedComplexity
156159
parsed_gemspec(gemspec).each do |dependency|
157160
next unless gemspec_declaration_finder.gemspec_includes_dependency?(dependency)
158161

159-
queue << Dependency.new(
162+
dep = Dependency.new(
160163
name: dependency.fetch("name"),
161164
version: dependency_version(dependency.fetch("name"))&.to_s,
162165
requirements: [{
@@ -171,6 +174,9 @@ def gemspec_dependencies # rubocop:disable Metrics/PerceivedComplexity
171174
}],
172175
package_manager: "bundler"
173176
)
177+
178+
gemspec.dependencies << dep
179+
queue << dep
174180
end
175181
end
176182
end
@@ -192,16 +198,23 @@ def lockfile_dependencies
192198
parsed_lockfile.specs.each do |dependency|
193199
next if dependency.source.is_a?(::Bundler::Source::Path)
194200

195-
dependencies <<
196-
Dependency.new(
197-
name: dependency.name,
198-
version: dependency_version(dependency.name)&.to_s,
199-
requirements: [],
200-
package_manager: "bundler",
201-
subdependency_metadata: [{
202-
production: production_dep_names.include?(dependency.name)
203-
}]
204-
)
201+
# if a dependency is listed in the lockfiles' DEPENDENCIES section,
202+
# then it is a direct dependency & we want to keep track of that fact
203+
is_direct = parsed_lockfile.dependencies.key?(dependency.name)
204+
205+
dep = Dependency.new(
206+
name: dependency.name,
207+
version: dependency_version(dependency.name)&.to_s,
208+
requirements: [],
209+
package_manager: "bundler",
210+
subdependency_metadata: [{
211+
production: production_dep_names.include?(dependency.name)
212+
}],
213+
direct_relationship: is_direct
214+
)
215+
216+
T.must(lockfile).dependencies << dep
217+
dependencies << dep
205218
end
206219

207220
dependencies

common/lib/dependabot/dependency.rb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,13 @@ def self.register_name_normaliser(package_manager, name_builder)
102102
directory: T.nilable(String),
103103
subdependency_metadata: T.nilable(T::Array[T::Hash[T.any(Symbol, String), String]]),
104104
removed: T::Boolean,
105-
metadata: T.nilable(T::Hash[T.any(Symbol, String), String])
105+
metadata: T.nilable(T::Hash[T.any(Symbol, String), String]),
106+
direct_relationship: T::Boolean
106107
).void
107108
end
108109
def initialize(name:, requirements:, package_manager:, version: nil,
109110
previous_version: nil, previous_requirements: nil, directory: nil,
110-
subdependency_metadata: [], removed: false, metadata: {})
111+
subdependency_metadata: [], removed: false, metadata: {}, direct_relationship: false)
111112
@name = name
112113
@version = T.let(
113114
case version
@@ -134,6 +135,7 @@ def initialize(name:, requirements:, package_manager:, version: nil,
134135
end
135136
@removed = removed
136137
@metadata = T.let(symbolize_keys(metadata || {}), T::Hash[Symbol, T.untyped])
138+
@direct_relationship = direct_relationship
137139

138140
check_values
139141
end
@@ -145,6 +147,12 @@ def top_level?
145147
requirements.any?
146148
end
147149

150+
# used to support lockfile parsing/DependencySubmission
151+
sig { returns(T::Boolean) }
152+
def direct?
153+
top_level? || @direct_relationship
154+
end
155+
148156
sig { returns(T::Boolean) }
149157
def removed?
150158
@removed

common/lib/dependabot/dependency_file.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ class DependencyFile
4040
sig { returns(T.nilable(String)) }
4141
attr_accessor :mode
4242

43+
sig { returns(T::Set[T.untyped]) }
44+
attr_accessor :dependencies
45+
4346
class ContentEncoding
4447
UTF_8 = "utf-8"
4548
BASE64 = "base64"
@@ -92,6 +95,7 @@ def initialize(name:, content:, directory: "/", type: "file",
9295
@content_encoding = content_encoding
9396
@operation = operation
9497
@mode = mode
98+
@dependencies = T.let(Set.new, T::Set[T.untyped])
9599
raise ArgumentError, "Invalid Git mode: #{mode}" if mode && !VALID_MODES.include?(mode)
96100

97101
# Make deleted override the operation. Deleted is kept when operation

common/spec/dependabot/dependency_spec.rb

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@
126126

127127
it { is_expected.to be(true) }
128128

129-
context "with subdependency metadata" do
129+
context "with false subdependency metadata" do
130130
let(:dependency_args) do
131131
{
132132
name: "dep",
@@ -141,6 +141,36 @@
141141
end
142142
end
143143

144+
describe "#direct?" do
145+
it "is true when the dependency is top-level" do
146+
dependency = described_class.new(name: "dep",
147+
package_manager: "dummy",
148+
requirements: [{ file: "a.rb", requirement: "1", groups: [], source: nil }])
149+
150+
expect(dependency.top_level?).to be(true)
151+
expect(dependency.direct?).to be(true)
152+
end
153+
154+
it "returns false when the dependency is not top-level" do
155+
dependency = described_class.new(name: "dep",
156+
package_manager: "dummy",
157+
requirements: [])
158+
159+
expect(dependency.top_level?).to be(false)
160+
expect(dependency.direct?).to be(false)
161+
end
162+
163+
it "returns true if you specify it directly" do
164+
dependency = described_class.new(name: "dep",
165+
package_manager: "dummy",
166+
requirements: [],
167+
direct_relationship: true)
168+
169+
expect(dependency.top_level?).to be(false)
170+
expect(dependency.direct?).to be(true)
171+
end
172+
end
173+
144174
describe "#display_name" do
145175
subject(:display_name) { described_class.new(**dependency_args).display_name }
146176

npm_and_yarn/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ ARG COREPACK_VERSION=0.33.0
55

66
# Check for updates at https://github.com/pnpm/pnpm/releases
77
# With every major release update, also update npm_and_yarn/lib/dependabot/npm_and_yarn/pnpm_package_manager.rb (Section : Update instructions)
8-
ARG PNPM_VERSION=10.11.1
8+
ARG PNPM_VERSION=10.14.0
99

1010
# Check for updates at https://github.com/yarnpkg/berry/releases
1111
# With every major release update, also update npm_and_yarn/lib/dependabot/npm_and_yarn/yarn_package_manager.rb (Section : Update instructions)
@@ -23,7 +23,7 @@ ARG NODEJS_VERSION=22
2323
# Check for updates at https://github.com/npm/cli/releases
2424
# This version should be compatible with the NODEJS_VERSION version declared above. See https://nodejs.org/en/download/releases as well
2525
# With every major release update, also update npm_and_yarn/lib/dependabot/npm_and_yarn/npm_package_manager.rb (Section : Update instructions)
26-
ARG NPM_VERSION=10.5.0
26+
ARG NPM_VERSION=10.9.3
2727

2828
# Install Node and npm
2929
RUN mkdir -p /etc/apt/keyrings \

npm_and_yarn/lib/dependabot/npm_and_yarn/metadata_finder.rb

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,13 +215,15 @@ def npm_listing
215215
sig { returns(String) }
216216
def dependency_url
217217
registry_url =
218-
if new_source.nil? then "https://registry.npmjs.org"
218+
if new_source.nil?
219+
# Check credentials for a configured registry before falling back to public registry
220+
configured_registry_from_credentials || "https://registry.npmjs.org"
219221
else
220222
new_source&.fetch(:url)
221223
end
222224

223-
# spaces must be escaped in base URL
224-
registry_url = registry_url.gsub(" ", "%20")
225+
# Remove trailing slashes and escape spaces for proper URL formatting
226+
registry_url = URI::DEFAULT_PARSER.escape(registry_url)&.gsub(%r{/+$}, "")
225227

226228
# NPM registries expect slashes to be escaped
227229
escaped_dependency_name = dependency.name.gsub("/", "%2F")
@@ -235,6 +237,23 @@ def registry_auth_headers
235237
{ "Authorization" => "Bearer #{auth_token}" }
236238
end
237239

240+
sig { returns(T.nilable(String)) }
241+
def configured_registry_from_credentials
242+
# Look for a credential that replaces the base registry (global registry replacement)
243+
replaces_base_cred = credentials.find { |cred| cred["type"] == "npm_registry" && cred.replaces_base? }
244+
return normalize_registry_url(replaces_base_cred["registry"]) if replaces_base_cred
245+
246+
nil
247+
end
248+
249+
sig { params(registry: T.nilable(String)).returns(T.nilable(String)) }
250+
def normalize_registry_url(registry)
251+
return nil unless registry
252+
return registry if registry.start_with?("http")
253+
254+
"https://#{registry}"
255+
end
256+
238257
sig { returns(String) }
239258
def dependency_registry
240259
if new_source.nil? then "registry.npmjs.org"

npm_and_yarn/spec/dependabot/npm_and_yarn/metadata_finder_spec.rb

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,4 +513,132 @@
513513
end
514514
end
515515
end
516+
517+
describe "#dependency_url" do
518+
subject(:dependency_url) { finder.send(:dependency_url) }
519+
520+
context "when no source information is available" do
521+
let(:dependency) do
522+
Dependabot::Dependency.new(
523+
name: dependency_name,
524+
version: "1.6.0",
525+
requirements: [
526+
{ file: "package.json", requirement: "^1.0", groups: [], source: nil }
527+
],
528+
package_manager: "npm_and_yarn"
529+
)
530+
end
531+
532+
context "without credentials" do
533+
let(:credentials) { [] }
534+
535+
it "falls back to public npm registry" do
536+
expect(dependency_url).to eq("https://registry.npmjs.org/etag")
537+
end
538+
end
539+
540+
context "with replaces-base credential" do
541+
let(:credentials) do
542+
[Dependabot::Credential.new({
543+
"type" => "npm_registry",
544+
"registry" => "jfrogghdemo.jfrog.io/artifactory/api/npm/e2e-tests-dependabot-npm/",
545+
"token" => "secret_token",
546+
"replaces-base" => true
547+
})]
548+
end
549+
550+
it "uses private registry from credentials" do
551+
expect(dependency_url).to eq("https://jfrogghdemo.jfrog.io/artifactory/api/npm/e2e-tests-dependabot-npm/etag")
552+
end
553+
554+
it "removes trailing slashes from registry URL" do
555+
expect(dependency_url).not_to include("npm//etag")
556+
end
557+
end
558+
559+
context "with registry URL that has no protocol" do
560+
let(:credentials) do
561+
[Dependabot::Credential.new({
562+
"type" => "npm_registry",
563+
"registry" => "npm.fury.io/dependabot",
564+
"token" => "secret_token",
565+
"replaces-base" => true
566+
})]
567+
end
568+
569+
it "adds https protocol" do
570+
expect(dependency_url).to eq("https://npm.fury.io/dependabot/etag")
571+
end
572+
end
573+
574+
context "with multiple credentials" do
575+
let(:credentials) do
576+
[
577+
Dependabot::Credential.new({
578+
"type" => "npm_registry",
579+
"registry" => "https://npm.fury.io/dependabot",
580+
"token" => "secret_token",
581+
"replaces-base" => true
582+
}),
583+
Dependabot::Credential.new({
584+
"type" => "npm_registry",
585+
"registry" => "another.registry.com",
586+
"token" => "another_secret_token"
587+
})
588+
]
589+
end
590+
591+
it "takes precedence over other credentials" do
592+
expect(dependency_url).to eq("https://npm.fury.io/dependabot/etag")
593+
end
594+
end
595+
596+
context "with scoped dependency name" do
597+
let(:dependency_name) { "@babel/core" }
598+
let(:credentials) do
599+
[Dependabot::Credential.new({
600+
"type" => "npm_registry",
601+
"registry" => "npm.fury.io/dependabot",
602+
"token" => "secret_token",
603+
"replaces-base" => true
604+
})]
605+
end
606+
607+
it "escapes dependency name properly" do
608+
expect(dependency_url).to eq("https://npm.fury.io/dependabot/@babel%2Fcore")
609+
end
610+
end
611+
end
612+
613+
context "when source information is available from lockfile" do
614+
let(:dependency) do
615+
Dependabot::Dependency.new(
616+
name: dependency_name,
617+
version: "1.6.0",
618+
requirements: [
619+
{
620+
file: "package.json",
621+
requirement: "^1.0",
622+
groups: [],
623+
source: { type: "registry", url: "https://npm.fury.io/dependabot" }
624+
}
625+
],
626+
package_manager: "npm_and_yarn"
627+
)
628+
end
629+
630+
let(:credentials) do
631+
[Dependabot::Credential.new({
632+
"type" => "npm_registry",
633+
"registry" => "different.registry.com",
634+
"token" => "secret_token",
635+
"replaces-base" => true
636+
})]
637+
end
638+
639+
it "uses source from lockfile, not credentials" do
640+
expect(dependency_url).to eq("https://npm.fury.io/dependabot/etag")
641+
end
642+
end
643+
end
516644
end

0 commit comments

Comments
 (0)