railsmaker-core 0.0.1
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/README.md +169 -0
- data/bin/railsmaker +344 -0
- data/lib/railsmaker/generators/app_generator.rb +157 -0
- data/lib/railsmaker/generators/auth_generator.rb +97 -0
- data/lib/railsmaker/generators/base_generator.rb +39 -0
- data/lib/railsmaker/generators/concerns/gsub_validation.rb +24 -0
- data/lib/railsmaker/generators/litestream_generator.rb +89 -0
- data/lib/railsmaker/generators/mailjet_generator.rb +68 -0
- data/lib/railsmaker/generators/opentelemetry_generator.rb +79 -0
- data/lib/railsmaker/generators/plausible_generator.rb +33 -0
- data/lib/railsmaker/generators/plausible_instrumentation_generator.rb +28 -0
- data/lib/railsmaker/generators/sentry_generator.rb +46 -0
- data/lib/railsmaker/generators/server_command_generator.rb +178 -0
- data/lib/railsmaker/generators/signoz_generator.rb +33 -0
- data/lib/railsmaker/generators/signoz_opentelemetry_generator.rb +37 -0
- data/lib/railsmaker/generators/templates/app/credentials.example.yml +14 -0
- data/lib/railsmaker/generators/templates/app/main_index.html.erb +71 -0
- data/lib/railsmaker/generators/templates/auth/app/controllers/omniauth_callbacks_controller.rb +18 -0
- data/lib/railsmaker/generators/templates/litestream/litestream.yml.erb +32 -0
- data/lib/railsmaker/generators/templates/opentelemetry/lograge.rb.erb +9 -0
- data/lib/railsmaker/generators/templates/shell_scripts/plausible.sh.erb +51 -0
- data/lib/railsmaker/generators/templates/shell_scripts/signoz.sh.erb +38 -0
- data/lib/railsmaker/generators/templates/shell_scripts/signoz_opentelemetry.sh.erb +49 -0
- data/lib/railsmaker/generators/templates/ui/app/assets/images/og-image.webp +0 -0
- data/lib/railsmaker/generators/templates/ui/app/assets/images/plausible-screenshot.png +0 -0
- data/lib/railsmaker/generators/templates/ui/app/assets/images/signoz-screenshot.png +0 -0
- data/lib/railsmaker/generators/templates/ui/app/controllers/demo_controller.rb +7 -0
- data/lib/railsmaker/generators/templates/ui/app/controllers/pages_controller.rb +4 -0
- data/lib/railsmaker/generators/templates/ui/app/helpers/seo_helper.rb +39 -0
- data/lib/railsmaker/generators/templates/ui/app/javascript/controllers/flash_controller.js +14 -0
- data/lib/railsmaker/generators/templates/ui/app/javascript/controllers/index.js +11 -0
- data/lib/railsmaker/generators/templates/ui/app/javascript/controllers/scroll_fade_controller.js +27 -0
- data/lib/railsmaker/generators/templates/ui/app/views/clearance_mailer/change_password.html.erb +8 -0
- data/lib/railsmaker/generators/templates/ui/app/views/clearance_mailer/change_password.text.erb +5 -0
- data/lib/railsmaker/generators/templates/ui/app/views/demo/analytics.html.erb +214 -0
- data/lib/railsmaker/generators/templates/ui/app/views/demo/index.html.erb +312 -0
- data/lib/railsmaker/generators/templates/ui/app/views/demo/support.html.erb +147 -0
- data/lib/railsmaker/generators/templates/ui/app/views/layouts/_navbar.html.erb +193 -0
- data/lib/railsmaker/generators/templates/ui/app/views/layouts/application.html.erb +62 -0
- data/lib/railsmaker/generators/templates/ui/app/views/main/index.html.erb +320 -0
- data/lib/railsmaker/generators/templates/ui/app/views/pages/privacy.html.erb +63 -0
- data/lib/railsmaker/generators/templates/ui/app/views/pages/terms.html.erb +54 -0
- data/lib/railsmaker/generators/templates/ui/app/views/passwords/create.html.erb +9 -0
- data/lib/railsmaker/generators/templates/ui/app/views/passwords/edit.html.erb +21 -0
- data/lib/railsmaker/generators/templates/ui/app/views/passwords/new.html.erb +26 -0
- data/lib/railsmaker/generators/templates/ui/app/views/sessions/new.html.erb +49 -0
- data/lib/railsmaker/generators/templates/ui/app/views/shared/_auth_layout.html.erb +24 -0
- data/lib/railsmaker/generators/templates/ui/app/views/shared/_flash.html.erb +19 -0
- data/lib/railsmaker/generators/templates/ui/app/views/shared/_footer.html.erb +52 -0
- data/lib/railsmaker/generators/templates/ui/app/views/shared/_structured_data.html.erb +20 -0
- data/lib/railsmaker/generators/templates/ui/app/views/users/new.html.erb +49 -0
- data/lib/railsmaker/generators/templates/ui/config/sitemap.rb +33 -0
- data/lib/railsmaker/generators/templates/ui/public/icon.png +0 -0
- data/lib/railsmaker/generators/templates/ui/public/icon.svg +5 -0
- data/lib/railsmaker/generators/templates/ui/public/robots.txt +8 -0
- data/lib/railsmaker/generators/ui_generator.rb +68 -0
- data/lib/railsmaker.rb +29 -0
- metadata +359 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b0ed25a62bb449bb3710dafb3d4cc089df97477d7b6439df3f8d8b1b0b6efbbd
|
4
|
+
data.tar.gz: d128667d1ef969f12940bd0f84a8081753afa2ab33432341564a746a5fb4169f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e503c24961bfe8c4919dfafe60ee4910c304f9d9145a179b42f02cbf7bef3564b0bc3b2efdc3d3f3dd4a24841ebf761b1b33dd061c7f7d7d40e27d68f2270b7e
|
7
|
+
data.tar.gz: de25342c872d7c3a49c5035fe98f90935a9fa31bde6954bbe28245c169ae0f9941fc9a7a9c83db42955dd98c346d9566cbe12e0ec15d069b459227dd4a9464f5
|
data/README.md
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
[](https://rubygems.org/gems/railsmaker)
|
2
|
+
[](https://buymeacoffee.com/sgerov)
|
3
|
+
[](https://railsmaker.com)
|
4
|
+
[](https://github.com/sgerov/railsmaker-sample)
|
5
|
+
[](./10-STEPS-TO-PROD.md)
|
6
|
+
|
7
|
+
# 📦 Railsmaker
|
8
|
+
|
9
|
+
Ship your MVP in hours, not weeks • Zero config needed • Save $120+/month and 20+ dev hours
|
10
|
+
|
11
|
+
## ⚡ Why Railsmaker?
|
12
|
+
- **Ship Faster**: From zero to production in 15 minutes
|
13
|
+
- **Growth Ready**: Built-in analytics, SEO, and monitoring
|
14
|
+
- **Own Your Data**: Fully self-hosted, full control, full flexibility
|
15
|
+
- **Cost Efficient**: You decide how much you want to spend
|
16
|
+
- **DX Focused**: Modern stack, zero configuration
|
17
|
+
|
18
|
+
## ✨ Features
|
19
|
+
|
20
|
+
#### Growth & Analytics
|
21
|
+
- **Privacy-focused**: Self-hosted Plausible and Signoz
|
22
|
+
- **SEO**: Auto-optimized meta-tags & sitemaps
|
23
|
+
- **Performance**: Lightning-fast ~50ms page loads
|
24
|
+
- **Mobile First**: Instant responsive layouts
|
25
|
+
|
26
|
+
#### Developer Experience
|
27
|
+
- **UI**: Latest TailwindCSS 4 + DaisyUI 5
|
28
|
+
- **Auth**: Battle-tested Clearance + OmniAuth
|
29
|
+
- **Storage**: SQLite + Litestream
|
30
|
+
- **Email**: Production-ready Mailjet integration
|
31
|
+
- **Modern Stack**: Rails 8, Ruby 3.2, Hotwire magic
|
32
|
+
|
33
|
+
#### Infrastructure
|
34
|
+
- **Monitoring**: Full SigNoz & Sentry integration
|
35
|
+
- **Deploy**: One-command Kamal deployments
|
36
|
+
- **Observability**: Enterprise-grade OpenTelemetry + Lograge
|
37
|
+
- **Scale-ready**: Global CDN support, multi-environment
|
38
|
+
|
39
|
+
## 🚀 Setup
|
40
|
+
|
41
|
+
### Prerequisites
|
42
|
+
- Ruby 3.x (`rbenv` or `rvm` recommended)
|
43
|
+
- Bundler: `gem install bundler`
|
44
|
+
- Bun: [Install guide](https://bun.sh)
|
45
|
+
- Git
|
46
|
+
- Dev tools:
|
47
|
+
- Ubuntu/Debian: `sudo apt install build-essential libyaml-dev`
|
48
|
+
- macOS: `xcode-select --install`
|
49
|
+
- Docker (for analytics & monitoring)
|
50
|
+
|
51
|
+
### 1. Bootstrapping your app
|
52
|
+
|
53
|
+
#### A. Set Required Environment Variables
|
54
|
+
|
55
|
+
```bash
|
56
|
+
# Docker registry access (required)
|
57
|
+
export KAMAL_REGISTRY_PASSWORD="docker-registry-password"
|
58
|
+
|
59
|
+
# Litestream backup configuration (optional)
|
60
|
+
export LITESTREAM_ACCESS_KEY_ID="access-key"
|
61
|
+
export LITESTREAM_SECRET_ACCESS_KEY="secret-access-key"
|
62
|
+
export LITESTREAM_BUCKET="https://eu2.yourbucketendpoint.com/"
|
63
|
+
export LITESTREAM_REGION="eu2"
|
64
|
+
```
|
65
|
+
|
66
|
+
#### B. Install and Deploy
|
67
|
+
|
68
|
+
```bash
|
69
|
+
gem install railsmaker-core
|
70
|
+
|
71
|
+
# Interactive wizard (2 minutes)
|
72
|
+
railsmaker new:wizard
|
73
|
+
|
74
|
+
# Deploy to any cloud
|
75
|
+
kamal setup
|
76
|
+
```
|
77
|
+
|
78
|
+
If you have chosen to include litestream keep in mind that the corresponding kamal accessory will also be deployed.
|
79
|
+
|
80
|
+
### 2. Setting up Monitoring (Optional)
|
81
|
+
|
82
|
+
#### A. Install SigNoz Server
|
83
|
+
```bash
|
84
|
+
railsmaker remote signoz \
|
85
|
+
--ssh-host=monitor.example.com \
|
86
|
+
--ssh-user=deploy
|
87
|
+
```
|
88
|
+
|
89
|
+
#### B. Add OpenTelemetry Collector to apps server
|
90
|
+
```bash
|
91
|
+
railsmaker remote signoz:opentelemetry \
|
92
|
+
--ssh-host=app.example.com \
|
93
|
+
--ssh-user=deploy \
|
94
|
+
--signoz-host=monitor.example.com \
|
95
|
+
--hostname=my-production-apps
|
96
|
+
```
|
97
|
+
|
98
|
+
### 3. Setting up Analytics (Optional)
|
99
|
+
```bash
|
100
|
+
railsmaker remote plausible \
|
101
|
+
--ssh-host=analytics.example.com \
|
102
|
+
--ssh-user=deploy \
|
103
|
+
--analytics-host=plausible.example.com
|
104
|
+
```
|
105
|
+
|
106
|
+
### Verification
|
107
|
+
|
108
|
+
- SigNoz Dashboard: `https://monitor.example.com:3301`
|
109
|
+
- Plausible Analytics: `https://analytics.example.com`
|
110
|
+
- Your App: `https://app.example.com`
|
111
|
+
|
112
|
+
> **Note**: All services are tested on Ubuntu 24.04 and macOS 15.2.
|
113
|
+
|
114
|
+
**For a more detailed guide, check out [10 Steps To Prod](./10-STEPS-TO-PROD.md).**
|
115
|
+
|
116
|
+
### Environment Requirements
|
117
|
+
|
118
|
+
- **SigNoz Server**: 2 CPU, 4GB RAM minimum
|
119
|
+
- **Plausible**: 1 CPU, 2GB RAM minimum
|
120
|
+
- **App Server**: 1 CPU, 2GB RAM minimum
|
121
|
+
|
122
|
+
You can decide how to split the services between your servers (e.g. SigNoz & Plausible on a separate server from the app or apps).
|
123
|
+
|
124
|
+
### Database Recovery
|
125
|
+
|
126
|
+
In case of DB failure, follow these steps to recover your data:
|
127
|
+
|
128
|
+
1. Stop the application:
|
129
|
+
```bash
|
130
|
+
kamal app stop
|
131
|
+
```
|
132
|
+
|
133
|
+
2. Remove existing database files:
|
134
|
+
```bash
|
135
|
+
kamal app exec /bin/sh -i
|
136
|
+
rm -rf ./storage/*
|
137
|
+
exit
|
138
|
+
```
|
139
|
+
|
140
|
+
3. Recover files and set proper ownership to files:
|
141
|
+
```bash
|
142
|
+
kamal restore-db-app
|
143
|
+
kamal restore-db-cache
|
144
|
+
kamal restore-db-queue
|
145
|
+
kamal restore-db-cable
|
146
|
+
kamal restore-db-ownership
|
147
|
+
```
|
148
|
+
|
149
|
+
4. Restart Litestream to initiate recovery:
|
150
|
+
```bash
|
151
|
+
kamal accessory reboot litestream
|
152
|
+
```
|
153
|
+
|
154
|
+
5. Start the application:
|
155
|
+
```bash
|
156
|
+
kamal app boot
|
157
|
+
```
|
158
|
+
|
159
|
+
### Cloudflare DNS
|
160
|
+
|
161
|
+
If you are relying on Cloudflare, make sure you set-up SSL/TLS to Full.
|
162
|
+
|
163
|
+
## Support
|
164
|
+
|
165
|
+
This project is **pay-what-you-want**. If it helps you ship faster:
|
166
|
+
|
167
|
+
[](https://buymeacoffee.com/sgerov)
|
168
|
+
|
169
|
+
*Give it a try at [railsmaker.com](https://railsmaker.com)*
|
data/bin/railsmaker
ADDED
@@ -0,0 +1,344 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'railsmaker'
|
5
|
+
require 'thor'
|
6
|
+
|
7
|
+
module RailsMaker
|
8
|
+
class BaseCLI < Thor
|
9
|
+
include Thor::Actions
|
10
|
+
|
11
|
+
no_commands do
|
12
|
+
def colorize(str, color)
|
13
|
+
case color
|
14
|
+
when :green then "\e[32m#{str}\e[0m"
|
15
|
+
when :red then "\e[31m#{str}\e[0m"
|
16
|
+
when :blue then "\e[34m#{str}\e[0m"
|
17
|
+
when :gray then "\e[90m#{str}\e[0m"
|
18
|
+
else str
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def status_indicator(value)
|
23
|
+
if value == true || (value.is_a?(String) && !value.empty?)
|
24
|
+
colorize('●', :green) + ' Enabled'
|
25
|
+
else
|
26
|
+
colorize('○', :red) + ' Disabled'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def print_summary(title, sections)
|
31
|
+
max_label_width = sections.values.flatten(1).map { |item| item[0].length }.max
|
32
|
+
|
33
|
+
say "\n#{colorize("═══ #{title} ═══", :blue)}\n\n"
|
34
|
+
|
35
|
+
sections.each do |section_title, items|
|
36
|
+
say colorize(section_title.upcase, :gray)
|
37
|
+
say colorize('─' * section_title.length, :gray)
|
38
|
+
|
39
|
+
items.each do |label, value, format = :default|
|
40
|
+
formatted_value = case format
|
41
|
+
when :status then status_indicator(value)
|
42
|
+
when :highlight then colorize(value, :blue)
|
43
|
+
else value.to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
say " #{label.ljust(max_label_width)} #{formatted_value}"
|
47
|
+
end
|
48
|
+
say "\n"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_generator_args(options)
|
53
|
+
options.flat_map { |key, value| ["--#{key}", value.to_s] }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class RemoteCLI < BaseCLI
|
59
|
+
map 'signoz:opentelemetry' => :signoz_opentelemetry
|
60
|
+
|
61
|
+
desc 'signoz SERVER', 'Install SigNoz observability server on a remote machine'
|
62
|
+
long_desc <<-LONGDESC
|
63
|
+
Installs SigNoz server on a remote machine. SigNoz is a full-stack open-source APM#{' '}
|
64
|
+
and observability tool.
|
65
|
+
|
66
|
+
Examples:
|
67
|
+
# Install using SSH key authentication
|
68
|
+
$ railsmaker remote signoz --ssh-host=example.com --ssh-user=deploy --key-path=~/.ssh/id_rsa
|
69
|
+
|
70
|
+
# Install using password authentication (will prompt for password)
|
71
|
+
$ railsmaker remote signoz --ssh-host=example.com --ssh-user=deploy
|
72
|
+
|
73
|
+
# Force reinstallation of existing instance
|
74
|
+
$ railsmaker remote signoz --ssh-host=example.com --ssh-user=deploy --force
|
75
|
+
LONGDESC
|
76
|
+
method_option :ssh_host, type: :string, required: true, desc: 'SSH host (e.g., 123.456.789.0)'
|
77
|
+
method_option :ssh_user, type: :string, default: 'root', desc: 'SSH user with sudo access'
|
78
|
+
method_option :key_path, type: :string, desc: 'Path to SSH key (if not provided, will use password auth)'
|
79
|
+
method_option :force, type: :boolean, default: false, desc: 'Force reinstallation if already installed'
|
80
|
+
def signoz
|
81
|
+
print_remote_summary('SigNoz Installation', options)
|
82
|
+
RailsMaker::Generators::SignozGenerator.start(to_generator_args(options))
|
83
|
+
end
|
84
|
+
|
85
|
+
desc 'signoz:opentelemetry', 'Install SigNoz OpenTelemetry collector on a remote machine'
|
86
|
+
long_desc <<-LONGDESC
|
87
|
+
Installs and configures the OpenTelemetry collector on a remote machine to send metrics,#{' '}
|
88
|
+
traces, and logs to your SigNoz server.
|
89
|
+
|
90
|
+
The collector will be configured to:
|
91
|
+
• Collect Docker container logs
|
92
|
+
• Forward metrics and traces
|
93
|
+
• Auto-discover running services
|
94
|
+
|
95
|
+
Examples:
|
96
|
+
# Basic installation
|
97
|
+
$ railsmaker remote signoz:opentelemetry --ssh-host=app.example.com --ssh-user=deploy \\
|
98
|
+
--signoz-host=signoz.example.com
|
99
|
+
|
100
|
+
# With custom hostname for better identification
|
101
|
+
$ railsmaker remote signoz:opentelemetry --ssh-host=app.example.com --ssh-user=deploy \\
|
102
|
+
--signoz-host=signoz.example.com --hostname=production-app-1
|
103
|
+
LONGDESC
|
104
|
+
method_option :ssh_host, type: :string, required: true, desc: 'SSH host where collector will be installed'
|
105
|
+
method_option :signoz_server_host, type: :string, required: true, desc: 'Host where SigNoz server is running'
|
106
|
+
method_option :ssh_user, type: :string, default: 'root', desc: 'SSH user with sudo access'
|
107
|
+
method_option :hostname, type: :string, default: 'rails-apps-1', desc: 'Custom hostname identifier of the server'
|
108
|
+
method_option :key_path, type: :string, desc: 'Path to SSH key (if not provided, will use password auth)'
|
109
|
+
method_option :force, type: :boolean, default: false, desc: 'Force reinstallation if already installed'
|
110
|
+
def signoz_opentelemetry
|
111
|
+
print_remote_summary('SigNoz OpenTelemetry Installation', options)
|
112
|
+
RailsMaker::Generators::SignozOpentelemetryGenerator.start(to_generator_args(options))
|
113
|
+
end
|
114
|
+
|
115
|
+
desc 'plausible', 'Install Plausible Analytics on a remote server'
|
116
|
+
long_desc <<-LONGDESC
|
117
|
+
Installs Plausible Analytics on a remote machine. Plausible is a lightweight,#{' '}
|
118
|
+
open-source alternative to Google Analytics.
|
119
|
+
|
120
|
+
Examples:
|
121
|
+
# Basic installation
|
122
|
+
$ railsmaker remote plausible --ssh-host=analytics.example.com --ssh-user=deploy \\
|
123
|
+
--analytics-host=plausible.example.com
|
124
|
+
|
125
|
+
# With custom SSH key
|
126
|
+
$ railsmaker remote plausible --ssh-host=analytics.example.com --ssh-user=deploy \\
|
127
|
+
--analytics-host=plausible.example.com --key-path=~/.ssh/analytics_key
|
128
|
+
LONGDESC
|
129
|
+
method_option :ssh_host, type: :string, required: true, desc: 'SSH host where Plausible will be installed'
|
130
|
+
method_option :analytics_host, type: :string, required: true, desc: 'Domain where Plausible will be accessible'
|
131
|
+
method_option :ssh_user, type: :string, default: 'root', desc: 'SSH user with sudo access'
|
132
|
+
method_option :key_path, type: :string, desc: 'Path to SSH key (if not provided, will use password auth)'
|
133
|
+
method_option :force, type: :boolean, default: false, desc: 'Force reinstallation if already installed'
|
134
|
+
def plausible
|
135
|
+
print_remote_summary('Plausible Installation', options)
|
136
|
+
RailsMaker::Generators::PlausibleGenerator.start(to_generator_args(options))
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def print_remote_summary(title, opts)
|
142
|
+
sections = {
|
143
|
+
'Connection Details' => [
|
144
|
+
['SSH Host', opts[:ssh_host], :highlight],
|
145
|
+
['SSH User', opts[:ssh_user], :highlight]
|
146
|
+
]
|
147
|
+
}
|
148
|
+
|
149
|
+
if opts[:signoz_server_host]
|
150
|
+
sections['Configuration'] = [
|
151
|
+
['SigNoz Host', opts[:signoz_server_host], :highlight],
|
152
|
+
['Custom Hostname', opts[:hostname], :highlight]
|
153
|
+
]
|
154
|
+
end
|
155
|
+
|
156
|
+
if opts[:analytics_host]
|
157
|
+
sections['Configuration'] = [
|
158
|
+
['Analytics Host', opts[:analytics_host], :highlight]
|
159
|
+
]
|
160
|
+
end
|
161
|
+
|
162
|
+
print_summary(title, sections)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class CLI < BaseCLI
|
167
|
+
map 'new:wizard' => :new_wizard
|
168
|
+
|
169
|
+
desc 'new', 'Generate a new Rails application with integrated features'
|
170
|
+
method_option :name, type: :string, required: true, aliases: '-n', desc: 'Application name'
|
171
|
+
method_option :docker, type: :string, required: true, aliases: '-d', desc: 'Docker username'
|
172
|
+
method_option :ip, type: :string, required: true, aliases: '-i', desc: 'Server IP address'
|
173
|
+
method_option :domain, type: :string, required: true, aliases: '-D', desc: 'Domain name'
|
174
|
+
method_option :auth, type: :boolean, default: true, desc: 'Include authentication'
|
175
|
+
method_option :mailjet, type: :boolean, default: true, desc: 'Configure Mailjet for email'
|
176
|
+
method_option :bucketname, type: :string, desc: 'Enable litestream backups (provide your BUCKETNAME)'
|
177
|
+
method_option :opentelemetry, type: :boolean, default: true, desc: 'Configure OpenTelemetry'
|
178
|
+
method_option :analytics, type: :string, desc: 'Set up Plausible Analytics (provide your ANALYTICS_DOMAIN)'
|
179
|
+
method_option :sentry, type: :boolean, default: true, desc: 'Configure Sentry error tracking'
|
180
|
+
method_option :ui, type: :boolean, default: true, desc: 'Include UI assets'
|
181
|
+
def new
|
182
|
+
self.destination_root = File.expand_path(options[:name], Dir.pwd)
|
183
|
+
say "Generating new Rails application: #{options[:name]}", :yellow
|
184
|
+
generate_application(options)
|
185
|
+
end
|
186
|
+
|
187
|
+
desc 'new:wizard', 'Launch an interactive wizard for generating a Rails application'
|
188
|
+
def new_wizard
|
189
|
+
say 'Welcome to the RailsMaker wizard!', :blue
|
190
|
+
wizard_options = collect_wizard_options
|
191
|
+
self.destination_root = File.expand_path(wizard_options[:name], Dir.pwd)
|
192
|
+
say "Generating new Rails application: #{wizard_options[:name]}", :yellow
|
193
|
+
generate_application(wizard_options)
|
194
|
+
end
|
195
|
+
|
196
|
+
desc 'remote', 'Manage remote services'
|
197
|
+
subcommand 'remote', RailsMaker::RemoteCLI
|
198
|
+
|
199
|
+
def self.exit_on_failure?
|
200
|
+
true
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
def cleanup_on_failure
|
206
|
+
return unless File.directory?(destination_root)
|
207
|
+
return unless $!.to_s.include?('Failed to generate app')
|
208
|
+
|
209
|
+
say_status 'cleanup', "Removing directory: #{destination_root}", :yellow
|
210
|
+
FileUtils.rm_rf(destination_root)
|
211
|
+
end
|
212
|
+
|
213
|
+
def generate_application(opts)
|
214
|
+
print_app_summary(opts)
|
215
|
+
return unless yes?('Do you want to proceed with the installation? (y/N)')
|
216
|
+
|
217
|
+
begin
|
218
|
+
generate_components(opts)
|
219
|
+
say 'Successfully generated RailsMaker template 🎉', :green
|
220
|
+
rescue StandardError => e
|
221
|
+
cleanup_on_failure
|
222
|
+
raise
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def print_app_summary(opts)
|
227
|
+
sections = {
|
228
|
+
'Application Settings' => [
|
229
|
+
['Name:', opts[:name], :highlight],
|
230
|
+
['Docker:', opts[:docker], :highlight],
|
231
|
+
['IP:', opts[:ip], :highlight],
|
232
|
+
['Domain:', opts[:domain], :highlight]
|
233
|
+
],
|
234
|
+
'Features & Integrations' => [
|
235
|
+
['Authentication', opts[:auth], :status],
|
236
|
+
['Mailjet Email', opts[:mailjet], :status],
|
237
|
+
['Litestream Backups', opts[:bucketname], :status],
|
238
|
+
['OpenTelemetry', opts[:opentelemetry], :status],
|
239
|
+
['Plausible Analytics', opts[:analytics], :status],
|
240
|
+
['Sentry Error Tracking', opts[:sentry], :status],
|
241
|
+
['UI Components', opts[:ui], :status]
|
242
|
+
]
|
243
|
+
}
|
244
|
+
|
245
|
+
print_summary('Configuration Summary', sections)
|
246
|
+
print_warnings(opts)
|
247
|
+
end
|
248
|
+
|
249
|
+
def print_warnings(opts)
|
250
|
+
warnings = []
|
251
|
+
if !opts[:ui] && opts[:analytics]
|
252
|
+
warnings << "- Analytics was enabled but won't be installed because UI is disabled"
|
253
|
+
end
|
254
|
+
|
255
|
+
if !opts[:ui] && opts[:auth]
|
256
|
+
warnings << "- Authentication was enabled but won't be installed because UI is disabled"
|
257
|
+
end
|
258
|
+
|
259
|
+
return unless warnings.any?
|
260
|
+
|
261
|
+
say "\nWarnings:", :yellow
|
262
|
+
warnings.each { |warning| say warning, :yellow }
|
263
|
+
end
|
264
|
+
|
265
|
+
def generate_components(opts)
|
266
|
+
generator_args = to_generator_args(opts)
|
267
|
+
|
268
|
+
RailsMaker::Generators::AppGenerator.start(generator_args)
|
269
|
+
|
270
|
+
if opts[:ui]
|
271
|
+
RailsMaker::Generators::AuthGenerator.start(generator_args) if opts[:auth]
|
272
|
+
RailsMaker::Generators::UiGenerator.start(generator_args)
|
273
|
+
RailsMaker::Generators::PlausibleInstrumentationGenerator.start(generator_args) if opts[:analytics]
|
274
|
+
end
|
275
|
+
|
276
|
+
RailsMaker::Generators::MailjetGenerator.start(generator_args) if opts[:mailjet]
|
277
|
+
RailsMaker::Generators::LitestreamGenerator.start(generator_args) if opts[:bucketname]
|
278
|
+
RailsMaker::Generators::OpentelemetryGenerator.start(generator_args) if opts[:opentelemetry]
|
279
|
+
RailsMaker::Generators::SentryGenerator.start(generator_args) if opts[:sentry]
|
280
|
+
end
|
281
|
+
|
282
|
+
def collect_wizard_options
|
283
|
+
wizard_options = {
|
284
|
+
name: options[:name],
|
285
|
+
docker: options[:docker],
|
286
|
+
ip: options[:ip],
|
287
|
+
domain: options[:domain],
|
288
|
+
auth: options[:auth],
|
289
|
+
mailjet: options[:mailjet],
|
290
|
+
bucketname: options[:bucketname],
|
291
|
+
opentelemetry: options[:opentelemetry],
|
292
|
+
analytics: options[:analytics],
|
293
|
+
sentry: options[:sentry],
|
294
|
+
ui: options[:ui]
|
295
|
+
}
|
296
|
+
|
297
|
+
# Only ask for values that weren't provided via command line
|
298
|
+
while wizard_options[:name].to_s.empty?
|
299
|
+
wizard_options[:name] = ask('Application name:')
|
300
|
+
say 'Application name is required', :red if wizard_options[:name].empty?
|
301
|
+
end
|
302
|
+
|
303
|
+
while wizard_options[:docker].to_s.empty?
|
304
|
+
wizard_options[:docker] = ask('Docker username:')
|
305
|
+
say 'Docker username is required', :red if wizard_options[:docker].empty?
|
306
|
+
end
|
307
|
+
|
308
|
+
while wizard_options[:ip].to_s.empty?
|
309
|
+
wizard_options[:ip] = ask('Server IP address:')
|
310
|
+
say 'Server IP address is required', :red if wizard_options[:ip].empty?
|
311
|
+
end
|
312
|
+
|
313
|
+
while wizard_options[:domain].to_s.empty?
|
314
|
+
wizard_options[:domain] = ask('Domain name:')
|
315
|
+
say 'Domain name is required', :red if wizard_options[:domain].empty?
|
316
|
+
end
|
317
|
+
|
318
|
+
wizard_options[:auth] = yes?('Include authentication? (y/N)') if wizard_options[:auth].nil?
|
319
|
+
wizard_options[:mailjet] = yes?('Configure Mailjet for email? (y/N)') if wizard_options[:mailjet].nil?
|
320
|
+
wizard_options[:bucketname] ||= ask('Litestream bucketname: Provide BUCKET_NAME (leave blank to skip):')
|
321
|
+
if wizard_options[:opentelemetry].nil?
|
322
|
+
wizard_options[:opentelemetry] =
|
323
|
+
yes?('Configure OpenTelemetry for metrics? (y/N)')
|
324
|
+
end
|
325
|
+
wizard_options[:analytics] ||= ask('Plausible Analytics: Provide ANALYTICS_DOMAIN (leave blank to skip):')
|
326
|
+
wizard_options[:sentry] = yes?('Configure Sentry error tracking? (y/N)') if wizard_options[:sentry].nil?
|
327
|
+
wizard_options[:ui] = yes?('Include UI assets? (y/N)') if wizard_options[:ui].nil?
|
328
|
+
|
329
|
+
wizard_options[:bucketname] = nil if wizard_options[:bucketname] && wizard_options[:bucketname].empty?
|
330
|
+
wizard_options[:analytics] = nil if wizard_options[:analytics] && wizard_options[:analytics].empty?
|
331
|
+
|
332
|
+
wizard_options
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
begin
|
338
|
+
RailsMaker::CLI.start
|
339
|
+
rescue StandardError => e
|
340
|
+
puts "\nError: #{e.message}"
|
341
|
+
puts e.backtrace if ENV['DEBUG']
|
342
|
+
puts "\nIf you need help, please create an issue at: https://github.com/sgerov/railsmaker/issues"
|
343
|
+
exit 1
|
344
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsMaker
|
4
|
+
module Generators
|
5
|
+
class AppGenerator < BaseGenerator
|
6
|
+
source_root File.expand_path('templates/app', __dir__)
|
7
|
+
|
8
|
+
class_option :name, type: :string, required: true, desc: 'Name of the application'
|
9
|
+
class_option :docker, type: :string, required: true, desc: 'Docker username'
|
10
|
+
class_option :ip, type: :string, required: true, desc: 'Server IP address'
|
11
|
+
class_option :domain, type: :string, required: true, desc: 'Domain name'
|
12
|
+
class_option :ui, type: :boolean, default: false, desc: 'Include UI assets?'
|
13
|
+
|
14
|
+
def check_required_env_vars
|
15
|
+
super(%w[
|
16
|
+
KAMAL_REGISTRY_PASSWORD
|
17
|
+
])
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate_app
|
21
|
+
self.destination_root = File.expand_path(options[:name], current_dir)
|
22
|
+
|
23
|
+
if !in_minitest? && File.directory?(destination_root)
|
24
|
+
say_status 'error', "Directory '#{options[:name]}' already exists", :red
|
25
|
+
raise BaseGeneratorError, 'Directory already exists'
|
26
|
+
end
|
27
|
+
|
28
|
+
say('Creating new Rails app')
|
29
|
+
rails_args = [options[:name]]
|
30
|
+
rails_args << '--javascript=bun' if options[:ui]
|
31
|
+
Rails::Generators::AppGenerator.start(rails_args)
|
32
|
+
|
33
|
+
setup_frontend if options[:ui]
|
34
|
+
|
35
|
+
validate_gsub_strings([
|
36
|
+
{
|
37
|
+
file: 'config/deploy.yml',
|
38
|
+
patterns: ['your-user', "web:\n - 192.168.0.1", 'ssl: true', 'app.example.com']
|
39
|
+
}
|
40
|
+
])
|
41
|
+
|
42
|
+
setup_kamal
|
43
|
+
|
44
|
+
say('Modifying ApplicationController to allow all browsers (mobile)')
|
45
|
+
comment_lines 'app/controllers/application_controller.rb', /allow_browser versions: :modern/
|
46
|
+
|
47
|
+
say('Generating main controller with a landing page')
|
48
|
+
generate :controller, 'main'
|
49
|
+
|
50
|
+
if options[:ui]
|
51
|
+
copy_file 'main_index.html.erb', 'app/views/main/index.html.erb'
|
52
|
+
else
|
53
|
+
create_file 'app/views/main/index.html.erb', "<h1>Welcome to #{options[:name]}</h1>"
|
54
|
+
end
|
55
|
+
|
56
|
+
copy_file 'credentials.example.yml', 'config/credentials.example.yml'
|
57
|
+
|
58
|
+
route "root 'main#index'"
|
59
|
+
rescue StandardError => e
|
60
|
+
say_status 'error', "Failed to generate app: #{e.message} in #{destination_root}", :red
|
61
|
+
raise BaseGeneratorError, e.message
|
62
|
+
end
|
63
|
+
|
64
|
+
def git_commit
|
65
|
+
git add: '.', commit: %(-m 'Initial railsmaker commit')
|
66
|
+
|
67
|
+
say 'Successfully created Rails app with RailsMaker', :green
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def setup_frontend
|
73
|
+
say('Adding Tailwind CSS')
|
74
|
+
gem 'tailwindcss-rails', '~> 4.0.0.rc5'
|
75
|
+
|
76
|
+
say('Installing gems')
|
77
|
+
run 'bundle install --quiet'
|
78
|
+
|
79
|
+
say('Setting up Tailwind')
|
80
|
+
run 'bun add -d tailwindcss@4.0.0 @tailwindcss/cli@4.0.0'
|
81
|
+
rails_command 'tailwindcss:install'
|
82
|
+
|
83
|
+
say('Installing DaisyUI')
|
84
|
+
run 'bun add -d daisyui@5.0.0-beta.2'
|
85
|
+
|
86
|
+
validate_gsub_strings([
|
87
|
+
{
|
88
|
+
file: 'app/views/layouts/application.html.erb',
|
89
|
+
patterns: ['<main class="container mx-auto mt-28 px-5 flex">', '<html>']
|
90
|
+
},
|
91
|
+
{
|
92
|
+
file: 'app/assets/tailwind/application.css',
|
93
|
+
patterns: ['@import "tailwindcss";']
|
94
|
+
},
|
95
|
+
{
|
96
|
+
file: 'Dockerfile',
|
97
|
+
patterns: ['bun.lockb', 'RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile',
|
98
|
+
'# Precompiling assets for production']
|
99
|
+
},
|
100
|
+
{
|
101
|
+
file: 'app/views/layouts/application.html.erb',
|
102
|
+
patterns: []
|
103
|
+
}
|
104
|
+
])
|
105
|
+
|
106
|
+
inject_into_file 'app/assets/tailwind/application.css', after: '@import "tailwindcss";' do
|
107
|
+
<<~RUBY
|
108
|
+
|
109
|
+
|
110
|
+
@plugin "daisyui" {
|
111
|
+
themes: light --default, dark --prefersdark, cupcake;
|
112
|
+
}
|
113
|
+
RUBY
|
114
|
+
end
|
115
|
+
gsub_file 'app/views/layouts/application.html.erb', '<html>', '<html data-theme="cupcake">'
|
116
|
+
gsub_file 'app/views/layouts/application.html.erb', '<main class="container mx-auto mt-28 px-5 flex">',
|
117
|
+
'<main>'
|
118
|
+
|
119
|
+
say('Dockerfile: fixing legacy bun.lockb')
|
120
|
+
gsub_file 'Dockerfile', 'bun.lockb', 'bun.lock'
|
121
|
+
|
122
|
+
say('Dockerfile: fixing for Apple Silicon amd64 emulation')
|
123
|
+
gsub_file 'Dockerfile',
|
124
|
+
'RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile',
|
125
|
+
'RUN TAILWINDCSS_INSTALL_DIR=node_modules/.bin SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile'
|
126
|
+
inject_into_file 'Dockerfile', before: /# Precompiling assets for production/ do
|
127
|
+
<<~RUBY
|
128
|
+
# Ensure bun handles tailwind node bash binary
|
129
|
+
RUN ln -s /usr/local/bun/bin/bun /usr/local/bun/bin/node\n
|
130
|
+
RUBY
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def setup_kamal
|
135
|
+
say('Configuring Kamal')
|
136
|
+
|
137
|
+
inject_into_file 'config/deploy.yml', after: 'service: railsmaker' do
|
138
|
+
"\ndeploy_timeout: 60 # to avoid timeout on first deploy"
|
139
|
+
end
|
140
|
+
gsub_file 'config/deploy.yml', 'your-user', options[:docker]
|
141
|
+
gsub_file 'config/deploy.yml', "web:\n - 192.168.0.1", "web:\n hosts:\n - #{options[:ip]}"
|
142
|
+
gsub_file 'config/deploy.yml', 'app.example.com', options[:domain]
|
143
|
+
inject_into_file 'config/deploy.yml', after: 'ssl: true' do
|
144
|
+
"\n forward_headers: true"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def current_dir
|
149
|
+
Dir.pwd
|
150
|
+
end
|
151
|
+
|
152
|
+
def in_minitest?
|
153
|
+
defined?(Minitest)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|