Skip to content

Commit e5c406c

Browse files
committed
feat: fall back to locally-installed tailwindcss executables
Closes #224
1 parent a4b2624 commit e5c406c

File tree

2 files changed

+93
-13
lines changed

2 files changed

+93
-13
lines changed

lib/tailwindcss/commands.rb

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,23 @@ def platform
1515
[:cpu, :os].map { |m| Gem::Platform.local.send(m) }.join("-")
1616
end
1717

18-
def executable(
19-
exe_path: File.expand_path(File.join(__dir__, "..", "..", "exe"))
20-
)
21-
if Tailwindcss::Upstream::NATIVE_PLATFORMS.keys.none? { |p| Gem::Platform.match(Gem::Platform.new(p)) }
22-
raise UnsupportedPlatformException, <<~MESSAGE
23-
tailwindcss-rails does not support the #{platform} platform
24-
Please install tailwindcss following instructions at https://tailwindcss.com/docs/installation
25-
MESSAGE
26-
end
27-
18+
def executable(exe_path: File.expand_path(File.join(__dir__, "..", "..", "exe")))
2819
exe_path = Dir.glob(File.expand_path(File.join(exe_path, "*", "tailwindcss"))).find do |f|
2920
Gem::Platform.match(Gem::Platform.new(File.basename(File.dirname(f))))
3021
end
3122

3223
if exe_path.nil?
24+
exe_path = find_executable_in_path("tailwindcss")
25+
end
26+
27+
if exe_path.nil?
28+
if Tailwindcss::Upstream::NATIVE_PLATFORMS.keys.none? { |p| Gem::Platform.match(Gem::Platform.new(p)) }
29+
raise UnsupportedPlatformException, <<~MESSAGE
30+
tailwindcss-rails does not support the #{platform} platform
31+
Please install tailwindcss following instructions at https://tailwindcss.com/docs/installation
32+
MESSAGE
33+
end
34+
3335
raise ExecutableNotFoundException, <<~MESSAGE
3436
Cannot find the tailwindcss executable for #{platform} in #{exe_path}
3537
@@ -72,6 +74,36 @@ def watch_command(poll: false, **kwargs)
7274
command << "-p" if poll
7375
end
7476
end
77+
78+
private
79+
80+
def find_executable_in_path(bin)
81+
# based on MakeMakefile.find_executable0
82+
executable_file = proc do |name|
83+
begin
84+
stat = File.stat(name)
85+
rescue SystemCallError
86+
else
87+
# look for a binary executable. ignore shim files and scripts.
88+
if stat.file? and stat.executable? and File.read(name,2) != "#!"
89+
next name
90+
end
91+
end
92+
end
93+
94+
if path ||= ENV['PATH']
95+
path = path.split(File::PATH_SEPARATOR)
96+
else
97+
path = %w[/usr/local/bin /usr/ucb /usr/bin /bin]
98+
end
99+
100+
file = nil
101+
path.each do |dir|
102+
dir.sub!(/\A"(.*)"\z/m, '\1') if /mswin|mingw/ =~ RUBY_PLATFORM
103+
return file if executable_file.call(file = File.join(dir, bin))
104+
end
105+
nil
106+
end
75107
end
76108
end
77109
end

test/lib/tailwindcss/commands_test.rb

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22
require "minitest/mock"
33

44
class Tailwindcss::CommandsTest < ActiveSupport::TestCase
5-
test ".platform is a string containing just the cpu and os (not the version)" do
6-
expected = "#{Gem::Platform.local.cpu}-#{Gem::Platform.local.os}"
7-
assert_equal(expected, Tailwindcss::Commands.platform)
5+
setup do
6+
@orig_path = ENV["PATH"]
7+
ENV["PATH"] = ""
8+
end
9+
10+
teardown do
11+
ENV["PATH"] = @orig_path
812
end
913

1014
def mock_exe_directory(platform)
@@ -18,6 +22,25 @@ def mock_exe_directory(platform)
1822
end
1923
end
2024

25+
def mock_path_binary(shim: false)
26+
Dir.mktmpdir do |dir|
27+
orig_path = ENV["PATH"]
28+
ENV["PATH"] = [dir, ENV["PATH"]].join(File::PATH_SEPARATOR)
29+
filepath = File.join(dir, "tailwindcss")
30+
FileUtils.touch(filepath)
31+
FileUtils.chmod("ugo+x", filepath)
32+
File.write(filepath, "#!/usr/bin/env ruby") if shim
33+
yield(dir)
34+
ensure
35+
ENV["PATH"] = orig_path
36+
end
37+
end
38+
39+
test ".platform is a string containing just the cpu and os (not the version)" do
40+
expected = "#{Gem::Platform.local.cpu}-#{Gem::Platform.local.os}"
41+
assert_equal(expected, Tailwindcss::Commands.platform)
42+
end
43+
2144
test ".executable returns the absolute path to the binary" do
2245
mock_exe_directory("sparc-solaris2.8") do |dir, executable|
2346
expected = File.expand_path(File.join(dir, "sparc-solaris2.8", "tailwindcss"))
@@ -26,6 +49,31 @@ def mock_exe_directory(platform)
2649
end
2750
end
2851

52+
test "when a packaged exe is not found, .executable returns the absolute path to `tailwindcss` that is in PATH" do
53+
mock_path_binary do |installed|
54+
expected = File.join(installed, "tailwindcss")
55+
assert_equal(expected, Tailwindcss::Commands.executable(exe_path: "/blurgh"))
56+
end
57+
end
58+
59+
test "when a packaged exe is not found, .executable ignores shims found in PATH" do
60+
mock_path_binary(shim: true) do |installed|
61+
assert_raises(Tailwindcss::Commands::ExecutableNotFoundException) do
62+
Tailwindcss::Commands.executable(exe_path: "/blurgh")
63+
end
64+
end
65+
end
66+
67+
test "when a packaged exe is found, .executable ignores what's in PATH" do
68+
mock_exe_directory("sparc-solaris2.8") do |dir, executable|
69+
mock_path_binary do |installed|
70+
expected = File.expand_path(File.join(dir, "sparc-solaris2.8", "tailwindcss"))
71+
assert_equal(expected, executable, "assert on setup")
72+
assert_equal(expected, Tailwindcss::Commands.executable(exe_path: dir))
73+
end
74+
end
75+
end
76+
2977
test ".executable raises UnsupportedPlatformException when we're not on a supported platform" do
3078
Gem::Platform.stub(:match, false) do # nothing is supported
3179
assert_raises(Tailwindcss::Commands::UnsupportedPlatformException) do

0 commit comments

Comments
 (0)