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*)
+ {{ content_for_layout }}
+
+ {{ content_for_layout }}
+
+
+ {{ content_for_layout }}
+
+