From a908ce39a5116de3b1424e70216d12c561d9b05e Mon Sep 17 00:00:00 2001 From: Massimiliano Lattanzio Date: Tue, 13 Jan 2026 14:54:19 +0100 Subject: [PATCH 1/6] Add install command to CLI interface Introduces a new install command to provide installation steps for users setting up the theme builder. --- lib/shopify_theme_builder/command_line.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/shopify_theme_builder/command_line.rb b/lib/shopify_theme_builder/command_line.rb index 49383b4..49c6122 100644 --- a/lib/shopify_theme_builder/command_line.rb +++ b/lib/shopify_theme_builder/command_line.rb @@ -34,6 +34,10 @@ def watch ) end + desc "install", "Set up your Shopify theme with Tailwind CSS, Stimulus JS, and the file watcher" + def install + end + desc "generate", "Generate an example component structure" method_option :type, type: :string, desc: "Type of component to generate ('section', 'block' or 'snippet')" method_option :name, type: :string, desc: "Name of the component to generate" From e7e92340b421c34749c9f192fb603794dbd98a77 Mon Sep 17 00:00:00 2001 From: Massimiliano Lattanzio Date: Tue, 13 Jan 2026 18:39:53 +0100 Subject: [PATCH 2/6] Add Tailwind CSS injection to install command Enhances the install process by automatically injecting Tailwind CSS stylesheet tags into theme files. --- lib/shopify_theme_builder/command_line.rb | 51 ++++ .../command_line_spec.rb | 228 ++++++++++++++++++ 2 files changed, 279 insertions(+) diff --git a/lib/shopify_theme_builder/command_line.rb b/lib/shopify_theme_builder/command_line.rb index 49c6122..4ae9d1d 100644 --- a/lib/shopify_theme_builder/command_line.rb +++ b/lib/shopify_theme_builder/command_line.rb @@ -36,6 +36,7 @@ def watch desc "install", "Set up your Shopify theme with Tailwind CSS, Stimulus JS, and the file watcher" def install + add_tailwind_to_theme end desc "generate", "Generate an example component structure" @@ -73,5 +74,55 @@ def exclude_pattern "schema.json" end + + def add_tailwind_to_theme + theme_file_path = + if File.exist?("snippets/stylesheets.liquid") + "snippets/stylesheets.liquid" + elsif File.exist?("layout/theme.liquid") + "layout/theme.liquid" + end + + unless theme_file_path + say_error "Error: Could not find a theme file to inject Tailwind CSS.", :red + return + end + + theme_file = File.read(theme_file_path) + + if theme_file.include?("tailwind-output.css") + say "Tailwind CSS already included in #{theme_file_path}. Skipping injection.", :blue + return + end + + injection_tag = "{{ 'tailwind-output.css' | asset_url | stylesheet_tag }}" + + if theme_file_path == "snippets/stylesheets.liquid" + add_tailwind_to_snippet(theme_file_path, theme_file, injection_tag) + else + add_tailwind_to_layout(theme_file_path, theme_file, injection_tag) + end + end + + def add_tailwind_to_snippet(theme_file_path, theme_file, injection_tag) + File.write(theme_file_path, "#{theme_file.chomp}\n#{injection_tag}\n") + say "Injected Tailwind CSS tag into #{theme_file_path}.", :green + end + + def add_tailwind_to_layout(theme_file_path, theme_file, injection_tag) + stylesheet_tag_regex = + /(\{\{\s*['"][^'"]+['"]\s*\|\s*asset_url\s*\|\s*stylesheet_tag(?:\s*:\s*((?!\}\}).)*)?\s*\}\})/ + if theme_file.match?(stylesheet_tag_regex) + updated_content = theme_file.sub( + stylesheet_tag_regex, + "\\1\n#{injection_tag}" + ) + File.write(theme_file_path, updated_content) + say "Injected Tailwind CSS tag into #{theme_file_path}.", :green + else + say_error "Error: Could not find a way to inject Tailwind CSS. Please manually add the CSS tag.", + :red + end + end end end diff --git a/spec/shopify_theme_builder/command_line_spec.rb b/spec/shopify_theme_builder/command_line_spec.rb index 0462229..3d0f9d9 100644 --- a/spec/shopify_theme_builder/command_line_spec.rb +++ b/spec/shopify_theme_builder/command_line_spec.rb @@ -383,6 +383,234 @@ end end + describe "#install" do + let(:cli) { described_class.new } + + before do + allow(File).to receive(:write) + allow(cli).to receive(:say) + allow(cli).to receive(:say_error) + end + + context "when layout/theme.liquid exists" do + let(:theme_file_path) { "layout/theme.liquid" } + let(:theme_content) do + <<~LIQUID + {{ 'application.css' | asset_url | stylesheet_tag }} + LIQUID + end + + before do + allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(false) + allow(File).to receive(:exist?).with("layout/theme.liquid").and_return(true) + allow(File).to receive(:read).with(theme_file_path).and_return(theme_content.dup) + end + + it "injects Tailwind CSS after existing stylesheet tag" do + cli.install + + expect(File).to have_received(:write).with( + theme_file_path, + a_string_including("{{ 'tailwind-output.css' | asset_url | stylesheet_tag }}") + ) + end + + it "shows success message" do + cli.install + + expect(cli).to have_received(:say).with( + "Injected Tailwind CSS tag into #{theme_file_path}.", + :green + ) + end + + it "places Tailwind CSS tag on new line after existing tag" do + cli.install + + expect(File).to have_received(:write).with( + theme_file_path, + a_string_matching(/application\.css.*?\n\{\{ 'tailwind-output\.css'/m) + ) + end + + context "with stylesheet tag containing attributes" do + let(:theme_content) do + <<~LIQUID + {{ 'base.css' | asset_url | stylesheet_tag: media: 'all' }} + LIQUID + end + + it "injects after stylesheet tag with attributes" do + cli.install + + expect(File).to have_received(:write).with( + theme_file_path, + a_string_matching(/base\.css.*?media.*?\n\{\{ 'tailwind-output\.css'/m) + ) + end + end + end + + context "when snippets/stylesheets.liquid exists" do + let(:snippets_file_path) { "snippets/stylesheets.liquid" } + let(:snippets_content) do + <<~LIQUID + {{ 'base.css' | asset_url | stylesheet_tag }} + {{ 'components.css' | asset_url | stylesheet_tag }} + LIQUID + end + + before do + allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(true) + allow(File).to receive(:exist?).with("layout/theme.liquid").and_return(true) + allow(File).to receive(:read).with(snippets_file_path).and_return(snippets_content.dup) + end + + it "prefers snippets file over layout file" do + cli.install + + expect(File).to have_received(:write).with(snippets_file_path, anything) + end + + it "appends Tailwind CSS tag at the end of the file" do + cli.install + + expect(File).to have_received(:write).with( + snippets_file_path, + a_string_ending_with("{{ 'tailwind-output.css' | asset_url | stylesheet_tag }}\n") + ) + end + + it "shows success message for snippet injection" do + cli.install + + expect(cli).to have_received(:say).with( + "Injected Tailwind CSS tag into #{snippets_file_path}.", + :green + ) + end + + context "when snippet file has trailing newline" do + let(:snippets_content) do + <<~LIQUID + {{ 'base.css' | asset_url | stylesheet_tag }} + + LIQUID + end + + it "adds Tailwind CSS tag preserving structure" do + cli.install + + expect(File).to have_received(:write).with( + snippets_file_path, + a_string_including("{{ 'tailwind-output.css' | asset_url | stylesheet_tag }}") + ) + end + end + + context "when snippet file has no trailing newline" do + let(:snippets_content) { "{{ 'base.css' | asset_url | stylesheet_tag }}" } + + it "adds newline before Tailwind CSS tag" do + cli.install + + expect(File).to have_received(:write).with( + snippets_file_path, + "{{ 'base.css' | asset_url | stylesheet_tag }}\n{{ 'tailwind-output.css' | asset_url | stylesheet_tag }}\n" + ) + end + end + end + + context "when Tailwind CSS is already included" do + let(:theme_content) do + <<~LIQUID + {{ 'application.css' | asset_url | stylesheet_tag }} + {{ 'tailwind-output.css' | asset_url | stylesheet_tag }} + LIQUID + end + + before do + allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(false) + allow(File).to receive(:exist?).with("layout/theme.liquid").and_return(true) + allow(File).to receive(:read).with("layout/theme.liquid").and_return(theme_content) + end + + it "skips injection" do + cli.install + + expect(File).not_to have_received(:write) + end + + it "shows skip message" do + cli.install + + expect(cli).to have_received(:say).with( + "Tailwind CSS already included in layout/theme.liquid. Skipping injection.", + :blue + ) + end + end + + context "when neither snippets nor layout file exists" do + before do + allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(false) + allow(File).to receive(:exist?).with("layout/theme.liquid").and_return(false) + end + + it "shows error message" do + cli.install + + expect(cli).to have_received(:say_error).with( + "Error: Could not find a theme file to inject Tailwind CSS.", + :red + ) + end + + it "does not write any files" do + cli.install + + expect(File).not_to have_received(:write) + end + + it "returns early without processing" do + result = cli.install + + expect(result).to be_nil + end + end + + context "when no stylesheet tags are found in layout file" do + let(:theme_content) do + <<~LIQUID +

Welcome

+

No stylesheets here

+ LIQUID + end + + before do + allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(false) + allow(File).to receive(:exist?).with("layout/theme.liquid").and_return(true) + allow(File).to receive(:read).with("layout/theme.liquid").and_return(theme_content) + end + + it "shows error message" do + cli.install + + expect(cli).to have_received(:say_error).with( + "Error: Could not find a way to inject Tailwind CSS. Please manually add the CSS tag.", + :red + ) + end + + it "does not write to the file" do + cli.install + + expect(File).not_to have_received(:write) + end + end + end + describe ".source_root" do it "returns the correct source root path" do expected_path = File.expand_path("../..", __dir__) From d71b16c78a91e34d3655c74a46a3cfed3de024f9 Mon Sep 17 00:00:00 2001 From: Massimiliano Lattanzio Date: Tue, 13 Jan 2026 19:00:13 +0100 Subject: [PATCH 3/6] Add Stimulus JS injection to theme installation Extends the install command to automatically inject Stimulus JS into Shopify themes alongside existing Tailwind CSS injection. --- lib/shopify_theme_builder/command_line.rb | 50 +++++ .../command_line_spec.rb | 185 ++++++++++++++++++ 2 files changed, 235 insertions(+) diff --git a/lib/shopify_theme_builder/command_line.rb b/lib/shopify_theme_builder/command_line.rb index 4ae9d1d..e14fabd 100644 --- a/lib/shopify_theme_builder/command_line.rb +++ b/lib/shopify_theme_builder/command_line.rb @@ -37,6 +37,7 @@ def watch desc "install", "Set up your Shopify theme with Tailwind CSS, Stimulus JS, and the file watcher" def install add_tailwind_to_theme + add_stimulus_to_theme end desc "generate", "Generate an example component structure" @@ -124,5 +125,54 @@ def add_tailwind_to_layout(theme_file_path, theme_file, injection_tag) :red end end + + def add_stimulus_to_theme + theme_file_path = + if File.exist?("snippets/scripts.liquid") + "snippets/scripts.liquid" + elsif File.exist?("layout/theme.liquid") + "layout/theme.liquid" + end + + unless theme_file_path + say_error "Error: Could not find a theme file to inject Stimulus JS.", :red + return + end + + theme_file = File.read(theme_file_path) + + if theme_file.include?("controllers.js") + say "Stimulus JS already included in #{theme_file_path}. Skipping injection.", :blue + return + end + + injection_tag = "" + + if theme_file_path == "snippets/scripts.liquid" + add_stimulus_to_snippet(theme_file_path, theme_file, injection_tag) + else + add_stimulus_to_layout(theme_file_path, theme_file, injection_tag) + end + end + + def add_stimulus_to_snippet(theme_file_path, theme_file, injection_tag) + File.write(theme_file_path, "#{theme_file.chomp}\n#{injection_tag}\n") + say "Injected Stimulus JS tag into #{theme_file_path}.", :green + end + + def add_stimulus_to_layout(theme_file_path, theme_file, injection_tag) + script_tag_regex = %r{(\s*)} + if theme_file.match?(script_tag_regex) + updated_content = theme_file.sub( + script_tag_regex, + "\\1\n#{injection_tag}\n" + ) + File.write(theme_file_path, updated_content) + say "Injected Stimulus JS tag into #{theme_file_path}.", :green + else + say_error "Error: Could not find a way to inject Stimulus JS. Please manually add the JS tag.", + :red + end + end end end diff --git a/spec/shopify_theme_builder/command_line_spec.rb b/spec/shopify_theme_builder/command_line_spec.rb index 3d0f9d9..2d233df 100644 --- a/spec/shopify_theme_builder/command_line_spec.rb +++ b/spec/shopify_theme_builder/command_line_spec.rb @@ -402,6 +402,7 @@ before do allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(false) + allow(File).to receive(:exist?).with("snippets/scripts.liquid").and_return(false) allow(File).to receive(:exist?).with("layout/theme.liquid").and_return(true) allow(File).to receive(:read).with(theme_file_path).and_return(theme_content.dup) end @@ -462,8 +463,10 @@ before do allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(true) + allow(File).to receive(:exist?).with("snippets/scripts.liquid").and_return(false) allow(File).to receive(:exist?).with("layout/theme.liquid").and_return(true) allow(File).to receive(:read).with(snippets_file_path).and_return(snippets_content.dup) + allow(File).to receive(:read).with("layout/theme.liquid").and_return("") end it "prefers snippets file over layout file" do @@ -532,6 +535,7 @@ before do allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(false) + allow(File).to receive(:exist?).with("snippets/scripts.liquid").and_return(false) allow(File).to receive(:exist?).with("layout/theme.liquid").and_return(true) allow(File).to receive(:read).with("layout/theme.liquid").and_return(theme_content) end @@ -555,6 +559,7 @@ context "when neither snippets nor layout file exists" do before do allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(false) + allow(File).to receive(:exist?).with("snippets/scripts.liquid").and_return(false) allow(File).to receive(:exist?).with("layout/theme.liquid").and_return(false) end @@ -590,6 +595,7 @@ before do allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(false) + allow(File).to receive(:exist?).with("snippets/scripts.liquid").and_return(false) allow(File).to receive(:exist?).with("layout/theme.liquid").and_return(true) allow(File).to receive(:read).with("layout/theme.liquid").and_return(theme_content) end @@ -609,6 +615,185 @@ expect(File).not_to have_received(:write) end end + + context "with Stimulus JS injection" do + let(:theme_file_path) { "layout/theme.liquid" } + let(:theme_content) do + <<~LIQUID + {{ 'application.css' | asset_url | stylesheet_tag }} + + + {{ content_for_layout }} + + LIQUID + end + + before do + allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(false) + allow(File).to receive(:exist?).with("snippets/scripts.liquid").and_return(false) + allow(File).to receive(:exist?).with("layout/theme.liquid").and_return(true) + allow(File).to receive(:read).with(theme_file_path).and_return(theme_content.dup) + end + + it "injects both Tailwind CSS and Stimulus JS" do + cli.install + + expect(File).to have_received(:write).twice + end + + it "injects Stimulus JS before closing body tag" do + cli.install + + expect(File).to have_received(:write).with( + theme_file_path, + a_string_including('') + ) + end + + it "shows success message for Stimulus injection" do + cli.install + + expect(cli).to have_received(:say).with( + "Injected Stimulus JS tag into #{theme_file_path}.", + :green + ) + end + + context "when snippets/scripts.liquid exists" do + let(:snippets_file_path) { "snippets/scripts.liquid" } + let(:snippets_content) do + <<~LIQUID + + LIQUID + end + + before do + allow(File).to receive(:exist?).with("snippets/scripts.liquid").and_return(true) + allow(File).to receive(:read).with(snippets_file_path).and_return(snippets_content.dup) + end + + it "prefers snippets/scripts.liquid over layout" do + cli.install + + script_tag = '' + expect(File).to have_received(:write).with( + snippets_file_path, + a_string_including(script_tag) + ) + end + + it "appends Stimulus JS tag at the end of snippet file" do + cli.install + + script_tag = "\n" + expect(File).to have_received(:write).with( + snippets_file_path, + a_string_ending_with(script_tag) + ) + end + + it "shows success message for snippet injection" do + cli.install + + expect(cli).to have_received(:say).with( + "Injected Stimulus JS tag into #{snippets_file_path}.", + :green + ) + end + end + + context "when Stimulus JS is already included" do + let(:theme_content) do + <<~LIQUID + {{ 'application.css' | asset_url | stylesheet_tag }} + + + {{ content_for_layout }} + + + LIQUID + end + + it "skips Stimulus JS injection" do + cli.install + + expect(cli).to have_received(:say).with( + "Stimulus JS already included in #{theme_file_path}. Skipping injection.", + :blue + ) + end + + it "still injects Tailwind CSS" do + cli.install + + expect(File).to have_received(:write).with( + theme_file_path, + a_string_including("{{ 'tailwind-output.css' | asset_url | stylesheet_tag }}") + ) + end + end + + context "when both Tailwind CSS and Stimulus JS are already included" do + let(:theme_content) do + <<~LIQUID + {{ 'application.css' | asset_url | stylesheet_tag }} + {{ 'tailwind-output.css' | asset_url | stylesheet_tag }} + + + {{ content_for_layout }} + + + LIQUID + end + + it "skips Tailwind CSS injection" do + cli.install + + expect(cli).to have_received(:say).with( + "Tailwind CSS already included in #{theme_file_path}. Skipping injection.", + :blue + ) + end + + it "skips Stimulus JS injection" do + cli.install + + expect(cli).to have_received(:say).with( + "Stimulus JS already included in #{theme_file_path}. Skipping injection.", + :blue + ) + end + + it "does not write to the file" do + cli.install + + expect(File).not_to have_received(:write) + end + end + + context "when no closing body tag is found" do + let(:theme_content) do + <<~LIQUID + {{ 'application.css' | asset_url | stylesheet_tag }} + +
+ {{ content_for_layout }} +
+ LIQUID + end + + it "shows error message" do + cli.install + + expect(cli).to have_received(:say_error).with( + "Error: Could not find a way to inject Stimulus JS. Please manually add the JS tag.", + :red + ) + end + end + end end describe ".source_root" do From 94ca7a3842ea1d547d9b012cdc0d39d459837763 Mon Sep 17 00:00:00 2001 From: Massimiliano Lattanzio Date: Tue, 13 Jan 2026 19:14:04 +0100 Subject: [PATCH 4/6] Add Procfile.dev integration for theme watcher Automatically adds the theme-builder watch command to Procfile.dev during installation when the file exists. --- lib/shopify_theme_builder/command_line.rb | 16 ++ .../command_line_spec.rb | 147 ++++++++++++++++++ 2 files changed, 163 insertions(+) diff --git a/lib/shopify_theme_builder/command_line.rb b/lib/shopify_theme_builder/command_line.rb index e14fabd..66bab0b 100644 --- a/lib/shopify_theme_builder/command_line.rb +++ b/lib/shopify_theme_builder/command_line.rb @@ -38,6 +38,7 @@ def watch def install add_tailwind_to_theme add_stimulus_to_theme + add_watcher_to_procfile end desc "generate", "Generate an example component structure" @@ -174,5 +175,20 @@ def add_stimulus_to_layout(theme_file_path, theme_file, injection_tag) :red end end + + def add_watcher_to_procfile + procfile_path = "Procfile.dev" + return unless File.exist?(procfile_path) + + procfile_content = File.read(procfile_path) + + if procfile_content.include?("theme-builder watch") + say "Watcher command already present in #{procfile_path}. Skipping addition.", :blue + return + end + + File.write(procfile_path, "#{procfile_content.chomp}\ntheme-builder: bundle exec theme-builder watch\n") + say "Added watcher command to #{procfile_path}.", :green + end end end diff --git a/spec/shopify_theme_builder/command_line_spec.rb b/spec/shopify_theme_builder/command_line_spec.rb index 2d233df..1f60421 100644 --- a/spec/shopify_theme_builder/command_line_spec.rb +++ b/spec/shopify_theme_builder/command_line_spec.rb @@ -404,6 +404,7 @@ allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(false) allow(File).to receive(:exist?).with("snippets/scripts.liquid").and_return(false) allow(File).to receive(:exist?).with("layout/theme.liquid").and_return(true) + allow(File).to receive(:exist?).with("Procfile.dev").and_return(false) allow(File).to receive(:read).with(theme_file_path).and_return(theme_content.dup) end @@ -465,6 +466,7 @@ allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(true) allow(File).to receive(:exist?).with("snippets/scripts.liquid").and_return(false) allow(File).to receive(:exist?).with("layout/theme.liquid").and_return(true) + allow(File).to receive(:exist?).with("Procfile.dev").and_return(false) allow(File).to receive(:read).with(snippets_file_path).and_return(snippets_content.dup) allow(File).to receive(:read).with("layout/theme.liquid").and_return("") end @@ -537,6 +539,7 @@ allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(false) allow(File).to receive(:exist?).with("snippets/scripts.liquid").and_return(false) allow(File).to receive(:exist?).with("layout/theme.liquid").and_return(true) + allow(File).to receive(:exist?).with("Procfile.dev").and_return(false) allow(File).to receive(:read).with("layout/theme.liquid").and_return(theme_content) end @@ -561,6 +564,7 @@ allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(false) allow(File).to receive(:exist?).with("snippets/scripts.liquid").and_return(false) allow(File).to receive(:exist?).with("layout/theme.liquid").and_return(false) + allow(File).to receive(:exist?).with("Procfile.dev").and_return(false) end it "shows error message" do @@ -597,6 +601,7 @@ allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(false) allow(File).to receive(:exist?).with("snippets/scripts.liquid").and_return(false) allow(File).to receive(:exist?).with("layout/theme.liquid").and_return(true) + allow(File).to receive(:exist?).with("Procfile.dev").and_return(false) allow(File).to receive(:read).with("layout/theme.liquid").and_return(theme_content) end @@ -632,6 +637,7 @@ allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(false) allow(File).to receive(:exist?).with("snippets/scripts.liquid").and_return(false) allow(File).to receive(:exist?).with("layout/theme.liquid").and_return(true) + allow(File).to receive(:exist?).with("Procfile.dev").and_return(false) allow(File).to receive(:read).with(theme_file_path).and_return(theme_content.dup) end @@ -794,6 +800,147 @@ end end end + + context "with Procfile.dev watcher integration" do + let(:procfile_path) { "Procfile.dev" } + let(:cli) { described_class.new } + let(:theme_file_path) { "layout/theme.liquid" } + let(:theme_content) do + <<~LIQUID + {{ 'application.css' | asset_url | stylesheet_tag }} + + + {{ content_for_layout }} + + LIQUID + end + + before do + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:exist?).with("snippets/stylesheets.liquid").and_return(false) + allow(File).to receive(:exist?).with("snippets/scripts.liquid").and_return(false) + allow(File).to receive(:exist?).with(theme_file_path).and_return(true) + allow(File).to receive(:read).with(theme_file_path).and_return(theme_content.dup) + allow(File).to receive(:write) + allow(cli).to receive(:say) + allow(cli).to receive(:say_error) + end + + context "when Procfile.dev exists" do + let(:procfile_content) do + <<~PROCFILE + web: bin/rails server -p 3000 + css: bin/rails tailwindcss:watch + PROCFILE + end + + before do + allow(File).to receive(:exist?).with(procfile_path).and_return(true) + allow(File).to receive(:read).with(procfile_path).and_return(procfile_content) + end + + it "adds the theme-builder watcher command to Procfile.dev" do + cli.install + + expect(File).to have_received(:write).with( + procfile_path, + a_string_ending_with("theme-builder: bundle exec theme-builder watch\n") + ) + end + + it "shows success message" do + cli.install + + expect(cli).to have_received(:say).with( + "Added watcher command to #{procfile_path}.", + :green + ) + end + + it "appends the command after existing content" do + cli.install + + expect(File).to have_received(:write).with( + procfile_path, + a_string_matching(%r{css: bin/rails tailwindcss:watch\ntheme-builder: bundle exec theme-builder watch\n\z}) + ) + end + end + + context "when Procfile.dev does not exist" do + before do + allow(File).to receive(:exist?).with(procfile_path).and_return(false) + end + + it "does not attempt to write to Procfile.dev" do + cli.install + + expect(File).not_to have_received(:write).with( + procfile_path, + anything + ) + end + + it "does not show any message about Procfile.dev" do + cli.install + + expect(cli).not_to have_received(:say).with( + a_string_matching(/Procfile/), + anything + ) + end + end + + context "when watcher command already exists in Procfile.dev" do + let(:procfile_content) do + <<~PROCFILE + web: bin/rails server -p 3000 + theme-builder: bundle exec theme-builder watch + PROCFILE + end + + before do + allow(File).to receive(:exist?).with(procfile_path).and_return(true) + allow(File).to receive(:read).with(procfile_path).and_return(procfile_content) + end + + it "skips adding the watcher command" do + cli.install + + expect(File).not_to have_received(:write).with( + procfile_path, + anything + ) + end + + it "shows skip message" do + cli.install + + expect(cli).to have_received(:say).with( + "Watcher command already present in #{procfile_path}. Skipping addition.", + :blue + ) + end + end + + context "when Procfile.dev has no trailing newline" do + let(:procfile_content) { "web: bin/rails server -p 3000" } + + before do + allow(File).to receive(:exist?).with(procfile_path).and_return(true) + allow(File).to receive(:read).with(procfile_path).and_return(procfile_content) + end + + it "adds the command with proper newlines" do + cli.install + + expect(File).to have_received(:write).with( + procfile_path, + "web: bin/rails server -p 3000\ntheme-builder: bundle exec theme-builder watch\n" + ) + end + end + end end describe ".source_root" do From 76e414b13f73d4141b8e4662402c7330e65d2f7a Mon Sep 17 00:00:00 2001 From: Massimiliano Lattanzio Date: Tue, 13 Jan 2026 19:15:23 +0100 Subject: [PATCH 5/6] Add installation command to README And simplifies setup instructions. --- README.md | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 87c9cf6..3530aa4 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,14 @@ Install the gem and add to the application's Gemfile by executing: bundle add shopify_theme_builder --group "development" ``` +Run the gem's install command: + +```bash +bundle exec theme-builder install +``` + +This will inject Tailwind CSS and Stimulus JS into your Shopify theme layout, and add an entry in the `Procfile.dev` to run the watcher if present. + ## Usage To watch for changes in the default components folder and build the theme, run: @@ -118,20 +126,6 @@ You can customize the component type, name and folder by providing additional op - `--name`: Specify the component name. - `--folder`: Specify the components folder (default is `_components`). -## After Running the Watcher - -The watcher will create a CSS file that can be included in your Shopify theme layout in this way: - -```liquid -{{ 'tailwind-output.css' | asset_url | stylesheet_tag }} -``` - -And a JavaScript file that can be included in your Shopify theme layout in this way: - -```liquid - -``` - ## Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. From 4d9e6650d69c79980e3022f0dbcc1717ba8ac0df Mon Sep 17 00:00:00 2001 From: Massimiliano Lattanzio Date: Tue, 13 Jan 2026 19:18:47 +0100 Subject: [PATCH 6/6] Exclude command line class from length metrics --- .rubocop.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.rubocop.yml b/.rubocop.yml index 5b3c81a..ddd9923 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -25,6 +25,7 @@ RSpec/NestedGroups: Metrics/ClassLength: Exclude: - 'lib/shopify_theme_builder/liquid_processor.rb' + - 'lib/shopify_theme_builder/command_line.rb' Metrics/MethodLength: Exclude: