rails-metro 0.1.0

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.
Files changed (150) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +17 -0
  3. data/LICENSE +21 -0
  4. data/README.md +267 -0
  5. data/exe/metro +3 -0
  6. data/lib/rails/metro/cli.rb +162 -0
  7. data/lib/rails/metro/config.rb +40 -0
  8. data/lib/rails/metro/feature_pack.rb +59 -0
  9. data/lib/rails/metro/feature_registry.rb +147 -0
  10. data/lib/rails/metro/packs/aasm_pack.rb +29 -0
  11. data/lib/rails/metro/packs/ab_testing_pack.rb +39 -0
  12. data/lib/rails/metro/packs/action_text_pack.rb +29 -0
  13. data/lib/rails/metro/packs/activeadmin_pack.rb +32 -0
  14. data/lib/rails/metro/packs/activity_feed_pack.rb +31 -0
  15. data/lib/rails/metro/packs/acts_as_list_pack.rb +28 -0
  16. data/lib/rails/metro/packs/acts_as_votable_pack.rb +32 -0
  17. data/lib/rails/metro/packs/administrate_pack.rb +31 -0
  18. data/lib/rails/metro/packs/after_party_pack.rb +31 -0
  19. data/lib/rails/metro/packs/ahoy_pack.rb +31 -0
  20. data/lib/rails/metro/packs/amplitude_pack.rb +45 -0
  21. data/lib/rails/metro/packs/annotate_pack.rb +30 -0
  22. data/lib/rails/metro/packs/api_cors_pack.rb +41 -0
  23. data/lib/rails/metro/packs/api_docs_pack.rb +34 -0
  24. data/lib/rails/metro/packs/api_guard_pack.rb +31 -0
  25. data/lib/rails/metro/packs/api_serialization_pack.rb +38 -0
  26. data/lib/rails/metro/packs/app_linting_pack.rb +35 -0
  27. data/lib/rails/metro/packs/audit_trail_pack.rb +31 -0
  28. data/lib/rails/metro/packs/authentication_pack.rb +31 -0
  29. data/lib/rails/metro/packs/authorization_pack.rb +31 -0
  30. data/lib/rails/metro/packs/avo_pack.rb +32 -0
  31. data/lib/rails/metro/packs/aws_ses_pack.rb +45 -0
  32. data/lib/rails/metro/packs/background_jobs_pack.rb +34 -0
  33. data/lib/rails/metro/packs/blazer_pack.rb +62 -0
  34. data/lib/rails/metro/packs/breadcrumbs_pack.rb +31 -0
  35. data/lib/rails/metro/packs/caching_pack.rb +30 -0
  36. data/lib/rails/metro/packs/charting_pack.rb +67 -0
  37. data/lib/rails/metro/packs/circuit_breaker_pack.rb +34 -0
  38. data/lib/rails/metro/packs/clicky_pack.rb +37 -0
  39. data/lib/rails/metro/packs/cloudinary_pack.rb +42 -0
  40. data/lib/rails/metro/packs/components_pack.rb +30 -0
  41. data/lib/rails/metro/packs/counter_culture_pack.rb +29 -0
  42. data/lib/rails/metro/packs/data_migrate_pack.rb +29 -0
  43. data/lib/rails/metro/packs/datadog_pack.rb +41 -0
  44. data/lib/rails/metro/packs/deployment_pack.rb +30 -0
  45. data/lib/rails/metro/packs/devise_pack.rb +33 -0
  46. data/lib/rails/metro/packs/doorkeeper_pack.rb +32 -0
  47. data/lib/rails/metro/packs/dotenv_pack.rb +36 -0
  48. data/lib/rails/metro/packs/elasticsearch_pack.rb +31 -0
  49. data/lib/rails/metro/packs/encryption_pack.rb +37 -0
  50. data/lib/rails/metro/packs/error_tracking_pack.rb +40 -0
  51. data/lib/rails/metro/packs/event_sourcing_pack.rb +31 -0
  52. data/lib/rails/metro/packs/faraday_pack.rb +42 -0
  53. data/lib/rails/metro/packs/fathom_pack.rb +36 -0
  54. data/lib/rails/metro/packs/feature_flags_pack.rb +34 -0
  55. data/lib/rails/metro/packs/friendly_id_pack.rb +31 -0
  56. data/lib/rails/metro/packs/geocoder_pack.rb +38 -0
  57. data/lib/rails/metro/packs/good_job_pack.rb +34 -0
  58. data/lib/rails/metro/packs/google_analytics_pack.rb +42 -0
  59. data/lib/rails/metro/packs/google_tag_manager_pack.rb +51 -0
  60. data/lib/rails/metro/packs/graphql_pack.rb +31 -0
  61. data/lib/rails/metro/packs/health_check_pack.rb +34 -0
  62. data/lib/rails/metro/packs/heap_pack.rb +39 -0
  63. data/lib/rails/metro/packs/honeybadger_pack.rb +37 -0
  64. data/lib/rails/metro/packs/hotwire_livereload_pack.rb +28 -0
  65. data/lib/rails/metro/packs/icons_pack.rb +28 -0
  66. data/lib/rails/metro/packs/invisible_captcha_pack.rb +35 -0
  67. data/lib/rails/metro/packs/kredis_pack.rb +31 -0
  68. data/lib/rails/metro/packs/lemon_squeezy_pack.rb +36 -0
  69. data/lib/rails/metro/packs/letter_opener_pack.rb +31 -0
  70. data/lib/rails/metro/packs/letter_opener_web_pack.rb +32 -0
  71. data/lib/rails/metro/packs/logging_pack.rb +36 -0
  72. data/lib/rails/metro/packs/madmin_pack.rb +32 -0
  73. data/lib/rails/metro/packs/mailgun_pack.rb +44 -0
  74. data/lib/rails/metro/packs/maintenance_mode_pack.rb +30 -0
  75. data/lib/rails/metro/packs/maintenance_tasks_pack.rb +32 -0
  76. data/lib/rails/metro/packs/markdown_pack.rb +48 -0
  77. data/lib/rails/metro/packs/matomo_pack.rb +48 -0
  78. data/lib/rails/metro/packs/meilisearch_pack.rb +37 -0
  79. data/lib/rails/metro/packs/mixpanel_pack.rb +47 -0
  80. data/lib/rails/metro/packs/mobility_pack.rb +32 -0
  81. data/lib/rails/metro/packs/mollie_pack.rb +36 -0
  82. data/lib/rails/metro/packs/multitenancy_pack.rb +35 -0
  83. data/lib/rails/metro/packs/newrelic_pack.rb +52 -0
  84. data/lib/rails/metro/packs/notifications_pack.rb +31 -0
  85. data/lib/rails/metro/packs/omniauth_pack.rb +49 -0
  86. data/lib/rails/metro/packs/paddle_pack.rb +37 -0
  87. data/lib/rails/metro/packs/pagination_pack.rb +44 -0
  88. data/lib/rails/metro/packs/passwordless_pack.rb +32 -0
  89. data/lib/rails/metro/packs/payments_pack.rb +35 -0
  90. data/lib/rails/metro/packs/paypal_pack.rb +35 -0
  91. data/lib/rails/metro/packs/pdf_pack.rb +45 -0
  92. data/lib/rails/metro/packs/performance_pack.rb +37 -0
  93. data/lib/rails/metro/packs/pghero_pack.rb +31 -0
  94. data/lib/rails/metro/packs/plausible_pack.rb +36 -0
  95. data/lib/rails/metro/packs/posthog_pack.rb +40 -0
  96. data/lib/rails/metro/packs/postmark_pack.rb +39 -0
  97. data/lib/rails/metro/packs/pretender_pack.rb +33 -0
  98. data/lib/rails/metro/packs/profiling_pack.rb +30 -0
  99. data/lib/rails/metro/packs/pundit_pack.rb +50 -0
  100. data/lib/rails/metro/packs/qr_code_pack.rb +37 -0
  101. data/lib/rails/metro/packs/r2_storage_pack.rb +48 -0
  102. data/lib/rails/metro/packs/rack_timeout_pack.rb +34 -0
  103. data/lib/rails/metro/packs/rails_i18n_pack.rb +31 -0
  104. data/lib/rails/metro/packs/ransack_pack.rb +29 -0
  105. data/lib/rails/metro/packs/rate_limiting_pack.rb +39 -0
  106. data/lib/rails/metro/packs/recaptcha_pack.rb +38 -0
  107. data/lib/rails/metro/packs/resend_pack.rb +37 -0
  108. data/lib/rails/metro/packs/revenuecat_pack.rb +36 -0
  109. data/lib/rails/metro/packs/rolify_pack.rb +32 -0
  110. data/lib/rails/metro/packs/rollbar_pack.rb +38 -0
  111. data/lib/rails/metro/packs/s3_storage_pack.rb +45 -0
  112. data/lib/rails/metro/packs/scenic_pack.rb +28 -0
  113. data/lib/rails/metro/packs/scheduling_pack.rb +46 -0
  114. data/lib/rails/metro/packs/search_pack.rb +32 -0
  115. data/lib/rails/metro/packs/security_pack.rb +30 -0
  116. data/lib/rails/metro/packs/sendgrid_pack.rb +44 -0
  117. data/lib/rails/metro/packs/sent_dm_pack.rb +36 -0
  118. data/lib/rails/metro/packs/seo_pack.rb +40 -0
  119. data/lib/rails/metro/packs/shoulda_matchers_pack.rb +37 -0
  120. data/lib/rails/metro/packs/sidekiq_pack.rb +42 -0
  121. data/lib/rails/metro/packs/simple_calendar_pack.rb +29 -0
  122. data/lib/rails/metro/packs/simple_form_pack.rb +30 -0
  123. data/lib/rails/metro/packs/simplecov_pack.rb +38 -0
  124. data/lib/rails/metro/packs/slack_notifier_pack.rb +36 -0
  125. data/lib/rails/metro/packs/soft_deletes_pack.rb +30 -0
  126. data/lib/rails/metro/packs/solidus_pack.rb +31 -0
  127. data/lib/rails/metro/packs/spreadsheets_pack.rb +29 -0
  128. data/lib/rails/metro/packs/statcounter_pack.rb +41 -0
  129. data/lib/rails/metro/packs/stimulus_components_pack.rb +33 -0
  130. data/lib/rails/metro/packs/storage_validations_pack.rb +28 -0
  131. data/lib/rails/metro/packs/strong_migrations_pack.rb +30 -0
  132. data/lib/rails/metro/packs/tagging_pack.rb +31 -0
  133. data/lib/rails/metro/packs/test_mocking_pack.rb +41 -0
  134. data/lib/rails/metro/packs/testing_pack.rb +39 -0
  135. data/lib/rails/metro/packs/twilio_pack.rb +38 -0
  136. data/lib/rails/metro/packs/vite_rails_pack.rb +31 -0
  137. data/lib/rails/metro/packs/web_push_pack.rb +39 -0
  138. data/lib/rails/metro/packs/webhooks_pack.rb +45 -0
  139. data/lib/rails/metro/packs/websockets_pack.rb +29 -0
  140. data/lib/rails/metro/template_compiler.rb +85 -0
  141. data/lib/rails/metro/tui/app.rb +119 -0
  142. data/lib/rails/metro/tui/steps/app_name_step.rb +57 -0
  143. data/lib/rails/metro/tui/steps/database_step.rb +53 -0
  144. data/lib/rails/metro/tui/steps/pack_step.rb +248 -0
  145. data/lib/rails/metro/tui/steps/review_step.rb +94 -0
  146. data/lib/rails/metro/tui/styles.rb +47 -0
  147. data/lib/rails/metro/tui.rb +37 -0
  148. data/lib/rails/metro/version.rb +5 -0
  149. data/lib/rails/metro.rb +9 -0
  150. metadata +206 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7984159d5d4815a8dd34c8e801453a681891477cd7462dc9ca2bc17a7afac16e
