dependabot-common 0.382.0 → 0.384.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b53185ee3c744622d14656ac582aaa779b5b9f3394497824d49561fe78fe2d8
4
- data.tar.gz: cd5ce58e980459603217ea2693475a224691f67149215d1fb5031472bc065507
3
+ metadata.gz: 4011821492cf0ae915acc4d9b0783f699f5522946eac07bbef8973e8692df1f9
4
+ data.tar.gz: 7502e7687e79b39ec94fad1c9318e1b40b7cdf7965c8c1818ebda3a7a914b8ee
5
5
  SHA512:
6
- metadata.gz: 1216f4ef150d1c76b7180f780b1d67831359c9f327bf1a00cf885d3f9b3313ce354a694803de8ec91c1eebc138e922d65cde4f4e5ede88f6da7ceb23c6306026
7
- data.tar.gz: 1377f5537d8d8e637945e35d59ba58dde50ec048f29abf6e9d50c4239ce2a7ca292aa379cfd7c28af7c9efe12723bdf71d55a007ad7e4839a177472f57f7a256
6
+ metadata.gz: c5f9708f6197bf0fe6e6dd22b971e41522f7deb6af1963786276dd5246d3177741768e4065d437c4ad3906ff0d0d91afd10b41a0108c0e553a14be2ae1dfde5f
7
+ data.tar.gz: 1e9ebca6d67ec61d619f36e71cfd02cd92c1aca3d6701b47ff4a94c95f2abcdb05277de57902d4dea2b1d5bb6410cabbc027c81bb0e8884bbee84d31cd43e2f4
@@ -102,33 +102,68 @@ module Dependabot
102
102
  T::Hash[String, String]
103
103
  )
104
104
 
105
- sig { params(cfg: T.nilable(T::Hash[Symbol, T.untyped])).returns(T::Array[IgnoreCondition]) }
105
+ sig { params(cfg: T.nilable(T::Hash[Symbol, T.anything])).returns(T::Array[IgnoreCondition]) }
106
106
  def ignore_conditions(cfg)
107
- ignores = cfg&.dig(:ignore) || []
108
- ignores.map do |ic|
107
+ array_values(cfg&.dig(:ignore)).map do |raw|
108
+ ic = hash_values(raw)
109
109
  IgnoreCondition.new(
110
- dependency_name: ic[:"dependency-name"],
111
- versions: ic[:versions],
112
- update_types: ic[:"update-types"]
110
+ dependency_name: T.must(string_value(ic[:"dependency-name"])),
111
+ versions: string_array(ic[:versions]),
112
+ update_types: string_array(ic[:"update-types"])
113
113
  )
114
114
  end
115
115
  end
116
116
 
117
117
  sig do
118
- params(cfg: T.nilable(T::Hash[Symbol, T.untyped])).returns(UpdateConfig::CommitMessageOptions)
118
+ params(cfg: T.nilable(T::Hash[Symbol, T.anything])).returns(UpdateConfig::CommitMessageOptions)
119
119
  end
120
120
  def commit_message_options(cfg)
121
- commit_message = cfg&.dig(:"commit-message") || {}
121
+ commit_message = hash_values(cfg&.dig(:"commit-message"))
122
+ prefix = string_value(commit_message[:prefix])
122
123
  UpdateConfig::CommitMessageOptions.new(
123
- prefix: commit_message[:prefix],
124
- prefix_development: commit_message[:"prefix-development"] || commit_message[:prefix],
125
- include: commit_message[:include]
124
+ prefix: prefix,
125
+ prefix_development: string_value(commit_message[:"prefix-development"]) || prefix,
126
+ include: string_value(commit_message[:include])
126
127
  )
127
128
  end
128
129
 
129
- sig { params(cfg: T.nilable(T::Hash[Symbol, T.untyped])).returns(T::Array[String]) }
130
+ sig { params(cfg: T.nilable(T::Hash[Symbol, T.anything])).returns(T::Array[String]) }
130
131
  def exclude_paths(cfg)
131
- Array(cfg&.dig(:"exclude-paths") || [])
132
+ string_array(cfg&.dig(:"exclude-paths")) || []
133
+ end
134
+
135
+ # The methods below narrow the loosely typed, parsed-YAML config values
136
+ # (Symbol-keyed hashes whose values are arbitrary) into the specific
137
+ # types the config objects expect.
138
+
139
+ sig { params(value: T.anything).returns(T.nilable(String)) }
140
+ def string_value(value)
141
+ case value
142
+ when String then value
143
+ end
144
+ end
145
+
146
+ sig { params(value: T.anything).returns(T::Hash[Symbol, T.anything]) }
147
+ def hash_values(value)
148
+ case value
149
+ when Hash then value
150
+ else {}
151
+ end
152
+ end
153
+
154
+ sig { params(value: T.anything).returns(T::Array[T.anything]) }
155
+ def array_values(value)
156
+ case value
157
+ when Array then value
158
+ else []
159
+ end
160
+ end
161
+
162
+ sig { params(value: T.anything).returns(T.nilable(T::Array[String])) }
163
+ def string_array(value)
164
+ case value
165
+ when Array then value.map(&:to_s)
166
+ end
132
167
  end
