Class: Seams::Generators::InstallGenerator

Inherits:
Rails::Generators::Base
  • Object
show all
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

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_loadObject



34
35
36
# File 'lib/generators/seams/install/install_generator.rb', line 34

def append_engines_to_eager_load
  template "seams_engines.rb.tt", "config/seams_engines.rb"
end

#create_architecture_docObject



141
142
143
# File 'lib/generators/seams/install/install_generator.rb', line 141

def create_architecture_doc
  template_if_missing "doc/ARCHITECTURE.md.tt", "doc/ARCHITECTURE.md"
end

#create_bin_seamsObject



126
127
128
129
130
# File 'lib/generators/seams/install/install_generator.rb', line 126

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_workflowObject



110
111
112
# File 'lib/generators/seams/install/install_generator.rb', line 110

def create_ci_workflow
  template "ci.yml.tt", ".github/workflows/ci.yml"
end

#create_deployment_templatesObject



114
115
116
117
118
119
120
121
122
123
124
# File 'lib/generators/seams/install/install_generator.rb', line 114

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_directoryObject



25
26
27
28
# File 'lib/generators/seams/install/install_generator.rb', line 25

def create_engines_directory
  empty_directory "engines"
  create_file "engines/.keep"
end

#create_helper_scriptsObject

Phase 1.5 — per-host helper scripts and architecture doc.



133
134
135
136
137
138
139
# File 'lib/generators/seams/install/install_generator.rb', line 133

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_host_rubocopObject



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/generators/seams/install/install_generator.rb', line 70

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_initializerObject



21
22
23
# File 'lib/generators/seams/install/install_generator.rb', line 21

def create_initializer
  template "seams.rb.tt", "config/initializers/seams.rb"
end

#create_rake_tasksObject



30
31
32
# File 'lib/generators/seams/install/install_generator.rb', line 30

def create_rake_tasks
  template "seams.rake.tt", "lib/tasks/seams.rake"
end

#create_ruby_versionObject



101
102
103
104
105
106
107
108
# File 'lib/generators/seams/install/install_generator.rb', line 101

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

#post_install_messageObject

rubocop:disable Metrics/AbcSize, Metrics/MethodLength



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/generators/seams/install/install_generator.rb', line 170

def post_install_message
  say ""
  say "  Seams is installed. Generate your first engine with:", :green
  say "    bin/seams core          (or bin/rails generate seams:core)"
  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 ""
  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/CURRENT_ATTRIBUTES.md (after install) for the per-request namespace cascade.", :yellow
  say "  See doc/WRITING_FOLLOW_UP_GENERATORS.md to write your own follow-up generator.", :yellow
  say ""
end

#wire_engines_into_application_rbObject



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/generators/seams/install/install_generator.rb', line 38

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_hostObject



145
146
147
148
149
150
151
152
153
154
# File 'lib/generators/seams/install/install_generator.rb', line 145

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.
  host_inject_gem("rspec-rails", "~> 7.1", group: :test)
end