From 92c59d951df15997dc5fa6c99c09d05a0bd6e9f1 Mon Sep 17 00:00:00 2001 From: Matt Brictson Date: Thu, 1 Aug 2024 10:33:41 -0700 Subject: [PATCH] Use correct Bundler version when updating gems 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. --- lib/bundle_update_interactive/bundler_commands.rb | 13 +++++++++++-- .../bundler_commands_test.rb | 14 +++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/bundle_update_interactive/bundler_commands.rb b/lib/bundle_update_interactive/bundler_commands.rb index 7f1f691..ec18297 100644 --- a/lib/bundle_update_interactive/bundler_commands.rb +++ b/lib/bundle_update_interactive/bundler_commands.rb @@ -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 diff --git a/test/bundle_update_interactive/bundler_commands_test.rb b/test/bundle_update_interactive/bundler_commands_test.rb index 9312fd8..261a689 100644 --- a/test/bundle_update_interactive/bundler_commands_test.rb +++ b/test/bundle_update_interactive/bundler_commands_test.rb @@ -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)