Skip to content

Commit

Permalink
Use correct Bundler version when updating gems (#29)
Browse files Browse the repository at this point in the history
Before, `update-interactive` would update gems using whatever `bundle`
binary was in the PATH. On Rails projects, there is often a binstub for
Bundler located at `bin/bundle`. If this `bin` directory is in the PATH,
then the binstub is executed instead of Bundler being executed directly.

Using the binstub is problematic, because the binstub locks the version
of Bundler to the `BUNDLED WITH` value of the lock file. The binstub
makes an exception for this behavior when running `bin/bundle update`,
so that the latest version of Bundler is used when updating gems.
However, this auto-detection only works when the binstub is invoked
directly in the shell. Since `update-interactive` wraps the invocation
of the update command, the binstub falls back to locking the version of
Bundler at whatever is in the lock file.

As a result, `update-interactive` would not update the lockfile with the
version of Bundler that the user intended.

Fix by using `Gem.bin_path` to get the correct Bundler executable,
rather than using a binstub that might be in the user's PATH.
  • Loading branch information
mattbrictson authored Aug 1, 2024
1 parent b783fea commit 40dd0a3
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 5 deletions.
13 changes: 11 additions & 2 deletions lib/bundle_update_interactive/bundler_commands.rb
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
# frozen_string_literal: true

require "bundler"
require "shellwords"

module BundleUpdateInteractive
module BundlerCommands
class << self
def update_gems_conservatively(*gems)
system "bundle update --conservative #{gems.flatten.map(&:shellescape).join(' ')}"
system "#{bundle_bin.shellescape} update --conservative #{gems.flatten.map(&:shellescape).join(' ')}"
end

def read_updated_lockfile(*gems)
command = ["bundle lock --print"]
command = ["#{bundle_bin.shellescape} lock --print"]
command << "--conservative" if gems.any?
command << "--update"
command.push(*gems.flatten.map(&:shellescape))

`#{command.join(" ")}`.tap { raise "bundle lock command failed" unless Process.last_status.success? }
end

private

def bundle_bin
Gem.bin_path("bundler", "bundle", Bundler::VERSION)
rescue Gem::GemNotFoundException
"bundle"
end
end
end
end
14 changes: 11 additions & 3 deletions test/bundle_update_interactive/bundler_commands_test.rb
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
# frozen_string_literal: true

require "test_helper"
require "bundler"

module BundleUpdateInteractive
class BundlerCommandsTest < Minitest::Test
def setup
Gem.stubs(:bin_path).with("bundler", "bundle", Bundler::VERSION).returns("/exe/bundle")
end

def test_read_updated_lockfile_runs_bundle_lock_and_captures_output
expect_backticks("bundle lock --print --update", captures: "bundler output")
expect_backticks("/exe/bundle lock --print --update", captures: "bundler output")
result = BundlerCommands.read_updated_lockfile

assert_equal "bundler output", result
end

def test_read_updated_lockfile_runs_bundle_lock_with_specified_gems_conservatively
expect_backticks("bundle lock --print --conservative --update actionpack railties", captures: "bundler output")
expect_backticks(
"/exe/bundle lock --print --conservative --update actionpack railties",
captures: "bundler output"
)
result = BundlerCommands.read_updated_lockfile("actionpack", "railties")

assert_equal "bundler output", result
end

def test_read_updated_lockfile_raises_if_bundler_fails_to_run
expect_backticks("bundle lock --print --update", success: false)
expect_backticks("/exe/bundle lock --print --update", success: false)

error = assert_raises(RuntimeError) { BundlerCommands.read_updated_lockfile }
assert_match(/bundle lock command failed/i, error.message)
Expand Down

0 comments on commit 40dd0a3

Please sign in to comment.