4
+ data.tar.gz: 76fb628dbd3ffb5ba2c903ce1939255529ba3e6b09b3fac2e8d518002d783f20
5
+ SHA512:
6
+ metadata.gz: af5c65587ee121c14352822e51500702c8eb6fb6573b8a07c9c6fe2124ec7cbe958a1b49f461037fcd379206c8ae8a995d1392cbd40b2a31fd523d40bc95dedf
7
+ data.tar.gz: 43d8d9d05da67ee9bed0fdad6c42d47d58cf7e9802c3034758c3a0dfcae77d9c7e44c4a8c75828ed8858dc832426c4f84e00627f9c2596ac736e350854dca243
data/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] - 2026-03-31
4
+
5
+ ### Added
6
+
7
+ - **130 feature packs** across 11 categories: admin, analytics, api, core, data, notifications, ops, payments, seo, testing, ui
8
+ - **`metro new`** CLI command — create a Rails app with selected packs (`--packs`, `--database`, `--rails_args`)
9
+ - **`metro tui`** — interactive 4-step wizard: app name → database → pack selection → review (requires `gem install bubbletea`)
10
+ - **`metro template`** — generate a template `.rb` file without running `rails new`
11
+ - **`metro from_config`** — generate from a saved `metro.yml` config file
12
+ - **`metro packs`** — list all available packs by category
13
+ - **FeatureRegistry** with topological dependency resolution, conflict detection, and circular dependency detection
14
+ - **TemplateCompiler** producing deterministic Rails Application Templates
15
+ - **Config** with YAML round-trip serialization (`metro.yml`)
16
+ - **Web API**: `FeatureRegistry#as_catalog` and `#validate_selection` for consumption by web UIs
17
+ - **67 tests**: unit, TUI step, golden file, and integration (actually runs `rails new` end-to-end)
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rails-metro contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,267 @@
1
+ # rails-metro
2
+
3
+ The ultimate Rails app scaffolding tool. Pick the features you need -- authentication, analytics, payments, background jobs, and 120+ more -- and get a production-ready Rails app in one command.
4
+
5
+ rails-metro compiles your selections into a [Rails Application Template](https://guides.rubyonrails.org/rails_application_templates.html), runs `rails new`, and hands you a standard Rails app with **no runtime dependency** on this gem.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ gem install rails-metro
11
+
12
+ # For the interactive TUI (optional)
13
+ gem install bubbletea
14
+ ```
15
+
16
+ ## Quick Start
17
+
18
+ ```bash
19
+ # Interactive mode (recommended)
20
+ metro tui
21
+
22
+ # CLI mode
23
+ metro new myapp --packs=authentication,posthog,background_jobs,deployment --database=postgresql
24
+
25
+ # Just generate the template file
26
+ metro template myapp --packs=posthog,blazer --output=template.rb
27
+
28
+ # Generate from a saved config
29
+ metro from_config metro.yml
30
+ ```
31
+
32
+ ## Commands
33
+
34
+ | Command | Description |
35
+ |---------|-------------|
36
+ | `metro tui` | Interactive TUI for configuring your app |
37
+ | `metro new APP_NAME` | Create a new Rails app with selected packs |
38
+ | `metro template APP_NAME` | Generate a template file without running rails new |
39
+ | `metro from_config PATH` | Generate from a saved `metro.yml` config |
40
+ | `metro packs` | List all available feature packs |
41
+ | `metro version` | Show version |
42
+
43
+ ### CLI Options
44
+
45
+ ```bash
46
+ metro new myapp \
47
+ --database=postgresql \ # sqlite3, postgresql, mysql2
48
+ --packs=auth,posthog,deploy \ # comma-separated pack names
49
+ --rails_args="--api" # pass-through to rails new
50
+ ```
51
+
52
+ ## Interactive TUI
53
+
54
+ The TUI walks you through four steps:
55
+
56
+ 1. **App name** -- validates format
57
+ 2. **Database** -- sqlite3, postgresql, or mysql2
58
+ 3. **Pack selection** -- browse by category, search with `/`, toggle with Space
59
+ 4. **Review** -- generate the app, export config, or export template
60
+
61
+ Dependencies are auto-resolved and conflicts are blocked inline.
62
+
63
+ Requires the `bubbletea` gem (`gem install bubbletea`).
64
+
65
+ ## Feature Packs (130)
66
+
67
+ ### Admin
68
+ - **activeadmin** -- ActiveAdmin -- classic, battle-tested admin framework
69
+ - **administrate** -- Administrate -- Thoughtbot's lightweight admin framework
70
+ - **avo** -- Avo -- modern, customizable admin panel framework
71
+ - **madmin** -- Madmin -- a modern admin framework by Chris Oliver (GoRails)
72
+
73
+ ### Analytics
74
+ - **ahoy** -- Ahoy for visit and event tracking (first-party analytics)
75
+ - **amplitude** -- Amplitude for product analytics, funnels, and retention
76
+ - **blazer** -- SQL analytics dashboards with Blazer
77
+ - **clicky** -- Clicky for real-time web analytics
78
+ - **fathom** -- Fathom -- simple, privacy-focused website analytics
79
+ - **google_analytics** -- Google Analytics 4 (GA4) for web analytics
80
+ - **google_tag_manager** -- Google Tag Manager for managing analytics and marketing tags
81
+ - **heap** -- Heap for auto-captured product analytics
82
+ - **matomo** -- Matomo -- self-hosted open-source web analytics
83
+ - **mixpanel** -- Mixpanel for event-based product analytics
84
+ - **plausible** -- Plausible -- privacy-focused analytics (no cookies, GDPR compliant)
85
+ - **posthog** -- PostHog product analytics (server-side + JS snippet)
86
+ - **statcounter** -- StatCounter for visitor stats and web analytics
87
+
88
+ ### API
89
+ - **api_cors** -- Rack CORS for cross-origin API requests
90
+ - **api_docs** -- Rswag for Swagger/OpenAPI documentation from tests
91
+ - **api_guard** -- API Guard for JWT authentication
92
+ - **api_serialization** -- Alba for fast, flexible JSON serialization
93
+ - **doorkeeper** -- Doorkeeper for OAuth2 provider
94
+ - **graphql** -- GraphQL API with graphql-ruby
95
+ - **webhooks** -- Incoming and outgoing webhook handling
96
+
97
+ ### Core
98
+ - **authentication** -- authentication-zero (2FA, recovery codes, you own the code)
99
+ - **authorization** -- Action Policy for authorization
100
+ - **background_jobs** -- Solid Queue + Mission Control Jobs (no Redis)
101
+ - **caching** -- Solid Cache (database-backed caching)
102
+ - **devise** -- Devise for full-featured authentication
103
+ - **faraday** -- Faraday for composable HTTP requests
104
+ - **good_job** -- GoodJob for Postgres-backed background jobs
105
+ - **invisible_captcha** -- Invisible Captcha for honeypot spam prevention
106
+ - **mobility** -- Mobility for translatable model attributes
107
+ - **multitenancy** -- acts_as_tenant for row-based multi-tenancy
108
+ - **omniauth** -- OmniAuth for social/OAuth login
109
+ - **passwordless** -- Passwordless for magic-link authentication
110
+ - **pundit** -- Pundit for simple, policy-based authorization
111
+ - **r2_storage** -- Cloudflare R2 for Active Storage (no egress fees)
112
+ - **rails_i18n** -- Rails I18n locale data for 100+ languages
113
+ - **recaptcha** -- reCAPTCHA v3 for bot protection
114
+ - **rolify** -- Rolify for role management
115
+ - **s3_storage** -- AWS S3 for Active Storage
116
+ - **scheduling** -- Recurring tasks with Solid Queue _(requires: background_jobs)_
117
+ - **sidekiq** -- Sidekiq for Redis-backed background jobs
118
+ - **websockets** -- Solid Cable (database-backed ActionCable)
119
+
120
+ ### Data
121
+ - **aasm** -- AASM for state machines
122
+ - **ab_testing** -- Split for A/B testing
123
+ - **activity_feed** -- Public Activity for timelines
124
+ - **acts_as_list** -- Acts As List for sortable records
125
+ - **acts_as_votable** -- Acts As Votable for likes/upvotes
126
+ - **annotate** -- Annotate models with schema info
127
+ - **audit_trail** -- PaperTrail for model versioning
128
+ - **counter_culture** -- Counter Culture for counter caches
129
+ - **data_migrate** -- Data Migrate for data migrations
130
+ - **elasticsearch** -- Searchkick for Elasticsearch search
131
+ - **encryption** -- Lockbox for field encryption
132
+ - **event_sourcing** -- Rails Event Store for event sourcing/CQRS
133
+ - **geocoder** -- Geocoder for address lookup and distance
134
+ - **kredis** -- Kredis for structured Redis types
135
+ - **meilisearch** -- Meilisearch for fast full-text search
136
+ - **scenic** -- Scenic for versioned database views
137
+ - **search** -- pg_search for PostgreSQL full-text search
138
+ - **soft_deletes** -- Discard for soft deletes
139
+ - **storage_validations** -- Active Storage file validations
140
+ - **strong_migrations** -- Catch unsafe migrations before they run
141
+ - **tagging** -- Acts As Taggable On
142
+
143
+ ### Notifications
144
+ - **aws_ses** -- Amazon SES for email delivery
145
+ - **mailgun** -- Mailgun for transactional email
146
+ - **notifications** -- Noticed for multi-channel notifications
147
+ - **postmark** -- Postmark for transactional email
148
+ - **resend** -- Resend for email delivery
149
+ - **sendgrid** -- SendGrid for email delivery
150
+ - **sent_dm** -- Sent.dm for multi-channel messaging
151
+ - **slack_notifier** -- Slack webhook notifications
152
+ - **twilio** -- Twilio for SMS, voice, and WhatsApp
153
+ - **web_push** -- Web Push for browser notifications
154
+
155
+ ### Ops
156
+ - **after_party** -- AfterParty for one-time post-deploy tasks
157
+ - **app_linting** -- Standard Ruby + ERB Lint
158
+ - **circuit_breaker** -- Circuitbox for circuit breaking
159
+ - **datadog** -- Datadog APM for tracing and metrics
160
+ - **deployment** -- Kamal for zero-downtime deploys
161
+ - **dotenv** -- Dotenv for .env files in development
162
+ - **error_tracking** -- Sentry for error tracking
163
+ - **feature_flags** -- Flipper for feature flags
164
+ - **health_check** -- OkComputer for health check endpoints
165
+ - **honeybadger** -- Honeybadger for error tracking
166
+ - **hotwire_livereload** -- Hotwire LiveReload for dev
167
+ - **letter_opener** -- Letter Opener for email preview (dev)
168
+ - **letter_opener_web** -- Letter Opener Web UI _(requires: letter_opener)_
169
+ - **logging** -- Lograge for structured request logs
170
+ - **maintenance_mode** -- Turnout for maintenance pages
171
+ - **maintenance_tasks** -- Maintenance Tasks (Shopify)
172
+ - **newrelic** -- New Relic APM
173
+ - **performance** -- Rack Mini Profiler + Bullet
174
+ - **pghero** -- PgHero for Postgres performance dashboard
175
+ - **pretender** -- Pretender for user impersonation
176
+ - **profiling** -- Stackprof + memory_profiler
177
+ - **rack_timeout** -- Rack::Timeout for request protection
178
+ - **rate_limiting** -- Rack::Attack for throttling
179
+ - **rollbar** -- Rollbar for error tracking
180
+ - **security** -- Brakeman + bundler-audit
181
+
182
+ ### Payments
183
+ - **lemon_squeezy** -- Lemon Squeezy (merchant of record)
184
+ - **mollie** -- Mollie for European payments
185
+ - **paddle** -- Paddle (merchant of record)
186
+ - **payments** -- Pay + Stripe for subscriptions
187
+ - **paypal** -- PayPal for payments and checkout
188
+ - **revenuecat** -- RevenueCat for subscription management
189
+ - **solidus** -- Solidus for full e-commerce
190
+
191
+ ### SEO
192
+ - **seo** -- meta-tags + sitemap_generator
193
+
194
+ ### Testing
195
+ - **shoulda_matchers** -- Shoulda Matchers for one-liner assertions
196
+ - **simplecov** -- SimpleCov for code coverage
197
+ - **test_mocking** -- VCR + WebMock for HTTP recording
198
+ - **testing** -- FactoryBot + Faker + Capybara
199
+
200
+ ### UI
201
+ - **action_text** -- Action Text for rich text editing
202
+ - **breadcrumbs** -- Gretel for breadcrumb navigation
203
+ - **charting** -- Chartkick + Groupdate for charts
204
+ - **cloudinary** -- Cloudinary for image/video management
205
+ - **components** -- ViewComponent for component-based views
206
+ - **friendly_id** -- FriendlyId for URL slugs
207
+ - **icons** -- Lucide Rails for SVG icons
208
+ - **markdown** -- Redcarpet for Markdown rendering
209
+ - **pagination** -- Pagy for pagination
210
+ - **pdf** -- Grover for PDF generation
211
+ - **qr_code** -- RQRCode for QR code generation
212
+ - **ransack** -- Ransack for search/filter forms
213
+ - **simple_calendar** -- Simple Calendar for calendar views
214
+ - **simple_form** -- Simple Form for form building
215
+ - **spreadsheets** -- Caxlsx for Excel generation
216
+ - **stimulus_components** -- Pre-built Stimulus controllers
217
+ - **vite_rails** -- Vite Rails for frontend bundling with HMR
218
+
219
+ ## Dependencies and Conflicts
220
+
221
+ Packs can declare dependencies and conflicts:
222
+
223
+ - **Dependencies** are auto-resolved. Selecting `scheduling` automatically adds `background_jobs`.
224
+ - **Conflicts** are mutually exclusive. You can't select both `authentication` and `devise`, or both `sidekiq` and `good_job`.
225
+
226
+ ## How It Works
227
+
228
+ 1. You select packs via the TUI, CLI flags, or a `metro.yml` config file
229
+ 2. rails-metro resolves dependencies and validates conflicts
230
+ 3. A Rails Application Template is compiled from your selections
231
+ 4. `rails new` runs with that template
232
+ 5. You get a standard Rails app -- no runtime dependency on rails-metro
233
+
234
+ ## Config File
235
+
236
+ Export and reuse configurations:
237
+
238
+ ```yaml
239
+ # metro.yml
240
+ app_name: myapp
241
+ database: postgresql
242
+ selected_packs:
243
+ - authentication
244
+ - posthog
245
+ - background_jobs
246
+ - deployment
247
+ rails_args: ""
248
+ ```
249
+
250
+ ```bash
251
+ metro from_config metro.yml
252
+ ```
253
+
254
+ ## Development
255
+
256
+ ```bash
257
+ bundle install
258
+ bundle exec rake test # Unit + golden file tests
259
+ bundle exec rake test:tui # TUI step tests
260
+ bundle exec rake test:integration # End-to-end (runs rails new)
261
+ bundle exec rake test:all # Everything
262
+ bundle exec rake standard # Lint
263
+ ```
264
+
265
+ ## License
266
+
267
+ MIT
data/exe/metro ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require "rails/metro"
3
+ Rails::Metro::CLI.start(ARGV)
@@ -0,0 +1,162 @@
1
+ require "thor"
2
+
3
+ module Rails
4
+ module Metro
5
+ class CLI < Thor
6
+ def self.exit_on_failure?
7
+ true
8
+ end
9
+
10
+ desc "new APP_NAME", "Create a new Rails app with selected feature packs"
11
+ option :database, type: :string, default: "sqlite3", desc: "Database adapter (sqlite3, postgresql, mysql2)"
12
+ option :packs, type: :string, default: "", desc: "Comma-separated list of feature packs to enable"
13
+ option :rails_args, type: :string, default: "", desc: "Additional arguments passed through to rails new"
14
+ def new(app_name)
15
+ selected_packs = options[:packs].split(",").map(&:strip).reject(&:empty?)
16
+
17
+ config = Config.new(
18
+ app_name: app_name,
19
+ database: options[:database],
20
+ selected_packs: selected_packs,
21
+ rails_args: options[:rails_args]
22
+ )
23
+
24
+ registry = FeatureRegistry.default
25
+ resolved_packs = registry.resolve(config.selected_packs)
26
+
27
+ compiler = TemplateCompiler.new(config: config, packs: resolved_packs)
28
+ template_path = compiler.compile_to_file
29
+
30
+ say "rails-metro v#{VERSION}", :green
31
+ say "Packs: #{resolved_packs.map(&:pack_name).join(", ")}", :cyan unless resolved_packs.empty?
32
+ say "Template: #{template_path}", :cyan
33
+ say ""
34
+
35
+ rails_cmd = build_rails_command(app_name, config, template_path)
36
+ say "Running: #{rails_cmd}", :yellow
37
+ system(rails_cmd)
38
+
39
+ notes = compiler.post_install_notes
40
+ if notes.any?
41
+ say ""
42
+ say "Post-install notes:", :green
43
+ notes.each { |note| say " - #{note}" }
44
+ end
45
+ ensure
46
+ File.unlink(template_path) if template_path && File.exist?(template_path)
47
+ end
48
+
49
+ desc "packs", "List available feature packs"
50
+ def packs
51
+ registry = FeatureRegistry.default
52
+ registry.categories.sort.each do |category, pack_classes|
53
+ say category, :green
54
+ pack_classes.sort_by(&:pack_name).each do |pack|
55
+ deps = pack.depends_on.any? ? " (requires: #{pack.depends_on.join(", ")})" : ""
56
+ say " #{pack.pack_name} - #{pack.description}#{deps}"
57
+ end
58
+ say ""
59
+ end
60
+ end
61
+
62
+ desc "template APP_NAME", "Generate a Rails template file without running rails new"
63
+ option :packs, type: :string, default: "", desc: "Comma-separated list of feature packs to enable"
64
+ option :output, type: :string, default: nil, desc: "Output file path (default: stdout)"
65
+ def template(app_name)
66
+ selected_packs = options[:packs].split(",").map(&:strip).reject(&:empty?)
67
+
68
+ config = Config.new(
69
+ app_name: app_name,
70
+ selected_packs: selected_packs
71
+ )
72
+
73
+ registry = FeatureRegistry.default
74
+ resolved_packs = registry.resolve(config.selected_packs)
75
+ compiler = TemplateCompiler.new(config: config, packs: resolved_packs)
76
+ content = compiler.compile
77
+
78
+ if options[:output]
79
+ File.write(options[:output], content)
80
+ say "Template written to #{options[:output]}", :green
81
+ else
82
+ puts content
83
+ end
84
+ end
85
+
86
+ desc "tui", "Launch interactive TUI for configuring your Rails app"
87
+ def tui
88
+ require_relative "tui"
89
+ config, action = Tui.run
90
+
91
+ return unless action
92
+
93
+ case action
94
+ when "g"
95
+ generate_from_config(config)
96
+ when "e"
97
+ path = "metro.yml"
98
+ File.write(path, config.to_yaml)
99
+ say "Config saved to #{path}", :green
100
+ when "t"
101
+ registry = FeatureRegistry.default
102
+ resolved_packs = config.selected_packs.empty? ? [] : registry.resolve(config.selected_packs)
103
+ compiler = TemplateCompiler.new(config: config, packs: resolved_packs)
104
+ path = "template.rb"
105
+ File.write(path, compiler.compile)
106
+ say "Template saved to #{path}", :green
107
+ end
108
+ end
109
+
110
+ desc "from_config PATH", "Generate a Rails app from a metro.yml config file"
111
+ def from_config(path)
112
+ unless File.exist?(path)
113
+ say "File not found: #{path}", :red
114
+ exit 1
115
+ end
116
+
117
+ config = Config.from_yaml(File.read(path))
118
+ generate_from_config(config)
119
+ end
120
+
121
+ desc "version", "Show rails-metro version"
122
+ def version
123
+ say "rails-metro v#{VERSION}"
124
+ end
125
+
126
+ private
127
+
128
+ def generate_from_config(config)
129
+ registry = FeatureRegistry.default
130
+ resolved_packs = config.selected_packs.empty? ? [] : registry.resolve(config.selected_packs)
131
+
132
+ compiler = TemplateCompiler.new(config: config, packs: resolved_packs)
133
+ template_path = compiler.compile_to_file
134
+
135
+ say "rails-metro v#{VERSION}", :green
136
+ say "Config: #{config.summary}", :cyan
137
+ say ""
138
+
139
+ rails_cmd = build_rails_command(config.app_name, config, template_path)
140
+ say "Running: #{rails_cmd}", :yellow
141
+ system(rails_cmd)
142
+
143
+ notes = compiler.post_install_notes
144
+ if notes.any?
145
+ say ""
146
+ say "Post-install notes:", :green
147
+ notes.each { |note| say " - #{note}" }
148
+ end
149
+ ensure
150
+ File.unlink(template_path) if template_path && File.exist?(template_path)
151
+ end
152
+
153
+ def build_rails_command(app_name, config, template_path)
154
+ parts = ["rails", "new", app_name]
155
+ parts += ["--database=#{config.database}"] unless config.database == "sqlite3"
156
+ parts += ["-m", template_path]
157
+ parts += config.rails_args.split unless config.rails_args.empty?
158
+ parts.join(" ")
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,40 @@
1
+ require "yaml"
2
+
3
+ module Rails
4
+ module Metro
5
+ class Config
6
+ attr_accessor :app_name, :database, :selected_packs, :rails_args
7
+
8
+ def initialize(app_name: nil, database: "sqlite3", selected_packs: [], rails_args: "")
9
+ @app_name = app_name
10
+ @database = database
11
+ @selected_packs = selected_packs
12
+ @rails_args = rails_args
13
+ end
14
+
15
+ def to_yaml
16
+ {
17
+ "app_name" => app_name,
18
+ "database" => database,
19
+ "selected_packs" => selected_packs,
20
+ "rails_args" => rails_args
21
+ }.to_yaml
22
+ end
23
+
24
+ def self.from_yaml(yaml_string)
25
+ data = YAML.safe_load(yaml_string)
26
+ new(
27
+ app_name: data["app_name"],
28
+ database: data.fetch("database", "sqlite3"),
29
+ selected_packs: data.fetch("selected_packs", []),
30
+ rails_args: data.fetch("rails_args", "")
31
+ )
32
+ end
33
+
34
+ def summary
35
+ packs_label = selected_packs.empty? ? "none" : selected_packs.join(", ")
36
+ "#{app_name} (#{database}) packs: #{packs_label}"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,59 @@
1
+ module Rails
2
+ module Metro
3
+ class FeaturePack
4
+ class << self
5
+ def pack_name(name = nil)
6
+ if name
7
+ @pack_name = name.to_s
8
+ else
9
+ @pack_name
10
+ end
11
+ end
12
+
13
+ def description(desc = nil)
14
+ if desc
15
+ @description = desc
16
+ else
17
+ @description || ""
18
+ end
19
+ end
20
+
21
+ def category(cat = nil)
22
+ if cat
23
+ @category = cat.to_s
24
+ else
25
+ @category || "general"
26
+ end
27
+ end
28
+
29
+ def depends_on(*pack_names)
30
+ if pack_names.any?
31
+ @dependencies = pack_names.map(&:to_s)
32
+ else
33
+ @dependencies || []
34
+ end
35
+ end
36
+
37
+ def conflicts_with(*pack_names)
38
+ if pack_names.any?
39
+ @conflicts = pack_names.map(&:to_s)
40
+ else
41
+ @conflicts || []
42
+ end
43
+ end
44
+ end
45
+
46
+ def gems
47
+ []
48
+ end
49
+
50
+ def template_lines
51
+ []
52
+ end
53
+
54
+ def post_install_notes
55
+ []
56
+ end
57
+ end
58
+ end
59
+ end