bundle_update_interactive 0.2.1 → 0.4.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 +4 -4
- data/README.md +33 -3
- data/exe/bundler-ui +2 -1
- data/exe/bundler-update-interactive +2 -1
- data/lib/bundle_update_interactive/bundler_commands.rb +7 -4
- data/lib/bundle_update_interactive/changelog_locator.rb +57 -33
- data/lib/bundle_update_interactive/cli/options.rb +52 -0
- data/lib/bundle_update_interactive/cli.rb +32 -27
- data/lib/bundle_update_interactive/error.rb +6 -0
- data/lib/bundle_update_interactive/gemfile.rb +4 -0
- data/lib/bundle_update_interactive/lockfile.rb +47 -31
- data/lib/bundle_update_interactive/lockfile_entry.rb +4 -4
- data/lib/bundle_update_interactive/outdated_gem.rb +26 -2
- data/lib/bundle_update_interactive/report.rb +6 -5
- data/lib/bundle_update_interactive/version.rb +1 -1
- data/lib/bundle_update_interactive.rb +1 -0
- metadata +5 -18
- data/lib/bundle_update_interactive/cli/thor_ext.rb +0 -76
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5eb0324c92b1a60c3611ce47993022fef9c3d69aeba735a752252cc2cffe8158
|
4
|
+
data.tar.gz: f68c74d7fc286abf743796b3323bed579736bf548ccc2a405b8a2433bf3eddf1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 874b63b22b4ff2ad9101f9a5a51b2947cf27009ff37096ae0ff3f00e3f5de5ba2ea32a4e2a2adae0d46bf4a523868deb5f8f7ec45599f988719a983e1bb9e16f
|
7
|
+
data.tar.gz: 7f134fec1b5304ed93921506a4e763a1f7ef7954f812b679cb63e271c99fc76bd291f68f1e9fd51a532a3d63a55e74ad01657767250924cd8a2a0d0d7baa69ce
|
data/README.md
CHANGED
@@ -75,17 +75,47 @@ If you discover a gem that is missing a changelog in `bundle update-interactive`
|
|
75
75
|
|
76
76
|
### Git diffs
|
77
77
|
|
78
|
-
If your `Gemfile` sources a gem from a
|
78
|
+
If your `Gemfile` sources a gem from a Git repo like this:
|
79
79
|
|
80
80
|
```ruby
|
81
81
|
gem "rails", github: "rails/rails", branch: "7-1-stable"
|
82
82
|
```
|
83
83
|
|
84
|
-
Then `bundle update-interactive` will show a
|
84
|
+
Then `bundle update-interactive` will show a diff link instead of a changelog, so you can see exactly what changed when the gem is updated. For example:
|
85
85
|
|
86
86
|
https://github.com/rails/rails/compare/5a8d894...77dfa65
|
87
87
|
|
88
|
-
|
88
|
+
This feature currently works for GitHub, GitLab, and Bitbucket repos.
|
89
|
+
|
90
|
+
### Limit impact by Gemfile groups
|
91
|
+
|
92
|
+
The effects of `bundle update-interactive` can be limited to one or more Gemfile groups using the `--exclusively` option:
|
93
|
+
|
94
|
+
```sh
|
95
|
+
bundle update-interactive --exclusively=group1,group2
|
96
|
+
```
|
97
|
+
|
98
|
+
This is especially useful when you want to safely update a subset of your lock file without introducing any risk to your application in production. The best way to do this is with `--exclusively=development,test`, which can be abbreviated to simply `-D`:
|
99
|
+
|
100
|
+
```sh
|
101
|
+
# Update non-production dependencies.
|
102
|
+
# This is equivalent to `bundle update-interactive --exclusively=development,test`
|
103
|
+
bundle update-interactive -D
|
104
|
+
```
|
105
|
+
|
106
|
+
The `--exclusively` and `-D` options will cause `update-interactive` to only consider gems that are used _exclusively_ by the specified Gemfile groups. Indirect dependencies that are shared with other Gemfile groups will not be updated.
|
107
|
+
|
108
|
+
For example, given this Gemfile:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
gem "rails"
|
112
|
+
|
113
|
+
group :test do
|
114
|
+
gem "capybara"
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
If `--exclusively=test` is used, `capybara` and its indirect dependency `xpath` are both exclusively used in test and can therefore be updated. However, capybara's `nokogiri` indirect dependency, which is also used in production via `rails` → `actionpack` → `nokogiri`, would not be allowed to update.
|
89
119
|
|
90
120
|
### Conservative updates
|
91
121
|
|
data/exe/bundler-ui
CHANGED
@@ -9,10 +9,13 @@ module BundleUpdateInteractive
|
|
9
9
|
system "bundle update --conservative #{gems.flatten.map(&:shellescape).join(' ')}"
|
10
10
|
end
|
11
11
|
|
12
|
-
def read_updated_lockfile
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
def read_updated_lockfile(*gems)
|
13
|
+
command = ["bundle lock --print"]
|
14
|
+
command << "--conservative" if gems.any?
|
15
|
+
command << "--update"
|
16
|
+
command.push(*gems.flatten.map(&:shellescape))
|
17
|
+
|
18
|
+
`#{command.join(" ")}`.tap { raise "bundle lock command failed" unless Process.last_status.success? }
|
16
19
|
end
|
17
20
|
end
|
18
21
|
end
|
@@ -3,55 +3,79 @@
|
|
3
3
|
require "faraday"
|
4
4
|
require "json"
|
5
5
|
|
6
|
-
GITHUB_PATTERN = %r{^(?:https?://)?github\.com/([^/]+/[^/]+)(?:\.git)?/?}.freeze
|
7
|
-
URI_KEYS = %w[source_code_uri homepage_uri bug_tracker_uri wiki_uri].freeze
|
8
|
-
FILE_PATTERN = /changelog|changes|history|news|release/i.freeze
|
9
|
-
EXT_PATTERN = /md|txt|rdoc/i.freeze
|
10
|
-
|
11
6
|
module BundleUpdateInteractive
|
12
7
|
class ChangelogLocator
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
8
|
+
GITHUB_PATTERN = %r{^(?:https?://)?github\.com/([^/]+/[^/]+)(?:\.git)?/?}.freeze
|
9
|
+
URI_KEYS = %w[source_code_uri homepage_uri bug_tracker_uri wiki_uri].freeze
|
10
|
+
FILE_PATTERN = /changelog|changes|history|news|release/i.freeze
|
11
|
+
EXT_PATTERN = /md|txt|rdoc/i.freeze
|
12
|
+
|
13
|
+
class GitHubRepo
|
14
|
+
def self.from_uris(*uris)
|
15
|
+
uris.flatten.each do |uri|
|
16
|
+
return new(Regexp.last_match(1)) if uri&.match(GITHUB_PATTERN)
|
17
|
+
end
|
18
|
+
nil
|
18
19
|
end
|
19
20
|
|
20
|
-
|
21
|
+
attr_reader :path
|
21
22
|
|
22
|
-
|
23
|
+
def initialize(path)
|
24
|
+
@path = path
|
25
|
+
end
|
23
26
|
|
24
|
-
|
27
|
+
def discover_changelog_uri(version)
|
28
|
+
repo_html = fetch_repo_html(follow_redirect: true)
|
29
|
+
return if repo_html.nil?
|
25
30
|
|
26
|
-
|
27
|
-
|
28
|
-
github_repo = guess_github_repo(data)
|
31
|
+
changelog_path = repo_html[%r{/(#{path}/blob/[^/]+/#{FILE_PATTERN}(?:\.#{EXT_PATTERN})?)"}i, 1]
|
32
|
+
return "https://github.com/#{changelog_path}" if changelog_path
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
if file_list.status == 301
|
33
|
-
github_repo = file_list.headers["Location"][GITHUB_PATTERN, 1]
|
34
|
-
file_list = Faraday.get(file_list.headers["Location"])
|
35
|
-
end
|
36
|
-
match = file_list.body.match(%r{/(#{github_repo}/blob/[^/]+/#{FILE_PATTERN}(?:\.#{EXT_PATTERN})?)"}i)
|
37
|
-
changelog_uri = "https://github.com/#{match[1]}" if match
|
34
|
+
releases_url = "https://github.com/#{path}/releases"
|
35
|
+
releases_url if Faraday.head("#{releases_url}/tag/v#{version}").success?
|
38
36
|
end
|
39
37
|
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
private
|
39
|
+
|
40
|
+
def fetch_repo_html(follow_redirect:)
|
41
|
+
response = Faraday.get("https://github.com/#{path}")
|
42
|
+
|
43
|
+
if response.status == 301 && follow_redirect
|
44
|
+
@path = response.headers["Location"][GITHUB_PATTERN, 1]
|
45
|
+
return fetch_repo_html(follow_redirect: false)
|
46
|
+
end
|
47
|
+
|
48
|
+
response.success? ? response.body : nil
|
43
49
|
end
|
50
|
+
end
|
44
51
|
|
45
|
-
|
52
|
+
def find_changelog_uri(name:, version: nil)
|
53
|
+
data = fetch_rubygems_data(name, version)
|
54
|
+
return if data.nil?
|
55
|
+
|
56
|
+
if (rubygems_changelog_uri = data["changelog_uri"])
|
57
|
+
rubygems_changelog_uri
|
58
|
+
elsif (github_repo = GitHubRepo.from_uris(data.values_at(*URI_KEYS)))
|
59
|
+
github_repo.discover_changelog_uri(data["version"])
|
60
|
+
end
|
46
61
|
end
|
47
62
|
|
48
63
|
private
|
49
64
|
|
50
|
-
def
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
65
|
+
def fetch_rubygems_data(name, version)
|
66
|
+
api_url = if version.nil?
|
67
|
+
"https://rubygems.org/api/v1/gems/#{name}.json"
|
68
|
+
else
|
69
|
+
"https://rubygems.org/api/v2/rubygems/#{name}/versions/#{version}.json"
|
70
|
+
end
|
71
|
+
|
72
|
+
response = Faraday.get(api_url)
|
73
|
+
|
74
|
+
# Try again without the version in case the version does not exist at rubygems for some reason.
|
75
|
+
# This can happen when using a pre-release Ruby that has a bundled gem newer than the published version.
|
76
|
+
return fetch_rubygems_data(name, nil) if !response.success? && !version.nil?
|
77
|
+
|
78
|
+
response.success? ? JSON.parse(response.body) : nil
|
55
79
|
end
|
56
80
|
end
|
57
81
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
|
5
|
+
module BundleUpdateInteractive
|
6
|
+
class CLI::Options
|
7
|
+
class << self
|
8
|
+
def parse(argv=ARGV)
|
9
|
+
options = new
|
10
|
+
remaining = build_parser(options).parse!(argv.dup)
|
11
|
+
raise Error, "update-interactive does not accept arguments. See --help for available options." if remaining.any?
|
12
|
+
|
13
|
+
options.freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def build_parser(options) # rubocop:disable Metrics/MethodLength
|
19
|
+
OptionParser.new do |parser|
|
20
|
+
parser.banner = "Usage: bundle update-interactive"
|
21
|
+
parser.on(
|
22
|
+
"--exclusively=GROUP",
|
23
|
+
"Update gems that exclusively belong to the specified Gemfile GROUP(s) (comma-separated)"
|
24
|
+
) do |value|
|
25
|
+
options.exclusively = value.split(",").map(&:strip).reject(&:empty?).map(&:to_sym)
|
26
|
+
end
|
27
|
+
parser.on(
|
28
|
+
"-D",
|
29
|
+
"Update development and test gems only; short for --exclusively=development,test"
|
30
|
+
) do
|
31
|
+
options.exclusively = %i[development test]
|
32
|
+
end
|
33
|
+
parser.on("-v", "--version", "Display bundle_update_interactive version") do
|
34
|
+
require "bundler"
|
35
|
+
puts "bundle_update_interactive/#{VERSION} bundler/#{Bundler::VERSION} #{RUBY_DESCRIPTION}"
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
parser.on("-h", "--help", "Show this help") do
|
39
|
+
puts parser
|
40
|
+
exit
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_accessor :exclusively
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
@exclusively = []
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,40 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "bundler"
|
4
4
|
|
5
5
|
module BundleUpdateInteractive
|
6
|
-
class CLI
|
6
|
+
class CLI
|
7
7
|
autoload :MultiSelect, "bundle_update_interactive/cli/multi_select"
|
8
|
+
autoload :Options, "bundle_update_interactive/cli/options"
|
8
9
|
autoload :Row, "bundle_update_interactive/cli/row"
|
9
10
|
autoload :Table, "bundle_update_interactive/cli/table"
|
10
|
-
autoload :ThorExt, "bundle_update_interactive/cli/thor_ext"
|
11
11
|
|
12
|
-
|
12
|
+
def run(argv: ARGV) # rubocop:disable Metrics/AbcSize
|
13
|
+
options = Options.parse(argv)
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
report = generate_report(options)
|
16
|
+
puts("No gems to update.").then { return } if report.updateable_gems.empty?
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
desc "ui", "Update Gemfile.lock interactively", hide: true
|
23
|
-
def ui # rubocop:disable Metrics/AbcSize
|
24
|
-
report = generate_report
|
25
|
-
say("No gems to update.") && return if report.updateable_gems.empty?
|
26
|
-
|
27
|
-
say
|
28
|
-
say legend
|
29
|
-
say
|
18
|
+
puts
|
19
|
+
puts legend
|
20
|
+
puts
|
30
21
|
selected_gems = MultiSelect.prompt_for_gems_to_update(report.updateable_gems)
|
31
|
-
|
22
|
+
puts("No gems to update.").then { return } if selected_gems.empty?
|
32
23
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
24
|
+
puts "\nUpdating the following gems."
|
25
|
+
puts
|
26
|
+
puts Table.new(selected_gems).render
|
27
|
+
puts
|
37
28
|
report.bundle_update!(*selected_gems.keys)
|
29
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
30
|
+
handle_exception(e)
|
38
31
|
end
|
39
32
|
|
40
33
|
private
|
@@ -51,9 +44,9 @@ module BundleUpdateInteractive
|
|
51
44
|
LEGEND
|
52
45
|
end
|
53
46
|
|
54
|
-
def generate_report
|
47
|
+
def generate_report(options)
|
55
48
|
whisper "Resolving latest gem versions..."
|
56
|
-
report = Report.generate
|
49
|
+
report = Report.generate(groups: options.exclusively)
|
57
50
|
updateable_gems = report.updateable_gems
|
58
51
|
return report if updateable_gems.empty?
|
59
52
|
|
@@ -65,7 +58,7 @@ module BundleUpdateInteractive
|
|
65
58
|
end
|
66
59
|
|
67
60
|
def whisper(message)
|
68
|
-
$stderr.puts(message)
|
61
|
+
$stderr.puts(message)
|
69
62
|
end
|
70
63
|
|
71
64
|
def progress(message, items, &block)
|
@@ -76,5 +69,17 @@ module BundleUpdateInteractive
|
|
76
69
|
end
|
77
70
|
$stderr.print("\n")
|
78
71
|
end
|
72
|
+
|
73
|
+
def handle_exception(error)
|
74
|
+
case error
|
75
|
+
when Errno::EPIPE
|
76
|
+
# Ignore
|
77
|
+
when BundleUpdateInteractive::Error, OptionParser::ParseError, Interrupt, Bundler::Dsl::DSLError
|
78
|
+
$stderr.puts BundleUpdateInteractive.pastel.red(error.message)
|
79
|
+
exit false
|
80
|
+
else
|
81
|
+
raise
|
82
|
+
end
|
83
|
+
end
|
79
84
|
end
|
80
85
|
end
|
@@ -1,54 +1,70 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "bundler"
|
4
|
+
require "set"
|
4
5
|
|
5
6
|
module BundleUpdateInteractive
|
6
7
|
class Lockfile
|
7
|
-
|
8
|
-
def self.parse(lockfile_contents=File.read("Gemfile.lock")) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
8
|
+
def self.parse(lockfile_contents=File.read("Gemfile.lock"))
|
9
9
|
parser = Bundler::LockfileParser.new(lockfile_contents)
|
10
|
-
|
11
|
-
|
12
|
-
exact_children = {}
|
10
|
+
new(parser.specs)
|
11
|
+
end
|
13
12
|
|
14
|
-
|
15
|
-
|
13
|
+
def initialize(specs)
|
14
|
+
@specs_by_name = {}
|
15
|
+
required_exactly = Set.new
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
specs.each do |spec|
|
18
|
+
specs_by_name[spec.name] = spec
|
19
|
+
spec.dependencies.each { |dep| required_exactly << dep.name if dep.requirement.exact? }
|
20
|
+
end
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
end
|
22
|
+
@entries_by_name = specs_by_name.transform_values do |spec|
|
23
|
+
build_entry(spec, required_exactly.include?(spec.name))
|
23
24
|
end
|
25
|
+
end
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
until traversal.empty?
|
29
|
-
name = traversal.pop
|
30
|
-
next if exact_dependencies.include?(name)
|
27
|
+
def entries
|
28
|
+
entries_by_name.values
|
29
|
+
end
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
def [](gem_name)
|
32
|
+
entries_by_name[gem_name]
|
33
|
+
end
|
35
34
|
|
36
|
-
|
37
|
-
|
35
|
+
def gems_exclusively_installed_by(gemfile:, groups:)
|
36
|
+
return [] if groups.empty?
|
38
37
|
|
39
|
-
|
40
|
-
|
38
|
+
other_group_gems = gemfile.dependencies.filter_map { |gem| gem.name unless (gem.groups & groups).any? }
|
39
|
+
other_group_gems &= entries_by_name.keys
|
40
|
+
gems_installed_by_other_groups = other_group_gems + traverse_transient_dependencies(*other_group_gems)
|
41
41
|
|
42
|
-
|
43
|
-
@entries = entries.freeze
|
42
|
+
entries_by_name.keys - gems_installed_by_other_groups
|
44
43
|
end
|
45
44
|
|
46
|
-
|
47
|
-
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_reader :entries_by_name, :specs_by_name
|
48
|
+
|
49
|
+
def build_entry(spec, exact)
|
50
|
+
exact_dependencies = traverse_transient_dependencies(spec.name) { |dep| dep.requirement.exact? }
|
51
|
+
LockfileEntry.new(spec, exact_dependencies, exact)
|
48
52
|
end
|
49
53
|
|
50
|
-
def
|
51
|
-
|
54
|
+
def traverse_transient_dependencies(*gem_names) # rubocop:disable Metrics/AbcSize
|
55
|
+
traversal = Set.new
|
56
|
+
stack = gem_names.flatten
|
57
|
+
until stack.empty?
|
58
|
+
specs_by_name[stack.pop].dependencies.each do |dep|
|
59
|
+
next if traversal.include?(dep.name)
|
60
|
+
next unless specs_by_name.key?(dep.name)
|
61
|
+
next if block_given? && !yield(dep)
|
62
|
+
|
63
|
+
traversal << dep.name
|
64
|
+
stack << dep.name
|
65
|
+
end
|
66
|
+
end
|
67
|
+
traversal.to_a
|
52
68
|
end
|
53
69
|
end
|
54
70
|
end
|
@@ -4,10 +4,10 @@ module BundleUpdateInteractive
|
|
4
4
|
class LockfileEntry
|
5
5
|
attr_reader :spec, :exact_dependencies
|
6
6
|
|
7
|
-
def initialize(spec, exact_dependencies,
|
7
|
+
def initialize(spec, exact_dependencies, exact_requirement)
|
8
8
|
@spec = spec
|
9
9
|
@exact_dependencies = exact_dependencies
|
10
|
-
@
|
10
|
+
@exact_requirement = exact_requirement
|
11
11
|
end
|
12
12
|
|
13
13
|
def name
|
@@ -28,8 +28,8 @@ module BundleUpdateInteractive
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
32
|
-
@
|
31
|
+
def exact_requirement?
|
32
|
+
@exact_requirement
|
33
33
|
end
|
34
34
|
|
35
35
|
def git_version
|
@@ -35,8 +35,8 @@ module BundleUpdateInteractive
|
|
35
35
|
return @changelog_uri if defined?(@changelog_uri)
|
36
36
|
|
37
37
|
@changelog_uri =
|
38
|
-
if
|
39
|
-
|
38
|
+
if (diff_url = build_git_diff_url)
|
39
|
+
diff_url
|
40
40
|
elsif rubygems_source?
|
41
41
|
changelog_locator.find_changelog_uri(name: name, version: updated_version.to_s)
|
42
42
|
else
|
@@ -56,10 +56,34 @@ module BundleUpdateInteractive
|
|
56
56
|
|
57
57
|
attr_reader :changelog_locator
|
58
58
|
|
59
|
+
def build_git_diff_url
|
60
|
+
return nil unless git_version_changed?
|
61
|
+
|
62
|
+
if github_repo
|
63
|
+
"https://github.com/#{github_repo}/compare/#{current_git_version}...#{updated_git_version}"
|
64
|
+
elsif gitlab_repo
|
65
|
+
"https://gitlab.com/os85/httpx/-/compare/#{current_git_version}...#{updated_git_version}"
|
66
|
+
elsif bitbucket_cloud_repo
|
67
|
+
"https://bitbucket.org/#{bitbucket_cloud_repo}/branches/compare/#{updated_git_version}..#{current_git_version}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
59
71
|
def github_repo
|
60
72
|
return nil unless updated_git_version
|
61
73
|
|
62
74
|
git_source_uri.to_s[%r{^(?:git@github.com:|https://github.com/)([^/]+/[^/]+?)(:?\.git)?(?:$|/)}i, 1]
|
63
75
|
end
|
76
|
+
|
77
|
+
def gitlab_repo
|
78
|
+
return nil unless updated_git_version
|
79
|
+
|
80
|
+
git_source_uri.to_s[%r{^(?:git@gitlab.com:|https://gitlab.com/)([^/]+/[^/]+?)(:?\.git)?(?:$|/)}i, 1]
|
81
|
+
end
|
82
|
+
|
83
|
+
def bitbucket_cloud_repo
|
84
|
+
return nil unless updated_git_version
|
85
|
+
|
86
|
+
git_source_uri.to_s[%r{(?:@|://)bitbucket.org[:/]([^/]+/[^/]+?)(:?\.git)?(?:$|/)}i, 1]
|
87
|
+
end
|
64
88
|
end
|
65
89
|
end
|
@@ -8,11 +8,12 @@ require "set"
|
|
8
8
|
module BundleUpdateInteractive
|
9
9
|
class Report
|
10
10
|
class << self
|
11
|
-
def generate
|
11
|
+
def generate(groups: [])
|
12
12
|
gemfile = Gemfile.parse
|
13
13
|
current_lockfile = Lockfile.parse
|
14
|
-
|
14
|
+
gems = groups.any? ? current_lockfile.gems_exclusively_installed_by(gemfile: gemfile, groups: groups) : nil
|
15
15
|
|
16
|
+
updated_lockfile = gems&.none? ? nil : Lockfile.parse(BundlerCommands.read_updated_lockfile(*Array(gems)))
|
16
17
|
new(gemfile: gemfile, current_lockfile: current_lockfile, updated_lockfile: updated_lockfile)
|
17
18
|
end
|
18
19
|
end
|
@@ -21,9 +22,9 @@ module BundleUpdateInteractive
|
|
21
22
|
|
22
23
|
def initialize(gemfile:, current_lockfile:, updated_lockfile:)
|
23
24
|
@current_lockfile = current_lockfile
|
24
|
-
@outdated_gems
|
25
|
+
@outdated_gems = current_lockfile.entries.each_with_object({}) do |current_lockfile_entry, hash|
|
25
26
|
name = current_lockfile_entry.name
|
26
|
-
updated_lockfile_entry = updated_lockfile[name]
|
27
|
+
updated_lockfile_entry = updated_lockfile && updated_lockfile[name]
|
27
28
|
next unless current_lockfile_entry.older_than?(updated_lockfile_entry)
|
28
29
|
|
29
30
|
hash[name] = build_outdated_gem(current_lockfile_entry, updated_lockfile_entry, gemfile[name]&.groups)
|
@@ -36,7 +37,7 @@ module BundleUpdateInteractive
|
|
36
37
|
|
37
38
|
def updateable_gems
|
38
39
|
@updateable_gems ||= outdated_gems.reject do |name, _|
|
39
|
-
current_lockfile[name].
|
40
|
+
current_lockfile[name].exact_requirement?
|
40
41
|
end.freeze
|
41
42
|
end
|
42
43
|
|
@@ -6,6 +6,7 @@ module BundleUpdateInteractive
|
|
6
6
|
autoload :BundlerCommands, "bundle_update_interactive/bundler_commands"
|
7
7
|
autoload :ChangelogLocator, "bundle_update_interactive/changelog_locator"
|
8
8
|
autoload :CLI, "bundle_update_interactive/cli"
|
9
|
+
autoload :Error, "bundle_update_interactive/error"
|
9
10
|
autoload :Gemfile, "bundle_update_interactive/gemfile"
|
10
11
|
autoload :Lockfile, "bundle_update_interactive/lockfile"
|
11
12
|
autoload :LockfileEntry, "bundle_update_interactive/lockfile_entry"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bundle_update_interactive
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Brictson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-07-
|
11
|
+
date: 2024-07-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -66,20 +66,6 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 0.8.0
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: thor
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '1.2'
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '1.2'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: tty-prompt
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -126,9 +112,10 @@ files:
|
|
126
112
|
- lib/bundle_update_interactive/changelog_locator.rb
|
127
113
|
- lib/bundle_update_interactive/cli.rb
|
128
114
|
- lib/bundle_update_interactive/cli/multi_select.rb
|
115
|
+
- lib/bundle_update_interactive/cli/options.rb
|
129
116
|
- lib/bundle_update_interactive/cli/row.rb
|
130
117
|
- lib/bundle_update_interactive/cli/table.rb
|
131
|
-
- lib/bundle_update_interactive/
|
118
|
+
- lib/bundle_update_interactive/error.rb
|
132
119
|
- lib/bundle_update_interactive/gemfile.rb
|
133
120
|
- lib/bundle_update_interactive/lockfile.rb
|
134
121
|
- lib/bundle_update_interactive/lockfile_entry.rb
|
@@ -160,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
160
147
|
- !ruby/object:Gem::Version
|
161
148
|
version: '0'
|
162
149
|
requirements: []
|
163
|
-
rubygems_version: 3.5.
|
150
|
+
rubygems_version: 3.5.16
|
164
151
|
signing_key:
|
165
152
|
specification_version: 4
|
166
153
|
summary: Adds an update-interactive command to Bundler
|
@@ -1,76 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "bundler"
|
4
|
-
|
5
|
-
class BundleUpdateInteractive::CLI
|
6
|
-
module ThorExt
|
7
|
-
# Configures Thor to behave more like a typical CLI, with better help and error handling.
|
8
|
-
#
|
9
|
-
# - Passing -h or --help to a command will show help for that command.
|
10
|
-
# - Unrecognized options will be treated as errors (instead of being silently ignored).
|
11
|
-
# - Error messages will be printed in red to stderr, without stack trace.
|
12
|
-
# - Full stack traces can be enabled by setting the VERBOSE environment variable.
|
13
|
-
# - Errors will cause Thor to exit with a non-zero status.
|
14
|
-
#
|
15
|
-
# To take advantage of this behavior, your CLI should subclass Thor and extend this module.
|
16
|
-
#
|
17
|
-
# class CLI < Thor
|
18
|
-
# extend ThorExt::Start
|
19
|
-
# end
|
20
|
-
#
|
21
|
-
# Start your CLI with:
|
22
|
-
#
|
23
|
-
# CLI.start
|
24
|
-
#
|
25
|
-
# In tests, prevent Kernel.exit from being called when an error occurs, like this:
|
26
|
-
#
|
27
|
-
# CLI.start(args, exit_on_failure: false)
|
28
|
-
#
|
29
|
-
module Start
|
30
|
-
def self.extended(base)
|
31
|
-
super
|
32
|
-
base.check_unknown_options!
|
33
|
-
end
|
34
|
-
|
35
|
-
def start(given_args=ARGV, config={})
|
36
|
-
config[:shell] ||= Thor::Base.shell.new
|
37
|
-
handle_help_switches(given_args) do |args|
|
38
|
-
dispatch(nil, args, nil, config)
|
39
|
-
end
|
40
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
41
|
-
handle_exception_on_start(e, config)
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
def handle_help_switches(given_args)
|
47
|
-
yield(given_args.dup)
|
48
|
-
rescue Thor::UnknownArgumentError => e
|
49
|
-
retry_with_args = []
|
50
|
-
|
51
|
-
if given_args.first == "help"
|
52
|
-
retry_with_args = ["help"] if given_args.length > 1
|
53
|
-
elsif (e.unknown & %w[-h --help]).any?
|
54
|
-
retry_with_args = ["help", (given_args - e.unknown).first]
|
55
|
-
end
|
56
|
-
raise unless retry_with_args.any?
|
57
|
-
|
58
|
-
yield(retry_with_args)
|
59
|
-
end
|
60
|
-
|
61
|
-
def handle_exception_on_start(error, config)
|
62
|
-
case error
|
63
|
-
when Errno::EPIPE
|
64
|
-
# Ignore
|
65
|
-
when Thor::Error, Interrupt, Bundler::Dsl::DSLError
|
66
|
-
raise unless config.fetch(:exit_on_failure, true)
|
67
|
-
|
68
|
-
config[:shell]&.say_error(error.message, :red)
|
69
|
-
exit(false)
|
70
|
-
else
|
71
|
-
raise
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|