Class: RubyCms::Generators::InstallGenerator

Inherits:
Rails::Generators::Base
  • Object
show all
Defined in:
lib/generators/ruby_cms/install_generator.rb

Constant Summary collapse

NEXT_STEPS_MESSAGE =
"\n\u2713 RubyCMS install complete.\n\nNext steps (if not already done):\n- rails db:migrate\n- rails ruby_cms:seed_permissions (includes manage_visitor_errors and manage_analytics)\n- rails ruby_cms:setup_admin (or: rails ruby_cms:grant_manage_admin [email protected])\n- To seed content blocks from YAML: add content under content_blocks in config/locales/<locale>.yml, then run rails ruby_cms:content_blocks:seed (or call it from db/seeds.rb).\n\nNotes:\n- If the host uses /admin already, remove or change those routes.\n- Avoid root to: redirect(\"/admin\") \u2014 use a real root or ruby_cms.unauthorized_redirect_path.\n- Review config/initializers/ruby_cms.rb (session, CSP).\n- Add 'css: bin/rails tailwindcss:watch' to Procfile.dev for Tailwind in development.\n- Visit /admin (sign in as the admin you configured).\n\nTracking:\n- Visitor errors: Automatically captured via ApplicationController (see /admin/visitor_errors)\n- Page views (Ahoy): Include RubyCms::PageTracking in your public controllers to track page views\n  Example: class PagesController < ApplicationController; include RubyCms::PageTracking; end\n- Analytics: View visit/event data in Ahoy tables (ahoy_visits, ahoy_events)\n"
SKIP_VIEW_DIRS =

Directories to skip when scanning for page templates

%w[layouts shared mailers components admin].freeze

Instance Method Summary collapse

Instance Method Details

#add_catch_all_routeObject



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/generators/ruby_cms/install_generator.rb', line 96

def add_catch_all_route
  routes_path = Rails.root.join("config/routes.rb")
  return unless routes_path.exist?

  content = File.read(routes_path)
  return if content.include?("ruby_cms/errors#not_found")

  # Add catch-all route at the end of the routes block (before final 'end')
  catch_all = "\n    # RubyCMS: Catch-all route for 404 error tracking (must be LAST)\n    match \"*path\", to: \"ruby_cms/errors#not_found\", via: :all,\n          constraints: ->(req) { !req.path.start_with?(\"/rails/\", \"/assets/\") }\n  ROUTE\n\n  # Insert before the last 'end' in the file\n  gsub_file routes_path, /(\\nend)\\s*\\z/ do\n    \"\#{catch_all}end\\n\"\n  end\n  say \"\u2713 Catch-all route: Added for 404 error tracking\", :green\nrescue StandardError => e\n  say \"\u26A0 Catch-all route: Could not add automatically: \#{e.message}. \" \\\n      \"Add manually at the END of routes.rb:\\n  \" \\\n      'match \"*path\", to: \"ruby_cms/errors#not_found\", via: :all, ' \\\n      'constraints: ->(req) { !req.path.start_with?(\"/rails/\", \"/assets/\") }',\n      :yellow\nend\n"

#add_current_user_to_authenticationObject



138
139
140
141
142
143
144
145
146
147
148
# File 'lib/generators/ruby_cms/install_generator.rb', line 138

def add_current_user_to_authentication
  auth_path = Rails.root.join("app/controllers/concerns/authentication.rb")
  return unless auth_path.exist?
  return if File.read(auth_path).include?("def current_user")

  gsub_file auth_path, "    helper_method :authenticated?\n",
            "    helper_method :authenticated?, :current_user\n"
  inject_into_file auth_path, after: "  private\n" do
    "    def current_user\n      Current.user\n    end\n\n"
  end
end

#add_page_tracking_to_home_controllerObject



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/generators/ruby_cms/install_generator.rb', line 171

def add_page_tracking_to_home_controller
  home_path = Rails.root.join("app/controllers/home_controller.rb")
  return unless home_path.exist?

  content = File.read(home_path)
  return if content.include?("RubyCms::PageTracking")

  inject_into_file home_path, after: /class HomeController.*\n/ do
    "  include RubyCms::PageTracking\n"
  end

  say "✓ Page tracking: Added RubyCms::PageTracking to HomeController", :green
rescue StandardError => e
  say "⚠ Page tracking: Could not add to HomeController: #{e.message}. " \
      "Add manually: include RubyCms::PageTracking",
      :yellow
end

#add_permittable_to_userObject



124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/generators/ruby_cms/install_generator.rb', line 124

def add_permittable_to_user
  user_path = Rails.root.join("app/models/user.rb")
  unless File.exist?(user_path)
    say "Skipping User: app/models/user.rb not found.", :yellow
    return
  end

  return if File.read(user_path).include?("RubyCms::Permittable")

  inject_into_file user_path, after: /class User .*\n/ do
    "  include RubyCms::Permittable\n"
  end