133
168
  end
134
169
  end
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/experiments"
@@ -28,10 +28,23 @@ module Dependabot
28
28
  sig { returns(T.nilable(String)) }
29
29
  attr_reader :group_by
30
30
 
31
+ # The following readers are parsed once from the raw rules hash so callers
32
+ # get typed access instead of repeatedly casting rules["patterns"] and
33
+ # friends at every use site. A nil value means the rule is absent (which the
34
+ # matching logic treats differently from an empty list).
35
+ sig { returns(T.nilable(T::Array[String])) }
36
+ attr_reader :patterns
37
+
38
+ sig { returns(T.nilable(T::Array[String])) }
39
+ attr_reader :exclude_patterns
40
+
41
+ sig { returns(T.nilable(T::Array[String])) }
42
+ attr_reader :update_types
43
+
31
44
  sig do
32
45
  params(
33
46
  name: String,
34
- rules: T::Hash[String, T.untyped],
47
+ rules: T::Hash[String, T.any(String, T::Array[String])],
35
48
  applies_to: T.nilable(String)
36
49
  )
37
50
  .void
@@ -41,7 +54,10 @@ module Dependabot
41
54
  # For backwards compatibility, if no applies_to is provided, default to "version-updates"
42
55
  @applies_to = T.let(applies_to || "version-updates", String)
43
56
  @rules = rules
44
- @group_by = T.let(rules["group-by"], T.nilable(String))
57
+ @group_by = T.let(string_rule(rules, "group-by"), T.nilable(String))
58
+ @patterns = T.let(string_array_rule(rules, "patterns"), T.nilable(T::Array[String]))
59
+ @exclude_patterns = T.let(string_array_rule(rules, "exclude-patterns"), T.nilable(T::Array[String]))
60
+ @update_types = T.let(string_array_rule(rules, "update-types"), T.nilable(T::Array[String]))
45
61
  @dependencies = T.let([], T::Array[Dependabot::Dependency])
46
62
  end
47
63
 
@@ -75,16 +91,18 @@ module Dependabot
75
91
 
76
92
  sig { params(dependency_name: String).returns(T::Boolean) }
77
93
  def matches_pattern?(dependency_name)
78
- return true unless rules.key?("patterns") # If no patterns are defined, we pass this check by default
94
+ patterns = self.patterns
95
+ return true if patterns.nil? # If no patterns are defined, we pass this check by default
79
96
 
80
- T.unsafe(rules["patterns"]).any? { |rule| WildcardMatcher.match?(rule, dependency_name) }
97
+ patterns.any? { |rule| WildcardMatcher.match?(rule, dependency_name) }
81
98
  end
82
99
 
83
100
  sig { params(dependency_name: String).returns(T::Boolean) }
84
101
  def matches_excluded_pattern?(dependency_name)
85
- return false unless rules.key?("exclude-patterns") # If there are no exclusions, fail by default
102
+ exclude_patterns = self.exclude_patterns
103
+ return false if exclude_patterns.nil? # If there are no exclusions, fail by default
86
104
 
87
- T.unsafe(rules["exclude-patterns"]).any? { |rule| WildcardMatcher.match?(rule, dependency_name) }
105
+ exclude_patterns.any? { |rule| WildcardMatcher.match?(rule, dependency_name) }
88
106
  end
89
107
 
90
108
  sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
@@ -102,5 +120,42 @@ module Dependabot
102
120
  def experimental_rules_enabled?
103
121
  Dependabot::Experiments.enabled?(:grouped_updates_experimental_rules)
104
122
  end
