dependabot-bundler 0.280.0 → 0.282.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dependabot/bundler/helpers.rb +1 -13
  3. data/lib/dependabot/bundler/package_manager.rb +6 -6
  4. data/lib/dependabot/bundler/update_checker/shared_bundler_helpers.rb +1 -2
  5. metadata +12 -37
  6. data/helpers/v1/.gitignore +0 -8
  7. data/helpers/v1/Gemfile +0 -7
  8. data/helpers/v1/build +0 -29
  9. data/helpers/v1/lib/functions/conflicting_dependency_resolver.rb +0 -89
  10. data/helpers/v1/lib/functions/dependency_source.rb +0 -90
  11. data/helpers/v1/lib/functions/file_parser.rb +0 -119
  12. data/helpers/v1/lib/functions/force_updater.rb +0 -173
  13. data/helpers/v1/lib/functions/lockfile_updater.rb +0 -218
  14. data/helpers/v1/lib/functions/version_resolver.rb +0 -141
  15. data/helpers/v1/lib/functions.rb +0 -172
  16. data/helpers/v1/monkey_patches/definition_bundler_version_patch.rb +0 -16
  17. data/helpers/v1/monkey_patches/definition_ruby_version_patch.rb +0 -22
  18. data/helpers/v1/monkey_patches/fileutils_keyword_splat_patch.rb +0 -20
  19. data/helpers/v1/monkey_patches/git_source_patch.rb +0 -62
  20. data/helpers/v1/monkey_patches/object_untaint_patch.rb +0 -17
  21. data/helpers/v1/monkey_patches/resolver_spec_group_sane_eql.rb +0 -18
  22. data/helpers/v1/patched_bundler +0 -34
  23. data/helpers/v1/run.rb +0 -38
  24. data/helpers/v1/spec/functions/conflicting_dependency_resolver_spec.rb +0 -118
  25. data/helpers/v1/spec/functions/dependency_source_spec.rb +0 -188
  26. data/helpers/v1/spec/functions/file_parser_spec.rb +0 -75
  27. data/helpers/v1/spec/functions/force_updater_spec.rb +0 -59
  28. data/helpers/v1/spec/functions/version_resolver_spec.rb +0 -105
  29. data/helpers/v1/spec/native_spec_helper.rb +0 -56
  30. data/helpers/v1/spec/shared_contexts.rb +0 -60
