neon_sakura 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.ai-reviewer/README.md +182 -0
- data/.ai-reviewer/ai-reviewer.sh +56 -0
- data/.ai-reviewer/build-system-prompt.sh +136 -0
- data/.ai-reviewer/extract-claude-sections.sh +32 -0
- data/.ai-reviewer/test-ai-reviewer.sh +40 -0
- data/.ai-reviewer-config.yml +190 -0
- data/.github/dependabot.yml +12 -0
- data/.github/settings.yml +70 -0
- data/.github/workflows/ai-pr-review-on-comment.yml +384 -0
- data/.github/workflows/ai-pr-review.yml +328 -0
- data/.github/workflows/license-check.yml +78 -0
- data/.github/workflows/lint.yml +79 -0
- data/.github/workflows/security.yml +131 -0
- data/.github/workflows/semgrep.yml +26 -0
- data/.github/workflows/test.yml +44 -0
- data/.gitignore +75 -0
- data/.rubocop.yml +33 -0
- data/.ruby-version +1 -0
- data/.simplecov +14 -0
- data/.stylelintignore +10 -0
- data/.stylelintrc.json +37 -0
- data/AGENTS.md +51 -0
- data/CHANGELOG.md +568 -0
- data/CLAUDE.md +632 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +327 -0
- data/LICENSE +21 -0
- data/README.md +1209 -0
- data/Rakefile +25 -0
- data/app/assets/images/cherry_blossom.svg +1525 -0
- data/app/assets/images/cherry_blossom_tree.png +0 -0
- data/app/assets/images/prysm-icon.png +0 -0
- data/app/assets/stylesheets/base.css +29 -0
- data/app/assets/stylesheets/components.css +1652 -0
- data/app/assets/stylesheets/forms.css +152 -0
- data/app/assets/stylesheets/loading.css +145 -0
- data/app/assets/stylesheets/neon_sakura.css +40 -0
- data/app/assets/stylesheets/pagy-tailwind.css +120 -0
- data/app/assets/stylesheets/theme-default.css +40 -0
- data/app/assets/stylesheets/theme-green.css +84 -0
- data/app/assets/stylesheets/theme-purple.css +94 -0
- data/app/assets/stylesheets/theme-red.css +84 -0
- data/app/assets/stylesheets/utility-borders.css +29 -0
- data/app/assets/stylesheets/utility-colors.css +185 -0
- data/app/assets/stylesheets/utility-effects.css +123 -0
- data/app/assets/stylesheets/utility-gradients.css +158 -0
- data/app/assets/stylesheets/utility-layout.css +132 -0
- data/app/assets/stylesheets/utility-reset.css +13 -0
- data/app/assets/stylesheets/utility-responsive.css +145 -0
- data/app/assets/stylesheets/utility-sizing.css +99 -0
- data/app/assets/stylesheets/utility-spacing.css +174 -0
- data/app/assets/stylesheets/utility-typography.css +97 -0
- data/app/controllers/errors_controller.rb +120 -0
- data/app/controllers/style_guide_controller.rb +117 -0
- data/app/helpers/errors_helper.rb +12 -0
- data/app/helpers/neon_sakura/navbar_helper.rb +43 -0
- data/app/helpers/style_guide_helper.rb +36 -0
- data/app/javascript/neon_sakura/dropdown.js +22 -0
- data/app/javascript/neon_sakura/navbar.js +71 -0
- data/app/javascript/neon_sakura/theme_switcher.js +187 -0
- data/app/views/errors/show.html.erb +105 -0
- data/app/views/layouts/error.html.erb +19 -0
- data/app/views/layouts/mission_control/jobs/_application_selection.html.erb +14 -0
- data/app/views/layouts/mission_control/jobs/_navigation.html.erb +21 -0
- data/app/views/layouts/mission_control/jobs/application.html.erb +453 -0
- data/app/views/layouts/style_guide.html.erb +416 -0
- data/app/views/shared/_file_upload.html.erb +184 -0
- data/app/views/shared/_footer.html.erb +23 -0
- data/app/views/shared/_header.html.erb +42 -0
- data/app/views/shared/_navbar.html.erb +306 -0
- data/app/views/shared/_profile_image_selector.html.erb +165 -0
- data/app/views/shared/_theme_switcher.html.erb +64 -0
- data/app/views/shared/icons/_adjustments.html.erb +10 -0
- data/app/views/shared/icons/_alert_circle.html.erb +3 -0
- data/app/views/shared/icons/_alert_triangle.html.erb +3 -0
- data/app/views/shared/icons/_archive.html.erb +3 -0
- data/app/views/shared/icons/_arrow_down.html.erb +3 -0
- data/app/views/shared/icons/_arrow_left.html.erb +3 -0
- data/app/views/shared/icons/_arrow_up.html.erb +3 -0
- data/app/views/shared/icons/_arrows_pointing_in.html.erb +10 -0
- data/app/views/shared/icons/_arrows_pointing_out.html.erb +10 -0
- data/app/views/shared/icons/_artemis_logo.html.erb +26 -0
- data/app/views/shared/icons/_auth_banner.html.erb +1 -0
- data/app/views/shared/icons/_bars.html.erb +10 -0
- data/app/views/shared/icons/_bell.html.erb +3 -0
- data/app/views/shared/icons/_book.html.erb +3 -0
- data/app/views/shared/icons/_bookmark.html.erb +3 -0
- data/app/views/shared/icons/_box.html.erb +3 -0
- data/app/views/shared/icons/_brain.html.erb +3 -0
- data/app/views/shared/icons/_briefcase.html.erb +3 -0
- data/app/views/shared/icons/_calendar.html.erb +3 -0
- data/app/views/shared/icons/_camera.html.erb +4 -0
- data/app/views/shared/icons/_chart_bar.html.erb +3 -0
- data/app/views/shared/icons/_chart_line.html.erb +10 -0
- data/app/views/shared/icons/_chart_pie.html.erb +11 -0
- data/app/views/shared/icons/_chat.html.erb +3 -0
- data/app/views/shared/icons/_check.html.erb +3 -0
- data/app/views/shared/icons/_check_circle.html.erb +3 -0
- data/app/views/shared/icons/_cherry_blossom.html.erb +1516 -0
- data/app/views/shared/icons/_cherry_blossom_silhouette.html.erb +1016 -0
- data/app/views/shared/icons/_cherry_blossom_single_flower.html.erb +1125 -0
- data/app/views/shared/icons/_cherry_blossom_tree.html.erb +159 -0
- data/app/views/shared/icons/_chevron_down.html.erb +3 -0
- data/app/views/shared/icons/_chevron_right.html.erb +9 -0
- data/app/views/shared/icons/_clipboard.html.erb +3 -0
- data/app/views/shared/icons/_clock.html.erb +3 -0
- data/app/views/shared/icons/_close.html.erb +3 -0
- data/app/views/shared/icons/_cog.html.erb +4 -0
- data/app/views/shared/icons/_crop.html.erb +10 -0
- data/app/views/shared/icons/_crown.html.erb +3 -0
- data/app/views/shared/icons/_disc.html.erb +3 -0
- data/app/views/shared/icons/_download.html.erb +3 -0
- data/app/views/shared/icons/_dragonfly.html.erb +58 -0
- data/app/views/shared/icons/_duplicate.html.erb +4 -0
- data/app/views/shared/icons/_edit.html.erb +3 -0
- data/app/views/shared/icons/_envelope.html.erb +3 -0
- data/app/views/shared/icons/_eraser.html.erb +10 -0
- data/app/views/shared/icons/_external_link.html.erb +3 -0
- data/app/views/shared/icons/_eye.html.erb +4 -0
- data/app/views/shared/icons/_file_csv.html.erb +10 -0
- data/app/views/shared/icons/_file_export.html.erb +10 -0
- data/app/views/shared/icons/_file_image.html.erb +10 -0
- data/app/views/shared/icons/_file_import.html.erb +10 -0
- data/app/views/shared/icons/_file_question.html.erb +6 -0
- data/app/views/shared/icons/_film.html.erb +3 -0
- data/app/views/shared/icons/_filter.html.erb +3 -0
- data/app/views/shared/icons/_folder.html.erb +3 -0
- data/app/views/shared/icons/_folder_open.html.erb +3 -0
- data/app/views/shared/icons/_folder_plus.html.erb +3 -0
- data/app/views/shared/icons/_globe.html.erb +3 -0
- data/app/views/shared/icons/_google.html.erb +11 -0
- data/app/views/shared/icons/_heart.html.erb +3 -0
- data/app/views/shared/icons/_heart_broken.html.erb +11 -0
- data/app/views/shared/icons/_heart_pulse.html.erb +4 -0
- data/app/views/shared/icons/_history.html.erb +11 -0
- data/app/views/shared/icons/_home.html.erb +10 -0
- data/app/views/shared/icons/_image.html.erb +3 -0
- data/app/views/shared/icons/_inbox.html.erb +3 -0
- data/app/views/shared/icons/_info_circle.html.erb +10 -0
- data/app/views/shared/icons/_key.html.erb +3 -0
- data/app/views/shared/icons/_layers.html.erb +10 -0
- data/app/views/shared/icons/_lightbulb.html.erb +10 -0
- data/app/views/shared/icons/_lightning.html.erb +3 -0
- data/app/views/shared/icons/_list.html.erb +3 -0
- data/app/views/shared/icons/_lock.html.erb +3 -0
- data/app/views/shared/icons/_logout.html.erb +3 -0
- data/app/views/shared/icons/_magazine.html.erb +3 -0
- data/app/views/shared/icons/_magic.html.erb +3 -0
- data/app/views/shared/icons/_minus.html.erb +10 -0
- data/app/views/shared/icons/_mobile.html.erb +10 -0
- data/app/views/shared/icons/_moon.html.erb +3 -0
- data/app/views/shared/icons/_network.html.erb +10 -0
- data/app/views/shared/icons/_new_item_banner.html.erb +1 -0
- data/app/views/shared/icons/_ouroboros.html.erb +24 -0
- data/app/views/shared/icons/_package.html.erb +3 -0
- data/app/views/shared/icons/_palette.html.erb +3 -0
- data/app/views/shared/icons/_paper_plane.html.erb +10 -0
- data/app/views/shared/icons/_photo.html.erb +10 -0
- data/app/views/shared/icons/_play.html.erb +4 -0
- data/app/views/shared/icons/_plus.html.erb +3 -0
- data/app/views/shared/icons/_pocket.html.erb +11 -0
- data/app/views/shared/icons/_prysm-icon.html.erb +34 -0
- data/app/views/shared/icons/_prysm.html.erb +13 -0
- data/app/views/shared/icons/_pushbullet-1.html.erb +29 -0
- data/app/views/shared/icons/_pushbullet-2.html.erb +2 -0
- data/app/views/shared/icons/_puzzle.html.erb +10 -0
- data/app/views/shared/icons/_qrcode.html.erb +3 -0
- data/app/views/shared/icons/_question.html.erb +3 -0
- data/app/views/shared/icons/_receipt.html.erb +10 -0
- data/app/views/shared/icons/_redo.html.erb +3 -0
- data/app/views/shared/icons/_refresh.html.erb +3 -0
- data/app/views/shared/icons/_rocket.html.erb +10 -0
- data/app/views/shared/icons/_rss.html.erb +3 -0
- data/app/views/shared/icons/_save.html.erb +3 -0
- data/app/views/shared/icons/_search.html.erb +3 -0
- data/app/views/shared/icons/_search_minus.html.erb +10 -0
- data/app/views/shared/icons/_search_plus.html.erb +10 -0
- data/app/views/shared/icons/_server_error.html.erb +6 -0
- data/app/views/shared/icons/_share.html.erb +3 -0
- data/app/views/shared/icons/_shield_check.html.erb +3 -0
- data/app/views/shared/icons/_sign_in.html.erb +3 -0
- data/app/views/shared/icons/_spinner.html.erb +4 -0
- data/app/views/shared/icons/_star.html.erb +3 -0
- data/app/views/shared/icons/_store.html.erb +10 -0
- data/app/views/shared/icons/_sun.html.erb +3 -0
- data/app/views/shared/icons/_sync.html.erb +3 -0
- data/app/views/shared/icons/_table.html.erb +3 -0
- data/app/views/shared/icons/_tag.html.erb +3 -0
- data/app/views/shared/icons/_tags.html.erb +11 -0
- data/app/views/shared/icons/_tools.html.erb +4 -0
- data/app/views/shared/icons/_trash.html.erb +3 -0
- data/app/views/shared/icons/_undo.html.erb +3 -0
- data/app/views/shared/icons/_unlock.html.erb +3 -0
- data/app/views/shared/icons/_upload.html.erb +3 -0
- data/app/views/shared/icons/_user.html.erb +3 -0
- data/app/views/shared/icons/_user_circle.html.erb +10 -0
- data/app/views/shared/icons/_user_plus.html.erb +10 -0
- data/app/views/shared/icons/_video.html.erb +3 -0
- data/app/views/shared/icons/_wrench.html.erb +11 -0
- data/app/views/style_guide/index.html.erb +77 -0
- data/app/views/style_guide/sections/_alerts.html.erb +114 -0
- data/app/views/style_guide/sections/_badges.html.erb +78 -0
- data/app/views/style_guide/sections/_buttons.html.erb +130 -0
- data/app/views/style_guide/sections/_cards.html.erb +84 -0
- data/app/views/style_guide/sections/_colors.html.erb +106 -0
- data/app/views/style_guide/sections/_file_upload.html.erb +135 -0
- data/app/views/style_guide/sections/_forms.html.erb +129 -0
- data/app/views/style_guide/sections/_gradients.html.erb +253 -0
- data/app/views/style_guide/sections/_header.html.erb +12 -0
- data/app/views/style_guide/sections/_icons.html.erb +55 -0
- data/app/views/style_guide/sections/_images.html.erb +40 -0
- data/app/views/style_guide/sections/_loading.html.erb +242 -0
- data/app/views/style_guide/sections/_pagination.html.erb +212 -0
- data/app/views/style_guide/sections/_profile_components.html.erb +203 -0
- data/app/views/style_guide/sections/_theme_switcher.html.erb +72 -0
- data/app/views/style_guide/sections/_typography.html.erb +65 -0
- data/bin/ai-optimize-claude-md +540 -0
- data/bin/ai-review-local +345 -0
- data/bin/ai-security-review +585 -0
- data/bin/brakeman +9 -0
- data/bin/install-hooks +57 -0
- data/bin/rake +7 -0
- data/bin/rubocop +10 -0
- data/bin/verify_setup.rb +31 -0
- data/config/brakeman.ignore +28 -0
- data/config/initializers/neon_sakura.rb +15 -0
- data/config/license_overrides.yml +13 -0
- data/config/routes.rb +21 -0
- data/config/theme_mappings.yml +61 -0
- data/docs/PRYSM_ASSETS.md +210 -0
- data/docs/plans/extract_ai_reviewer_plan.md +151 -0
- data/docs/plans/neon_sakura_gem_plan.md +138 -0
- data/lib/neon_sakura/configuration.rb +94 -0
- data/lib/neon_sakura/engine.rb +48 -0
- data/lib/neon_sakura/icon_helper.rb +54 -0
- data/lib/neon_sakura/profile_helper.rb +24 -0
- data/lib/neon_sakura/stylesheet_helper.rb +40 -0
- data/lib/neon_sakura/theme_helper.rb +63 -0
- data/lib/neon_sakura/theme_importer.rb +112 -0
- data/lib/neon_sakura/version.rb +5 -0
- data/lib/neon_sakura.rb +13 -0
- data/neon_sakura.gemspec +50 -0
- data/package.json +18 -0
- data/scripts/git-hooks/post-merge +132 -0
- data/scripts/git-hooks/pre-commit +123 -0
- data/scripts/git-hooks/pre-push +127 -0
- data/scripts/license-check.rb +587 -0
- data/settings.local.json +12 -0
- data/yarn.lock +778 -0
- metadata +503 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NeonSakura
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :app_name, :show_version, :show_header, :nav_links, :enable_error_pages,
|
|
6
|
+
:nav_position, :app_icon, :show_search, :default_theme, :available_themes,
|
|
7
|
+
:enable_theme_persistence, :theme_api_endpoint, :enable_style_guide,
|
|
8
|
+
:style_guide_navbar_position, :style_guide_pagy_examples, :sticky_navbar,
|
|
9
|
+
:version_module
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@app_name = "My Application"
|
|
13
|
+
@show_version = true
|
|
14
|
+
@show_header = true
|
|
15
|
+
@enable_error_pages = true
|
|
16
|
+
@nav_position = :under_heading
|
|
17
|
+
@app_icon = nil
|
|
18
|
+
@show_search = false
|
|
19
|
+
@version_module = nil # Optional module that provides .commit_hash (e.g., YourApp::Version)
|
|
20
|
+
@nav_links = [
|
|
21
|
+
{ name: "Home", path: "/" },
|
|
22
|
+
{ name: "About", path: "/about" }
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
# Theme configuration (v0.1.1+)
|
|
26
|
+
# By default, all 4 themes are available
|
|
27
|
+
@default_theme = { name: "purple", mode: "dark" }
|
|
28
|
+
@available_themes = [
|
|
29
|
+
{ name: "green", mode: "light", label: "Green Light" },
|
|
30
|
+
{ name: "green", mode: "dark", label: "Green Dark" },
|
|
31
|
+
{ name: "purple", mode: "light", label: "Purple Light" },
|
|
32
|
+
{ name: "purple", mode: "dark", label: "Purple Dark" }
|
|
33
|
+
]
|
|
34
|
+
@enable_theme_persistence = true
|
|
35
|
+
@theme_api_endpoint = nil
|
|
36
|
+
|
|
37
|
+
# Style guide configuration (v0.1.3+)
|
|
38
|
+
@enable_style_guide = true
|
|
39
|
+
@style_guide_navbar_position = :end
|
|
40
|
+
@style_guide_pagy_examples = true # Auto-detect: show if Pagy available, skip if not
|
|
41
|
+
|
|
42
|
+
# Navbar configuration
|
|
43
|
+
@sticky_navbar = false # Off by default for backward compatibility
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Auto-inject style guide link into nav_links
|
|
47
|
+
# Called by engine initializer in development/test environments
|
|
48
|
+
def inject_style_guide_link!
|
|
49
|
+
return unless enable_style_guide
|
|
50
|
+
return unless Rails.env.development? || Rails.env.test?
|
|
51
|
+
|
|
52
|
+
# Don't inject twice
|
|
53
|
+
return if nav_links.any? { |link| link[:path] == "/style-guide" }
|
|
54
|
+
|
|
55
|
+
style_guide_link = {
|
|
56
|
+
type: "link",
|
|
57
|
+
name: "Style Guide",
|
|
58
|
+
path: "/style-guide",
|
|
59
|
+
icon: "palette",
|
|
60
|
+
condition: "Rails.env.development? || Rails.env.test?",
|
|
61
|
+
aria_label: "View style guide"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
case style_guide_navbar_position
|
|
65
|
+
when :start
|
|
66
|
+
nav_links.unshift(style_guide_link)
|
|
67
|
+
when :before_logout
|
|
68
|
+
logout_index = nav_links.find_index { |link| link[:type] == "button" }
|
|
69
|
+
if logout_index
|
|
70
|
+
nav_links.insert(logout_index, style_guide_link)
|
|
71
|
+
else
|
|
72
|
+
nav_links << style_guide_link
|
|
73
|
+
end
|
|
74
|
+
when Integer
|
|
75
|
+
nav_links.insert(style_guide_navbar_position, style_guide_link)
|
|
76
|
+
else # :end or default
|
|
77
|
+
nav_links << style_guide_link
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
class << self
|
|
83
|
+
attr_accessor :configuration
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.configure
|
|
87
|
+
self.configuration ||= Configuration.new
|
|
88
|
+
yield(configuration) if block_given?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def self.config
|
|
92
|
+
configuration || Configuration.new
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support"
|
|
4
|
+
require "action_dispatch"
|
|
5
|
+
require "rails/engine"
|
|
6
|
+
require "propshaft"
|
|
7
|
+
|
|
8
|
+
module NeonSakura
|
|
9
|
+
class Engine < ::Rails::Engine
|
|
10
|
+
# Initialize the engine
|
|
11
|
+
config.assets.enabled = true
|
|
12
|
+
|
|
13
|
+
# Ensure stylesheets are added to asset paths
|
|
14
|
+
initializer "neon_sakura.assets" do |app|
|
|
15
|
+
if app.config.respond_to?(:assets)
|
|
16
|
+
app.config.assets.paths << root.join("app", "assets", "stylesheets")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Add engine views to application view paths
|
|
21
|
+
initializer "neon_sakura.view_paths" do |app|
|
|
22
|
+
ActiveSupport.on_load(:action_controller) do
|
|
23
|
+
append_view_path(NeonSakura::Engine.root.join("app", "views"))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
ActiveSupport.on_load(:action_mailer) do
|
|
27
|
+
append_view_path(NeonSakura::Engine.root.join("app", "views"))
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Include helpers in ActionView
|
|
32
|
+
initializer "neon_sakura.helpers" do
|
|
33
|
+
ActiveSupport.on_load(:action_view) do
|
|
34
|
+
include NeonSakura::IconHelper
|
|
35
|
+
include NeonSakura::ThemeHelper
|
|
36
|
+
include NeonSakura::StylesheetHelper
|
|
37
|
+
include NeonSakura::ProfileHelper
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Auto-inject style guide link into navbar (development/test only)
|
|
42
|
+
initializer "neon_sakura.style_guide_navbar", after: :load_config_initializers do
|
|
43
|
+
if NeonSakura.configuration.enable_style_guide && (Rails.env.development? || Rails.env.test?)
|
|
44
|
+
NeonSakura.configuration.inject_style_guide_link!
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NeonSakura
|
|
4
|
+
module IconHelper
|
|
5
|
+
# Renders an icon partial with the given options
|
|
6
|
+
# @param name [String] The icon name (without underscore prefix)
|
|
7
|
+
# @param options [Hash] Options for the icon
|
|
8
|
+
# @option options [String] :css_class CSS class for the icon
|
|
9
|
+
# @option options [Boolean] :aria_hidden Whether the icon is aria-hidden
|
|
10
|
+
# @option options [String] :aria_label Label for accessibility
|
|
11
|
+
# All other options are passed through to the icon partial as local_assigns
|
|
12
|
+
# @return [String] Rendered icon partial
|
|
13
|
+
def render_icon(name, options = {})
|
|
14
|
+
# Set default CSS class if not provided
|
|
15
|
+
options[:css_class] ||= "w-4 h-4"
|
|
16
|
+
|
|
17
|
+
# Pass all options to the partial as local_assigns
|
|
18
|
+
render "shared/icons/#{name}", options
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Get a list of all available icons
|
|
22
|
+
# @return [Array<String>] Array of icon names
|
|
23
|
+
def available_icons
|
|
24
|
+
# Use gem's icon directory when running within the gem, otherwise use app's directory
|
|
25
|
+
root_path = defined?(NeonSakura::Engine) ? NeonSakura::Engine.root : Rails.root
|
|
26
|
+
icon_files = Dir.glob(File.join(root_path, "app", "views", "shared", "icons", "_*.html.erb"))
|
|
27
|
+
icon_files.map do |file|
|
|
28
|
+
File.basename(file, ".html.erb").gsub(/^_/, "")
|
|
29
|
+
end.sort
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Check if an icon exists
|
|
33
|
+
# @param name [String] The icon name to check
|
|
34
|
+
# @return [Boolean] Whether the icon exists
|
|
35
|
+
def icon_exists?(name)
|
|
36
|
+
# Use gem's icon directory when running within the gem, otherwise use app's directory
|
|
37
|
+
root_path = defined?(NeonSakura::Engine) ? NeonSakura::Engine.root : Rails.root
|
|
38
|
+
File.exist?(File.join(root_path, "app", "views", "shared", "icons", "_#{name}.html.erb"))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Renders a list of icons with consistent styling
|
|
42
|
+
# @param icon_names [Array<String>] Array of icon names
|
|
43
|
+
# @param options [Hash] Options for rendering
|
|
44
|
+
# @return [String] HTML string with rendered icons
|
|
45
|
+
def render_icon_list(icon_names, options = {})
|
|
46
|
+
css_class = options[:css_class] || "flex items-center gap-2"
|
|
47
|
+
icons_html = icon_names.map do |icon_name|
|
|
48
|
+
render_icon(icon_name, options.merge(css_class: options[:icon_css_class]))
|
|
49
|
+
end.join("\n")
|
|
50
|
+
|
|
51
|
+
"<div class=\"#{css_class}\">#{icons_html}</div>".html_safe
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NeonSakura
|
|
4
|
+
module ProfileHelper
|
|
5
|
+
# Generate Gravatar URL for email address
|
|
6
|
+
#
|
|
7
|
+
# @param email [String] Email address to generate Gravatar for
|
|
8
|
+
# @param size [Integer] Image size in pixels (default: 80)
|
|
9
|
+
# @return [String] Gravatar URL
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# gravatar_url("user@example.com") # => "https://www.gravatar.com/avatar/..."
|
|
13
|
+
# gravatar_url("user@example.com", size: 120) # => "https://www.gravatar.com/avatar/...?s=120"
|
|
14
|
+
#
|
|
15
|
+
# Note: MD5 is used for Gravatar GUID generation (public identifier, not sensitive data)
|
|
16
|
+
def gravatar_url(email, size: 80)
|
|
17
|
+
return "" if email.blank?
|
|
18
|
+
|
|
19
|
+
require "digest/md5"
|
|
20
|
+
hash = Digest::MD5.hexdigest(email.downcase.strip) # nosemgrep: weak-hashes-md5
|
|
21
|
+
"https://www.gravatar.com/avatar/#{hash}?s=#{size}&d=identicon"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NeonSakura
|
|
4
|
+
module StylesheetHelper
|
|
5
|
+
# Load all neon_sakura stylesheets in the correct cascade order
|
|
6
|
+
# This helper is needed because Propshaft doesn't process @import statements
|
|
7
|
+
# like Sprockets did. Each CSS file must be loaded individually.
|
|
8
|
+
#
|
|
9
|
+
# Usage in your layout:
|
|
10
|
+
# <%= neon_sakura_stylesheets %>
|
|
11
|
+
def neon_sakura_stylesheets
|
|
12
|
+
files = [
|
|
13
|
+
# Themes first - defines CSS variables
|
|
14
|
+
"theme-default",
|
|
15
|
+
"theme-purple",
|
|
16
|
+
"theme-green",
|
|
17
|
+
"theme-red",
|
|
18
|
+
# Utilities - uses CSS variables
|
|
19
|
+
"utility-reset",
|
|
20
|
+
"utility-layout",
|
|
21
|
+
"utility-sizing",
|
|
22
|
+
"utility-spacing",
|
|
23
|
+
"utility-colors",
|
|
24
|
+
"utility-borders",
|
|
25
|
+
"utility-typography",
|
|
26
|
+
"utility-gradients",
|
|
27
|
+
"utility-effects",
|
|
28
|
+
"utility-responsive",
|
|
29
|
+
# Loading indicators
|
|
30
|
+
"loading",
|
|
31
|
+
# Components and forms - uses both
|
|
32
|
+
"components",
|
|
33
|
+
"forms",
|
|
34
|
+
"pagy-tailwind"
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
safe_join(files.map { |file| stylesheet_link_tag(file, "data-turbo-track": "reload") })
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NeonSakura
|
|
4
|
+
module ThemeHelper
|
|
5
|
+
# Returns the current theme as a hash with :name and :mode keys
|
|
6
|
+
# Priority: session/cookies (from JS localStorage) -> default_theme from config
|
|
7
|
+
def current_theme
|
|
8
|
+
# Try to get theme from session (set by JavaScript via cookie)
|
|
9
|
+
theme_name = cookies[:theme_name] || session[:theme_name]
|
|
10
|
+
theme_mode = cookies[:theme_mode] || session[:theme_mode]
|
|
11
|
+
|
|
12
|
+
if theme_name && theme_mode
|
|
13
|
+
{ name: theme_name.to_s, mode: theme_mode.to_s }
|
|
14
|
+
else
|
|
15
|
+
# Fall back to default theme from configuration
|
|
16
|
+
NeonSakura.config.default_theme
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns HTML data attributes for the current theme
|
|
21
|
+
# Usage: <html <%= theme_data_attributes %>>
|
|
22
|
+
def theme_data_attributes
|
|
23
|
+
theme = current_theme
|
|
24
|
+
"data-theme-name='#{theme[:name]}' data-theme-mode='#{theme[:mode]}'".html_safe
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns available themes as JSON for JavaScript
|
|
28
|
+
def available_themes_json
|
|
29
|
+
NeonSakura.config.available_themes.to_json.html_safe
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns the number of available themes
|
|
33
|
+
def theme_count
|
|
34
|
+
NeonSakura.config.available_themes.length
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Helper to determine theme switcher mode
|
|
38
|
+
# Returns: :disabled (1 theme), :toggle (2 themes), :dropdown (3+ themes)
|
|
39
|
+
def theme_switcher_mode
|
|
40
|
+
case theme_count
|
|
41
|
+
when 0, 1
|
|
42
|
+
:disabled
|
|
43
|
+
when 2
|
|
44
|
+
:toggle
|
|
45
|
+
else
|
|
46
|
+
:dropdown
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Renders an icon partial for the current theme
|
|
51
|
+
# @param name [String] The icon name (without underscore prefix)
|
|
52
|
+
# @param options [Hash] Options for the icon
|
|
53
|
+
# All options are passed through to the icon partial as local_assigns
|
|
54
|
+
# @return [String] Rendered icon partial
|
|
55
|
+
def render_theme_icon(name, options = {})
|
|
56
|
+
# Set default CSS class if not provided
|
|
57
|
+
options[:css_class] ||= "w-4 h-4"
|
|
58
|
+
|
|
59
|
+
# Pass all options to the partial as local_assigns
|
|
60
|
+
render "shared/icons/#{name}", options
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NeonSakura
|
|
4
|
+
# ThemeImporter provides utilities to import themes from other CSS frameworks
|
|
5
|
+
# like Bootstrap, Material UI, etc. and convert them to neon_sakura's theme structure
|
|
6
|
+
class ThemeImporter
|
|
7
|
+
# Import a Bootstrap theme
|
|
8
|
+
# @param bootstrap_vars [Hash] Bootstrap color variables
|
|
9
|
+
# @return [Hash] neon_sakura theme structure
|
|
10
|
+
def self.from_bootstrap(bootstrap_vars)
|
|
11
|
+
{
|
|
12
|
+
name: bootstrap_vars[:name] || "bootstrap",
|
|
13
|
+
mode: bootstrap_vars[:mode] || detect_mode(bootstrap_vars),
|
|
14
|
+
label: bootstrap_vars[:label] || "Bootstrap Theme",
|
|
15
|
+
colors: {
|
|
16
|
+
accent: bootstrap_vars[:primary] || "#0d6efd",
|
|
17
|
+
background: bootstrap_vars[:"body-bg"] || "#ffffff",
|
|
18
|
+
surface: bootstrap_vars[:light] || "#f8f9fa",
|
|
19
|
+
text_primary: bootstrap_vars[:"body-color"] || "#212529",
|
|
20
|
+
text_secondary: bootstrap_vars[:secondary] || "#6c757d",
|
|
21
|
+
notification: bootstrap_vars[:success] || "#198754",
|
|
22
|
+
alert: bootstrap_vars[:danger] || "#dc3545",
|
|
23
|
+
border: bootstrap_vars[:border] || "#dee2e6"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Import a Material UI theme
|
|
29
|
+
# @param material_vars [Hash] Material UI palette variables
|
|
30
|
+
# @return [Hash] neon_sakura theme structure
|
|
31
|
+
def self.from_material_ui(material_vars)
|
|
32
|
+
palette = material_vars[:palette] || {}
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
name: material_vars[:name] || "material",
|
|
36
|
+
mode: material_vars[:mode] || (palette[:mode] == "dark" ? "dark" : "light"),
|
|
37
|
+
label: material_vars[:label] || "Material Theme",
|
|
38
|
+
colors: {
|
|
39
|
+
accent: palette.dig(:primary, :main) || "#1976d2",
|
|
40
|
+
background: palette.dig(:background, :default) || "#ffffff",
|
|
41
|
+
surface: palette.dig(:background, :paper) || "#ffffff",
|
|
42
|
+
text_primary: palette.dig(:text, :primary) || "rgba(0, 0, 0, 0.87)",
|
|
43
|
+
text_secondary: palette.dig(:text, :secondary) || "rgba(0, 0, 0, 0.6)",
|
|
44
|
+
notification: palette.dig(:success, :main) || "#2e7d32",
|
|
45
|
+
alert: palette.dig(:error, :main) || "#d32f2f",
|
|
46
|
+
border: palette.dig(:divider) || "rgba(0, 0, 0, 0.12)"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Import from a generic hash of color variables
|
|
52
|
+
# @param theme_hash [Hash] Arbitrary theme hash
|
|
53
|
+
# @return [Hash] neon_sakura theme structure
|
|
54
|
+
def self.from_hash(theme_hash)
|
|
55
|
+
{
|
|
56
|
+
name: theme_hash[:name] || "custom",
|
|
57
|
+
mode: theme_hash[:mode] || detect_mode(theme_hash),
|
|
58
|
+
label: theme_hash[:label] || "Custom Theme",
|
|
59
|
+
colors: {
|
|
60
|
+
accent: theme_hash[:accent] || theme_hash[:primary] || "#3b82f6",
|
|
61
|
+
background: theme_hash[:background] || theme_hash[:bg] || "#ffffff",
|
|
62
|
+
surface: theme_hash[:surface] || theme_hash[:card] || "#f9fafb",
|
|
63
|
+
text_primary: theme_hash[:text_primary] || theme_hash[:text] || "#111827",
|
|
64
|
+
text_secondary: theme_hash[:text_secondary] || "#6b7280",
|
|
65
|
+
notification: theme_hash[:notification] || theme_hash[:success] || "#10b981",
|
|
66
|
+
alert: theme_hash[:alert] || theme_hash[:error] || theme_hash[:danger] || "#ef4444",
|
|
67
|
+
border: theme_hash[:border] || "#e5e7eb"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Generate CSS custom properties for a theme
|
|
73
|
+
# @param theme [Hash] Theme structure from import methods
|
|
74
|
+
# @return [String] CSS custom properties
|
|
75
|
+
def self.to_css_variables(theme)
|
|
76
|
+
colors = theme[:colors]
|
|
77
|
+
css = ":root[data-theme-name=\"#{theme[:name]}\"][data-theme-mode=\"#{theme[:mode]}\"] {\n"
|
|
78
|
+
|
|
79
|
+
colors.each do |key, value|
|
|
80
|
+
css_var_name = key.to_s.gsub("_", "-")
|
|
81
|
+
css += " --color-#{css_var_name}: #{value};\n"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
css += "}\n"
|
|
85
|
+
css
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
# Detect light/dark mode based on background color brightness
|
|
91
|
+
# @param theme_vars [Hash] Theme color variables
|
|
92
|
+
# @return [String] "light" or "dark"
|
|
93
|
+
def self.detect_mode(theme_vars)
|
|
94
|
+
bg = theme_vars[:background] || theme_vars[:"body-bg"] || "#ffffff"
|
|
95
|
+
|
|
96
|
+
# Convert hex to RGB and calculate brightness
|
|
97
|
+
if bg.start_with?("#")
|
|
98
|
+
hex = bg.delete("#")
|
|
99
|
+
r = hex[0..1].to_i(16)
|
|
100
|
+
g = hex[2..3].to_i(16)
|
|
101
|
+
b = hex[4..5].to_i(16)
|
|
102
|
+
|
|
103
|
+
# Calculate perceived brightness (0-255)
|
|
104
|
+
brightness = (r * 0.299 + g * 0.587 + b * 0.114)
|
|
105
|
+
|
|
106
|
+
brightness > 127 ? "light" : "dark"
|
|
107
|
+
else
|
|
108
|
+
"light" # Default to light if we can't parse the color
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
data/lib/neon_sakura.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "neon_sakura/version"
|
|
4
|
+
require_relative "neon_sakura/configuration"
|
|
5
|
+
require_relative "neon_sakura/icon_helper"
|
|
6
|
+
require_relative "neon_sakura/theme_helper"
|
|
7
|
+
require_relative "neon_sakura/stylesheet_helper"
|
|
8
|
+
require_relative "neon_sakura/profile_helper"
|
|
9
|
+
require_relative "neon_sakura/engine"
|
|
10
|
+
|
|
11
|
+
module NeonSakura
|
|
12
|
+
class Error < StandardError; end
|
|
13
|
+
end
|
data/neon_sakura.gemspec
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require 'neon_sakura/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = 'neon_sakura'
|
|
9
|
+
spec.version = NeonSakura::VERSION
|
|
10
|
+
spec.authors = [ 'trex22' ]
|
|
11
|
+
spec.email = [ 'contact@jasonchalom.com' ]
|
|
12
|
+
|
|
13
|
+
spec.summary = 'Shared styling and theming components for Rails applications'
|
|
14
|
+
spec.description = 'A gem that extracts the styling and theming components from my Rails applications for reuse across multiple projects'
|
|
15
|
+
spec.homepage = 'https://github.com/trex22/neon_sakura'
|
|
16
|
+
spec.license = 'MIT'
|
|
17
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 3.4.7')
|
|
18
|
+
|
|
19
|
+
spec.metadata['source_code_uri'] = 'https://github.com/trex22/neon_sakura'
|
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/trex22/neon_sakura/blob/main/CHANGELOG.md'
|
|
21
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/trex22/neon_sakura/issues'
|
|
22
|
+
|
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
25
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
spec.bindir = 'exe'
|
|
29
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
30
|
+
spec.require_paths = [ 'lib' ]
|
|
31
|
+
|
|
32
|
+
# Dependencies
|
|
33
|
+
spec.add_dependency 'rails', '>= 8.0.0'
|
|
34
|
+
spec.add_dependency 'propshaft', '>= 1.3.1'
|
|
35
|
+
|
|
36
|
+
# Development dependencies
|
|
37
|
+
spec.add_development_dependency 'brakeman', '~> 7.1.1'
|
|
38
|
+
spec.add_development_dependency 'bundler', '~> 2.7.2'
|
|
39
|
+
spec.add_development_dependency 'bundler-audit', '~> 0.9.3'
|
|
40
|
+
spec.add_development_dependency 'minitest', '~> 6.0.1'
|
|
41
|
+
spec.add_development_dependency 'minitest-focus', '~> 1.4.0'
|
|
42
|
+
spec.add_development_dependency 'minitest-reporters', '~> 1.7.1'
|
|
43
|
+
spec.add_development_dependency 'mocha', '~> 3.0.1'
|
|
44
|
+
spec.add_development_dependency 'pry', '~> 0.16.0'
|
|
45
|
+
spec.add_development_dependency 'rake', '~> 13.3.1'
|
|
46
|
+
spec.add_development_dependency 'rubocop-rails-omakase', '~> 1.1.0'
|
|
47
|
+
spec.add_development_dependency 'simplecov', '~> 0.22.0'
|
|
48
|
+
spec.add_development_dependency 'timecop', '~> 0.9.10'
|
|
49
|
+
spec.add_development_dependency 'webmock', '~> 3.26.1'
|
|
50
|
+
end
|
data/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "neon_sakura",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "A Rails engine for reusable styling and theming components",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"lint:css": "stylelint 'app/assets/stylesheets/**/*.css'",
|
|
8
|
+
"lint:css:fix": "stylelint 'app/assets/stylesheets/**/*.css' --fix"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"stylelint": "^16.12.0",
|
|
12
|
+
"stylelint-config-standard": "^36.0.1"
|
|
13
|
+
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18.0.0",
|
|
16
|
+
"yarn": ">=1.22.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Neon Sakura post-merge hook
|
|
4
|
+
# Auto-runs database migrations and asset precompilation after merges
|
|
5
|
+
|
|
6
|
+
echo "🔄 Running post-merge tasks..."
|
|
7
|
+
|
|
8
|
+
# Check if we're in a Rails app
|
|
9
|
+
if [ ! -f "Gemfile" ]; then
|
|
10
|
+
echo "❌ Not a Rails application, skipping post-merge tasks"
|
|
11
|
+
exit 0
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Colors for output
|
|
15
|
+
RED='\033[0;31m'
|
|
16
|
+
GREEN='\033[0;32m'
|
|
17
|
+
YELLOW='\033[1;33m'
|
|
18
|
+
BLUE='\033[0;34m'
|
|
19
|
+
NC='\033[0m' # No Color
|
|
20
|
+
|
|
21
|
+
# Track if any tasks were performed
|
|
22
|
+
tasks_performed=0
|
|
23
|
+
|
|
24
|
+
# 1. Check for new migrations and run them
|
|
25
|
+
check_and_run_migrations() {
|
|
26
|
+
echo -e "${BLUE}Checking for new migrations...${NC}"
|
|
27
|
+
|
|
28
|
+
# Check if there are pending migrations
|
|
29
|
+
pending_migrations=$(bundle exec rails db:migrate:status 2>/dev/null | grep "^\s*down" | wc -l)
|
|
30
|
+
|
|
31
|
+
if [ "$pending_migrations" -gt 0 ]; then
|
|
32
|
+
echo -e "${YELLOW}Found $pending_migrations pending migration(s)${NC}"
|
|
33
|
+
echo -e "${BLUE}Running migrations...${NC}"
|
|
34
|
+
|
|
35
|
+
if bundle exec rails db:migrate; then
|
|
36
|
+
echo -e "${GREEN}✅ Migrations completed successfully${NC}"
|
|
37
|
+
tasks_performed=$((tasks_performed + 1))
|
|
38
|
+
else
|
|
39
|
+
echo -e "${RED}❌ Migration failed${NC}"
|
|
40
|
+
echo -e "${RED}Please run 'bundle exec rails db:migrate' manually${NC}"
|
|
41
|
+
return 1
|
|
42
|
+
fi
|
|
43
|
+
else
|
|
44
|
+
echo -e "${GREEN}✅ No pending migrations${NC}"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
return 0
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# 2. Check for Gemfile changes and run bundle install
|
|
51
|
+
check_and_bundle_install() {
|
|
52
|
+
echo -e "${BLUE}Checking for Gemfile changes...${NC}"
|
|
53
|
+
|
|
54
|
+
# Check if Gemfile or Gemfile.lock changed in the merge
|
|
55
|
+
if git diff HEAD@{1} HEAD --name-only | grep -E "^Gemfile|^Gemfile\.lock$" > /dev/null; then
|
|
56
|
+
echo -e "${YELLOW}Gemfile changes detected${NC}"
|
|
57
|
+
echo -e "${BLUE}Running bundle install...${NC}"
|
|
58
|
+
|
|
59
|
+
if bundle install; then
|
|
60
|
+
echo -e "${GREEN}✅ Bundle install completed${NC}"
|
|
61
|
+
tasks_performed=$((tasks_performed + 1))
|
|
62
|
+
else
|
|
63
|
+
echo -e "${RED}❌ Bundle install failed${NC}"
|
|
64
|
+
echo -e "${RED}Please run 'bundle install' manually${NC}"
|
|
65
|
+
return 1
|
|
66
|
+
fi
|
|
67
|
+
else
|
|
68
|
+
echo -e "${GREEN}✅ No Gemfile changes detected${NC}"
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
return 0
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# 3. Check for asset changes and precompile if needed
|
|
75
|
+
check_and_precompile_assets() {
|
|
76
|
+
echo -e "${BLUE}Checking for asset changes...${NC}"
|
|
77
|
+
|
|
78
|
+
# Check if asset files changed
|
|
79
|
+
asset_changes=$(git diff HEAD@{1} HEAD --name-only | grep -E "^app/assets/|^app/javascript/|^config/importmap\.rb|^config/routes\.rb" | wc -l)
|
|
80
|
+
|
|
81
|
+
if [ "$asset_changes" -gt 0 ] && [ -f "config/application.rb" ]; then
|
|
82
|
+
# Check if we're using asset pipeline
|
|
83
|
+
if grep -q "propshaft" Gemfile 2>/dev/null; then
|
|
84
|
+
echo -e "${YELLOW}Asset changes detected${NC}"
|
|
85
|
+
echo -e "${BLUE}Precompiling assets...${NC}"
|
|
86
|
+
|
|
87
|
+
# Only precompile in development if specifically configured
|
|
88
|
+
if bundle exec rails assets:precompile RAILS_ENV=development > /dev/null 2>&1; then
|
|
89
|
+
echo -e "${GREEN}✅ Assets precompiled successfully${NC}"
|
|
90
|
+
tasks_performed=$((tasks_performed + 1))
|
|
91
|
+
else
|
|
92
|
+
echo -e "${YELLOW}⚠️ Asset precompilation skipped (likely not needed in development)${NC}"
|
|
93
|
+
fi
|
|
94
|
+
else
|
|
95
|
+
echo -e "${GREEN}✅ No asset pipeline detected${NC}"
|
|
96
|
+
fi
|
|
97
|
+
else
|
|
98
|
+
echo -e "${GREEN}✅ No asset changes detected${NC}"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
return 0
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Run all checks
|
|
105
|
+
failed_tasks=0
|
|
106
|
+
|
|
107
|
+
if ! check_and_bundle_install; then
|
|
108
|
+
failed_tasks=$((failed_tasks + 1))
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
if ! check_and_run_migrations; then
|
|
112
|
+
failed_tasks=$((failed_tasks + 1))
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
if ! check_and_precompile_assets; then
|
|
116
|
+
failed_tasks=$((failed_tasks + 1))
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# Summary
|
|
120
|
+
echo ""
|
|
121
|
+
if [ $failed_tasks -gt 0 ]; then
|
|
122
|
+
echo -e "${RED}❌ $failed_tasks post-merge task(s) failed${NC}"
|
|
123
|
+
echo -e "${YELLOW}Please address the issues above manually${NC}"
|
|
124
|
+
elif [ $tasks_performed -gt 0 ]; then
|
|
125
|
+
echo -e "${GREEN}🎉 $tasks_performed post-merge task(s) completed successfully!${NC}"
|
|
126
|
+
else
|
|
127
|
+
echo -e "${GREEN}✅ No post-merge tasks needed${NC}"
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# Note: We don't exit with error code for post-merge hooks
|
|
131
|
+
# as they shouldn't block the merge operation
|
|
132
|
+
exit 0
|