123
+
124
+ # Reads a rule whose value is expected to be a single string (e.g.
125
+ # "group-by"), returning nil when the key is absent or the value is not a
126
+ # string.
127
+ sig do
128
+ params(
129
+ rules: T::Hash[String, T.any(String, T::Array[String])],
130
+ key: String
131
+ )
132
+ .returns(T.nilable(String))
133
+ end
134
+ def string_rule(rules, key)
135
+ value = rules[key]
136
+ value.is_a?(String) ? value : nil
137
+ end
138
+
139
+ # Reads a rule whose value is expected to be a list of strings (e.g.
140
+ # "patterns", "exclude-patterns", "update-types"). Returns nil when the key
141
+ # is absent so callers can distinguish "rule not set" from "empty list", and
142
+ # coerces a lone string into a single-element list.
143
+ sig do
144
+ params(
145
+ rules: T::Hash[String, T.any(String, T::Array[String])],
146
+ key: String
147
+ )
148
+ .returns(T.nilable(T::Array[String]))
149
+ end
150
+ def string_array_rule(rules, key)
151
+ return nil unless rules.key?(key)
152
+
153
+ value = rules[key]
154
+ case value
155
+ when Array then value.grep(String)
156
+ when String then [value]
157
+ else []
158
+ end
159
+ end
105
160
  end
106
161
  end
@@ -97,6 +97,11 @@ module Dependabot
97
97
  "error-type": "path_dependencies_not_reachable",
98
98
  "error-detail": { dependencies: error.dependencies }
99
99
  }
100
+ when Dependabot::PrivateRegistryConfigNotFound
101
+ {
102
+ "error-type": "private_registry_config_not_found",
103
+ "error-detail": { source: error.source }
104
+ }
100
105
  when Dependabot::PrivateSourceAuthenticationFailure
101
106
  {
102
107
  "error-type": "private_source_authentication_failure",
@@ -238,6 +243,16 @@ module Dependabot
238
243
  "error-type": "dependency_file_not_resolvable",
239
244
  "error-detail": { message: error.message }
240
245
  }
246
+ when Dependabot::BlockedDependencyVersion
247
+ {
248
+ "error-type": "blocked_dependency_version",
249
+ "error-detail": {
250
+ "dependency-name": error.dependency_name,
251
+ "blocked-version": error.blocked_version,
252
+ "version-requirement": error.version_requirement,
253
+ reason: error.reason
254
+ }.compact
255
+ }
241
256
  when Dependabot::DependencyFileNotEvaluatable