end

#add_visitor_error_captureObject



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/generators/ruby_cms/install_generator.rb', line 150

def add_visitor_error_capture
  ac_path = Rails.root.join("app/controllers/application_controller.rb")
  return unless ac_path.exist?

  content = File.read(ac_path)
  return if content.include?("RubyCms::VisitorErrorCapture")

  to_inject = "  include RubyCms::VisitorErrorCapture\n"
  to_inject += "  rescue_from StandardError, with: :handle_visitor_error\n" \
    unless content.include?("rescue_from StandardError")

  inject_into_file ac_path, after: /class ApplicationController.*\n/ do
    to_inject
  end
  say "✓ Visitor error capture: Added to ApplicationController", :green
rescue StandardError => e
  say "⚠ Visitor error capture: Could not add to ApplicationController: #{e.message}. " \
      "Add manually: include RubyCms::VisitorErrorCapture and rescue_from StandardError, with: :handle_visitor_error",
      :yellow
end

#copy_fallback_cssObject



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/generators/ruby_cms/install_generator.rb', line 189

def copy_fallback_css
  src_dir = RubyCms::Engine.root.join("app/assets/stylesheets/ruby_cms")
  dest_dir = Rails.root.join("app/assets/stylesheets/ruby_cms")

  return unless src_dir.exist?

  FileUtils.mkdir_p(dest_dir)
  copy_admin_css(dest_dir)
  # Don't copy component files - only the compiled admin.css is needed
  # copy_components_css(src_dir, dest_dir)
  say "✓ Task css/copy: Combined component CSS into " \
      "app/assets/stylesheets/ruby_cms/admin.css", :green
rescue StandardError => e
  say "⚠ Task css/copy: Could not copy CSS files: #{e.message}.", :yellow
end

#create_admin_layoutObject



205
206
207
208
209
210
211
212
213
214
# File 'lib/generators/ruby_cms/install_generator.rb', line 205

def create_admin_layout
  layout_path = Rails.root.join("app/views/layouts/admin.html.erb")
  return if File.exist?(layout_path)

  template "admin.html.erb", layout_path.to_s
  say "✓ Layout admin: Created app/views/layouts/admin.html.erb", :green
rescue StandardError => e
  say "⚠ Layout admin: Could not create admin.html.erb: #{e.message}. " \
      "Create it manually using the RubyCMS template.", :yellow
end

#create_initializerObject



87
88
89
90
# File 'lib/generators/ruby_cms/install_generator.rb', line 87

def create_initializer
  @detected_pages = detect_page_templates
  template "ruby_cms.rb", "config/initializers/ruby_cms.rb"
end

#install_action_textObject



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/generators/ruby_cms/install_generator.rb', line 260

def install_action_text
  migrate_dir = Rails.root.join("db/migrate")
  return unless migrate_dir.directory?

  if action_text_already_installed?(migrate_dir)
    say "ℹ Task action_text: Existing Action Text setup detected. Skipping action_text:install.",
        :cyan
  else
    say "ℹ Task action_text: Installing Action Text for rich text/image content blocks.", :cyan
    run "bin/rails action_text:install"
    say "✓ Task action_text: Installed Action Text", :green
  end

  configure_action_text_assets
rescue StandardError => e
  say "⚠ Task action_text: Could not install: #{e.message}. Rich text will be disabled.",
      :yellow
end

#install_ahoyObject



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/generators/ruby_cms/install_generator.rb', line 241

def install_ahoy
  if ahoy_already_installed?
    say "ℹ Task ahoy: Existing Ahoy setup detected (tables or migrations). Skipping ahoy:install.",
        :cyan
    configure_ahoy_server_side_only
    return
  end

  say "ℹ Task ahoy: Installing Ahoy for visit/event tracking.", :cyan
  run "bin/rails generate ahoy:install"
  add_ahoy_security_fields_migration
  configure_ahoy_server_side_only
  say "✓ Task ahoy: Installed Ahoy (visits, events, tracking)", :green
rescue StandardError => e
  say "⚠ Task ahoy: Could not install: #{e.message}. " \
      "Run 'rails g ahoy:install' manually.",
      :yellow
end

#install_ruby_uiObject



490
491
492
493
494
495
496
497
498
499
500
501
# File 'lib/generators/ruby_cms/install_generator.rb', line 490

def install_ruby_ui
  gemfile = Rails.root.join("Gemfile")
  gemfile_content = File.read(gemfile)
  return if ruby_ui_in_gemfile?(gemfile_content)

  add_ruby_ui_gem
rescue StandardError => e
  say "⚠ Task ruby_ui: Could not add: #{e.message}. " \
      "Run 'bundle add ruby_ui --group development --require false' manually.",
      :yellow
  nil
