Class: Seams::Generators::InstallGenerator
- Inherits:
-
Rails::Generators::Base
- Object
- Rails::Generators::Base
- Seams::Generators::InstallGenerator
- Includes:
- HostInjector
- Defined in:
- lib/generators/seams/install/install_generator.rb
Overview
Adds the Seams framework to a host Rails application:
- config/initializers/seams.rb (configure adapters)
- config/initializers/seams_engines.rb (load engines/* into autoload)
- engines/.keep (where future engines live)
- lib/tasks/seams.rake (rake namespace)
Run with: bin/rails generate seams:install
Instance Method Summary collapse
- #append_engines_to_eager_load ⇒ Object
- #create_architecture_doc ⇒ Object
- #create_bin_seams ⇒ Object
- #create_ci_workflow ⇒ Object
- #create_deployment_templates ⇒ Object
- #create_engines_directory ⇒ Object
-
#create_helper_scripts ⇒ Object
Phase 1.5 — per-host helper scripts and architecture doc.
-
#create_herb_config ⇒ Object
--- Quality toolchain (default on; opt out per tool) -----------------.
- #create_host_rubocop ⇒ Object
- #create_initializer ⇒ Object
- #create_lefthook_config ⇒ Object
- #create_rake_tasks ⇒ Object
- #create_ruby_version ⇒ Object
- #create_strong_migrations_initializer ⇒ Object
- #install_quality_gems ⇒ Object
-
#post_install_message ⇒ Object
rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength.
- #wire_engines_into_application_rb ⇒ Object
- #wire_into_host ⇒ Object
Methods included from HostInjector
#host_inject_gem, #host_inject_include_in_application_controller, #host_inject_include_in_user, #host_inject_mount, #host_uninject_gem, #host_uninject_include, #host_uninject_mount, #routes_draw_anchor
Instance Method Details
#append_engines_to_eager_load ⇒ Object
51 52 53 |
# File 'lib/generators/seams/install/install_generator.rb', line 51 def append_engines_to_eager_load template "seams_engines.rb.tt", "config/seams_engines.rb" end |
#create_architecture_doc ⇒ Object
193 194 195 |
# File 'lib/generators/seams/install/install_generator.rb', line 193 def create_architecture_doc template_if_missing "doc/ARCHITECTURE.md.tt", "doc/ARCHITECTURE.md" end |
#create_bin_seams ⇒ Object
143 144 145 146 147 |
# File 'lib/generators/seams/install/install_generator.rb', line 143 def create_bin_seams template "bin_seams.tt", "bin/seams" full_path = File.join(destination_root, "bin/seams") File.chmod(0o755, full_path) if File.exist?(full_path) end |
#create_ci_workflow ⇒ Object
127 128 129 |
# File 'lib/generators/seams/install/install_generator.rb', line 127 def create_ci_workflow template "ci.yml.tt", ".github/workflows/ci.yml" end |
#create_deployment_templates ⇒ Object
131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/generators/seams/install/install_generator.rb', line 131 def create_deployment_templates # Skip any file the host already has — Rails 8 ships its own # Dockerfile and bin/docker-entrypoint. template_if_missing "Dockerfile.tt", "Dockerfile" template_if_missing "docker-entrypoint.tt", "bin/docker-entrypoint" template_if_missing "Procfile.tt", "Procfile" template_if_missing "deploy.yml.tt", "config/deploy.yml" full = File.join(destination_root, "bin/docker-entrypoint") File.chmod(0o755, full) if File.exist?(full) end |
#create_engines_directory ⇒ Object
42 43 44 45 |
# File 'lib/generators/seams/install/install_generator.rb', line 42 def create_engines_directory empty_directory "engines" create_file "engines/.keep" end |
#create_helper_scripts ⇒ Object
Phase 1.5 — per-host helper scripts and architecture doc.
185 186 187 188 189 190 191 |
# File 'lib/generators/seams/install/install_generator.rb', line 185 def create_helper_scripts template_if_missing "script/collate_coverage.rb.tt", "script/collate_coverage.rb" template_if_missing "script/run_affected_tests.sh.tt", "script/run_affected_tests.sh" runner = File.join(destination_root, "script/run_affected_tests.sh") File.chmod(0o755, runner) if File.exist?(runner) end |
#create_herb_config ⇒ Object
--- Quality toolchain (default on; opt out per tool) -----------------
151 152 153 154 155 |
# File 'lib/generators/seams/install/install_generator.rb', line 151 def create_herb_config return unless [:herb] template_if_missing "herb.yml.tt", ".herb.yml" end |
#create_host_rubocop ⇒ Object
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/generators/seams/install/install_generator.rb', line 87 def create_host_rubocop # Three cases: # 1. Host has no .rubocop.yml → write the seams baseline. # 2. Host already has one → don't overwrite, but inject an # `engines/**/*` Exclude so host RuboCop (which may use # rubocop-rails-omakase or another flavor) doesn't lint # engine code under rules written for application code. # Engines have their own self-contained .rubocop.yml. host_path = File.join(destination_root, ".rubocop.yml") unless File.exist?(host_path) template "rubocop.yml.tt", ".rubocop.yml" return end return if File.read(host_path).include?("engines/**/*") say " inject .rubocop.yml (Exclude engines + seams.rake)", :green append_to_file(host_path, <<~YML) # Engines have their own self-contained .rubocop.yml. Linting them # from the host runs gem-style code under whatever flavor of rules # the host uses (omakase / etc.) and produces noisy false positives. # The gem-generated lib/tasks/seams.rake is excluded for the same # reason. AllCops: Exclude: - "engines/**/*" - "lib/tasks/seams.rake" YML end |
#create_initializer ⇒ Object
38 39 40 |
# File 'lib/generators/seams/install/install_generator.rb', line 38 def create_initializer template "seams.rb.tt", "config/initializers/seams.rb" end |
#create_lefthook_config ⇒ Object
163 164 165 166 167 |
# File 'lib/generators/seams/install/install_generator.rb', line 163 def create_lefthook_config return unless [:lefthook] template_if_missing "lefthook.yml.tt", "lefthook.yml" end |
#create_rake_tasks ⇒ Object
47 48 49 |
# File 'lib/generators/seams/install/install_generator.rb', line 47 def create_rake_tasks template "seams.rake.tt", "lib/tasks/seams.rake" end |
#create_ruby_version ⇒ Object
118 119 120 121 122 123 124 125 |
# File 'lib/generators/seams/install/install_generator.rb', line 118 def create_ruby_version # The host CI workflow does `ruby-version: ".ruby-version"`, so the # host needs a `.ruby-version` file. Rails 8's `rails new` doesn't # ship one. Don't overwrite if the host has pinned their own. return if File.exist?(File.join(destination_root, ".ruby-version")) template "ruby-version.tt", ".ruby-version" end |
#create_strong_migrations_initializer ⇒ Object
157 158 159 160 161 |
# File 'lib/generators/seams/install/install_generator.rb', line 157 def create_strong_migrations_initializer return unless [:strong_migrations] template_if_missing "strong_migrations.rb.tt", "config/initializers/strong_migrations.rb" end |
#install_quality_gems ⇒ Object
169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/generators/seams/install/install_generator.rb', line 169 def install_quality_gems # The CI workflow (and lefthook) shell out to these, so make sure the # gems are in the host Gemfile. All idempotent — skipped if present. host_inject_gem("rubocop", group: :development) host_inject_gem("brakeman", group: :development) host_inject_gem("bundler-audit", group: :development) host_inject_gem("herb", group: :development) if [:herb] host_inject_gem("lefthook", group: :development) if [:lefthook] # strong_migrations is a dev/CI guard — it vets migrations where they're # written and in CI, and is kept OUT of the production path. The # initializer is guarded with `if defined?(StrongMigrations)` so a # production boot (where the gem isn't loaded) is unaffected. host_inject_gem("strong_migrations", group: %i[development test]) if [:strong_migrations] end |
#post_install_message ⇒ Object
rubocop:disable-next Metrics/AbcSize, Metrics/MethodLength
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/generators/seams/install/install_generator.rb', line 223 def say "" say " Seams is installed. Generate your first engine with:", :green say " bin/seams core (or bin/rails generate seams:core)" say "" say " Quality toolchain (installed by default — opt out with --no-<tool>):", :yellow say " rubocop + brakeman + bundle-audit (in CI)" \ "#{", herb (HTML+ERB lint)" if [:herb]}" \ "#{", strong_migrations" if [:strong_migrations]}" say " HTML+ERB linting (herb) is opt-in — add --herb (arm64-darwin only).", :yellow unless [:herb] say " bundle install then bin/rails db:prepare" if [:lefthook] say " Activate the git hooks (pre-commit lint, pre-push specs):", :yellow say " bundle exec lefthook install" end say "" say " Canonical generators (run in this order):", :yellow say " bin/seams core - Core engine (concerns, audit log)" say " bin/seams auth - Auth engine (Identity, sessions, OAuth)" say " bin/seams accounts - Accounts engine (tenant + Membership + system actor)" say " bin/seams notifications - Notifications engine" say " bin/seams billing - Billing engine" say " bin/seams teams - Teams engine" say "" say " Optional engines (generate after the canonical six are in place):", :yellow say " bin/seams admin - Admin engine (Administrate-backed dashboards" say " for the canonical models, Pundit-gated," say " audit-log auto-write). Requires auth + accounts." say " bin/seams design - Design engine (ui_* component partials," say " Tailwind v4 tokens, Design::FormBuilder)." say " Non-isolated: ui_* helpers resolve host-wide." say "" say " Follow-up generators (extend an already-installed engine):", :yellow say " bin/rails generate seams:auth:add_oauth_provider <name>" say " - add a new OAuth provider adapter" say " (e.g. linkedin, apple, microsoft)" say "" say " Other useful commands:", :yellow say " bin/seams list - list engines + their events" say " bin/seams resolve --eject <engine>/<file>" say " - mark a host file as host-owned" say " (skipped on regenerate)" say " bin/seams resolve --list-markers <engine>" say " - list insertion-point markers" say " bin/seams resolve --list-ejected - list every ejected file under engines/" say "" say " Recommended order: core -> auth -> accounts -> notifications -> billing -> teams.", :yellow say " Optional: append `admin` last for an Administrate-backed admin surface.", :yellow say " See doc/reference/CURRENT_ATTRIBUTES.md (after install) for the per-request namespace cascade.", :yellow say " See doc/how-to/WRITING_FOLLOW_UP_GENERATORS.md to write your own follow-up generator.", :yellow say "" end |
#wire_engines_into_application_rb ⇒ Object
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/generators/seams/install/install_generator.rb', line 55 def wire_engines_into_application_rb # Each engine under engines/ must be required BEFORE # Rails.application.initialize! so its Railtie registers paths # (db/migrate, app/*) and initializers with the host. The # `require_relative` is injected directly after Bundler.require. application_rb = File.join(destination_root, "config/application.rb") return unless File.exist?(application_rb) snippet = %(require_relative "seams_engines") contents = File.read(application_rb) return if contents.include?(snippet) # The default Rails 8 application.rb contains # `Bundler.require(*Rails.groups)` verbatim. If a host has # customised it (Rails 4-style asset groups, multi-arg # Bundler.require, brace-form do-block, trailing comment, ...), # the regex misses and Thor silently warns "File unchanged!" # — leaving the host bootable but with engines never required. # That is the worst kind of failure: silent + production-bug. # Print a loud red warning so the user knows to wire it by hand. anchor = /Bundler\.require\(\*Rails\.groups\)\n/ unless contents.match?(anchor) say " WARNING config/application.rb has no `Bundler.require(*Rails.groups)` line — " \ "add `#{snippet}` manually after Bundler.require so engines load before initialize!", :red return end say " inject config/application.rb (require_relative \"seams_engines\")", :green inject_into_file(application_rb, "\n#{snippet}\n", after: anchor) end |
#wire_into_host ⇒ Object
197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/generators/seams/install/install_generator.rb', line 197 def wire_into_host # Auto-add seams to the host Gemfile if not already present — # covers the `gem install seams` global-install path. Pinned to # a pessimistic 0.x to keep major-version bumps explicit. host_inject_gem("seams", "~> #{Seams::VERSION}") # Every Seams host needs rspec-rails so the per-engine # spec/dummy specs can actually run. Idempotent — skipped if # the host already has these gems. The range lets bundler pick # rspec-rails 8.x on Rails 7.2+ and fall back to 7.1.x on Rails 7.1. host_inject_gem("rspec-rails", ">= 7.1", "< 9", group: :test) end |