242
257
  {
243
258
  "error-type": "dependency_file_not_evaluatable",
@@ -687,6 +702,22 @@ module Dependabot
687
702
  # Source level errors #
688
703
  #######################
689
704
 
705
+ class PrivateRegistryConfigNotFound < DependabotError
706
+ extend T::Sig
707
+
708
+ sig { returns(String) }
709
+ attr_reader :source
710
+
711
+ sig { params(source: String).void }
712
+ def initialize(source)
713
+ @source = T.let(sanitize_source(source), String)
714
+ msg = "Private npm registries require either a .npmrc file in your repository, " \
715
+ "or explicit `scope`/`replaces-base` configuration in dependabot.yml. " \
716
+ "Registry: #{@source}"
717
+ super(msg)
718
+ end
719
+ end
720
+
690
721
  class PrivateSourceAuthenticationFailure < DependabotError
691
722
  extend T::Sig
692
723
 
@@ -709,10 +740,10 @@ module Dependabot
709
740
  sig { returns(String) }
710
741
  attr_reader :source
711
742
 
712
- sig { params(source: T.nilable(String)).void }
713
- def initialize(source)
743
+ sig { params(source: T.nilable(String), error_message: T.nilable(String)).void }
744
+ def initialize(source, error_message = nil)
714
745
  @source = T.let(sanitize_source(T.must(source)), String)
715
- msg = "Bad response error while accessing source: #{@source}"
746
+ msg = error_message ? sanitize_source(error_message) : "Bad response error while accessing source: #{@source}"
716
747
  super(msg)
717
748
  end
718
749
  end
@@ -901,6 +932,46 @@ module Dependabot
901
932
  # Raised by UpdateChecker if all candidate updates are ignored
902
933
  class AllVersionsIgnored < DependabotError; end
903
934
 
935
+ # Raised when regenerating a lockfile would introduce or change a transitive
936
+ # (indirect) dependency to a version that matches a configured blocked version.
937
+ # The offending change is rejected so the blocked version is never shipped,
938
+ # while other dependencies are still allowed to update.
939
+ class BlockedDependencyVersion < DependabotError
940
+ extend T::Sig
941
+
942
+ sig { returns(String) }
943
+ attr_reader :dependency_name
944
+
945
+ sig { returns(String) }
946
+ attr_reader :blocked_version
947
+
948
+ sig { returns(String) }
949
+ attr_reader :version_requirement
950
+
951
+ sig { returns(T.nilable(String)) }
952
+ attr_reader :reason
953
+
954
+ sig do
955
+ params(
956
+ dependency_name: String,
957
+ blocked_version: String,
958
+ version_requirement: String,
959
+ reason: T.nilable(String)
960
+ ).void
961
+ end
962
+ def initialize(dependency_name:, blocked_version:, version_requirement:, reason: nil)
963
+ @dependency_name = dependency_name
964
+ @blocked_version = blocked_version
965
+ @version_requirement = version_requirement
966
+ @reason = reason
967
+
968
+ msg = "Update blocked: transitive dependency #{dependency_name} #{blocked_version} " \
969
+ "matches blocked version requirement '#{version_requirement}'"
970
+ msg += " (reason: #{reason})" if reason && !reason.empty?
971
+ super(msg)
972
+ end
973
+ end
974
+
904
975
  # Raised by FileParser if processing may execute external code in the update context
905
976
  class UnexpectedExternalCode < DependabotError; end
906
977
 
@@ -0,0 +1,114 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "dependabot/clients/github_with_retries"
6
+ require "dependabot/shared_helpers"
7
+ require "dependabot/source"
8
+
9
+ module Dependabot
10
+ # Shared logic for resolving release dates from git-based sources for cooldown
11
+ # purposes. Used by ecosystems that rely on git tags (pre-commit, GitHub Actions)
12
+ # rather than package registries.
13
+ #
14
+ # Priority: GitHub Release published_at > tag creation date (for-each-ref) > commit date.
15
+ #
16
+ # Including classes must implement:
17
+ # - `cooldown_source_url` — returns the git source URL
18
+ # - `cooldown_credentials` — returns the credentials array
19
+ module GitCooldownDateResolver
20
+ extend T::Sig
21
+ extend T::Helpers
22
+
23
+ abstract!
24
+
25
+ # The git source URL for the dependency (e.g. "https://github.com/owner/repo")
26
+ sig { abstract.returns(T.nilable(String)) }
27
+ def cooldown_source_url; end
28
+
29
+ # Credentials for GitHub API access
30
+ sig { abstract.returns(T::Array[Dependabot::Credential]) }
31
+ def cooldown_credentials; end
32
+
33
+ # Strips the `tags/` prefix that GitCommitChecker may add when the pinned
34
+ # ref starts with `tags/`, preventing construction of invalid refs like
35
+ # `refs/tags/tags/v1.0.0`.
36
+ sig { params(tag_name: String).returns(String) }
37
+ def normalize_tag_name(tag_name)
38
+ tag_name.delete_prefix("tags/")
39
+ end
40
+
41
+ # Resolves the best available date for a candidate tag.
42
+ # Priority: GitHub Release published_at > tag creation date > commit date.
43
+ sig { params(tag_name: String, commit_sha: String).returns(Time) }
44
+ def resolve_candidate_date(tag_name, commit_sha)
45
+ releases = cached_github_releases
46
+ unless releases.empty?
47
+ release = releases.find { |r| r.tag_name == tag_name }
48
+ return release.published_at if release&.published_at
49
+ end
50
+
51
+ tag_creation_date(tag_name, commit_sha)
52
+ end
53
+
54
+ # Looks up the GitHub Release published_at date for a given tag name.
55
+ # Returns nil if no release exists for this tag.
56
+ sig { params(tag_name: String).returns(T.nilable(Time)) }
57
+ def github_release_published_at(tag_name)
58
+ releases = cached_github_releases
59
+ return nil if releases.empty?
60
+
61
+ release = releases.find { |r| r.tag_name == tag_name }
62
+ return nil unless release&.published_at
63
+
64
+ release.published_at
65
+ rescue StandardError => e
66
+ Dependabot.logger.debug("Error fetching GitHub release date for #{tag_name}: #{e.message}")
67
+ nil
68
+ end
69
+
70
+ # Returns the tag creation date for cooldown purposes (used inside bare clone).
71
+ # Priority: tag creation date from for-each-ref > commit date fallback.
72
+ sig { params(tag_name: String, commit_sha: String).returns(Time) }
73
+ def tag_creation_date(tag_name, commit_sha)
74
+ tag_date_str = SharedHelpers.run_shell_command(
75
+ "git for-each-ref --format=\"%(creatordate:iso)\" \"refs/tags/#{tag_name}\"",
76
+ fingerprint: "git for-each-ref --format=\"%(creatordate:iso)\" \"refs/tags/<tag_name>\""
77
+ ).strip
78
+
79
+ if tag_date_str.empty?
80
+ tag_date_str = SharedHelpers.run_shell_command(
81
+ "git show --no-patch --format=\"%cd\" --date=iso #{commit_sha}",
82
+ fingerprint: "git show --no-patch --format=\"%cd\" --date=iso <commit_sha>"
83
+ ).strip
84
+ end
85
+
86
+ Time.parse(tag_date_str)
87
+ end
88
+
89
+ # Fetches and caches GitHub releases for the dependency source.
90
+ # Returns an empty array for non-GitHub sources.
91
+ sig { returns(T::Array[T.untyped]) } # rubocop:disable Sorbet/ForbidTUntyped
92
+ def cached_github_releases
93
+ @cached_github_releases ||= T.let(
94
+ begin
95
+ url = cooldown_source_url
96
+ source = Source.from_url(url)
97
+ if source&.provider == "github"
98
+ client = Dependabot::Clients::GithubWithRetries.for_source(
99
+ source: T.must(source),
100
+ credentials: cooldown_credentials
101
+ )
102
+ client.releases(T.must(source).repo, per_page: 100)
103
+ else
104
+ []
105
+ end
106
+ rescue StandardError => e
107
+ Dependabot.logger.debug("Error fetching GitHub releases: #{e.message}")
108
+ []
109
+ end,
110
+ T.nilable(T::Array[T.untyped]) # rubocop:disable Sorbet/ForbidTUntyped
111
+ )
112
+ end
113
+ end
114
+ end
@@ -60,7 +60,7 @@ module Dependabot
60
60
  # Converts the Notice object to a hash.
61
61
  # @return [Hash] The hash representation of the notice.
62
62
  sig { returns(T::Hash[Symbol, T.any(String, T::Boolean)]) }
63
- def to_hash
63
+ def to_h
64
64
  {
65
65
  mode: @mode,
66
66
  type: @type,
@@ -1,7 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "cgi"
5
4
  require "excon"
6
5
  require "nokogiri"
7
6
  require "sorbet-runtime"
@@ -1,6 +1,7 @@
1
1
  # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "digest"
4
5
  require "sorbet-runtime"
5
6
 
6
7
  module Dependabot
@@ -70,9 +71,10 @@ module Dependabot
70
71
  sanitized_name = sanitized_name.gsub("/", separator)
71
72
 
72
73
  # Shorten the ref in case users refs have length limits
73
- if max_length && (sanitized_name.length > T.must(max_length))
74
- sha = T.must(Digest::SHA1.hexdigest(sanitized_name)[0, T.must(max_length)])
75
- sanitized_name[[T.must(max_length) - sha.size, 0].max..] = sha
74
+ branch_name_max_length = max_length
75
+ if branch_name_max_length && (sanitized_name.length > branch_name_max_length)
76
+ sha = T.must(Digest::SHA1.hexdigest(sanitized_name)[0, branch_name_max_length])
77
+ sanitized_name[[branch_name_max_length - sha.size, 0].max..] = sha
76
78
  end
77
79
 
78
80
  sanitized_name
@@ -0,0 +1,70 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Dependabot
7
+ class PullRequestCreator
8
+ # Typed view over the `commit_message_options` hash threaded into the PR
9
+ # creator's title and prefix builders. The raw options arrive as a loosely
10
+ # typed hash sourced from user config, so this parses the known keys once at
11
+ # the boundary and lets downstream builders use typed readers instead of
12
+ # digging an untyped hash.
13
+ class CommitMessageOptions
14
+ extend T::Sig
15
+
16
+ sig { returns(T.nilable(String)) }
17
+ attr_reader :prefix
18
+
19
+ sig { returns(T.nilable(String)) }
20
+ attr_reader :prefix_development
21
+
22
+ sig { returns(T::Boolean) }
23
+ attr_reader :include_scope
24
+
25
+ sig do
26
+ params(
27
+ prefix: T.nilable(String),
28
+ prefix_development: T.nilable(String),
29
+ include_scope: T::Boolean
30
+ ).void
31
+ end
32
+ def initialize(prefix: nil, prefix_development: nil, include_scope: false)
33
+ @prefix = prefix
34
+ @prefix_development = prefix_development
35
+ @include_scope = include_scope
36
+ end
37
+
38
+ # Parses a raw options hash. Unknown keys are ignored, and absent or
39
+ # non-string prefixes become nil.
40
+ sig { params(options: T::Hash[Symbol, T.anything]).returns(CommitMessageOptions) }
41
+ def self.from_hash(options)
42
+ new(
43
+ prefix: coerce_string(options[:prefix]),
44
+ prefix_development: coerce_string(options[:prefix_development]),
45
+ include_scope: options[:include_scope] ? true : false
46
+ )
47
+ end
48
+
49
+ sig { params(value: T.anything).returns(T.nilable(String)) }
50
+ def self.coerce_string(value)
51
+ case value
52
+ when String then value
53
+ end
54
+ end
55
+ private_class_method :coerce_string
56
+
57
+ # True when an explicit (non-nil) prefix was provided.
58
+ sig { returns(T::Boolean) }
59
+ def prefix?
60
+ !prefix.nil?
61
+ end
62
+
63
+ # True when an explicit (non-nil) development prefix was provided.
64
+ sig { returns(T::Boolean) }
65
+ def prefix_development?
66
+ !prefix_development.nil?
67
+ end
68
+ end
69
+ end
70
+ end
@@ -247,7 +247,7 @@ module Dependabot
247
247
  end
248
248
  end
249
249
 
250
- sig { params(details: T::Hash[String, T.untyped]).returns(String) }
250
+ sig { params(details: T::Hash[String, T.anything]).returns(String) }
251
251
  def vulnerability_version_range_lines(details)
252
252
  msg = ""
253
253
  %w(
@@ -256,9 +256,12 @@ module Dependabot
256
256
  affected_versions
257
257
  ).each do |tp|
258
258
  type = T.must(tp.split("_").first).capitalize
259
- next unless details[tp]
259
+ versions = case (value = details[tp])
260
+ when Array then value
261
+ end
262
+ next unless versions
260
263
 
261
- versions_string = details[tp].any? ? details[tp].join("; ") : "none"
264
+ versions_string = versions.any? ? versions.join("; ") : "none"
262
265
  versions_string = versions_string.gsub(/(?<!\\)~/, '\~')
263
266
  msg += "> #{type} versions: #{versions_string}\n"
264
267
  end
@@ -1,9 +1,10 @@
1
- # typed: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
5
5
  require "dependabot/dependency"
6
6
  require "dependabot/logger"
7
+ require "dependabot/pull_request_creator/commit_message_options"
7
8
  require "dependabot/pull_request_creator/pr_name_prefixer"
8
9
 
9
10
  module Dependabot
@@ -25,7 +26,7 @@ module Dependabot
25
26
  sig { returns(T.nilable(Dependabot::PullRequestCreator::PrNamePrefixer)) }
26
27
  attr_reader :prefixer
27
28
 
28
- sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
29
+ sig { returns(T.nilable(CommitMessageOptions)) }
29
30
  attr_reader :commit_message_options
30
31
 
31
32
  sig { returns(T.nilable(T::Array[Dependabot::Dependency])) }
@@ -35,14 +36,17 @@ module Dependabot
35
36
  params(
36
37
  base_title: String,
37
38
  prefixer: T.nilable(Dependabot::PullRequestCreator::PrNamePrefixer),
38
- commit_message_options: T.nilable(T::Hash[Symbol, T.untyped]),
39
+ commit_message_options: T.nilable(T::Hash[Symbol, T.anything]),
39
40
  dependencies: T.nilable(T::Array[Dependabot::Dependency])
40
41
  ).void
41
42
  end
42
43
  def initialize(base_title:, prefixer: nil, commit_message_options: nil, dependencies: nil)
43
44
  @base_title = base_title
44
45
  @prefixer = prefixer
45
- @commit_message_options = commit_message_options
46
+ @commit_message_options = T.let(
47
+ commit_message_options && CommitMessageOptions.from_hash(commit_message_options),
48
+ T.nilable(CommitMessageOptions)
49
+ )
46
50
  @dependencies = dependencies
47
51
  end
48
52
 
@@ -85,12 +89,12 @@ module Dependabot
85
89
  # but without requiring source/credentials.
86
90
  sig { returns(String) }
87
91
  def build_explicit_prefix
88
- return "" unless commit_message_options&.key?(:prefix)
92
+ return "" unless commit_message_options&.prefix?
89
93
 
90
94
  prefix = explicit_prefix_string
91
95
  return "" if prefix.empty?
92
96
 
93
- prefix += "(#{scope})" if commit_message_options&.dig(:include_scope)
97
+ prefix += "(#{scope})" if commit_message_options&.include_scope
94
98
  # Append colon after alphanumeric or closing bracket to follow
95
99
  # conventional commit format (e.g., "chore: ..." or "fix(deps): ...")
96
100
  prefix += ":" if prefix.match?(/[A-Za-z0-9\)\]]\Z/)
