Class: RailsMarkup::Generators::InstallGenerator

Inherits:
Rails::Generators::Base
  • Object
show all
Includes:
ActiveRecord::Generators::Migration
Defined in:
lib/generators/rails_markup/install_generator.rb

Constant Summary collapse

TABLE_NAME_PATTERN =

Conservative SQL identifier: leading letter/underscore, then letters, digits, underscores. Blocks names that would produce invalid Ruby/SQL or inject into the migration/initializer templates (e.g. "feedback-items", quotes, newlines).

/\A[a-zA-Z_][a-zA-Z0-9_]*\z/

Instance Method Summary collapse

Instance Method Details

#configure_mcpObject



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

def configure_mcp
  require_relative "../../rails_markup/mcp_config"
  config = RailsMarkup::McpConfig.new(dir: destination_root)
  dev_url = detect_dev_url
  env_updates = {
    "RAILS_MARKUP_DEV_URL" => dev_url,
    "RAILS_MARKUP_MOUNT_PATH" => options[:mount_path]
  }
  config.update_env(env_updates)
  say_status :create, ".mcp.json (dev URL: #{dev_url})", :green
end

#configure_table_nameObject

Keep the model's config.table_name in lockstep with the table the migration actually creates when a custom name is requested.



49
50
51
52
53
54
55
# File 'lib/generators/rails_markup/install_generator.rb', line 49

def configure_table_name
  return if options[:table_name] == "rails_markup_annotations"

  gsub_file "config/initializers/rails_markup.rb",
    /^\s*#?\s*config\.table_name\s*=.*$/,
    %(  config.table_name = "#{options[:table_name]}")
end

#copy_migrationObject



38
39
40
41
# File 'lib/generators/rails_markup/install_generator.rb', line 38

def copy_migration
  migration_template "create_rails_markup_annotations.rb.erb",
    "db/migrate/create_rails_markup_annotations.rb"
end

#create_auth_controllerObject



57
58
59
# File 'lib/generators/rails_markup/install_generator.rb', line 57

def create_auth_controller
  template "auth_controller.rb.erb", "app/controllers/rails_markup_auth_controller.rb"
end

#create_bin_wrapperObject



132
133
134
135
# File 'lib/generators/rails_markup/install_generator.rb', line 132

def create_bin_wrapper
  template "bin_markup.erb", "bin/markup"
  chmod "bin/markup", 0o755
end

#create_initializerObject



43
44
45
# File 'lib/generators/rails_markup/install_generator.rb', line 43

def create_initializer
  template "initializer.rb.erb", "config/initializers/rails_markup.rb"
end

#inject_procfileObject



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/generators/rails_markup/install_generator.rb', line 109

def inject_procfile
  procfile = File.join(destination_root, "Procfile.dev")
  return unless File.exist?(procfile)

  content = File.read(procfile)

  if content =~ /^markup:/
    unless content =~ /^markup:\s*bin\/markup\s+server/
      gsub_file "Procfile.dev", /^markup:.*$/, "markup: bin/markup server"
      say_status :update, "Procfile.dev (markup → bin/markup server)", :green
    else
      say_status :skip, "Procfile.dev already uses bin/markup server", :yellow
    end
  else
    # Ensure the existing content ends with a newline so the markup
    # process starts on its own line even when Procfile.dev has no
    # trailing newline (otherwise it glues onto the last process).
    leading = content.empty? || content.end_with?("\n") ? "" : "\n"
    append_to_file "Procfile.dev", "#{leading}markup: bin/markup server\n"
    say_status :append, "Procfile.dev (added markup server)", :green
  end
end

#inject_toolbar_into_layoutObject



67
68
69
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
100
101
102
103
104
105
106
107
# File 'lib/generators/rails_markup/install_generator.rb', line 67