@@ -1,173 +0,0 @@
1
- # typed: true
2
- # frozen_string_literal: true
3
-
4
- module Functions
5
- class ForceUpdater
6
- class TransitiveDependencyError < StandardError; end
7
-
8
- def initialize(dependency_name:, target_version:, gemfile_name:,
9
- lockfile_name:, update_multiple_dependencies:)
10
- @dependency_name = dependency_name
11
- @target_version = target_version
12
- @gemfile_name = gemfile_name
13
- @lockfile_name = lockfile_name
14
- @update_multiple_dependencies = update_multiple_dependencies
15
- end
16
-
17
- def run
18
- # Only allow upgrades. Otherwise it's unlikely that this
19
- # resolution will be found by the FileUpdater
20
- Bundler.settings.set_command_option(
21
- "only_update_to_newer_versions",
22
- true
23
- )
24
-
25
- dependencies_to_unlock = []
26
-
27
- begin
28
- definition = build_definition(dependencies_to_unlock: dependencies_to_unlock)
29
- definition.resolve_remotely!
30
- specs = definition.resolve
31
- updates = [{ name: dependency_name }] +
32
- dependencies_to_unlock.map { |dep| { name: dep.name } }
33
- specs = specs.map do |dep|
34
- {
35
- name: dep.name,
36
- version: dep.version
37
- }
38
- end
39
- [updates, specs]
40
- rescue Bundler::VersionConflict => e
41
- raise unless update_multiple_dependencies?
42
-
43
- # TODO: Not sure this won't unlock way too many things...
44
- new_dependencies_to_unlock =
45
- new_dependencies_to_unlock_from(
46
- error: e,
47
- already_unlocked: dependencies_to_unlock
48
- )
49
-
50
- raise if new_dependencies_to_unlock.none?
51
-
52
- dependencies_to_unlock += new_dependencies_to_unlock
53
- retry
54
- end
55
- end
56
-
57
- private
58
-
59
- attr_reader :dependency_name
60
- attr_reader :target_version
61
- attr_reader :gemfile_name
62
- attr_reader :lockfile_name
63
- attr_reader :credentials
64
- attr_reader :update_multiple_dependencies
65
- alias update_multiple_dependencies? update_multiple_dependencies
66
-
67
- def new_dependencies_to_unlock_from(error:, already_unlocked:)
68
- potentials_deps =
69
- relevant_conflicts(error, already_unlocked)
70
- .flat_map(&:requirement_trees)
71
- .reject do |tree|
72
- # If the final requirement wasn't specific, it can't be binding
73
- next true if tree.last.requirement == Gem::Requirement.new(">= 0")
74
-
75
- # If the conflict wasn't for the dependency we're updating then
76
- # we don't have enough info to reject it
77
- next false unless tree.last.name == dependency_name
78
-
79
- # If the final requirement *was* for the dependency we're updating
80
- # then we can ignore the tree if it permits the target version
81
- tree.last.requirement.satisfied_by?(
82
- Gem::Version.new(target_version)
83
- )
84
- end.map(&:first)
85
-
86
- potentials_deps
87
- .reject { |dep| already_unlocked.map(&:name).include?(dep.name) }
88
- .reject { |dep| [dependency_name, "ruby\0"].include?(dep.name) }
89
- .uniq
90
- end
91
-
92
- def relevant_conflicts(error, dependencies_being_unlocked)
93
- names = [*dependencies_being_unlocked.map(&:name), dependency_name]
94
-
95
- # For a conflict to be relevant to the updates we're making it must be
96
- # 1) caused by a new requirement introduced by our unlocking, or
97
- # 2) caused by an old requirement that prohibits the update.
98
- # Hence, we look at the beginning and end of the requirement trees
99
- error.cause.conflicts.values
100
- .select do |conflict|
101
- conflict.requirement_trees.any? do |t|
102
- names.include?(t.last.name) || names.include?(t.first.name)
103
- end
104
- end
105
- end
106
-
107
- def build_definition(dependencies_to_unlock:)
108
- gems_to_unlock = dependencies_to_unlock.map(&:name) + [dependency_name]
109
- definition = Bundler::Definition.build(
110
- gemfile_name,
111
- lockfile_name,
112
- gems: gems_to_unlock + subdependencies,
113
- lock_shared_dependencies: true
114
- )
115
-
116
- # Remove the Gemfile / gemspec requirements on the gems we're
117
- # unlocking (i.e., completely unlock them)
118
- gems_to_unlock.each do |gem_name|
119
- unlock_gem(definition: definition, gem_name: gem_name)
120
- end
121
-
122
- dep = definition.dependencies
123
- .find { |d| d.name == dependency_name }
124
-
125
- # If the dependency is not found in the Gemfile it means this is a
126
- # transitive dependency that we can't force update.
127
- raise TransitiveDependencyError unless dep
128
-
129
- # Set the requirement for the gem we're forcing an update of
130
- new_req = Gem::Requirement.create("= #{target_version}")
131
- dep.instance_variable_set(:@requirement, new_req)
132
- dep.source = nil if dep.source.is_a?(Bundler::Source::Git)
133
-
134
- definition
135
- end
136
-
137
- def lockfile
138
- return @lockfile if defined?(@lockfile)
139
-
140
- @lockfile =
141
- begin
142
- return unless lockfile_name && File.exist?(lockfile_name)
143
-
144
- File.read(lockfile_name)
145
- end
146
- end
147
-
148
- def subdependencies
149
- # If there's no lockfile we don't need to worry about
150
- # subdependencies
151
- return [] unless lockfile
152
-
153
- all_deps = Bundler::LockfileParser.new(lockfile)
154
- .specs.map { |x| x.name.to_s }
155
- top_level = Bundler::Definition
156
- .build(gemfile_name, lockfile_name, {})
157
- .dependencies.map { |x| x.name.to_s }
158
-
159
- all_deps - top_level
160
- end
161
-
162
- def unlock_gem(definition:, gem_name:)
163
- dep = definition.dependencies.find { |d| d.name == gem_name }
164
- version = definition.locked_gems.specs
165
- .find { |d| d.name == gem_name }.version
166
-
167
- dep&.instance_variable_set(
168
- :@requirement,
169
- Gem::Requirement.create(">= #{version}")
170
- )
171
- end
172
- end
173
- end
@@ -1,218 +0,0 @@
1
- # typed: true
2
- # frozen_string_literal: true
3
-
4
- require "fileutils"
5
-
6
- module Functions
7
- class LockfileUpdater
8
- RETRYABLE_ERRORS = [Bundler::HTTPError].freeze
9
- GEM_NOT_FOUND_ERROR_REGEX =
10
- /
11
- locked\sto\s(?<name>[^\s]+)\s\(|
12
- not\sfind\s(?<name>[^\s]+)-\d|
13
- has\s(?<name>[^\s]+)\slocked\sat
14
- /x
15
-
16
- def initialize(gemfile_name:, lockfile_name:, dependencies:)
17
- @gemfile_name = gemfile_name
18
- @lockfile_name = lockfile_name
19
- @dependencies = dependencies
20
- end
21
-
22
- def run
23
- generate_lockfile
24
- end
25
-
26
- private
27
-
28
- attr_reader :gemfile_name
29
- attr_reader :lockfile_name
30
- attr_reader :dependencies
31
-
32
- def generate_lockfile # rubocop:disable Metrics/PerceivedComplexity
33
- dependencies_to_unlock = dependencies.map { |d| d.fetch("name") }
34
-
35
- begin
36
- definition = build_definition(dependencies_to_unlock)
37
-
38
- old_reqs = lock_deps_being_updated_to_exact_versions(definition)
39
-
40
- definition.resolve_remotely!
41
-
42
- old_reqs.each do |dep_name, old_req|
43
- d_dep = definition.dependencies.find { |d| d.name == dep_name }
44
- if old_req == :none then definition.dependencies.delete(d_dep)
45
- else
46
- d_dep.instance_variable_set(:@requirement, old_req)
47
- end
48
- end
49
-
50
- cache_vendored_gems(definition) if Bundler.app_cache.exist?
51
-
52
- definition.to_lock
53
- rescue Bundler::GemNotFound => e
54
- unlock_yanked_gem(dependencies_to_unlock, e) && retry
55
- rescue Bundler::VersionConflict => e
56
- unlock_blocking_subdeps(dependencies_to_unlock, e) && retry
57
- rescue *RETRYABLE_ERRORS
58
- raise if @retrying
59
-
60
- @retrying = true
61
- sleep(rand(1.0..5.0))
62
- retry
63
- end
64
- end
65
-
66
- def cache_vendored_gems(definition)
67
- # Dependencies that have been unlocked for the update (including
68
- # sub-dependencies)
69
- unlocked_gems = definition.instance_variable_get(:@unlock)
70
- .fetch(:gems)
71
- bundler_opts = {
72
- cache_all: true,
73
- cache_all_platforms: true,
74
- no_prune: true
75
- }
76
-
77
- Bundler.settings.temporary(**bundler_opts) do
78
- # Fetch and cache gems on all platforms without pruning
79
- Bundler::Runtime.new(nil, definition).cache
80
-
81
- # Only prune unlocked gems (the original implementation is in
82
- # Bundler::Runtime)
83
- cache_path = Bundler.app_cache
84
- resolve = definition.resolve
85
- prune_gem_cache(resolve, cache_path, unlocked_gems)
86
- prune_git_and_path_cache(resolve, cache_path)
87
- end
88
- end
89
-
90
- # Copied from Bundler::Runtime: Modified to only prune gems that have
91
- # been unlocked
92
- def prune_gem_cache(resolve, cache_path, unlocked_gems)
93
- cached_gems = Dir["#{cache_path}/*.gem"]
94
-
95
- outdated_gems = cached_gems.reject do |path|
96
- spec = Bundler.rubygems.spec_from_gem path
97
-
98
- !unlocked_gems.include?(spec.name) || resolve.any? do |s|
99
- s.name == spec.name && s.version == spec.version &&
100
- !s.source.is_a?(Bundler::Source::Git)
101
- end
102
- end
103
-
104
- return unless outdated_gems.any?
105
-
106
- outdated_gems.each do |path|
107
- File.delete(path)
108
- end
109
- end
110
-
111
- # Copied from Bundler::Runtime
112
- def prune_git_and_path_cache(resolve, cache_path)
113
- cached_git_and_path = Dir["#{cache_path}/*/.bundlecache"]
114
-
115
- outdated_git_and_path = cached_git_and_path.reject do |path|
116
- name = File.basename(File.dirname(path))
117
-
118
- resolve.any? do |s|
119
- s.source.respond_to?(:app_cache_dirname) &&
120
- s.source.app_cache_dirname == name
121
- end
122
- end
123
-
124
- return unless outdated_git_and_path.any?
125
-
126
- outdated_git_and_path.each do |path|
127
- path = File.dirname(path)
128
- FileUtils.rm_rf(path)
129
- end
130
- end
131
-
132
- def unlock_yanked_gem(dependencies_to_unlock, error)
133
- raise unless error.message.match?(GEM_NOT_FOUND_ERROR_REGEX)
134
-
135
- gem_name = error.message.match(GEM_NOT_FOUND_ERROR_REGEX)
136
- .named_captures["name"]
137
- raise if dependencies_to_unlock.include?(gem_name)
138
-
139
- dependencies_to_unlock << gem_name
140
- end
141
-
142
- def unlock_blocking_subdeps(dependencies_to_unlock, error)
143
- all_deps = Bundler::LockfileParser.new(lockfile)
144
- .specs.map { |x| x.name.to_s }
145
- top_level = build_definition([]).dependencies
146
- .map { |x| x.name.to_s }
147
- allowed_new_unlocks = all_deps - top_level - dependencies_to_unlock
148
-
149
- raise if allowed_new_unlocks.none?
150
-
151
- # Unlock any sub-dependencies that Bundler reports caused the
152
- # conflict
153
- potentials_deps =
154
- error.cause.conflicts.values
155
- .flat_map(&:requirement_trees)
156
- .filter_map do |tree|
157
- tree.find { |req| allowed_new_unlocks.include?(req.name) }
158
- end.map(&:name)
159
-
160
- # If there are specific dependencies we can unlock, unlock them
161
- return dependencies_to_unlock.append(*potentials_deps) if potentials_deps.any?
162
-
163
- # Fall back to unlocking *all* sub-dependencies. This is required
164
- # because Bundler's VersionConflict objects don't include enough
165
- # information to chart the full path through all conflicts unwound
166
- dependencies_to_unlock.append(*allowed_new_unlocks)
167
- end
168
-
169
- def build_definition(dependencies_to_unlock)
170
- defn = Bundler::Definition.build(
171
- gemfile_name,
172
- lockfile_name,
173
- gems: dependencies_to_unlock
174
- )
175
-
176
- # Bundler unlocks the sub-dependencies of gems it is passed even
177
- # if those sub-deps are top-level dependencies. We only want true
178
- # subdeps unlocked, like they were in the UpdateChecker, so we
179
- # mutate the unlocked gems array.
180
- unlocked = defn.instance_variable_get(:@unlock).fetch(:gems)
181
- must_not_unlock = defn.dependencies.map { |x| x.name.to_s } -
182
- dependencies_to_unlock
183
- unlocked.reject! { |n| must_not_unlock.include?(n) }
184
-
185
- defn
186
- end
187
-
188
- def lock_deps_being_updated_to_exact_versions(definition)
189
- dependencies.each_with_object({}) do |dep, old_reqs|
190
- defn_dep = definition.dependencies.find do |d|
191
- d.name == dep.fetch("name")
192
- end
193
-
194
- if defn_dep.nil?
195
- definition.dependencies <<
196
- Bundler::Dependency.new(dep.fetch("name"), dep.fetch("version"))
197
- old_reqs[dep.fetch("name")] = :none
198
- elsif git_dependency?(dep) &&
199
- defn_dep.source.is_a?(Bundler::Source::Git)
200
- defn_dep.source.unlock!
201
- elsif Gem::Version.correct?(dep.fetch("version"))
202
- new_req = Gem::Requirement.create("= #{dep.fetch('version')}")
203
- old_reqs[dep.fetch("name")] = defn_dep.requirement
204
- defn_dep.instance_variable_set(:@requirement, new_req)
205
- end
206
- end
207
- end
208
-
209
- def git_dependency?(dep)
210
- sources = dep.fetch("requirements").map { |r| r.fetch("source") }
211
- sources.all? { |s| s&.fetch("type", nil) == "git" }
212
- end
213
-
214
- def lockfile
215
- @lockfile ||= File.read(lockfile_name)
216
- end
217
- end
218
- end
@@ -1,141 +0,0 @@
1
- # typed: true
2
- # frozen_string_literal: true
3
-
4
- module Functions
5
- class VersionResolver
6
- GEM_NOT_FOUND_ERROR_REGEX = /locked to (?<name>[^\s]+) \(/
7
-
8
- attr_reader :dependency_name
9
- attr_reader :dependency_requirements
10
- attr_reader :gemfile_name
11
- attr_reader :lockfile_name
12
-
13
- def initialize(dependency_name:, dependency_requirements:,
14
- gemfile_name:, lockfile_name:)
15
- @dependency_name = dependency_name
16
- @dependency_requirements = dependency_requirements
17
- @gemfile_name = gemfile_name
18
- @lockfile_name = lockfile_name
19
- end
20
-
21
- def version_details
22
- dep = dependency_from_definition
23
-
24
- # If the dependency wasn't found in the definition, but *is*
25
- # included in a gemspec, it's because the Gemfile didn't import
26
- # the gemspec. This is unusual, but the correct behaviour if/when
27
- # it happens is to behave as if the repo was gemspec-only.
28
- return "latest" if dep.nil? && dependency_requirements.any?
29
-
30
- # Otherwise, if the dependency wasn't found it's because it is a
31
- # subdependency that was removed when attempting to update it.
32
- return nil if dep.nil?
33
-
34
- # If the dependency is Bundler itself then we can't trust the
35
- # version that has been returned (it's the version Dependabot is
36
- # running on, rather than the true latest resolvable version).
37
- return nil if dep.name == "bundler"
38
-
39
- details = {
40
- version: dep.version,
41
- ruby_version: ruby_version,
42
- fetcher: fetcher_class(dep)
43
- }
44
- details[:commit_sha] = dep.source.revision if dep.source.instance_of?(::Bundler::Source::Git)
45
- details
46
- end
47
-
48
- private
49
-
50
- # rubocop:disable Metrics/PerceivedComplexity
51
- def dependency_from_definition(unlock_subdependencies: true)
52
- dependencies_to_unlock = [dependency_name]
53
- dependencies_to_unlock += subdependencies if unlock_subdependencies
54
- begin
55
- definition = build_definition(dependencies_to_unlock)
56
- definition.resolve_remotely!
57
- rescue ::Bundler::GemNotFound => e
58
- unlock_yanked_gem(dependencies_to_unlock, e) && retry
59
- rescue ::Bundler::HTTPError => e
60
- # Retry network errors
61
- # Note: in_a_native_bundler_context will also retry `Bundler::HTTPError` errors
62
- # up to three times meaning we'll end up retrying this error up to six times
63
- # TODO: Could we get rid of this retry logic and only rely on
64
- # SharedBundlerHelpers.in_a_native_bundler_context
65
- attempt ||= 1
66
- attempt += 1
67
- raise if attempt > 3 || !e.message.include?("Network error")
68
-
69
- retry
70
- end
71
-
72
- dep = definition.resolve.find { |d| d.name == dependency_name }
73
- return dep if dep
74
- return if dependency_requirements.any? || !unlock_subdependencies
75
-
76
- # If no definition was found and we're updating a sub-dependency,
77
- # try again but without unlocking any other sub-dependencies
78
- dependency_from_definition(unlock_subdependencies: false)
79
- end
80
- # rubocop:enable Metrics/PerceivedComplexity
81
-
82
- def subdependencies
83
- # If there's no lockfile we don't need to worry about
84
- # subdependencies
85
- return [] unless lockfile
86
-
87
- all_deps = ::Bundler::LockfileParser.new(lockfile)
88
- .specs.map { |x| x.name.to_s }.uniq
89
- top_level = build_definition([]).dependencies
90
- .map { |x| x.name.to_s }
91
-
92
- all_deps - top_level
93
- end
94
-
95
- def build_definition(dependencies_to_unlock)
96
- # NOTE: we lock shared dependencies to avoid any top-level
97
- # dependencies getting unlocked (which would happen if they were
98
- # also subdependencies of the dependency being unlocked)
99
- ::Bundler::Definition.build(
100
- gemfile_name,
101
- lockfile_name,
102
- gems: dependencies_to_unlock,
103
- lock_shared_dependencies: true
104
- )
105
- end
106
-
107
- def unlock_yanked_gem(dependencies_to_unlock, error)
108
- raise unless error.message.match?(GEM_NOT_FOUND_ERROR_REGEX)
109
-
110
- gem_name = error.message.match(GEM_NOT_FOUND_ERROR_REGEX)
111
- .named_captures["name"]
112
- raise if dependencies_to_unlock.include?(gem_name)
113
-
114
- dependencies_to_unlock << gem_name
115
- end
116
-
117
- def lockfile
118
- return @lockfile if defined?(@lockfile)
119
-
120
- @lockfile =
121
- begin
122
- return unless lockfile_name
123
- return unless File.exist?(lockfile_name)
124
-
125
- File.read(lockfile_name)
126
- end
127
- end
128
-
129
- def fetcher_class(dep)
130
- return unless dep.source.is_a?(::Bundler::Source::Rubygems)
131
-
132
- dep.source.fetchers.first.fetchers.first.class.to_s
133
- end
134
-
135
- def ruby_version
136
- return nil unless gemfile_name
137
-
138
- @ruby_version ||= build_definition([]).ruby_version&.gem_version
139
- end
140
- end
141
- end
@@ -1,172 +0,0 @@
1
- # typed: true
2
- # frozen_string_literal: true
3
-
4
- require "functions/file_parser"
5
- require "functions/force_updater"
6
- require "functions/lockfile_updater"
7
- require "functions/dependency_source"
8
- require "functions/version_resolver"
9
- require "functions/conflicting_dependency_resolver"
10
-
11
- module Functions
12
- def self.parsed_gemfile(**args)
13
- set_bundler_flags_and_credentials(dir: args.fetch(:dir), credentials: [])
14
- FileParser.new(lockfile_name: args.fetch(:lockfile_name))
15
- .parsed_gemfile(gemfile_name: args.fetch(:gemfile_name))
16
- end
17
-
18
- def self.parsed_gemspec(**args)
19
- set_bundler_flags_and_credentials(dir: args.fetch(:dir), credentials: [])
20
- FileParser.new(lockfile_name: args.fetch(:lockfile_name))
21
- .parsed_gemspec(gemspec_name: args.fetch(:gemspec_name))
22
- end
23
-
24
- def self.vendor_cache_dir(**args)
25
- set_bundler_flags_and_credentials(dir: args.fetch(:dir), credentials: [])
26
- Bundler.settings.app_cache_path
27
- end
28
-
29
- def self.update_lockfile(**args)
30
- set_bundler_flags_and_credentials(dir: args.fetch(:dir), credentials: args.fetch(:credentials))
31
- LockfileUpdater.new(
32
- gemfile_name: args.fetch(:gemfile_name),
33
- lockfile_name: args.fetch(:lockfile_name),
34
- dependencies: args.fetch(:dependencies)
35
- ).run
36
- end
37
-
38
- def self.force_update(**args)
39
- set_bundler_flags_and_credentials(dir: args.fetch(:dir), credentials: args.fetch(:credentials))
40
- ForceUpdater.new(
41
- dependency_name: args.fetch(:dependency_name),
42
- target_version: args.fetch(:target_version),
43
- gemfile_name: args.fetch(:gemfile_name),
44
- lockfile_name: args.fetch(:lockfile_name),
45
- update_multiple_dependencies: args.fetch(:update_multiple_dependencies)
46
- ).run
47
- end
48
-
49
- def self.dependency_source_type(**args)
50
- set_bundler_flags_and_credentials(dir: args.fetch(:dir), credentials: args.fetch(:credentials))
51
-
52
- DependencySource.new(
53
- gemfile_name: args.fetch(:gemfile_name),
54
- dependency_name: args.fetch(:dependency_name)
55
- ).type
56
- end
57
-
58
- def self.dependency_source_latest_git_version(**args)
59
- set_bundler_flags_and_credentials(dir: args.fetch(:dir), credentials: args.fetch(:credentials))
60
- DependencySource.new(
61
- gemfile_name: args.fetch(:gemfile_name),
62
- dependency_name: args.fetch(:dependency_name)
63
- ).latest_git_version(
64
- dependency_source_url: args.fetch(:dependency_source_url),
65
- dependency_source_branch: args.fetch(:dependency_source_branch)
66
- )
67
- end
68
-
69
- def self.private_registry_versions(**args)
70
- set_bundler_flags_and_credentials(dir: args.fetch(:dir), credentials: args.fetch(:credentials))
71
-
72
- DependencySource.new(
73
- gemfile_name: args.fetch(:gemfile_name),
74
- dependency_name: args.fetch(:dependency_name)
75
- ).private_registry_versions
76
- end
77
-
78
- def self.resolve_version(**args)
79
- set_bundler_flags_and_credentials(dir: args.fetch(:dir), credentials: args.fetch(:credentials))
80
- VersionResolver.new(
81
- dependency_name: args.fetch(:dependency_name),
82
- dependency_requirements: args.fetch(:dependency_requirements),
83
- gemfile_name: args.fetch(:gemfile_name),
84
- lockfile_name: args.fetch(:lockfile_name)
85
- ).version_details
86
- end
87
-
88
- def self.jfrog_source(**args)
89
- # Set flags and credentials
90
- set_bundler_flags_and_credentials(dir: args.fetch(:dir), credentials: args.fetch(:credentials))
91
-
92
- Bundler::Definition.build(args.fetch(:gemfile_name), nil, {})
93
- .send(:sources)
94
- .rubygems_remotes
95
- .find { |uri| uri.host.include?("jfrog") }
96
- &.host
97
- end
98
-
99
- def self.git_specs(**args)
100
- set_bundler_flags_and_credentials(dir: args.fetch(:dir), credentials: args.fetch(:credentials))
101
-
102
- git_specs = Bundler::Definition.build(args.fetch(:gemfile_name), nil, {}).dependencies
103
- .select do |spec|
104
- spec.source.is_a?(Bundler::Source::Git)
105
- end
106
- git_specs.map do |spec|
107
- # Piggy-back off some private Bundler methods to configure the
108
- # URI with auth details in the same way Bundler does.
109
- git_proxy = spec.source.send(:git_proxy)
110
- auth_uri = spec.source.uri.gsub("git://", "https://")
111
- auth_uri = git_proxy.send(:configured_uri_for, auth_uri)
112
- auth_uri += ".git" unless auth_uri.end_with?(".git")
113
- auth_uri += "/info/refs?service=git-upload-pack"
114
- {
115
- uri: spec.source.uri,
116
- auth_uri: auth_uri
117
- }
118
- end
119
- end
120
-
121
- def self.conflicting_dependencies(**args)
122
- set_bundler_flags_and_credentials(dir: args.fetch(:dir), credentials: args.fetch(:credentials))
123
- ConflictingDependencyResolver.new(
124
- dependency_name: args.fetch(:dependency_name),
125
- target_version: args.fetch(:target_version),
126
- lockfile_name: args.fetch(:lockfile_name)
127
- ).conflicting_dependencies
128
- end
129
-
130
- def self.set_bundler_flags_and_credentials(dir:, credentials:)
131
- dir = Pathname.new(dir) if dir
132
- Bundler.instance_variable_set(:@root, dir)
133
-
134
- # Remove installed gems from the default Rubygems index
135
- Gem::Specification.all =
136
- Gem::Specification.send(:default_stubs, "*.gemspec")
137
-
138
- # Set auth details
139
- relevant_credentials(credentials).each do |cred|
140
- token = cred["token"] ||
141
- "#{cred['username']}:#{cred['password']}"
142
-
143
- Bundler.settings.set_command_option(
144
- cred.fetch("host"),
145
- token.gsub("@", "%40F").gsub("?", "%3F")
146
- )
147
- end
148
-
149
- # Use HTTPS for GitHub if lockfile
150
- Bundler.settings.set_command_option("github.https", "true")
151
-
152
- # Native helpers rely on dependency unlocking, so Bundler should never be frozen
153
- Bundler.settings.set_command_option("frozen", "false")
154
- end
155
-
156
- def self.relevant_credentials(credentials)
157
- [
158
- *git_source_credentials(credentials),
159
- *private_registry_credentials(credentials)
160
- ].select { |cred| cred["password"] || cred["token"] }
161
- end
162
-
163
- def self.private_registry_credentials(credentials)
164
- credentials
165
- .select { |cred| cred["type"] == "rubygems_server" }
166
- end
167
-
168
- def self.git_source_credentials(credentials)
169
- credentials
170
- .select { |cred| cred["type"] == "git_source" }
171
- end
172
- end