@@ -101,11 +105,11 @@ module Dependabot
101
105
  sig { returns(String) }
102
106
  def explicit_prefix_string
103
107
  if production_dependencies?
104
- commit_message_options&.dig(:prefix).to_s
105
- elsif commit_message_options&.key?(:prefix_development)
106
- commit_message_options&.dig(:prefix_development).to_s
108
+ commit_message_options&.prefix.to_s
109
+ elsif commit_message_options&.prefix_development?
110
+ commit_message_options&.prefix_development.to_s
107
111
  else
108
- commit_message_options&.dig(:prefix).to_s
112
+ commit_message_options&.prefix.to_s
109
113
  end
110
114
  end
111
115
 
@@ -9,6 +9,7 @@ require "dependabot/clients/codecommit"
9
9
  require "dependabot/clients/github_with_retries"
10
10
  require "dependabot/clients/gitlab_with_retries"
11
11
  require "dependabot/pull_request_creator"
12
+ require "dependabot/pull_request_creator/commit_message_options"
12
13
 
13
14
  module Dependabot
14
15
  class PullRequestCreator
@@ -39,7 +40,7 @@ module Dependabot
39
40
  dependencies: T::Array[Dependency],
40
41
  credentials: T::Array[Dependabot::Credential],
41
42
  security_fix: T::Boolean,
