diff --git a/CHANGELOG.md b/CHANGELOG.md index 93e893c..bdf289f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 1.5.5 + +Fix the PATH built by `asdf:map_bins`. SSHKit exports the environment inside +double quotes (`PATH="..."`), where a shell never expands a tilde, so the +default `asdf_path` of `~/.asdf` produced two dead entries and left the asdf +shims unreachable. The PATH now carries `$HOME`; an absolute `asdf_path` is +still used as is. + ## 1.5.3 Fix ASDF update when git data is not up-to-date. diff --git a/lib/capistrano/asdf/version.rb b/lib/capistrano/asdf/version.rb index 3f6b96e..4d71e3d 100644 --- a/lib/capistrano/asdf/version.rb +++ b/lib/capistrano/asdf/version.rb @@ -2,6 +2,6 @@ module Capistrano module Asdf - VERSION = "1.5.4" + VERSION = "1.5.5" end end diff --git a/lib/capistrano/tasks/asdf.rake b/lib/capistrano/tasks/asdf.rake index 20355c6..2716ecc 100644 --- a/lib/capistrano/tasks/asdf.rake +++ b/lib/capistrano/tasks/asdf.rake @@ -88,7 +88,10 @@ namespace :asdf do end task :map_bins do - path = "#{fetch(:asdf_path)}/shims:#{fetch(:asdf_path)}/bin:" + (SSHKit.config.default_env[:path] || "$PATH") + # SSHKit exports the environment inside double quotes (PATH="..."), where a + # shell leaves a tilde alone, so the PATH has to carry $HOME instead. + asdf_home = fetch(:asdf_path).sub(%r{\A~(?=/|\z)}, "$HOME") + path = "#{asdf_home}/shims:#{asdf_home}/bin:" + (SSHKit.config.default_env[:path] || "$PATH") SSHKit.config.default_env[:path] = path asdf_prefix = fetch(:asdf_prefix, -> { "#{fetch(:asdf_path)}/bin/asdf exec" }) diff --git a/test/capistrano/test_map_bins.rb b/test/capistrano/test_map_bins.rb new file mode 100644 index 0000000..3948146 --- /dev/null +++ b/test/capistrano/test_map_bins.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require "test_helper" + +require "capistrano/all" +require "sshkit" + +# A Capfile mixes the DSL into the top-level object; the rake file under test +# reaches for `Capistrano::DSL.stages` as it loads, which relies on it. +include Capistrano::DSL # standard:disable Style/MixinUsage + +module Capistrano + # `asdf:map_bins` prepends the asdf shims to the PATH that SSHKit exports in + # front of every remote command. SSHKit writes that environment inside double + # quotes (`PATH="..."`), and a shell does not expand a tilde there, so the + # PATH must be built from `$HOME` rather than from `~`. + class TestMapBins < Minitest::Test + include Capistrano::DSL + + def setup + # `capistrano/all` turns Rake's trace on; keep it out of the test output. + Rake.application.options.trace = false + Rake::Task.clear + Capistrano::Configuration.reset! + SSHKit.config.default_env = {} + # The rake file hooks itself onto this task as it loads. + Rake::Task.define_task("deploy:updating") + load File.expand_path("../../lib/capistrano/tasks/asdf.rake", __dir__) + Rake::Task["load:defaults"].invoke + # Keep the task away from its jemalloc branch, which opens an SSH connection. + set :asdf_tools, %w[nodejs] + end + + def teardown + SSHKit.config.default_env = {} + end + + def test_the_exported_path_holds_no_tilde + Rake::Task["asdf:map_bins"].invoke + + assert_equal "$HOME/.asdf/shims:$HOME/.asdf/bin:$PATH", exported_path + end + + def test_it_keeps_a_path_already_set_by_the_application + SSHKit.config.default_env = {path: "/opt/custom/bin:$PATH"} + + Rake::Task["asdf:map_bins"].invoke + + assert_equal "$HOME/.asdf/shims:$HOME/.asdf/bin:/opt/custom/bin:$PATH", exported_path + end + + def test_an_absolute_asdf_path_is_left_alone + set :asdf_path, "/opt/asdf" + + Rake::Task["asdf:map_bins"].invoke + + assert_equal "/opt/asdf/shims:/opt/asdf/bin:$PATH", exported_path + end + + # What the shell actually receives, tilde expansion included. + def exported_path + command = SSHKit::Command.new(:node, "--version").to_command + + command[/PATH="([^"]*)"/, 1] + end + end +end