end

#install_tailwindObject



446
447
448
449
450
451
452
453
454
455
# File 'lib/generators/ruby_cms/install_generator.rb', line 446

def install_tailwind
  gemfile = Rails.root.join("Gemfile")
  tailwind_css = detect_tailwind_entry_css_path

  install_tailwind_if_needed(gemfile, tailwind_css)
  configure_tailwind(tailwind_css)
rescue StandardError => e
  say "⚠ Task tailwind: Could not install: #{e.message}. Add tailwindcss-rails manually.",
      :yellow
end

#mount_engineObject



92
93
94
# File 'lib/generators/ruby_cms/install_generator.rb', line 92

def mount_engine
  route 'mount RubyCms::Engine => "/"'
end

#run_authenticationObject



34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/generators/ruby_cms/install_generator.rb', line 34

def run_authentication
  user_path = Rails.root.join("app/models/user.rb")
  return if File.exist?(user_path)

  say "ℹ Task authentication: User model not found. " \
      "Running 'rails g authentication' (Rails 8+).", :cyan
  @authentication_attempted = true
  run "bin/rails generate authentication"
  run "bundle install"
rescue StandardError => e
  say "⚠ Could not run 'rails g authentication': #{e.message}.", :yellow
  say "   On Rails 8+, run 'rails g authentication' and 'bundle install' manually.", :yellow
end

#run_migrateObject



969
970
971
972
973
974
975
976
977
978
# File 'lib/generators/ruby_cms/install_generator.rb', line 969

def run_migrate
  say "ℹ Task db:migrate: Running db:migrate.", :cyan
  success = run("bin/rails db:migrate")
  raise "db:migrate failed" unless success

  say "✓ Task db:migrate: Completed", :green
rescue StandardError => e
  say "⚠ Task db:migrate: Failed: #{e.message}. Run rails db:create db:migrate if needed.",
      :yellow
end

#run_ruby_ui_installObject



503
504
505
506
507
508
509
510
# File 'lib/generators/ruby_cms/install_generator.rb', line 503

def run_ruby_ui_install
  gemfile = Rails.root.join("Gemfile")
  gemfile_content = File.read(gemfile)
  return unless ruby_ui_in_gemfile?(gemfile_content)
  return if ruby_ui_already_installed?

  install_ruby_ui_generator
end

#run_seed_permissionsObject



980
981
982
983
984
985
986
# File 'lib/generators/ruby_cms/install_generator.rb', line 980

def run_seed_permissions
  say "ℹ Task permissions: Seeding RubyCMS permissions.", :cyan
  success = seed_permissions_via_open3
  say_seed_permissions_outcome(success)
rescue StandardError => e
  say_seed_permissions_error(e)
end

#run_setup_adminObject



988
989
990
991
992
993
994
995
# File 'lib/generators/ruby_cms/install_generator.rb', line 988

def run_setup_admin
  return if skip_setup_admin_due_to_existing_admin?
  return unless setup_admin_tty?

  run_setup_admin_task
rescue StandardError => e
  say_setup_admin_error(e)
end

#show_next_stepsObject



1119
1120
1121
# File 'lib/generators/ruby_cms/install_generator.rb', line 1119

def show_next_steps
  say NEXT_STEPS_MESSAGE, :green
end

#verify_application_controllerObject



73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/generators/ruby_cms/install_generator.rb', line 73

def verify_application_controller
  ac_path = Rails.root.join("app/controllers/application_controller.rb")
  return unless ac_path.exist?

  content = File.read(ac_path)
  return if content.include?("include Authentication")
  return if @authentication_warning_shown

  say "ℹ Task authentication: ApplicationController does not include " \
      "Authentication. Ensure /admin is protected.",
      :yellow
  @authentication_warning_shown = true
end

#verify_authObject



48
49
50
51
52
# File 'lib/generators/ruby_cms/install_generator.rb', line 48

def verify_auth
  verify_user_model
  verify_session_model
  verify_application_controller
end

#verify_session_modelObject



65
66
67
68
69
70
71
# File 'lib/generators/ruby_cms/install_generator.rb', line 65

def verify_session_model
  return if defined?(::Session)

  say "ℹ Task authentication: Session model not found. " \
      "The host app should provide authentication.",
      :yellow
end

#verify_user_modelObject



54
55
56
57
58
59
60
61
62
63
# File 'lib/generators/ruby_cms/install_generator.rb', line 54

def verify_user_model
  return if defined?(::User)

  message = if @authentication_attempted
              "Run 'rails db:migrate' if the authentication generator succeeded."
            else
              "User model not found. Run 'rails g authentication' before using /admin."
            end
  say "ℹ Task authentication: #{message}", :yellow
end