42
- commit_message_options: T.nilable(T::Hash[Symbol, T.untyped])
43
+ commit_message_options: T.nilable(T::Hash[Symbol, T.anything])
43
44
  )
44
45
  .void
45
46
  end
@@ -54,7 +55,10 @@ module Dependabot
54
55
  @source = source
55
56
  @credentials = credentials
56
57
  @security_fix = security_fix
57
- @commit_message_options = commit_message_options
58
+ @commit_message_options = T.let(
59
+ commit_message_options && CommitMessageOptions.from_hash(commit_message_options),
60
+ T.nilable(CommitMessageOptions)
61
+ )
58
62
  end
59
63
 
60
64
  sig { returns(String) }
@@ -85,7 +89,7 @@ module Dependabot
85
89
  sig { returns(T::Array[Dependabot::Credential]) }
86
90
  attr_reader :credentials
87
91
 
88
- sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
92
+ sig { returns(T.nilable(CommitMessageOptions)) }
89
93
  attr_reader :commit_message_options
90
94
 
91
95
  sig { returns(T::Boolean) }
@@ -96,7 +100,7 @@ module Dependabot
96
100
  sig { returns(T.nilable(String)) }
97
101
  def commit_prefix
98
102
  # If a preferred prefix has been explicitly provided, use it
