Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ bundle ui

- `--commit` [applies each gem update in a discrete git commit](#git-commits)
- `--latest` [modifies the Gemfile if necessary to allow the latest gem versions](#allow-latest-versions)
- `--only-explicit` [updates Gemfile gems only (excluding indirect dependencies)](#exclude-indirect-dependencies)
- `-D` / `--exclusively=GROUP` [limits updatable gems by Gemfile groups](#limit-impact-by-gemfile-groups)

## Features
Expand Down Expand Up @@ -145,6 +146,16 @@ https://github.com/rails/rails/compare/5a8d894...77dfa65

This feature currently works for GitHub, GitLab, and Bitbucket repos.

### Exclude indirect dependencies

Just like with `bundle outdated`, you can pass `--only-explicit` to limit updates to only gems that are explicitly listed in the Gemfile.

```sh
bundle update-interactive --only-explicit
```

This will omit indirect dependencies from the list of gems that can be updated.

### Limit impact by Gemfile groups

The effects of `bundle update-interactive` can be limited to one or more Gemfile groups using the `--exclusively` option:
Expand Down
2 changes: 1 addition & 1 deletion lib/bundle_update_interactive/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def legend
def generate_report(options)
whisper "Resolving latest gem versions..."
updater_class = options.latest? ? Latest::Updater : Updater
updater = updater_class.new(groups: options.exclusively)
updater = updater_class.new(groups: options.exclusively, only_explicit: options.only_explicit?)

report = updater.generate_report
unless report.empty?
Expand Down
10 changes: 9 additions & 1 deletion lib/bundle_update_interactive/cli/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ def build_parser(options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLengt
parser.on("--latest", "Modify the Gemfile to allow the latest gem versions") do
options.latest = true
end
parser.on("--only-explicit", "Update Gemfile gems only (no indirect dependencies)") do
options.only_explicit = true
end
parser.on(
"--exclusively=GROUP",
"Update gems exclusively belonging to the specified Gemfile GROUP(s)"
Expand All @@ -94,12 +97,13 @@ def build_parser(options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLengt
end

attr_accessor :exclusively
attr_writer :commit, :latest
attr_writer :commit, :latest, :only_explicit

def initialize
@exclusively = []
@commit = false
@latest = false
@only_explicit = false
end

def commit?
Expand All @@ -109,6 +113,10 @@ def commit?
def latest?
@latest
end

def only_explicit?
@only_explicit
end
end
end
end
4 changes: 4 additions & 0 deletions lib/bundle_update_interactive/gemfile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,9 @@ def [](name)
def dependencies
@dependencies.values
end

def gem_names
dependencies.map(&:name)
end
end
end
9 changes: 6 additions & 3 deletions lib/bundle_update_interactive/updater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

module BundleUpdateInteractive
class Updater
def initialize(groups: [])
def initialize(groups: [], only_explicit: false)
@only_explicit = only_explicit
@gemfile = Gemfile.parse
@current_lockfile = Lockfile.parse
@candidate_gems = current_lockfile.gems_exclusively_installed_by(gemfile: gemfile, groups: groups) if groups.any?
Expand Down Expand Up @@ -32,12 +33,14 @@ def modified_gemfile?

private

attr_reader :gemfile, :current_lockfile, :candidate_gems
attr_reader :gemfile, :current_lockfile, :candidate_gems, :only_explicit

def find_updatable_gems
return {} if candidate_gems && candidate_gems.empty?

build_outdated_gems(BundlerCommands.read_updated_lockfile(*Array(candidate_gems)))
updatable = build_outdated_gems(BundlerCommands.read_updated_lockfile(*Array(candidate_gems)))
updatable = updatable.slice(*gemfile.gem_names) if only_explicit
updatable
end

def build_outdated_gems(lockfile_contents)
Expand Down
7 changes: 7 additions & 0 deletions test/bundle_update_interactive/cli/options_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def test_defaults
assert_empty options.exclusively
refute_predicate options, :latest?
refute_predicate options, :commit?
refute_predicate options, :only_explicit?
end

def test_allows_exclusive_groups_to_be_specified_as_comma_separated
Expand All @@ -71,6 +72,12 @@ def test_latest_can_be_enabled
assert_predicate options, :latest?
end

def test_only_explicit_can_be_enabled
options = Options.parse(["--only_explicit"])

assert_predicate options, :only_explicit?
end

def test_raises_exception_when_given_a_positional_argment
error = assert_raises(BundleUpdateInteractive::Error) do
Options.parse(%w[hello])
Expand Down
4 changes: 4 additions & 0 deletions test/fixtures/integration/with_indirect/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

source "https://rubygems.org"
gem "mail"
30 changes: 30 additions & 0 deletions test/fixtures/integration/with_indirect/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
GEM
remote: https://rubygems.org/
specs:
date (3.4.0)
mail (2.8.0)
mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
mini_mime (1.1.4)
net-imap (0.5.1)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.2)
timeout
net-smtp (0.5.0)
net-protocol
timeout (0.4.2)

PLATFORMS
arm64-darwin-24
ruby

DEPENDENCIES
mail

BUNDLED WITH
2.5.23
9 changes: 9 additions & 0 deletions test/integration/cli_integration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ def test_updates_lock_file_based_on_selected_gem_while_honoring_gemfile_requirem
LOCK
end

def test_omits_indirect_gems_when_only_explicit_option_is_passed
out, _gemfile, _lockfile = within_fixture_copy("integration/with_indirect") do
run_bundle_update_interactive(argv: ["--only-explicit"], key_presses: "\n")
end

assert_includes out, "1 gem can be updated."
assert_includes out, "‣ ⬡ mail"
end

def test_updates_lock_file_and_gemfile_to_accommodate_latest_version_when_latest_option_is_specified
latest_minitest_version = fetch_latest_gem_version_from_rubygems_api("minitest")

Expand Down