diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c2e291b..f95b54d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,3 +33,19 @@ jobs: - name: Run tests run: bin/test + + user-journey: + strategy: + fail-fast: false + matrix: + plat: ["ubuntu", "windows", "macos"] + runs-on: ${{matrix.plat}}-latest + steps: + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.1" + bundler-cache: true + bundler: latest + - run: test/integration/user_journey_test.sh + shell: bash diff --git a/exe/tailwindcss b/exe/tailwindcss index 4e957483..ce4a4437 100755 --- a/exe/tailwindcss +++ b/exe/tailwindcss @@ -1,46 +1,19 @@ #! /usr/bin/env ruby # because rubygems shims assume a gem's executables are Ruby -require "shellwords" -require "tailwindcss/upstream" - -supported_platforms = Tailwindcss::Upstream::NATIVE_PLATFORMS.keys -platform = [:cpu, :os].map { |m| Gem::Platform.local.send(m) }.join("-") - -if supported_platforms.none? { |supported_platform| Gem::Platform.match(supported_platform) } - STDERR.puts(<<~ERRMSG) - ERROR: tailwindcss-rails does not support the #{platform} platform - Please install tailwindcss following instructions at https://tailwindcss.com/docs/installation - ERRMSG +require "tailwindcss/commands" + +begin + command = [Tailwindcss::Commands.executable, *ARGV] + puts command.inspect + if Gem.win_platform? + # use system rather than exec as exec inexplicably fails to find the executable on Windows + # see related https://github.com/rubys/sprockets-esbuild/pull/4 + system(*command, exception: true) + else + exec(*command) + end +rescue Tailwindcss::Commands::UnsupportedPlatformException, Tailwindcss::Commands::ExecutableNotFoundException => e + STDERR.puts("ERROR: " + e.message) exit 1 end - -exe_path = Dir.glob(File.join(__dir__, "*", "tailwindcss")).find do |f| - Gem::Platform.match(File.basename(File.dirname(f))) -end -if exe_path.nil? - STDERR.puts(<<~ERRMSG) - ERROR: Cannot find the tailwindcss executable for #{platform} in #{__dir__} - If you're using bundler, please make sure you're on the latest bundler version: - - gem install bundler - bundle update --bundler - - Then make sure your lock file includes this platform by running: - - bundle lock --add-platform #{platform} - bundle install - - See `bundle lock --help` output for details. - - If you're still seeing this message after taking those steps, try running - `bundle config` and ensure `force_ruby_platform` isn't set to `true`. See - https://github.com/rails/tailwindcss-rails#check-bundle_force_ruby_platform - for more details. - ERRMSG - exit 1 -end - -command = Shellwords.join([exe_path, ARGV].flatten) -puts "+ #{command}" -exec(command) diff --git a/lib/tailwindcss-rails.rb b/lib/tailwindcss-rails.rb index 198aac3d..112809c5 100644 --- a/lib/tailwindcss-rails.rb +++ b/lib/tailwindcss-rails.rb @@ -1,6 +1,7 @@ module Tailwindcss end -require "tailwindcss/upstream" -require "tailwindcss/version" -require "tailwindcss/engine" +require_relative "tailwindcss/upstream" +require_relative "tailwindcss/version" +require_relative "tailwindcss/engine" +require_relative "tailwindcss/commands" diff --git a/lib/tailwindcss/commands.rb b/lib/tailwindcss/commands.rb new file mode 100644 index 00000000..1c4b4a3e --- /dev/null +++ b/lib/tailwindcss/commands.rb @@ -0,0 +1,73 @@ +require_relative "upstream" + +module Tailwindcss + module Commands + # raised when the host platform is not supported by upstream tailwindcss's binary releases + class UnsupportedPlatformException < StandardError + end + + # raised when the tailwindcss executable could not be found where we expected it to be + class ExecutableNotFoundException < StandardError + end + + class << self + def platform + [:cpu, :os].map { |m| Gem::Platform.local.send(m) }.join("-") + end + + def executable( + exe_path: File.expand_path(File.join(__dir__, "..", "..", "exe")) + ) + if Tailwindcss::Upstream::NATIVE_PLATFORMS.keys.none? { |p| Gem::Platform.match(p) } + raise UnsupportedPlatformException, <<~MESSAGE + tailwindcss-rails does not support the #{platform} platform + Please install tailwindcss following instructions at https://tailwindcss.com/docs/installation + MESSAGE + end + + exe_path = Dir.glob(File.expand_path(File.join(exe_path, "*", "tailwindcss"))).find do |f| + Gem::Platform.match(File.basename(File.dirname(f))) + end + + if exe_path.nil? + raise ExecutableNotFoundException, <<~MESSAGE + Cannot find the tailwindcss executable for #{platform} in #{exe_path} + + If you're using bundler, please make sure you're on the latest bundler version: + + gem install bundler + bundle update --bundler + + Then make sure your lock file includes this platform by running: + + bundle lock --add-platform #{platform} + bundle install + + See `bundle lock --help` output for details. + + If you're still seeing this message after taking those steps, try running + `bundle config` and ensure `force_ruby_platform` isn't set to `true`. See + https://github.com/rails/tailwindcss-rails#check-bundle_force_ruby_platform + for more details. + MESSAGE + end + + exe_path + end + + def compile_command(**kwargs) + [ + executable(**kwargs), + "-i", Rails.root.join("app/assets/stylesheets/application.tailwind.css").to_s, + "-o", Rails.root.join("app/assets/builds/tailwind.css").to_s, + "-c", Rails.root.join("config/tailwind.config.js").to_s, + "--minify", + ] + end + + def watch_command(**kwargs) + compile_command(**kwargs) << "-w" + end + end + end +end diff --git a/lib/tasks/build.rake b/lib/tasks/build.rake index 66f8c441..eb792730 100644 --- a/lib/tasks/build.rake +++ b/lib/tasks/build.rake @@ -1,14 +1,16 @@ -TAILWIND_COMPILE_COMMAND = "#{RbConfig.ruby} #{Pathname.new(__dir__).to_s}/../../exe/tailwindcss -i '#{Rails.root.join("app/assets/stylesheets/application.tailwind.css")}' -o '#{Rails.root.join("app/assets/builds/tailwind.css")}' -c '#{Rails.root.join("config/tailwind.config.js")}' --minify" - namespace :tailwindcss do desc "Build your Tailwind CSS" task :build do - system(TAILWIND_COMPILE_COMMAND, exception: true) + command = Tailwindcss::Commands.compile_command + puts command.inspect + system(*command, exception: true) end desc "Watch and build your Tailwind CSS on file changes" task :watch do - system "#{TAILWIND_COMPILE_COMMAND} -w" + command = Tailwindcss::Commands.watch_command + puts command.inspect + system(*command) end end diff --git a/test/integration/user_journey_test.sh b/test/integration/user_journey_test.sh new file mode 100755 index 00000000..134e6354 --- /dev/null +++ b/test/integration/user_journey_test.sh @@ -0,0 +1,27 @@ +#! /usr/bin/env bash +# reproduce the documented user journey for installing and running tailwindcss-rails +# this is run in the CI pipeline, non-zero exit code indicates a failure + +set -o pipefail +set -eux + +# fetch the upstream executables +bundle exec rake download + +# create a rails app in a directory with spaces in the name (#176, #184) +rm -rf "Has A Space" +mkdir "Has A Space" +pushd "Has A Space" + +gem install rails +rails new test-app --skip-bundle +pushd test-app + +# install tailwindcss-rails +echo 'gem "tailwindcss-rails", path: "../.."' >> Gemfile +bundle install + +bin/rails tailwindcss:install + +# ensure rake tasks don't exec (#188) +bin/rails tailwindcss:build about | grep "About your application" diff --git a/test/lib/tailwindcss/commands_test.rb b/test/lib/tailwindcss/commands_test.rb new file mode 100644 index 00000000..184f82ca --- /dev/null +++ b/test/lib/tailwindcss/commands_test.rb @@ -0,0 +1,65 @@ +require "test_helper" +require "minitest/mock" + +class Tailwindcss::CommandsTest < ActiveSupport::TestCase + test ".platform is a string containing just the cpu and os (not the version)" do + expected = "#{Gem::Platform.local.cpu}-#{Gem::Platform.local.os}" + assert_equal(expected, Tailwindcss::Commands.platform) + end + + def mock_exe_directory(platform) + Dir.mktmpdir do |dir| + FileUtils.mkdir(File.join(dir, platform)) + path = File.join(dir, platform, "tailwindcss") + FileUtils.touch(path) + Gem::Platform.stub(:match, true) do + yield(dir, path) + end + end + end + + test ".executable returns the absolute path to the binary" do + mock_exe_directory("sparc-solaris2.8") do |dir, executable| + expected = File.expand_path(File.join(dir, "sparc-solaris2.8", "tailwindcss")) + assert_equal(expected, executable, "assert on setup") + assert_equal(expected, Tailwindcss::Commands.executable(exe_path: dir)) + end + end + + test ".executable raises UnsupportedPlatformException when we're not on a supported platform" do + Gem::Platform.stub(:match, false) do # nothing is supported + assert_raises(Tailwindcss::Commands::UnsupportedPlatformException) do + Tailwindcss::Commands.executable + end + end + end + + test ".executable raises ExecutableNotFoundException when we can't find the executable we expect" do + Dir.mktmpdir do |dir| # empty directory + assert_raises(Tailwindcss::Commands::ExecutableNotFoundException) do + Tailwindcss::Commands.executable(exe_path: dir) + end + end + end + + test ".compile_command" do + mock_exe_directory("sparc-solaris2.8") do |dir, executable| + Rails.stub(:root, File) do # Rails.root won't work in this test suite + actual = Tailwindcss::Commands.compile_command(exe_path: dir) + assert_kind_of(Array, actual) + assert_equal(executable, actual.first) + end + end + end + + test ".watch_command" do + mock_exe_directory("sparc-solaris2.8") do |dir, executable| + Rails.stub(:root, File) do # Rails.root won't work in this test suite + actual = Tailwindcss::Commands.watch_command(exe_path: dir) + assert_kind_of(Array, actual) + assert_equal(executable, actual.first) + assert_includes(actual, "-w") + end + end + end +end