- <%%= form.submit "Sign in", class: "rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white inline-block font-medium cursor-pointer" %>
+ <%%= form.submit "Sign in", class: "w-full sm:w-auto text-center rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white inline-block font-medium cursor-pointer" %>
diff --git a/lib/generators/tailwindcss/scaffold/scaffold_generator.rb b/lib/generators/tailwindcss/scaffold/scaffold_generator.rb
index 39c2da1f..8748210d 100644
--- a/lib/generators/tailwindcss/scaffold/scaffold_generator.rb
+++ b/lib/generators/tailwindcss/scaffold/scaffold_generator.rb
@@ -1,5 +1,6 @@
require "rails/generators/erb/scaffold/scaffold_generator"
require "rails/generators/resource_helpers"
+require File.expand_path("../../test_unit/scaffold/scaffold_generator.rb", __dir__)
module Tailwindcss
module Generators
diff --git a/lib/generators/tailwindcss/scaffold/templates/_form.html.erb.tt b/lib/generators/tailwindcss/scaffold/templates/_form.html.erb.tt
index 93490a15..1821201c 100644
--- a/lib/generators/tailwindcss/scaffold/templates/_form.html.erb.tt
+++ b/lib/generators/tailwindcss/scaffold/templates/_form.html.erb.tt
@@ -12,32 +12,32 @@
<%% end %>
<% attributes.each do |attribute| -%>
-
+
">
<% if attribute.password_digest? -%>
<%%= form.label :password %>
- <%%= form.password_field :password, class: ["block shadow rounded-md border outline-none px-3 py-2 mt-2 w-full", {"border-gray-400 focus:outline-blue-600": <%= model_resource_name %>.errors[:password].none?, "border-red-400 focus:outline-red-600": <%= model_resource_name %>.errors[:password].any?}] %>
+ <%%= form.password_field :password, class: ["block shadow-sm rounded-md border px-3 py-2 mt-2 w-full", {"border-gray-400 focus:outline-blue-600": <%= model_resource_name %>.errors[:password].none?, "border-red-400 focus:outline-red-600": <%= model_resource_name %>.errors[:password].any?}] %>
<%%= form.label :password_confirmation %>
- <%%= form.password_field :password_confirmation, class: ["block shadow rounded-md border outline-none px-3 py-2 mt-2 w-full", {"border-gray-400 focus:outline-blue-600": <%= model_resource_name %>.errors[:password_confirmation].none?, "border-red-400 focus:outline-red-600": <%= model_resource_name %>.errors[:password_confirmation].any?}] %>
+ <%%= form.password_field :password_confirmation, class: ["block shadow-sm rounded-md border px-3 py-2 mt-2 w-full", {"border-gray-400 focus:outline-blue-600": <%= model_resource_name %>.errors[:password_confirmation].none?, "border-red-400 focus:outline-red-600": <%= model_resource_name %>.errors[:password_confirmation].any?}] %>
<% elsif attribute.attachments? -%>
<%%= form.label :<%= attribute.column_name %> %>
- <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, multiple: true, class: ["block shadow rounded-md border outline-none px-3 py-2 mt-2 w-full", {"border-gray-400 focus:outline-blue-600": <%= model_resource_name %>.errors[:password].none?, "border-red-400 focus:outline-red-600": <%= model_resource_name %>.errors[:password].any?}] %>
+ <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, multiple: true, class: ["block shadow-sm rounded-md border px-3 py-2 mt-2 w-full", {"border-gray-400 focus:outline-blue-600": <%= model_resource_name %>.errors[:<%= attribute.column_name %>].none?, "border-red-400 focus:outline-red-600": <%= model_resource_name %>.errors[:<%= attribute.column_name %>].any?}] %>
<% else -%>
<%%= form.label :<%= attribute.column_name %> %>
<% if attribute.field_type == :textarea || attribute.field_type == :text_area -%>
- <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, rows: 4, class: ["block shadow rounded-md border outline-none px-3 py-2 mt-2 w-full", {"border-gray-400 focus:outline-blue-600": <%= model_resource_name %>.errors[:<%= attribute.column_name %>].none?, "border-red-400 focus:outline-red-600": <%= model_resource_name %>.errors[:<%= attribute.column_name %>].any?}] %>
+ <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, rows: 4, class: ["block shadow-sm rounded-md border px-3 py-2 mt-2 w-full", {"border-gray-400 focus:outline-blue-600": <%= model_resource_name %>.errors[:<%= attribute.column_name %>].none?, "border-red-400 focus:outline-red-600": <%= model_resource_name %>.errors[:<%= attribute.column_name %>].any?}] %>
<% elsif attribute.field_type == :checkbox || attribute.field_type == :check_box -%>
- <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, class: ["block shadow rounded-md border outline-none mt-2 h-5 w-5", {"border-gray-400 focus:outline-blue-600": <%= model_resource_name %>.errors[:<%= attribute.column_name %>].none?, "border-red-400 focus:outline-red-600": <%= model_resource_name %>.errors[:<%= attribute.column_name %>].any?}] %>
+ <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, class: ["block shadow-sm rounded-md border order-first h-5 w-5", {"border-gray-400 focus:outline-blue-600": <%= model_resource_name %>.errors[:<%= attribute.column_name %>].none?, "border-red-400 focus:outline-red-600": <%= model_resource_name %>.errors[:<%= attribute.column_name %>].any?}] %>
<% else -%>
- <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, class: ["block shadow rounded-md border outline-none px-3 py-2 mt-2 w-full", {"border-gray-400 focus:outline-blue-600": <%= model_resource_name %>.errors[:<%= attribute.column_name %>].none?, "border-red-400 focus:outline-red-600": <%= model_resource_name %>.errors[:<%= attribute.column_name %>].any?}] %>
+ <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, class: ["block shadow-sm rounded-md border px-3 py-2 mt-2 w-full", {"border-gray-400 focus:outline-blue-600": <%= model_resource_name %>.errors[:<%= attribute.column_name %>].none?, "border-red-400 focus:outline-red-600": <%= model_resource_name %>.errors[:<%= attribute.column_name %>].any?}] %>
<% end -%>
<% end -%>
<% end -%>
- <%%= form.submit class: "rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white inline-block font-medium cursor-pointer" %>
+ <%%= form.submit class: "w-full sm:w-auto rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white inline-block font-medium cursor-pointer" %>
<%% end %>
diff --git a/lib/generators/tailwindcss/scaffold/templates/edit.html.erb.tt b/lib/generators/tailwindcss/scaffold/templates/edit.html.erb.tt
index 74edd3de..08d92d3a 100644
--- a/lib/generators/tailwindcss/scaffold/templates/edit.html.erb.tt
+++ b/lib/generators/tailwindcss/scaffold/templates/edit.html.erb.tt
@@ -5,6 +5,6 @@
<%%= render "form", <%= singular_table_name %>: @<%= singular_table_name %> %>
- <%%= link_to "Show this <%= human_name.downcase %>", <%= model_resource_name(prefix: "@") %>, class: "ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
- <%%= link_to "Back to <%= human_name.pluralize.downcase %>", <%= index_helper(type: :path) %>, class: "ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
+ <%%= link_to "Show this <%= human_name.downcase %>", <%= model_resource_name(prefix: "@") %>, class: "w-full sm:w-auto text-center mt-2 sm:mt-0 sm:ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
+ <%%= link_to "Back to <%= human_name.pluralize.downcase %>", <%= index_helper(type: :path) %>, class: "w-full sm:w-auto text-center mt-2 sm:mt-0 sm:ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
diff --git a/lib/generators/tailwindcss/scaffold/templates/index.html.erb.tt b/lib/generators/tailwindcss/scaffold/templates/index.html.erb.tt
index 3d337f95..12a0db16 100644
--- a/lib/generators/tailwindcss/scaffold/templates/index.html.erb.tt
+++ b/lib/generators/tailwindcss/scaffold/templates/index.html.erb.tt
@@ -10,13 +10,17 @@
<%%= link_to "New <%= human_name.downcase %>", new_<%= singular_route_name %>_path, class: "rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white block font-medium" %>
+
<%% if @<%= plural_table_name %>.any? %>
<%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %>
- <%%= render <%= singular_table_name %> %>
-
- <%%= link_to "Show this <%= human_name.downcase %>", <%= model_resource_name(singular_table_name) %>, class: "ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
-
+
+ <%%= render <%= singular_table_name %> %>
+
+ <%%= link_to "Show", <%= model_resource_name(singular_table_name) %>, class: "w-full sm:w-auto text-center rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
+ <%%= link_to "Edit", <%= edit_helper(singular_table_name, type: :path) %>, class: "w-full sm:w-auto text-center rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
+ <%%= button_to "Destroy", <%= model_resource_name %>, method: :delete, class: "w-full sm:w-auto rounded-md px-3.5 py-2.5 text-white bg-red-600 hover:bg-red-500 font-medium cursor-pointer", data: { turbo_confirm: "Are you sure?" } %>
+
+
<%% end %>
<%% else %>
No <%= human_name.downcase.pluralize %> found.
diff --git a/lib/generators/tailwindcss/scaffold/templates/new.html.erb.tt b/lib/generators/tailwindcss/scaffold/templates/new.html.erb.tt
index 0a10d967..32ad9b83 100644
--- a/lib/generators/tailwindcss/scaffold/templates/new.html.erb.tt
+++ b/lib/generators/tailwindcss/scaffold/templates/new.html.erb.tt
@@ -5,5 +5,5 @@
<%%= render "form", <%= singular_table_name %>: @<%= singular_table_name %> %>
- <%%= link_to "Back to <%= human_name.pluralize.downcase %>", <%= index_helper(type: :path) %>, class: "ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
+ <%%= link_to "Back to <%= human_name.pluralize.downcase %>", <%= index_helper(type: :path) %>, class: "w-full sm:w-auto text-center mt-2 sm:mt-0 sm:ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
diff --git a/lib/generators/tailwindcss/scaffold/templates/partial.html.erb.tt b/lib/generators/tailwindcss/scaffold/templates/partial.html.erb.tt
index 88dc3eda..433b7a33 100644
--- a/lib/generators/tailwindcss/scaffold/templates/partial.html.erb.tt
+++ b/lib/generators/tailwindcss/scaffold/templates/partial.html.erb.tt
@@ -1,16 +1,18 @@
-
+
<% attributes.reject(&:password_digest?).each do |attribute| -%>
-
+
<%= attribute.human_name %>:
<% if attribute.attachment? -%>
- <%%= link_to <%= singular_name %>.<%= attribute.column_name %>.filename, <%= singular_name %>.<%= attribute.column_name %> if <%= singular_name %>.<%= attribute.column_name %>.attached? %>
+ <%%= link_to <%= singular_name %>.<%= attribute.column_name %>.filename, <%= singular_name %>.<%= attribute.column_name %>, class: "text-gray-700 underline hover:no-underline" if <%= singular_name %>.<%= attribute.column_name %>.attached? %>
<% elsif attribute.attachments? -%>
<%% <%= singular_name %>.<%= attribute.column_name %>.each do |<%= attribute.singular_name %>| %>
-
<%%= link_to <%= attribute.singular_name %>.filename, <%= attribute.singular_name %> %>
+
<%%= link_to <%= attribute.singular_name %>.filename, <%= attribute.singular_name %>, class: "text-gray-700 underline hover:no-underline" %>
<%% end %>
+<% elsif attribute.type == :boolean -%>
+ <%%= <%= singular_name %>.<%= attribute.column_name %>? ? "Yes" : "No" %>
<% else -%>
<%%= <%= singular_name %>.<%= attribute.column_name %> %>
<% end -%>
-
+
<% end -%>
diff --git a/lib/generators/tailwindcss/scaffold/templates/show.html.erb.tt b/lib/generators/tailwindcss/scaffold/templates/show.html.erb.tt
index 3a49e9a0..2b6687d2 100644
--- a/lib/generators/tailwindcss/scaffold/templates/show.html.erb.tt
+++ b/lib/generators/tailwindcss/scaffold/templates/show.html.erb.tt
@@ -9,9 +9,7 @@
<%%= render @<%= singular_table_name %> %>
- <%%= link_to "Edit this <%= human_name.downcase %>", <%= edit_helper(type: :path) %>, class: "mt-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
- <%%= link_to "Back to <%= human_name.pluralize.downcase %>", <%= index_helper %>_path, class: "ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
-
- <%%= button_to "Destroy this <%= human_name.downcase %>", <%= model_resource_name(prefix: "@") %>, method: :delete, class: "mt-2 rounded-md px-3.5 py-2.5 text-white bg-red-600 hover:bg-red-500 font-medium" %>
-
+ <%%= link_to "Edit this <%= human_name.downcase %>", <%= edit_helper(type: :path) %>, class: "w-full sm:w-auto text-center rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
+ <%%= link_to "Back to <%= human_name.pluralize.downcase %>", <%= index_helper %>_path, class: "w-full sm:w-auto text-center mt-2 sm:mt-0 sm:ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
+ <%%= button_to "Destroy this <%= human_name.downcase %>", <%= model_resource_name(prefix: "@") %>, method: :delete, form_class: "sm:inline-block mt-2 sm:mt-0 sm:ml-2", class: "w-full rounded-md px-3.5 py-2.5 text-white bg-red-600 hover:bg-red-500 font-medium cursor-pointer", data: { turbo_confirm: "Are you sure?" } %>
diff --git a/lib/generators/test_unit/scaffold/scaffold_generator.rb b/lib/generators/test_unit/scaffold/scaffold_generator.rb
new file mode 100644
index 00000000..ff809b32
--- /dev/null
+++ b/lib/generators/test_unit/scaffold/scaffold_generator.rb
@@ -0,0 +1,21 @@
+require "rails/generators/test_unit/scaffold/scaffold_generator"
+
+module TestUnit # :nodoc:
+ module Generators # :nodoc:
+ class ScaffoldGenerator < Base # :nodoc:
+ def fix_system_test
+ if turbo_defined?
+ gsub_file File.join("test/system", class_path, "#{file_name.pluralize}_test.rb"),
+ /(click_on.*Destroy this.*)$/,
+ "accept_confirm { \\1 }"
+ end
+ end
+
+ private
+
+ def turbo_defined?
+ defined?(Turbo)
+ end
+ end
+ end
+end
diff --git a/lib/install/application.css b/lib/install/application.css
new file mode 100644
index 00000000..f1d8c73c
--- /dev/null
+++ b/lib/install/application.css
@@ -0,0 +1 @@
+@import "tailwindcss";
diff --git a/lib/install/application.tailwind.css b/lib/install/application.tailwind.css
deleted file mode 100644
index 8666d2f3..00000000
--- a/lib/install/application.tailwind.css
+++ /dev/null
@@ -1,13 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-/*
-
-@layer components {
- .btn-primary {
- @apply py-2 px-4 bg-blue-200;
- }
-}
-
-*/
diff --git a/lib/install/tailwindcss.rb b/lib/install/install_tailwindcss.rb
similarity index 63%
rename from lib/install/tailwindcss.rb
rename to lib/install/install_tailwindcss.rb
index 2c2ba50c..3d471d07 100644
--- a/lib/install/tailwindcss.rb
+++ b/lib/install/install_tailwindcss.rb
@@ -1,19 +1,23 @@
APPLICATION_LAYOUT_PATH = Rails.root.join("app/views/layouts/application.html.erb")
CENTERING_CONTAINER_INSERTION_POINT = /^\s*<%= yield %>/.freeze
+TAILWIND_ASSET_PATH = Rails.root.join("app/assets/tailwind/application.css")
if APPLICATION_LAYOUT_PATH.exist?
- say "Add Tailwindcss include tags and container element in application layout"
- insert_into_file APPLICATION_LAYOUT_PATH.to_s, <<~ERB.indent(4), before: /^\s*<%= stylesheet_link_tag/
- <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
- ERB
+ unless File.read(APPLICATION_LAYOUT_PATH).match?(/stylesheet_link_tag :app/)
+ say "Add Tailwindcss include tags in application layout"
+ insert_into_file APPLICATION_LAYOUT_PATH.to_s, <<~ERB.indent(4), before: /^\s*<%= stylesheet_link_tag/
+ <%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %>
+ ERB
+ end
+ say "Add Tailwindcss container element in application layout"
if File.open(APPLICATION_LAYOUT_PATH).read =~ /\n\s*<%= yield %>\n\s*<\/body>/
insert_into_file APPLICATION_LAYOUT_PATH.to_s, %(
\n ), before: CENTERING_CONTAINER_INSERTION_POINT
insert_into_file APPLICATION_LAYOUT_PATH.to_s, %(\n ), after: CENTERING_CONTAINER_INSERTION_POINT
end
else
say "Default application.html.erb is missing!", :red
- say %( Add <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %> within the tag in your custom layout.)
+ say %( Add <%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %> within the tag in your custom layout.)
end
say "Build into app/assets/builds"
@@ -28,14 +32,9 @@
append_to_file(".gitignore", %(\n/app/assets/builds/*\n!/app/assets/builds/.keep\n))
end
-unless Rails.root.join("config/tailwind.config.js").exist?
- say "Add default config/tailwindcss.config.js"
- copy_file "#{__dir__}/tailwind.config.js", "config/tailwind.config.js"
-end
-
-unless Rails.root.join("app/assets/stylesheets/application.tailwind.css").exist?
- say "Add default app/assets/stylesheets/application.tailwind.css"
- copy_file "#{__dir__}/application.tailwind.css", "app/assets/stylesheets/application.tailwind.css"
+unless TAILWIND_ASSET_PATH.exist?
+ say "Add default #{TAILWIND_ASSET_PATH}"
+ copy_file "#{__dir__}/application.css", TAILWIND_ASSET_PATH
end
if Rails.root.join("Procfile.dev").exist?
diff --git a/lib/install/tailwind.config.js b/lib/install/tailwind.config.js
deleted file mode 100644
index c3deef14..00000000
--- a/lib/install/tailwind.config.js
+++ /dev/null
@@ -1,22 +0,0 @@
-const defaultTheme = require('tailwindcss/defaultTheme')
-
-module.exports = {
- content: [
- './public/*.html',
- './app/helpers/**/*.rb',
- './app/javascript/**/*.js',
- './app/views/**/*.{erb,haml,html,slim}'
- ],
- theme: {
- extend: {
- fontFamily: {
- sans: ['Inter var', ...defaultTheme.fontFamily.sans],
- },
- },
- },
- plugins: [
- // require('@tailwindcss/forms'),
- // require('@tailwindcss/typography'),
- // require('@tailwindcss/container-queries'),
- ]
-}
diff --git a/lib/install/upgrade_tailwindcss.rb b/lib/install/upgrade_tailwindcss.rb
new file mode 100644
index 00000000..d6ecadab
--- /dev/null
+++ b/lib/install/upgrade_tailwindcss.rb
@@ -0,0 +1,69 @@
+TAILWIND_CONFIG_PATH = Rails.root.join("config/tailwind.config.js")
+APPLICATION_LAYOUT_PATH = Rails.root.join("app/views/layouts/application.html.erb")
+POSTCSS_CONFIG_PATH = Rails.root.join("config/postcss.config.js")
+OLD_TAILWIND_ASSET_PATH = Rails.root.join("app/assets/stylesheets/application.tailwind.css")
+TAILWIND_ASSET_PATH = Rails.root.join("app/assets/tailwind/application.css")
+
+unless TAILWIND_CONFIG_PATH.exist?
+ say "Default tailwind.config.js is missing!", :red
+ abort
+end
+
+if File.read(TAILWIND_CONFIG_PATH).match?(/defaultTheme/)
+ say "Removing references to 'defaultTheme' from #{TAILWIND_CONFIG_PATH}"
+ gsub_file TAILWIND_CONFIG_PATH.to_s, /^(.*defaultTheme)/, "// \\1"
+end
+
+if POSTCSS_CONFIG_PATH.exist?
+ say "Moving PostCSS configuration to application root directory"
+ copy_file POSTCSS_CONFIG_PATH, Rails.root.join("postcss.config.js")
+ remove_file POSTCSS_CONFIG_PATH
+end
+
+if APPLICATION_LAYOUT_PATH.exist?
+ if File.read(APPLICATION_LAYOUT_PATH).match?(/"inter-font"/)
+ say "Strip Inter font CSS from application layout"
+ gsub_file APPLICATION_LAYOUT_PATH.to_s, %r{, "inter-font"}, ""
+ else
+ say "Inter font CSS not detected.", :green
+ end
+
+ if File.read(APPLICATION_LAYOUT_PATH).match?(/stylesheet_link_tag :app/) &&
+ File.read(APPLICATION_LAYOUT_PATH).match?(/stylesheet_link_tag "tailwind"/)
+ say "Remove unnecessary stylesheet_link_tag from application layout"
+ gsub_file APPLICATION_LAYOUT_PATH.to_s, %r{^\s*<%= stylesheet_link_tag "tailwind".*%>$}, ""
+ end
+else
+ say "Default application.html.erb is missing!", :red
+ say %( Please check your layouts and remove any "inter-font" stylesheet links.)
+end
+
+if OLD_TAILWIND_ASSET_PATH.exist?
+ say "Moving #{OLD_TAILWIND_ASSET_PATH} to #{TAILWIND_ASSET_PATH}"
+ copy_file OLD_TAILWIND_ASSET_PATH, TAILWIND_ASSET_PATH
+ remove_file OLD_TAILWIND_ASSET_PATH
+end
+
+if system("npx --version")
+ # We're pinning to v4.1.4 because v4.1.5 of the upgrade tool introduces a dependency version check
+ # on tailwind and I haven't been able to figure out how to get that to work reliably and I am
+ # extremely frustrated with the whole thing. See #544
+ #
+ # At some point we will probably need to unpin this at which point I am sincerely hoping that
+ # someone else will do it.
+ say "Running the upstream Tailwind CSS upgrader"
+ command = Shellwords.join(["npx", "@tailwindcss/upgrade@4.1.4", "--force", "--config", TAILWIND_CONFIG_PATH.to_s])
+ success = run(command, abort_on_failure: false)
+ unless success
+ say "The upgrade tool failed!", :red
+ say %( You probably need to update your configuration. Please read the error messages,)
+ say %( and check the Tailwind CSS upgrade guide at https://tailwindcss.com/docs/upgrade-guide.)
+ abort
+ end
+else
+ say "Could not run the Tailwind upgrade tool. Please see https://tailwindcss.com/docs/upgrade-guide for manual instructions.", :red
+ abort
+end
+
+say "Compile initial Tailwind build"
+run "rails tailwindcss:build"
diff --git a/lib/puma/plugin/tailwindcss.rb b/lib/puma/plugin/tailwindcss.rb
index e614d62a..4624d7e8 100644
--- a/lib/puma/plugin/tailwindcss.rb
+++ b/lib/puma/plugin/tailwindcss.rb
@@ -1,4 +1,5 @@
require "puma/plugin"
+require "tailwindcss/commands"
Puma::Plugin.create do
attr_reader :puma_pid, :tailwind_pid, :log_writer
@@ -11,8 +12,11 @@ def start(launcher)
# Using IO.popen(command, 'r+') will avoid watch_command read from $stdin.
# If we use system(*command) instead, IRB and Debug can't read from $stdin
# correctly bacause some keystrokes will be taken by watch_command.
- IO.popen(Tailwindcss::Commands.watch_command, 'r+') do |io|
- IO.copy_stream(io, $stdout)
+ begin
+ IO.popen(Tailwindcss::Commands.watch_command, 'r+') do |io|
+ IO.copy_stream(io, $stdout)
+ end
+ rescue Interrupt
end
end
diff --git a/lib/tailwindcss-rails.rb b/lib/tailwindcss-rails.rb
index 2b86ff1c..bdfb592d 100644
--- a/lib/tailwindcss-rails.rb
+++ b/lib/tailwindcss-rails.rb
@@ -2,5 +2,6 @@ module Tailwindcss
end
require_relative "tailwindcss/version"
+require_relative "tailwindcss/engines"
require_relative "tailwindcss/engine"
require_relative "tailwindcss/commands"
diff --git a/lib/tailwindcss/commands.rb b/lib/tailwindcss/commands.rb
index 26c5178a..99ad30e0 100644
--- a/lib/tailwindcss/commands.rb
+++ b/lib/tailwindcss/commands.rb
@@ -4,16 +4,18 @@ module Tailwindcss
module Commands
class << self
def compile_command(debug: false, **kwargs)
+ debug = ENV["TAILWINDCSS_DEBUG"].present? if ENV.key?("TAILWINDCSS_DEBUG")
+ rails_root = defined?(Rails) ? Rails.root : Pathname.new(Dir.pwd)
+
command = [
Tailwindcss::Ruby.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,
+ "-i", rails_root.join("app/assets/tailwind/application.css").to_s,
+ "-o", rails_root.join("app/assets/builds/tailwind.css").to_s,
]
command << "--minify" unless (debug || rails_css_compressor?)
- postcss_path = Rails.root.join("config/postcss.config.js")
+ postcss_path = rails_root.join("postcss.config.js")
command += ["--postcss", postcss_path.to_s] if File.exist?(postcss_path)
command
@@ -27,6 +29,12 @@ def watch_command(always: false, poll: false, **kwargs)
end
end
+ def command_env(verbose:)
+ {}.tap do |env|
+ env["DEBUG"] = "1" if verbose
+ end
+ end
+
def rails_css_compressor?
defined?(Rails) && Rails&.application&.config&.assets&.css_compressor.present?
end
diff --git a/lib/tailwindcss/engine.rb b/lib/tailwindcss/engine.rb
index 4b9b9fdc..7b88c5f1 100644
--- a/lib/tailwindcss/engine.rb
+++ b/lib/tailwindcss/engine.rb
@@ -2,14 +2,16 @@
module Tailwindcss
class Engine < ::Rails::Engine
- initializer "tailwindcss.assets" do
- Rails.application.config.assets.precompile += %w( inter-font.css )
- end
-
initializer "tailwindcss.disable_generator_stylesheets" do
Rails.application.config.generators.stylesheets = false
end
+ initializer "tailwindcss.exclude_asset_path", before: "propshaft.append_assets_path" do
+ if Rails.application.config.assets.excluded_paths # the app may not be using Propshaft
+ Rails.application.config.assets.excluded_paths << Rails.root.join("app/assets/tailwind")
+ end
+ end
+
config.app_generators do |g|
g.template_engine :tailwindcss
end
diff --git a/lib/tailwindcss/engines.rb b/lib/tailwindcss/engines.rb
new file mode 100644
index 00000000..d1d6bf63
--- /dev/null
+++ b/lib/tailwindcss/engines.rb
@@ -0,0 +1,23 @@
+module Tailwindcss
+ module Engines
+ class << self
+ def bundle
+ FileUtils.mkdir_p(Rails.root.join("app/assets/builds/tailwind"))
+ Rails::Engine.subclasses.select do |engine|
+ engine.root.join("app/assets/tailwind/#{engine.engine_name}/engine.css").exist?
+ end.each do |engine|
+ file_path = Rails.root.join("app/assets/builds/tailwind/#{engine.engine_name}.css")
+ FileUtils.rm(file_path) if File.exist?(file_path)
+ template = <<~TEMPLATE
+ /* DO NOT MODIFY THIS FILE, it was auto-generated by tailwindcss-rails */
+
+ @import "#{engine.root.join("app/assets/tailwind/#{engine.engine_name}/engine.css")}";
+ TEMPLATE
+ File.open(file_path, 'w') do |file|
+ file.puts template
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/tailwindcss/version.rb b/lib/tailwindcss/version.rb
index 9e5b6e3d..d1d65d16 100644
--- a/lib/tailwindcss/version.rb
+++ b/lib/tailwindcss/version.rb
@@ -1,3 +1,3 @@
module Tailwindcss
- VERSION = "3.2.0"
+ VERSION = "4.3.0.rc2"
end
diff --git a/lib/tasks/build.rake b/lib/tasks/build.rake
index 3044ff05..d176f2cd 100644
--- a/lib/tasks/build.rake
+++ b/lib/tasks/build.rake
@@ -1,23 +1,36 @@
namespace :tailwindcss do
desc "Build your Tailwind CSS"
- task build: :environment do |_, args|
+ task build: [:environment, :engines] do |_, args|
debug = args.extras.include?("debug")
+ verbose = args.extras.include?("verbose")
+
command = Tailwindcss::Commands.compile_command(debug: debug)
- puts command.inspect if args.extras.include?("verbose")
- system(*command, exception: true)
+ env = Tailwindcss::Commands.command_env(verbose: verbose)
+ puts "Running: #{Shellwords.join(command)}" if verbose
+
+ system(env, *command, exception: true)
end
desc "Watch and build your Tailwind CSS on file changes"
- task watch: :environment do |_, args|
+ task watch: [:environment, :engines] do |_, args|
debug = args.extras.include?("debug")
poll = args.extras.include?("poll")
always = args.extras.include?("always")
+ verbose = args.extras.include?("verbose")
+
command = Tailwindcss::Commands.watch_command(always: always, debug: debug, poll: poll)
- puts command.inspect if args.extras.include?("verbose")
- system(*command)
+ env = Tailwindcss::Commands.command_env(verbose: verbose)
+ puts "Running: #{Shellwords.join(command)}" if verbose
+
+ system(env, *command)
rescue Interrupt
puts "Received interrupt, exiting tailwindcss:watch" if args.extras.include?("verbose")
end
+
+ desc "Create Tailwind CSS entry point files for Rails Engines"
+ task engines: :environment do
+ Tailwindcss::Engines.bundle
+ end
end
Rake::Task["assets:precompile"].enhance(["tailwindcss:build"])
diff --git a/lib/tasks/install.rake b/lib/tasks/install.rake
index 2a571cad..f94ec2a1 100644
--- a/lib/tasks/install.rake
+++ b/lib/tasks/install.rake
@@ -1,6 +1,6 @@
namespace :tailwindcss do
desc "Install Tailwind CSS into the app"
task :install do
- system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../install/tailwindcss.rb", __dir__)}"
+ system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../install/install_tailwindcss.rb", __dir__)}"
end
end
diff --git a/lib/tasks/upgrade.rake b/lib/tasks/upgrade.rake
new file mode 100644
index 00000000..05047523
--- /dev/null
+++ b/lib/tasks/upgrade.rake
@@ -0,0 +1,6 @@
+namespace :tailwindcss do
+ desc "Upgrade app from Tailwind CSS v3 to v4"
+ task :upgrade do
+ system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../install/upgrade_tailwindcss.rb", __dir__)}"
+ end
+end
diff --git a/tailwindcss-rails.gemspec b/tailwindcss-rails.gemspec
index 8d893029..45c19c28 100644
--- a/tailwindcss-rails.gemspec
+++ b/tailwindcss-rails.gemspec
@@ -19,5 +19,19 @@ Gem::Specification.new do |spec|
spec.files = Dir["{app,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
spec.add_dependency "railties", ">= 7.0.0"
- spec.add_dependency "tailwindcss-ruby"
+ spec.add_dependency "tailwindcss-ruby", "~> 4.0"
+
+ # TODO: remove this after a suitable period of time after the v4.0.0 release.
+ spec.post_install_message = <<~TEXT
+ == Upgrading to Tailwind CSS v4 ==
+
+ If you are upgrading to tailwindcss-rails 4.x, please read the upgrade guide at:
+
+ https://github.com/rails/tailwindcss-rails/blob/main/README.md#upgrading-your-application-from-tailwind-v3-to-v4
+
+ If you're not ready to upgrade yet, please pin to version 3 in your Gemfile:
+
+ gem "tailwindcss-rails", "~> 3.3.1"
+
+ TEXT
end
diff --git a/test/integration/user_journey_test.sh b/test/integration/user_install_test.sh
similarity index 68%
rename from test/integration/user_journey_test.sh
rename to test/integration/user_install_test.sh
index 14f4597a..bfd7a87c 100755
--- a/test/integration/user_journey_test.sh
+++ b/test/integration/user_install_test.sh
@@ -7,9 +7,10 @@ set -eux
# set up dependencies
rm -f Gemfile.lock
-bundle remove actionmailer
+bundle remove actionmailer || true
+bundle remove rails || true
bundle add rails --skip-install ${RAILSOPTS:-}
-bundle install
+bundle install --prefer-local
# do our work a directory with spaces in the name (#176, #184)
rm -rf "My Workspace"
@@ -18,8 +19,8 @@ pushd "My Workspace"
# create a rails app
bundle exec rails -v
-bundle exec rails new test-app --skip-bundle
-pushd test-app
+bundle exec rails new test-install --skip-bundle
+pushd test-install
# make sure to use the same version of rails (e.g., install from git source if necessary)
bundle remove rails --skip-install
@@ -28,21 +29,16 @@ bundle add rails --skip-install ${RAILSOPTS:-}
# use the tailwindcss-rails under test
bundle add tailwindcss-rails --skip-install --path="../.."
bundle add tailwindcss-ruby --skip-install ${TAILWINDCSSOPTS:-}
-bundle install
-bundle show --paths
+bundle install --prefer-local
+bundle show --paths | fgrep tailwind
bundle binstubs --all
-if bundle show | fgrep tailwindcss-ruby | fgrep -q "(4." ; then
- TAILWIND4=1
-else
- TAILWIND4=0
-fi
-
# install tailwindcss
bin/rails tailwindcss:install
# TEST: tailwind was installed correctly
-grep -q tailwind app/views/layouts/application.html.erb
+grep -q "
> Rakefile
@@ -51,12 +47,9 @@ task :still_here do
end
EOF
-if [[ $TAILWIND4 = 1 ]] ; then
- cat > app/assets/stylesheets/application.tailwind.css <> app/assets/tailwind/application.css < "Rails 8.0.0.beta" ]] ; then
+ # install auth templates
+ bin/rails generate authentication
+ grep -q PasswordsController app/controllers/passwords_controller.rb
+fi
+
+# install scaffold templates
+bin/rails generate scaffold post title:string body:text published:boolean
+grep -q "Show this post" app/views/posts/index.html.erb
+
+# upgrade time!
+bundle remove tailwindcss-rails --skip-install
+bundle remove tailwindcss-ruby --skip-install
+
+bundle add tailwindcss-rails --skip-install --path="../.."
+bundle add tailwindcss-ruby --skip-install ${TAILWINDCSSOPTS:---version 4.0.0}
+
+bundle install --prefer-local
+bundle show --paths | fgrep tailwind
+bundle binstubs --all
+
+# create a postcss file
+cat < config/postcss.config.js
+module.exports = {
+ plugins: {
+ autoprefixer: {},
+ },
+}
+EOF
+
+bin/rails tailwindcss:upgrade
+
+# TEST: removal of inter-font CSS
+if grep -q inter-font app/views/layouts/application.html.erb ; then
+ echo "FAIL: inter-font CSS not removed"
+ exit 1
+fi
+
+# TEST: moving the postcss file
+test ! -a config/postcss.config.js
+test -a postcss.config.js
+
+# TEST: moving application.tailwind.css
+test ! -a app/assets/stylesheets/application.tailwind.css
+test -a app/assets/tailwind/application.css
+
+# generate CSS
+bin/rails tailwindcss:build[verbose]
+grep -q "py-2" app/assets/builds/tailwind.css
+
+echo "OK"
diff --git a/test/lib/tailwindcss/commands_test.rb b/test/lib/tailwindcss/commands_test.rb
index 1ec6ce2b..d09481a4 100644
--- a/test/lib/tailwindcss/commands_test.rb
+++ b/test/lib/tailwindcss/commands_test.rb
@@ -15,7 +15,6 @@ def setup
assert_kind_of(Array, actual)
assert_equal(executable, actual.first)
assert_includes(actual, "-i")
- assert_includes(actual, "-c")
assert_includes(actual, "-o")
end
end
@@ -34,6 +33,32 @@ def setup
end
end
+ test ".compile_command debug environment variable" do
+ begin
+ Rails.stub(:root, File) do # Rails.root won't work in this test suite
+ ENV["TAILWINDCSS_DEBUG"] = ""
+ actual = Tailwindcss::Commands.compile_command
+ assert_kind_of(Array, actual)
+ assert_includes(actual, "--minify")
+
+ actual = Tailwindcss::Commands.compile_command(debug: true)
+ assert_kind_of(Array, actual)
+ assert_includes(actual, "--minify")
+
+ ENV["TAILWINDCSS_DEBUG"] = "any non-blank value"
+ actual = Tailwindcss::Commands.compile_command
+ assert_kind_of(Array, actual)
+ refute_includes(actual, "--minify")
+
+ actual = Tailwindcss::Commands.compile_command(debug: true)
+ assert_kind_of(Array, actual)
+ refute_includes(actual, "--minify")
+ end
+ ensure
+ ENV.delete('TAILWINDCSS_DEBUG')
+ end
+ end
+
test ".compile_command when Rails compression is on" do
Rails.stub(:root, File) do # Rails.root won't work in this test suite
Tailwindcss::Commands.stub(:rails_css_compressor?, true) do
@@ -58,8 +83,7 @@ def setup
assert_equal(executable, actual.first)
refute_includes(actual, "--postcss")
- config_file = Rails.root.join("config/postcss.config.js")
- FileUtils.mkdir_p(Rails.root.join("config"))
+ config_file = Rails.root.join("postcss.config.js")
FileUtils.touch(config_file)
actual = Tailwindcss::Commands.compile_command
assert_kind_of(Array, actual)
diff --git a/test/lib/tailwindcss/engines_test.rb b/test/lib/tailwindcss/engines_test.rb
new file mode 100644
index 00000000..f4c1b0d2
--- /dev/null
+++ b/test/lib/tailwindcss/engines_test.rb
@@ -0,0 +1,89 @@
+require "test_helper"
+require "minitest/mock"
+
+class Tailwindcss::EnginesTest < ActiveSupport::TestCase
+ def setup
+ super
+ @tmpdir_path = Pathname.new(TAILWINDCSS_TEST_APP_ROOT)
+ @builds_dir = @tmpdir_path.join("app/assets/builds/tailwind")
+ end
+
+ test "bundle creates the builds directory" do
+ Rails.stub(:root, @tmpdir_path) do
+ Tailwindcss::Engines.bundle
+ assert Dir.exist?(@builds_dir), "Expected directory #{@builds_dir} to be created"
+ end
+ end
+
+ test "bundle generates CSS files for engine's tailwind assets" do
+ Rails.stub(:root, @tmpdir_path) do
+ setup_mock_engine("mock_engine", @tmpdir_path)
+
+ Tailwindcss::Engines.bundle
+
+ css_file_path = @builds_dir.join("mock_engine.css")
+ assert File.exist?(css_file_path), "Expected file #{css_file_path} to be created"
+
+ content = File.read(css_file_path)
+ assert_match(/DO NOT MODIFY THIS FILE/, content)
+ assert_match(/@import ".*\/app\/assets\/tailwind\/mock_engine\/engine.css"/, content)
+ end
+ end
+
+ test "bundle removes existing files before generating new ones" do
+ Rails.stub(:root, @tmpdir_path) do
+ setup_mock_engine("mock_engine", @tmpdir_path)
+
+ FileUtils.mkdir_p(@builds_dir)
+ css_file_path = @builds_dir.join("mock_engine.css")
+ File.write(css_file_path, "OLD CONTENT")
+
+ Tailwindcss::Engines.bundle
+
+ content = File.read(css_file_path)
+ assert_no_match(/OLD CONTENT/, content)
+ assert_match(/DO NOT MODIFY THIS FILE/, content)
+ end
+ end
+
+ test "bundle only processes engines with tailwind assets" do
+ Rails.stub(:root, @tmpdir_path) do
+ setup_mock_engine("engine_with_assets", @tmpdir_path)
+
+ Class.new(Rails::Engine) do
+ define_singleton_method(:engine_name) { "engine_without_assets" }
+ define_singleton_method(:root) { Pathname.new(Dir.mktmpdir) }
+ end
+
+ Tailwindcss::Engines.bundle
+
+ assert File.exist?(@builds_dir.join("engine_with_assets.css")), "Expected CSS file for engine with assets"
+ refute File.exist?(@builds_dir.join("engine_without_assets.css")), "Expected no CSS file for engine without assets"
+ end
+ end
+
+ test "bundle handles multiple engines" do
+ Rails.stub(:root, @tmpdir_path) do
+ setup_mock_engine("engine1", @tmpdir_path)
+ setup_mock_engine("engine2", @tmpdir_path)
+
+ Tailwindcss::Engines.bundle
+
+ assert File.exist?(@builds_dir.join("engine1.css")), "Expected CSS file for engine1"
+ assert File.exist?(@builds_dir.join("engine2.css")), "Expected CSS file for engine2"
+ end
+ end
+
+ private
+
+ def setup_mock_engine(name, root_path)
+ tailwind_dir = root_path.join("app/assets/tailwind/#{name}")
+ FileUtils.mkdir_p(tailwind_dir)
+ File.write(tailwind_dir.join("engine.css"), "/* Test CSS */")
+
+ Class.new(Rails::Engine) do
+ define_singleton_method(:engine_name) { name }
+ define_singleton_method(:root) { root_path }
+ end
+ end
+end