99
- return prefix_from_explicitly_provided_details if commit_message_options&.key?(:prefix)
103
+ return prefix_from_explicitly_provided_details if commit_message_options&.prefix?
100
104
 
101
105
  # Otherwise, if there is a previous Dependabot commit and it used a
102
106
  # known style, use that as our model for subsequent commits
@@ -112,7 +116,7 @@ module Dependabot
112
116
  prefix = explicitly_provided_prefix_string
113
117
  return if prefix.empty?
114
118
 
115
- prefix += "(#{scope})" if commit_message_options&.dig(:include_scope)
119
+ prefix += "(#{scope})" if commit_message_options&.include_scope
116
120
  prefix += ":" if prefix.match?(/[A-Za-z0-9\)\]]\Z/)
117
121
  prefix += " " unless prefix.end_with?(" ")
118
122
  prefix
@@ -121,14 +125,14 @@ module Dependabot
121
125
  # rubocop:disable Metrics/PerceivedComplexity
122
126
  sig { returns(String) }
123
127
  def explicitly_provided_prefix_string
124
- raise "No explicitly provided prefix!" unless commit_message_options&.key?(:prefix)
128
+ raise "No explicitly provided prefix!" unless commit_message_options&.prefix?
125
129
 
126
130
  if dependencies.any?(&:production?)
127
- commit_message_options&.dig(:prefix).to_s
128
- elsif commit_message_options&.key?(:prefix_development)
129
- commit_message_options&.dig(:prefix_development).to_s
131
+ commit_message_options&.prefix.to_s
132
+ elsif commit_message_options&.prefix_development?
133
+ commit_message_options&.prefix_development.to_s
130
134
  else
131
- commit_message_options&.dig(:prefix).to_s
135
+ commit_message_options&.prefix.to_s
132
136
  end
133
137
  end
134
138
  # rubocop:enable Metrics/PerceivedComplexity
@@ -197,7 +197,7 @@ module Dependabot
197
197
  milestone: nil,
198
198
  branch_name_separator: "/",
199
199
  branch_name_prefix: "dependabot",
200
- branch_name_max_length: nil,
200
+ branch_name_max_length: 100,
201
201
  label_language: false,
202
202
  automerge_candidate: false,
203
203
  github_redirection_service: DEFAULT_GITHUB_REDIRECTION_SERVICE,
@@ -40,7 +40,7 @@ module Dependabot
40
40
  sig { returns(T.nilable(String)) }
41
41
  attr_reader :commit_message
42
42
 
43
- sig { returns(T::Hash[Symbol, T.untyped]) }
43
+ sig { returns(T::Hash[Symbol, Integer]) }
44
44
  attr_reader :provider_metadata
45
45
 
46
46
  sig do
@@ -54,7 +54,7 @@ module Dependabot
54
54
  author_details: T.nilable(T::Hash[Symbol, String]),
