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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +17 -0
- data/LICENSE +21 -0
- data/README.md +267 -0
- data/exe/metro +3 -0
- data/lib/rails/metro/cli.rb +162 -0
- data/lib/rails/metro/config.rb +40 -0
- data/lib/rails/metro/feature_pack.rb +59 -0
- data/lib/rails/metro/feature_registry.rb +147 -0
- data/lib/rails/metro/packs/aasm_pack.rb +29 -0
- data/lib/rails/metro/packs/ab_testing_pack.rb +39 -0
- data/lib/rails/metro/packs/action_text_pack.rb +29 -0
- data/lib/rails/metro/packs/activeadmin_pack.rb +32 -0
- data/lib/rails/metro/packs/activity_feed_pack.rb +31 -0
- data/lib/rails/metro/packs/acts_as_list_pack.rb +28 -0
- data/lib/rails/metro/packs/acts_as_votable_pack.rb +32 -0
- data/lib/rails/metro/packs/administrate_pack.rb +31 -0
- data/lib/rails/metro/packs/after_party_pack.rb +31 -0
- data/lib/rails/metro/packs/ahoy_pack.rb +31 -0
- data/lib/rails/metro/packs/amplitude_pack.rb +45 -0
- data/lib/rails/metro/packs/annotate_pack.rb +30 -0
- data/lib/rails/metro/packs/api_cors_pack.rb +41 -0
- data/lib/rails/metro/packs/api_docs_pack.rb +34 -0
- data/lib/rails/metro/packs/api_guard_pack.rb +31 -0
- data/lib/rails/metro/packs/api_serialization_pack.rb +38 -0
- data/lib/rails/metro/packs/app_linting_pack.rb +35 -0
- data/lib/rails/metro/packs/audit_trail_pack.rb +31 -0
- data/lib/rails/metro/packs/authentication_pack.rb +31 -0
- data/lib/rails/metro/packs/authorization_pack.rb +31 -0
- data/lib/rails/metro/packs/avo_pack.rb +32 -0
- data/lib/rails/metro/packs/aws_ses_pack.rb +45 -0
- data/lib/rails/metro/packs/background_jobs_pack.rb +34 -0
- data/lib/rails/metro/packs/blazer_pack.rb +62 -0
- data/lib/rails/metro/packs/breadcrumbs_pack.rb +31 -0
- data/lib/rails/metro/packs/caching_pack.rb +30 -0
- data/lib/rails/metro/packs/charting_pack.rb +67 -0
- data/lib/rails/metro/packs/circuit_breaker_pack.rb +34 -0
- data/lib/rails/metro/packs/clicky_pack.rb +37 -0
- data/lib/rails/metro/packs/cloudinary_pack.rb +42 -0
- data/lib/rails/metro/packs/components_pack.rb +30 -0
- data/lib/rails/metro/packs/counter_culture_pack.rb +29 -0
- data/lib/rails/metro/packs/data_migrate_pack.rb +29 -0
- data/lib/rails/metro/packs/datadog_pack.rb +41 -0
- data/lib/rails/metro/packs/deployment_pack.rb +30 -0
- data/lib/rails/metro/packs/devise_pack.rb +33 -0
- data/lib/rails/metro/packs/doorkeeper_pack.rb +32 -0
- data/lib/rails/metro/packs/dotenv_pack.rb +36 -0
- data/lib/rails/metro/packs/elasticsearch_pack.rb +31 -0
- data/lib/rails/metro/packs/encryption_pack.rb +37 -0
- data/lib/rails/metro/packs/error_tracking_pack.rb +40 -0
- data/lib/rails/metro/packs/event_sourcing_pack.rb +31 -0
- data/lib/rails/metro/packs/faraday_pack.rb +42 -0
- data/lib/rails/metro/packs/fathom_pack.rb +36 -0
- data/lib/rails/metro/packs/feature_flags_pack.rb +34 -0
- data/lib/rails/metro/packs/friendly_id_pack.rb +31 -0
- data/lib/rails/metro/packs/geocoder_pack.rb +38 -0
- data/lib/rails/metro/packs/good_job_pack.rb +34 -0
- data/lib/rails/metro/packs/google_analytics_pack.rb +42 -0
- data/lib/rails/metro/packs/google_tag_manager_pack.rb +51 -0
- data/lib/rails/metro/packs/graphql_pack.rb +31 -0
- data/lib/rails/metro/packs/health_check_pack.rb +34 -0
- data/lib/rails/metro/packs/heap_pack.rb +39 -0
- data/lib/rails/metro/packs/honeybadger_pack.rb +37 -0
- data/lib/rails/metro/packs/hotwire_livereload_pack.rb +28 -0
- data/lib/rails/metro/packs/icons_pack.rb +28 -0
- data/lib/rails/metro/packs/invisible_captcha_pack.rb +35 -0
- data/lib/rails/metro/packs/kredis_pack.rb +31 -0
- data/lib/rails/metro/packs/lemon_squeezy_pack.rb +36 -0
- data/lib/rails/metro/packs/letter_opener_pack.rb +31 -0
- data/lib/rails/metro/packs/letter_opener_web_pack.rb +32 -0
- data/lib/rails/metro/packs/logging_pack.rb +36 -0
- data/lib/rails/metro/packs/madmin_pack.rb +32 -0
- data/lib/rails/metro/packs/mailgun_pack.rb +44 -0
- data/lib/rails/metro/packs/maintenance_mode_pack.rb +30 -0
- data/lib/rails/metro/packs/maintenance_tasks_pack.rb +32 -0
- data/lib/rails/metro/packs/markdown_pack.rb +48 -0
- data/lib/rails/metro/packs/matomo_pack.rb +48 -0
- data/lib/rails/metro/packs/meilisearch_pack.rb +37 -0
- data/lib/rails/metro/packs/mixpanel_pack.rb +47 -0
- data/lib/rails/metro/packs/mobility_pack.rb +32 -0
- data/lib/rails/metro/packs/mollie_pack.rb +36 -0
- data/lib/rails/metro/packs/multitenancy_pack.rb +35 -0
- data/lib/rails/metro/packs/newrelic_pack.rb +52 -0
- data/lib/rails/metro/packs/notifications_pack.rb +31 -0
- data/lib/rails/metro/packs/omniauth_pack.rb +49 -0
- data/lib/rails/metro/packs/paddle_pack.rb +37 -0
- data/lib/rails/metro/packs/pagination_pack.rb +44 -0
- data/lib/rails/metro/packs/passwordless_pack.rb +32 -0
- data/lib/rails/metro/packs/payments_pack.rb +35 -0
- data/lib/rails/metro/packs/paypal_pack.rb +35 -0
- data/lib/rails/metro/packs/pdf_pack.rb +45 -0
- data/lib/rails/metro/packs/performance_pack.rb +37 -0
- data/lib/rails/metro/packs/pghero_pack.rb +31 -0
- data/lib/rails/metro/packs/plausible_pack.rb +36 -0
- data/lib/rails/metro/packs/posthog_pack.rb +40 -0
- data/lib/rails/metro/packs/postmark_pack.rb +39 -0
- data/lib/rails/metro/packs/pretender_pack.rb +33 -0
- data/lib/rails/metro/packs/profiling_pack.rb +30 -0
- data/lib/rails/metro/packs/pundit_pack.rb +50 -0
- data/lib/rails/metro/packs/qr_code_pack.rb +37 -0
- data/lib/rails/metro/packs/r2_storage_pack.rb +48 -0
- data/lib/rails/metro/packs/rack_timeout_pack.rb +34 -0
- data/lib/rails/metro/packs/rails_i18n_pack.rb +31 -0
- data/lib/rails/metro/packs/ransack_pack.rb +29 -0
- data/lib/rails/metro/packs/rate_limiting_pack.rb +39 -0
- data/lib/rails/metro/packs/recaptcha_pack.rb +38 -0
- data/lib/rails/metro/packs/resend_pack.rb +37 -0
- data/lib/rails/metro/packs/revenuecat_pack.rb +36 -0
- data/lib/rails/metro/packs/rolify_pack.rb +32 -0
- data/lib/rails/metro/packs/rollbar_pack.rb +38 -0
- data/lib/rails/metro/packs/s3_storage_pack.rb +45 -0
- data/lib/rails/metro/packs/scenic_pack.rb +28 -0
- data/lib/rails/metro/packs/scheduling_pack.rb +46 -0
- data/lib/rails/metro/packs/search_pack.rb +32 -0
- data/lib/rails/metro/packs/security_pack.rb +30 -0
- data/lib/rails/metro/packs/sendgrid_pack.rb +44 -0
- data/lib/rails/metro/packs/sent_dm_pack.rb +36 -0
- data/lib/rails/metro/packs/seo_pack.rb +40 -0
- data/lib/rails/metro/packs/shoulda_matchers_pack.rb +37 -0
- data/lib/rails/metro/packs/sidekiq_pack.rb +42 -0
- data/lib/rails/metro/packs/simple_calendar_pack.rb +29 -0
- data/lib/rails/metro/packs/simple_form_pack.rb +30 -0
- data/lib/rails/metro/packs/simplecov_pack.rb +38 -0
- data/lib/rails/metro/packs/slack_notifier_pack.rb +36 -0
- data/lib/rails/metro/packs/soft_deletes_pack.rb +30 -0
- data/lib/rails/metro/packs/solidus_pack.rb +31 -0
- data/lib/rails/metro/packs/spreadsheets_pack.rb +29 -0
- data/lib/rails/metro/packs/statcounter_pack.rb +41 -0
- data/lib/rails/metro/packs/stimulus_components_pack.rb +33 -0
- data/lib/rails/metro/packs/storage_validations_pack.rb +28 -0
- data/lib/rails/metro/packs/strong_migrations_pack.rb +30 -0
- data/lib/rails/metro/packs/tagging_pack.rb +31 -0
- data/lib/rails/metro/packs/test_mocking_pack.rb +41 -0
- data/lib/rails/metro/packs/testing_pack.rb +39 -0
- data/lib/rails/metro/packs/twilio_pack.rb +38 -0
- data/lib/rails/metro/packs/vite_rails_pack.rb +31 -0
- data/lib/rails/metro/packs/web_push_pack.rb +39 -0
- data/lib/rails/metro/packs/webhooks_pack.rb +45 -0
- data/lib/rails/metro/packs/websockets_pack.rb +29 -0
- data/lib/rails/metro/template_compiler.rb +85 -0
- data/lib/rails/metro/tui/app.rb +119 -0
- data/lib/rails/metro/tui/steps/app_name_step.rb +57 -0
- data/lib/rails/metro/tui/steps/database_step.rb +53 -0
- data/lib/rails/metro/tui/steps/pack_step.rb +248 -0
- data/lib/rails/metro/tui/steps/review_step.rb +94 -0
- data/lib/rails/metro/tui/styles.rb +47 -0
- data/lib/rails/metro/tui.rb +37 -0
- data/lib/rails/metro/version.rb +5 -0
- data/lib/rails/metro.rb +9 -0
- 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,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
|