def inject_toolbar_into_layout
  layout_path = "app/views/layouts/#{options[:layout]}.html.erb"

  unless File.exist?(File.join(destination_root, layout_path))
    say_status :skip, "#{layout_path} not found — add the toolbar manually", :yellow
    return
  end

  # Gate the toolbar on the same authorization contract as the engine's
  # auth controller (current_user.admin?), not merely on the partial
  # existing — otherwise the FAB/toolbar chrome ships to every visitor,
  # including logged-out and non-admin users. Adjust this condition if
  # your app authorizes annotators differently.
  toolbar_block = <<~ERB.indent(4)
    <%# Rails Markup annotation toolbar (admins only) %>
    <% if respond_to?(:current_user) && current_user.respond_to?(:admin?) && current_user.admin? %>
      <%= render "rails_markup/shared/toolbar" %>
    <% end %>
  ERB

  content = File.read(File.join(destination_root, layout_path))

  if content.include?("rails_markup/shared/toolbar")
    # Upgrade the pre-1.2.3 partial-existence gate, which rendered the
    # toolbar for every visitor, to the admin-gated block. Leave any
    # custom (hand-edited) block untouched so we don't clobber it.
    # Match the exact historical generated block (allowing ERB trim tags)
    # — including its own render line — so we never swallow a hand-written
    # block that merely happens to contain lookup_context…end.
    legacy = /^[ \t]*<%#\s*Rails Markup annotation toolbar\s*%>\r?\n[ \t]*<%-?\s*if\s+lookup_context\.exists\?\("rails_markup\/shared\/toolbar".*?-?%>\r?\n[ \t]*<%=\s*render\s+"rails_markup\/shared\/toolbar"\s*-?%>\r?\n[ \t]*<%-?\s*end\s*-?%>\r?\n?/m
    if content =~ legacy
      gsub_file layout_path, legacy, toolbar_block
      say_status :update, "upgraded toolbar to admin-gated render in #{layout_path}", :green
    else
      say_status :skip, "toolbar already present in #{layout_path} (left as-is)", :yellow
    end
    return
  end

  inject_into_file layout_path, toolbar_block, before: %r{</body>}
end

#mount_engineObject



61
62
63
64
65
# File 'lib/generators/rails_markup/install_generator.rb', line 61

def mount_engine
  mount_path = options[:mount_path]
  route_line = %{mount RailsMarkup::Engine, at: "#{mount_path}" if defined?(RailsMarkup::Engine)}
  route route_line
end

#show_post_installObject



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/generators/rails_markup/install_generator.rb', line 149

def show_post_install
  say ""
  say "Rails Markup installed successfully!", :green
  say ""
  say "Created:"
  say "  - db/migrate/*_create_rails_markup_annotations.rb"
  say "  - config/initializers/rails_markup.rb"
  say "  - app/controllers/rails_markup_auth_controller.rb"
  say "  - bin/markup"
  say "  - .mcp.json (MCP config for AI agents)"
  say ""
  say "Next steps:"
  say "  1. Run migrations:  rails db:migrate"
  say "  2. Visit dashboard: #{options[:mount_path]}"
  say "  3. Configure auth in config/initializers/rails_markup.rb"
  say ""
  dev_url = detect_dev_url
  unless dev_url == "http://localhost:3000"
    say "Detected dev server: #{dev_url} (from Procfile.dev)"
  end
  say ""
  say "Not using localhost:3000? Update your dev URL:", :yellow
  say "  bin/markup configure --dev-url=http://YOUR_HOST:PORT"
  say ""
  say "CLI commands:"
  say "  bin/markup fetch              # See pending annotations"
  say "  bin/markup fetch --env=production  # From production"
  say "  bin/markup configure --prod-url=URL --prod-token=TOKEN"
  say "  bin/markup status             # Show current config"
  say ""
end

#validate_table_nameObject

Raises:

  • (Thor::Error)


30
31
32
33
34
35
36
# File 'lib/generators/rails_markup/install_generator.rb', line 30

def validate_table_name
  return if options[:table_name].match?(TABLE_NAME_PATTERN)

  raise Thor::Error,
    "Invalid --table-name #{options[:table_name].inspect}: use only letters, " \
    "digits, and underscores, starting with a letter or underscore."
end