55
55
  signature_key: T.nilable(String),
56
56
  commit_message: T.nilable(String),
57
- provider_metadata: T::Hash[Symbol, T.untyped]
57
+ provider_metadata: T::Hash[Symbol, Integer]
58
58
  )
59
59
  .void
60
60
  end
@@ -84,7 +84,7 @@ module Dependabot
84
84
 
85
85
  # TODO: Each implementation returns a client-specific type.
86
86
  # We should standardise this to return a `Dependabot::Branch` type instead.
87
- sig { returns(T.untyped) }
87
+ sig { returns(T.untyped) } # rubocop:disable Sorbet/ForbidTUntyped
88
88
  def update
89
89
  case source.provider
90
90
  when "github" then github_updater.update
@@ -120,7 +120,7 @@ module Dependabot
120
120
  files: files,
121
121
  credentials: credentials,
122
122
  pull_request_number: pull_request_number,
123
- target_project_id: T.cast(provider_metadata[:target_project_id], T.nilable(Integer))
123
+ target_project_id: provider_metadata[:target_project_id]
124
124
  )
125
125
  end
126
126
 
@@ -3,6 +3,8 @@
3
3
 
4
4
  require "sorbet-runtime"
5
5
 
6
+ require "dependabot/dependency_requirement"
7
+
6
8
  module Dependabot
7
9
  module RequirementsUpdater
8
10
  module Base
@@ -15,7 +17,7 @@ module Dependabot
15
17
 
16
18
  interface!
17
19
 
18
- sig { abstract.returns(T::Array[T::Hash[Symbol, T.untyped]]) }
20
+ sig { abstract.returns(T::Array[Dependabot::DependencyRequirement]) }
19
21
  def updated_requirements; end
20
22
 
21
23
  private
@@ -32,6 +32,19 @@ module Dependabot
32
32
  version.to_s.match?(ANCHORED_VERSION_PATTERN)
33
33
  end
34
34
 
35
+ # RubyGems 4 eagerly computes a numeric sort key in Gem::Version#initialize and
36
+ # skips it for prereleases. Dependabot's subclasses override #prerelease? with
37
+ # ecosystem-specific semantics, so a version with alphabetic segments (e.g.
38
+ # "1.0.0.RELEASE" or "2.0.0.post1") can report prerelease? == false and reach
39
+ # RubyGems' radix comparison, which raises when a segment is a String. Returning
40
+ # nil for those versions falls back to the segment-by-segment comparison.
41
+ sig { override.overridable.returns(T.nilable(Integer)) }
42
+ def compute_sort_key
43
+ return if canonical_segments.any?(String)
44
+
45
+ super
46
+ end
47
+
35
48
  sig { overridable.returns(String) }
36
49
  def to_semver
37
50
  @original_version
data/lib/dependabot.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Dependabot
5
- VERSION = "0.382.0"
5
+ VERSION = "0.384.0"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-common
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.382.0
4
+ version: 0.384.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -552,6 +552,7 @@ files:
552
552
  - lib/dependabot/file_updaters/base.rb
553
553
  - lib/dependabot/file_updaters/vendor_updater.rb
554
554
  - lib/dependabot/git_commit_checker.rb
555
+ - lib/dependabot/git_cooldown_date_resolver.rb
555
556
  - lib/dependabot/git_metadata_fetcher.rb
556
557
  - lib/dependabot/git_ref.rb
557
558
  - lib/dependabot/git_tag_details.rb
@@ -579,6 +580,7 @@ files:
579
580
  - lib/dependabot/pull_request_creator/branch_namer/multi_ecosystem_strategy.rb
580
581
  - lib/dependabot/pull_request_creator/branch_namer/solo_strategy.rb
581
582
  - lib/dependabot/pull_request_creator/codecommit.rb
583
+ - lib/dependabot/pull_request_creator/commit_message_options.rb
582
584
  - lib/dependabot/pull_request_creator/commit_signer.rb
583
585
  - lib/dependabot/pull_request_creator/github.rb
584
586
  - lib/dependabot/pull_request_creator/gitlab.rb
@@ -619,7 +621,7 @@ licenses:
619
621
  - MIT
620
622
  metadata:
621
623
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
622
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.382.0
624
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.384.0
623
625
  rdoc_options: []
624
626
  require_paths:
625
627
  - lib
@@ -634,7 +636,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
634
636
  - !ruby/object:Gem::Version
635
637
  version: 3.3.7
636
638
  requirements: []
637
- rubygems_version: 3.7.2
639
+ rubygems_version: 4.0.14
638
640
  specification_version: 4
639
641
  summary: Shared code used across Dependabot Core
640
642
  test_files: []