rolemodel-rails 2.0.1 → 2.2.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 +4 -4
- data/README.md +121 -2
- data/lib/generators/rolemodel/all_generator.rb +1 -0
- data/lib/generators/rolemodel/core_setup/README.md +27 -0
- data/lib/generators/rolemodel/core_setup/USAGE +12 -0
- data/lib/generators/rolemodel/core_setup/core_setup_generator.rb +19 -0
- data/lib/generators/rolemodel/github/README.md +4 -0
- data/lib/generators/rolemodel/github/templates/workflows/deploy-production.yml +34 -0
- data/lib/generators/rolemodel/github/templates/workflows/deploy-staging.yml +35 -0
- data/lib/generators/rolemodel/good_job/good_job_generator.rb +0 -2
- data/lib/generators/rolemodel/heroku/README.md +20 -1
- data/lib/generators/rolemodel/heroku/heroku_generator.rb +35 -0
- data/lib/generators/rolemodel/heroku/templates/app.json.tt +5 -2
- data/lib/generators/rolemodel/heroku/templates/deploy-app/SKILL.md +202 -0
- data/lib/generators/rolemodel/linters/eslint/eslint_generator.rb +1 -0
- data/lib/generators/rolemodel/linters/rubocop/rubocop_generator.rb +5 -1
- data/lib/generators/rolemodel/lograge/README.md +1 -1
- data/lib/generators/rolemodel/lograge/templates/config/initializers/lograge.rb +1 -1
- data/lib/generators/rolemodel/optics/base/base_generator.rb +1 -0
- data/lib/generators/rolemodel/react/react_generator.rb +1 -0
- data/lib/generators/rolemodel/readme/templates/README.md.erb +2 -4
- data/lib/generators/rolemodel/sentry/README.md +25 -0
- data/lib/generators/rolemodel/sentry/USAGE +13 -0
- data/lib/generators/rolemodel/sentry/sentry_generator.rb +126 -0
- data/lib/generators/rolemodel/sentry/templates/app/javascript/initializers/sentry.js +34 -0
- data/lib/generators/rolemodel/sentry/templates/config/initializers/sentry.rb +68 -0
- data/lib/generators/rolemodel/simple_form/README.md +6 -0
- data/lib/generators/rolemodel/simple_form/USAGE +3 -0
- data/lib/generators/rolemodel/simple_form/simple_form_generator.rb +11 -0
- data/lib/generators/rolemodel/simple_form/templates/app/inputs/switch_checkbox_input.rb +1 -1
- data/lib/generators/rolemodel/tailored_select/USAGE +2 -1
- data/lib/generators/rolemodel/tailored_select/tailored_select_generator.rb +15 -0
- data/lib/generators/rolemodel/testing/factory_bot/templates/support/factory_bot.rb +2 -0
- data/lib/generators/rolemodel/testing/jasmine_playwright/jasmine_playwright_generator.rb +2 -2
- data/lib/generators/rolemodel/testing/rspec/templates/spec/rails_helper.rb.tt +4 -1
- data/lib/generators/rolemodel/testing/rspec/templates/spec/support/capybara_drivers.rb.tt +2 -0
- data/lib/generators/rolemodel/testing/rspec/templates/spec/support/helpers/action_cable_helper.rb +2 -0
- data/lib/generators/rolemodel/testing/rspec/templates/spec/support/helpers/test_element_helper.rb +2 -0
- data/lib/generators/rolemodel/testing/rspec/templates/spec/support/helpers.rb.tt +3 -1
- data/lib/generators/rolemodel/testing/vitest/vitest_generator.rb +1 -0
- data/lib/generators/rolemodel/ui_components/modals/modals_generator.rb +1 -0
- data/lib/generators/rolemodel/ui_components/modals/templates/app/helpers/turbo_frame_link_helper.rb +2 -0
- data/lib/generators/rolemodel/ui_components/navbar/navbar_generator.rb +1 -0
- data/lib/generators/rolemodel/webpack/README.md +2 -1
- data/lib/generators/rolemodel/webpack/templates/webpack.config.js +2 -12
- data/lib/generators/rolemodel/webpack/webpack_generator.rb +19 -11
- data/lib/rolemodel/generator_base.rb +37 -0
- data/lib/rolemodel/utility/task_tools.rb +27 -0
- data/lib/rolemodel/version.rb +1 -1
- data/lib/rolemodel-rails.rb +1 -0
- metadata +13 -12
- data/lib/generators/rolemodel/linters/rubocop/templates/lib/cops/form_error_response.rb +0 -54
- data/lib/generators/rolemodel/linters/rubocop/templates/lib/cops/no_chrome_tag.rb +0 -45
- data/lib/generators/rolemodel/semaphore/README.md +0 -15
- data/lib/generators/rolemodel/semaphore/USAGE +0 -11
- data/lib/generators/rolemodel/semaphore/semaphore_generator.rb +0 -25
- data/lib/generators/rolemodel/semaphore/templates/heroku-deployment-commands.sh +0 -3
- data/lib/generators/rolemodel/semaphore/templates/production-deploy.yml.erb +0 -17
- data/lib/generators/rolemodel/semaphore/templates/semaphore.yml.erb +0 -145
- data/lib/generators/rolemodel/semaphore/templates/staging-deploy.yml.erb +0 -17
- data/lib/generators/rolemodel/webpack/templates/app/javascript/initializers/honeybadger.js +0 -26
- /data/lib/generators/rolemodel/{simple_form → tailored_select}/templates/app/inputs/tailored_select_input.rb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f1cf52276aac5192b4349379e6f7792232fbdabae218e3d3ac6fbde81cd1ae01
|
|
4
|
+
data.tar.gz: 3d13d291529c487fb93b8d7c7816acb05e3b686fd4129173208611c0c714558d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2fdd780f5256aac4772e65e9282569956b2b7e8a2fd663caa068d072a720a7905a721953725df5ccf07d671f600e31d7ec781831782128b63182b8cf586af61b
|
|
7
|
+
data.tar.gz: 453e93d083a80ff9ce7a2b2ea1f02079e334e2a4ae5028ee91d7b67d0c6cb7d7d8f013e9bab777a9da2f77b767f354af38688a2ac101ba8e94dd28c5979e22ee
|
data/README.md
CHANGED
|
@@ -40,7 +40,14 @@ And then execute:
|
|
|
40
40
|
|
|
41
41
|
## Usage
|
|
42
42
|
|
|
43
|
-
Run
|
|
43
|
+
Run the core generators (recommended on a new app — sets up our standard
|
|
44
|
+
baseline so the app is ready to push straight to Heroku)
|
|
45
|
+
|
|
46
|
+
```shell
|
|
47
|
+
bin/rails g rolemodel:core_setup
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or run every generator, including app-specific extras like React, SaaS/Devise, and GoodJob
|
|
44
51
|
|
|
45
52
|
```shell
|
|
46
53
|
bin/rails g rolemodel:all
|
|
@@ -66,6 +73,7 @@ bin/rails g
|
|
|
66
73
|
|
|
67
74
|
## Generators
|
|
68
75
|
|
|
76
|
+
* [Core Setup](./lib/generators/rolemodel/core_setup)
|
|
69
77
|
* [Github](./lib/generators/rolemodel/github)
|
|
70
78
|
* [Semaphore](./lib/generators/rolemodel/semaphore)
|
|
71
79
|
* [Heroku](./lib/generators/rolemodel/heroku)
|
|
@@ -102,9 +110,120 @@ bin/rails g
|
|
|
102
110
|
* [Tailored Select](./lib/generators/rolemodel/tailored_select)
|
|
103
111
|
* [Lograge](./lib/generators/rolemodel/lograge)
|
|
104
112
|
|
|
113
|
+
## Utilities
|
|
114
|
+
|
|
115
|
+
### `Rolemodel::Utility::TaskTools`
|
|
116
|
+
|
|
117
|
+
A mixin of helper methods for writing friendlier, more informative Rake tasks. It provides consistent, migration-style console output, progress indication for long-running loops, an opt-in dry-run mode, and sanitized positional task arguments.
|
|
118
|
+
|
|
119
|
+
#### Requiring & Including
|
|
120
|
+
|
|
121
|
+
The module is available at runtime (rolemodel-rails must not be restricted to the `development` gem group). Require it and `include` it inside the `namespace` block of your `.rake` file:
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
require 'rolemodel/utility/task_tools'
|
|
125
|
+
|
|
126
|
+
namespace :reports do
|
|
127
|
+
include Rolemodel::Utility::TaskTools
|
|
128
|
+
|
|
129
|
+
task total: :environment do
|
|
130
|
+
@total = GeneratedReport.count
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
namespace :clear do
|
|
134
|
+
desc 'Delete all non-current report records'
|
|
135
|
+
task expired: :total do
|
|
136
|
+
say_with_time "Detecting & Deleting Expired Reports among #{@total} Total" do
|
|
137
|
+
deleted_reports = 0
|
|
138
|
+
GeneratedReport.find_each.with_index do |report, index|
|
|
139
|
+
indicate_progress(index, @total)
|
|
140
|
+
next if report.current?
|
|
141
|
+
|
|
142
|
+
report.destroy unless dry_run?
|
|
143
|
+
deleted_reports += 1
|
|
144
|
+
end
|
|
145
|
+
say "#{deleted_reports}/#{@total} Records Deleted"
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
> [!NOTE]
|
|
153
|
+
> `include` inside a `namespace` block adds the helper methods to the anonymous object that evaluates your task bodies, making them (and any plain methods you define alongside them) available to every task in that namespace.
|
|
154
|
+
|
|
155
|
+
#### Helper Methods
|
|
156
|
+
|
|
157
|
+
##### `say(message)`
|
|
158
|
+
|
|
159
|
+
Prints a message using the same style as Rails migrations. Pass `subitem: true` to indent the line beneath a preceding `say`.
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
say 'Doing some important stuff!'
|
|
163
|
+
say 'Like this one specific thing!', subitem: true
|
|
164
|
+
#=> -- Doing some important stuff!
|
|
165
|
+
#=> -> Like this one specific thing!
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
##### `say_with_time(message, &block)`
|
|
169
|
+
|
|
170
|
+
Wraps `say` and prints the block's real execution time as an indented subitem. Use it to announce and time a unit of work.
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
say_with_time "Deleting all #{@total} Records" do
|
|
174
|
+
GeneratedReport.destroy_all unless dry_run?
|
|
175
|
+
end
|
|
176
|
+
#=> -- Deleting all 42 Records
|
|
177
|
+
#=> -> 0.0198s
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
##### `indicate_progress(index, total = nil, report_interval: 9)`
|
|
181
|
+
|
|
182
|
+
Renders an animated spinner (and, when `total` is given, a completion percentage) for a long-running loop. Call it once per iteration with the current index; it only redraws every `report_interval` iterations so the animation stays eye-trackable. Pass a reduced `report_interval` when iterations are very slow.
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
GeneratedReport.find_each.with_index do |report, index|
|
|
186
|
+
indicate_progress(index, @total)
|
|
187
|
+
# ...
|
|
188
|
+
end
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
##### `dry_run?`
|
|
192
|
+
|
|
193
|
+
Returns `true` when the `DRY_RUN` environment variable is present, enabling a dry-run pattern for your tasks. Guard any code that writes to the database or file system with `unless dry_run?` so the task still produces its console feedback without performing the side effects.
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
report.destroy unless dry_run?
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```shell
|
|
200
|
+
DRY_RUN=true rake reports:clear:expired
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
##### `sanitize_arguments(args, defaults = {})`
|
|
204
|
+
|
|
205
|
+
Improves the usability of positional `Rake::Task` arguments, which cannot declare default values and are awkward to skip. Pass the task's `args` and a hash of defaults; it returns a hash with whitespace stripped, blank/skipped values (passed as `_`) replaced by your defaults.
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
desc 'Seed a dev user with the given name and email'
|
|
209
|
+
task :dev, %i[name email] => :environment do |_, args|
|
|
210
|
+
name, email = sanitize_arguments(args, name: 'RoleModel', email: 'it-support@rolemodelsoftware.com').values_at(:name, :email)
|
|
211
|
+
say_with_time "Seeding Dev User (name: #{name}, email: #{email}, role: Admin)" do
|
|
212
|
+
User.find_or_create(name:, email:, role: 'admin') unless dry_run?
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```shell
|
|
218
|
+
# Skip the name argument with `_` to fall back to its default
|
|
219
|
+
DRY_RUN=true rake users:dev[_,bob@example.com]
|
|
220
|
+
#=> -- Seeding Dev User (name: RoleModel, email: bob@example.com, role: Admin)
|
|
221
|
+
#=> -> 0.0000s
|
|
222
|
+
```
|
|
223
|
+
|
|
105
224
|
## Development
|
|
106
225
|
|
|
107
|
-
Install the versions of Node and Ruby specified in `.node-version` and `.ruby-version` on your machine. https://asdf-vm.com/ is a great tool for managing language versions. Then run `
|
|
226
|
+
Install the versions of Node and Ruby specified in `.node-version` and `.ruby-version` on your machine. https://asdf-vm.com/ is a great tool for managing language versions. Then run `corepack enable` to activate the Yarn 4+ version pinned by each project's `packageManager` field.
|
|
108
227
|
|
|
109
228
|
## Adding new Generators
|
|
110
229
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Core Setup Generator
|
|
2
|
+
|
|
3
|
+
Runs the core generators every new Rails app should have. Unlike
|
|
4
|
+
`rolemodel:all`, this skips the app-specific extras (React, SaaS/Devise,
|
|
5
|
+
GoodJob, Kaminari, etc.) so you get just the standard baseline — a new Rails
|
|
6
|
+
app you can push straight to Heroku right after generation.
|
|
7
|
+
|
|
8
|
+
## Prerequisites
|
|
9
|
+
|
|
10
|
+
- A freshly generated Rails app
|
|
11
|
+
|
|
12
|
+
## What you get
|
|
13
|
+
|
|
14
|
+
- [GitHub](../github) — standard GitHub configuration
|
|
15
|
+
- [Heroku](../heroku) — standard Heroku deployment configuration
|
|
16
|
+
- [Readme](../readme) — standard project README
|
|
17
|
+
- [Webpack](../webpack) — Webpack v5 for JS and CSS
|
|
18
|
+
- [Sentry](../sentry) — error monitoring for Ruby and JavaScript
|
|
19
|
+
- [Slim](../slim) — Slim templates
|
|
20
|
+
- [Optics](../optics) — Optics design system
|
|
21
|
+
- [Testing](../testing) — RSpec, FactoryBot, parallel_tests, TestProf
|
|
22
|
+
(pass `--js-runner` to include jasmine-playwright-runner)
|
|
23
|
+
- [SimpleForm](../simple_form) — SimpleForm with our configuration
|
|
24
|
+
- [Linters](../linters) — Rubocop and ESLint
|
|
25
|
+
- [UI Components](../ui_components) — flash, the modal pattern, & Turbo 8 support
|
|
26
|
+
- [Editors](../editors) — EditorConfig and recommended VSCode extensions
|
|
27
|
+
- [Lograge](../lograge) — condensed request logging
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Runs the core generators every new Rails app should have
|
|
3
|
+
|
|
4
|
+
Sets up GitHub config, Heroku deployment, README, Webpack, Sentry, Slim,
|
|
5
|
+
Optics, testing (RSpec & friends), SimpleForm, linters, Turbo 8+ support,
|
|
6
|
+
and Lograge. The result is an app that is ready to push directly to Heroku.
|
|
7
|
+
|
|
8
|
+
Pass --js-runner to also include jasmine-playwright-runner for browser
|
|
9
|
+
JS testing.
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
rails generate rolemodel:core_setup
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Rolemodel
|
|
2
|
+
class CoreSetupGenerator < ::Rolemodel::GeneratorBase
|
|
3
|
+
def run_the_core_generators
|
|
4
|
+
generate 'rolemodel:github'
|
|
5
|
+
generate 'rolemodel:heroku'
|
|
6
|
+
generate 'rolemodel:readme'
|
|
7
|
+
generate 'rolemodel:webpack'
|
|
8
|
+
generate 'rolemodel:sentry'
|
|
9
|
+
generate 'rolemodel:slim'
|
|
10
|
+
generate 'rolemodel:optics:all'
|
|
11
|
+
generate 'rolemodel:testing:all'
|
|
12
|
+
generate 'rolemodel:simple_form'
|
|
13
|
+
generate 'rolemodel:linters:all'
|
|
14
|
+
generate 'rolemodel:ui_components:flash'
|
|
15
|
+
generate 'rolemodel:ui_components:modals'
|
|
16
|
+
generate 'rolemodel:lograge'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -9,6 +9,10 @@ It doesn't need to be run first, but the parallel_tests generator must be run in
|
|
|
9
9
|
- CI workflow
|
|
10
10
|
- A sensible default `ci.yml` to get you started with Github Actions. This will run linters, model tests, and system tests.
|
|
11
11
|
- Along with the `ci.yml`, your `database.yml` will be modified to be able to be run in GHA.
|
|
12
|
+
- Deploy workflows
|
|
13
|
+
- `deploy-staging.yml` deploys to Heroku on every push to `main` (or manually); `deploy-production.yml` deploys manually via `workflow_dispatch`.
|
|
14
|
+
- Both target a GitHub deployment environment (`Staging`/`Production`) that provides `HEROKU_APP_NAME` and `HEROKU_APP_URL` variables, and authenticate with the org-level `HEROKU_IT_SUPPORT_API_KEY` secret and `HEROKU_IT_SUPPORT_EMAIL` variable.
|
|
15
|
+
- Note: the staging workflow will fail on pushes to `main` until the environment exists — run the `deploy-app` agent skill (installed by the heroku generator) to create the Heroku app and the GitHub environment.
|
|
12
16
|
- Pull Request Template
|
|
13
17
|
- When you open a Pull Request in Github it will use the Markdown file as a [template](./templates/pull_request_template.md) for the content of the PR.
|
|
14
18
|
- Helpful for reminding collaborators to add specific details to the PR.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Deploy to Heroku Production
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
sha:
|
|
7
|
+
description: 'Specific SHA or Branch name (optional)'
|
|
8
|
+
required: false
|
|
9
|
+
type: string
|
|
10
|
+
|
|
11
|
+
concurrency: production-deployment
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
deploy:
|
|
15
|
+
name: Deploy
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
timeout-minutes: 20
|
|
18
|
+
environment:
|
|
19
|
+
name: Production
|
|
20
|
+
url: ${{ vars.HEROKU_APP_URL }}
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v6
|
|
23
|
+
|
|
24
|
+
- name: Install Heroku CLI
|
|
25
|
+
run: |
|
|
26
|
+
curl https://cli-assets.heroku.com/install.sh | sh
|
|
27
|
+
|
|
28
|
+
- uses: akhileshns/heroku-deploy@v3.15.15
|
|
29
|
+
with:
|
|
30
|
+
heroku_api_key: ${{ secrets.HEROKU_IT_SUPPORT_API_KEY }}
|
|
31
|
+
heroku_app_name: ${{ vars.HEROKU_APP_NAME }}
|
|
32
|
+
heroku_email: ${{ vars.HEROKU_IT_SUPPORT_EMAIL }}
|
|
33
|
+
branch: ${{ inputs.sha || 'HEAD' }}
|
|
34
|
+
healthcheck: '${{ vars.HEROKU_APP_URL }}/up'
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Deploy to Heroku Staging
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
sha:
|
|
7
|
+
description: 'Specific SHA or Branch name (optional)'
|
|
8
|
+
required: false
|
|
9
|
+
type: string
|
|
10
|
+
push: { branches: [ staging ] }
|
|
11
|
+
|
|
12
|
+
concurrency: staging-deployment
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
deploy:
|
|
16
|
+
name: Deploy
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
timeout-minutes: 20
|
|
19
|
+
environment:
|
|
20
|
+
name: Staging
|
|
21
|
+
url: ${{ vars.HEROKU_APP_URL }}
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v6
|
|
24
|
+
|
|
25
|
+
- name: Install Heroku CLI
|
|
26
|
+
run: |
|
|
27
|
+
curl https://cli-assets.heroku.com/install.sh | sh
|
|
28
|
+
|
|
29
|
+
- uses: akhileshns/heroku-deploy@v3.15.15
|
|
30
|
+
with:
|
|
31
|
+
heroku_api_key: ${{ secrets.HEROKU_IT_SUPPORT_API_KEY }}
|
|
32
|
+
heroku_app_name: ${{ vars.HEROKU_APP_NAME }}
|
|
33
|
+
heroku_email: ${{ vars.HEROKU_IT_SUPPORT_EMAIL }}
|
|
34
|
+
branch: ${{ inputs.sha || 'HEAD' }}
|
|
35
|
+
healthcheck: "${{ vars.HEROKU_APP_URL }}/up"
|
|
@@ -64,8 +64,6 @@ module Rolemodel
|
|
|
64
64
|
|
|
65
65
|
def finishing_notes
|
|
66
66
|
say <<~NOTES
|
|
67
|
-
*** Reminder to update Honeybadger gem to version 5.7.0 or later to get correct GoodJob error notifications in Honeybadger
|
|
68
|
-
|
|
69
67
|
*** Reminder to also update your job classes to include appropriate concurrency controls (enqueue_limit/perform_limit with keys)
|
|
70
68
|
NOTES
|
|
71
69
|
end
|
|
@@ -4,5 +4,24 @@
|
|
|
4
4
|
|
|
5
5
|
* Procfile configured for a server process and a release command to run migrations
|
|
6
6
|
* Basic app.json preconfigured with a script to initialize the database and common environment variables, addons, and buildpacks
|
|
7
|
-
|
|
8
7
|
This is the basic config needed to deploy to Heroku.
|
|
8
|
+
|
|
9
|
+
## Deploying the app
|
|
10
|
+
|
|
11
|
+
Deployment setup itself is handled by the `deploy-app` agent skill, which ships inside the
|
|
12
|
+
`rolemodel-rails` gem rather than being copied into your repo — it's one-time setup, so nothing
|
|
13
|
+
skill-related is committed to the app. When the generator finishes, it prints the path to the
|
|
14
|
+
skill's `SKILL.md`; point any coding agent at that file to deploy the app. You can also locate
|
|
15
|
+
it yourself:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
$(bundle show rolemodel-rails)/lib/generators/rolemodel/heroku/templates/deploy-app/SKILL.md
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The skill cleans up the generated Gemfile (merges duplicate groups, removes comments,
|
|
22
|
+
alphabetizes), verifies the test suite and RuboCop pass, creates the Sentry project and wires
|
|
23
|
+
up the DSN, creates and deploys the Heroku staging app (buildpacks, dynos, Postgres,
|
|
24
|
+
Papertrail), and creates the GitHub `Staging` environment with the
|
|
25
|
+
`HEROKU_APP_NAME`/`HEROKU_APP_URL` variables the deploy workflow consumes. The skill is
|
|
26
|
+
LLM-agnostic (Agent Skills format). Requires the Heroku CLI, the GitHub CLI, and the Sentry
|
|
27
|
+
MCP server.
|
|
@@ -15,6 +15,33 @@ module Rolemodel
|
|
|
15
15
|
template 'Procfile'
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
def pin_ruby_version_for_buildpack
|
|
19
|
+
say 'Pin the Ruby version in the Gemfile so the Heroku buildpack respects it.', :green
|
|
20
|
+
|
|
21
|
+
# A bare .ruby-version file is not enough: without a `ruby` directive the version
|
|
22
|
+
# never lands in Gemfile.lock, so the Heroku Ruby buildpack falls back to its own
|
|
23
|
+
# default and can install an incompatible Ruby. Tie the Gemfile to .ruby-version.
|
|
24
|
+
gemfile = File.join(destination_root, 'Gemfile')
|
|
25
|
+
return if File.exist?(gemfile) && File.read(gemfile).match?(/^\s*ruby\s/)
|
|
26
|
+
|
|
27
|
+
inject_into_file 'Gemfile', "\nruby file: '.ruby-version'\n", after: /^source .*$/
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def use_database_url_in_production
|
|
31
|
+
say 'Point the production database at DATABASE_URL for Heroku.', :green
|
|
32
|
+
|
|
33
|
+
# Rails' generated production block hardcodes database/username/<APP>_DATABASE_PASSWORD,
|
|
34
|
+
# none of which exist on Heroku, where the Postgres add-on provides a full DATABASE_URL.
|
|
35
|
+
# Replace the whole block with a url-based config.
|
|
36
|
+
gsub_file 'config/database.yml',
|
|
37
|
+
/^production:\n(?:[ \t]+.*\n?)+/,
|
|
38
|
+
<<~YAML
|
|
39
|
+
production:
|
|
40
|
+
<<: *default
|
|
41
|
+
url: <%= ENV["DATABASE_URL"] %>
|
|
42
|
+
YAML
|
|
43
|
+
end
|
|
44
|
+
|
|
18
45
|
def force_ssl
|
|
19
46
|
say 'Require SSL for production environment.', :green
|
|
20
47
|
|
|
@@ -48,5 +75,13 @@ module Rolemodel
|
|
|
48
75
|
end
|
|
49
76
|
RAKE
|
|
50
77
|
end
|
|
78
|
+
|
|
79
|
+
def show_deploy_app_skill_path
|
|
80
|
+
# The deploy-app skill is one-time deployment setup, so it ships inside the
|
|
81
|
+
# rolemodel-rails gem instead of being copied into the app. Point the user
|
|
82
|
+
# at it so they can hand it to a coding agent when they're ready to deploy.
|
|
83
|
+
say "\nWhen you're ready to deploy, point your coding agent at the deploy-app skill:", :green
|
|
84
|
+
say File.expand_path('templates/deploy-app/SKILL.md', __dir__), :yellow
|
|
85
|
+
end
|
|
51
86
|
end
|
|
52
87
|
end
|
|
@@ -4,12 +4,15 @@
|
|
|
4
4
|
"postdeploy": "bin/rails db:seed"
|
|
5
5
|
},
|
|
6
6
|
"env": {
|
|
7
|
-
"
|
|
7
|
+
"SENTRY_DSN": {
|
|
8
8
|
"required": true
|
|
9
9
|
},
|
|
10
|
-
"
|
|
10
|
+
"SENTRY_ENVIRONMENT": {
|
|
11
11
|
"required": true,
|
|
12
12
|
"value": "review-app"
|
|
13
|
+
},
|
|
14
|
+
"SENTRY_AUTH_TOKEN": {
|
|
15
|
+
"required": false
|
|
13
16
|
}
|
|
14
17
|
},
|
|
15
18
|
"addons": [
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deploy-app
|
|
3
|
+
description: >
|
|
4
|
+
Set up deployment for a RoleModel Rails app after running the rolemodel_rails
|
|
5
|
+
core_setup generator. Cleans up the generated Gemfile, verifies RuboCop, the test
|
|
6
|
+
suite, and a local production smoke test pass, creates the Sentry project and wires
|
|
7
|
+
up the DSN, creates and deploys the Heroku app (buildpacks, dynos, Postgres,
|
|
8
|
+
Papertrail), and creates the GitHub deployment environment + deploy workflow. Use
|
|
9
|
+
when the user says "deploy app",
|
|
10
|
+
"set up staging", "set up heroku", "set up sentry for deploy", or "set up production
|
|
11
|
+
deploy". Defaults to staging; pass "production" for the production flow.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Deploy App
|
|
15
|
+
|
|
16
|
+
Completes deployment setup for a RoleModel Rails app in five phases: Gemfile cleanup,
|
|
17
|
+
green build (RuboCop + tests + production smoke test), Sentry, Heroku, GitHub. Default
|
|
18
|
+
mode is **staging**. If invoked with `production`, follow the
|
|
19
|
+
[Production mode](#production-mode) differences.
|
|
20
|
+
|
|
21
|
+
Every step must be **idempotent**: check whether the resource already exists before
|
|
22
|
+
creating it, and skip or repair rather than failing. Report each phase's outcome as you
|
|
23
|
+
go. Ask the user questions **one at a time**.
|
|
24
|
+
|
|
25
|
+
## Phase 0: Preflight
|
|
26
|
+
|
|
27
|
+
Run these checks before touching anything. Stop and tell the user how to fix any that
|
|
28
|
+
fail:
|
|
29
|
+
|
|
30
|
+
1. `git remote get-url origin` — must be a GitHub repo. Derive `REPO_NAME` from
|
|
31
|
+
`gh repo view --json name -q .name` (fallback: basename of the origin URL).
|
|
32
|
+
2. `gh auth status` — GitHub CLI authenticated.
|
|
33
|
+
3. `heroku auth:whoami` — Heroku CLI installed and logged in.
|
|
34
|
+
4. Sentry MCP tools available (try listing organizations). If no Sentry MCP server is
|
|
35
|
+
connected, ask the user to add/authorize the Sentry MCP server
|
|
36
|
+
(https://mcp.sentry.dev) in their coding agent's MCP configuration and stop — do not
|
|
37
|
+
fall back to another mechanism.
|
|
38
|
+
5. Read `Procfile` — note whether a `worker` process exists.
|
|
39
|
+
6. Read `config/initializers/sentry.rb` — determine how the DSN is consumed:
|
|
40
|
+
- reads `ENV['SENTRY_DSN']` → **env-var mode** (RoleModel standard)
|
|
41
|
+
- reads `Rails.application.credentials...` → **credentials mode**
|
|
42
|
+
7. Deploy-readiness (recent `rolemodel-rails` bakes these in; older apps need the manual
|
|
43
|
+
fixes noted in each phase). Confirm the generated app already has:
|
|
44
|
+
- `terser-webpack-plugin` in `package.json` (webpack config imports it)
|
|
45
|
+
- custom cops under `.rubocop/cops/`, not `lib/cops/` (the latter crashes prod boot)
|
|
46
|
+
- `config/initializers/lograge.rb` uses `controller.try(:current_user)`
|
|
47
|
+
- `config/database.yml` `production:` block uses `url: <%= ENV["DATABASE_URL"] %>`
|
|
48
|
+
- a `ruby` directive in the `Gemfile` (so the version reaches `Gemfile.lock`)
|
|
49
|
+
- `stackprof` in the `Gemfile` (Sentry profiling)
|
|
50
|
+
If any are missing, apply the fix inline and note the app predates the generator update.
|
|
51
|
+
|
|
52
|
+
Set `APP_NAME` = `<REPO_NAME>-staging` (staging) or `<REPO_NAME>` (production), but
|
|
53
|
+
confirm it with the user before creating anything on Heroku.
|
|
54
|
+
|
|
55
|
+
## Phase 1: Gemfile cleanup
|
|
56
|
+
|
|
57
|
+
The rolemodel_rails generators append gems as they run, leaving multiple blocks for the
|
|
58
|
+
same group and top-level gems scattered between them. Normalize the Gemfile first (skip
|
|
59
|
+
this phase if it's already clean):
|
|
60
|
+
|
|
61
|
+
1. Merge duplicate `group` blocks so each unique group combination
|
|
62
|
+
(e.g. `:development, :test`, `:development`, `:test`) appears exactly once.
|
|
63
|
+
2. Remove all comments, including commented-out gems (e.g. `# gem "redis" ...`).
|
|
64
|
+
3. Alphabetize the gems — the top-level list and the list within each group.
|
|
65
|
+
4. Preserve every gem's version constraint and options (`require:`, `platforms:`,
|
|
66
|
+
`path:`, etc.) exactly as written. File order: `source`, then `ruby` (if present),
|
|
67
|
+
then top-level gems, then the group blocks.
|
|
68
|
+
5. Run `bundle install` and confirm it succeeds. `git diff Gemfile.lock` must show no
|
|
69
|
+
changes — this is an ordering-only cleanup; if the lockfile changed, you altered a
|
|
70
|
+
dependency and must fix it.
|
|
71
|
+
|
|
72
|
+
## Phase 2: Green build + production smoke test
|
|
73
|
+
|
|
74
|
+
Do not deploy a broken app. This is a **verification gate**: on a current generated app
|
|
75
|
+
these should pass with no changes. A failure here usually means a generator regression —
|
|
76
|
+
fix the root cause (and flag it for the generator), never paper over it.
|
|
77
|
+
|
|
78
|
+
1. RuboCop: `bin/rubocop` (fallback: `bundle exec rubocop`). If there are offenses,
|
|
79
|
+
apply safe autocorrections (`rubocop -a`), then fix the remainder by hand.
|
|
80
|
+
2. Test suite: `bundle exec rspec` (fallback: `bin/rspec`). Fix any failures at the
|
|
81
|
+
root cause — never skip, pend, or delete tests to get to green.
|
|
82
|
+
3. **Local production smoke test** — this is what catches the boot/asset/DB failures that
|
|
83
|
+
only surface under `RAILS_ENV=production` (eager loading, `force_ssl`, DATABASE_URL).
|
|
84
|
+
Run against a throwaway local Postgres DB (do not touch development/test data):
|
|
85
|
+
```
|
|
86
|
+
export PROD="RAILS_ENV=production SECRET_KEY_BASE=smoketest \
|
|
87
|
+
DATABASE_URL=postgres://$(whoami)@localhost/<repo>_prodsmoke"
|
|
88
|
+
env $PROD bundle exec rails db:create
|
|
89
|
+
env $PROD bundle exec rails assets:precompile # webpack + asset pipeline
|
|
90
|
+
env $PROD RAILS_SERVE_STATIC_FILES=true PORT=3999 bundle exec puma -C config/puma.rb &
|
|
91
|
+
sleep 6
|
|
92
|
+
curl -s -o /dev/null -w '%{http_code}\n' -H 'X-Forwarded-Proto: https' \
|
|
93
|
+
http://localhost:3999/up # expect 200 (plain http returns 301 due to force_ssl)
|
|
94
|
+
kill %1; env $PROD bundle exec rails db:drop
|
|
95
|
+
```
|
|
96
|
+
`assets:precompile` succeeding and `/up` → 200 is the gate. If `/up` 500s, read the
|
|
97
|
+
server log for the root cause before proceeding.
|
|
98
|
+
4. Re-run until clean, then commit the Gemfile cleanup and any fixes (the deploy later
|
|
99
|
+
pushes this branch, so everything must be committed).
|
|
100
|
+
|
|
101
|
+
## Phase 3: Sentry project + DSN
|
|
102
|
+
|
|
103
|
+
1. List the Sentry organizations/teams via the Sentry MCP. Ask the user which team the
|
|
104
|
+
project belongs under (skip the question if there is only one option).
|
|
105
|
+
2. Check whether a project named `REPO_NAME` already exists in that org. If it does,
|
|
106
|
+
reuse it and fetch its DSN instead of creating a duplicate.
|
|
107
|
+
3. Otherwise create the project: slug/name = `REPO_NAME`, platform `ruby-rails`,
|
|
108
|
+
assigned to the confirmed team.
|
|
109
|
+
4. Fetch the project's client key (DSN).
|
|
110
|
+
5. Deliver the DSN according to the mode detected in preflight:
|
|
111
|
+
- **env-var mode**: no code change needed. The DSN is set as a Heroku config var in
|
|
112
|
+
Phase 4 (`SENTRY_DSN`, plus `SENTRY_ENVIRONMENT=staging`). Mention that local
|
|
113
|
+
error reporting (if ever wanted) uses the same `SENTRY_DSN` env var.
|
|
114
|
+
- **credentials mode**: do NOT edit credentials yourself. Print the DSN and exact
|
|
115
|
+
instructions — e.g. `bin/rails credentials:edit --environment production`, add
|
|
116
|
+
`sentry_dsn: <DSN>` — then wait for the user to confirm they've saved it. Verify
|
|
117
|
+
afterwards with
|
|
118
|
+
`bin/rails runner "abort 'missing' unless Rails.application.credentials.sentry_dsn"`
|
|
119
|
+
(adjust env/key path to match the initializer) before moving on.
|
|
120
|
+
|
|
121
|
+
## Phase 4: Heroku app
|
|
122
|
+
|
|
123
|
+
1. `heroku teams` — ask the user which team to create the app in. **Do not create the
|
|
124
|
+
app until they confirm the team and the app name** (default `<REPO_NAME>-staging`).
|
|
125
|
+
2. Ensure the current branch is pushed: check `git status` and
|
|
126
|
+
`git rev-parse @ @{u}`. If there's no upstream or local is ahead, ask the user to
|
|
127
|
+
push (or push for them if they say so) before continuing.
|
|
128
|
+
3. Create the app if it doesn't exist (`heroku apps:info -a $APP_NAME` to check):
|
|
129
|
+
`heroku apps:create $APP_NAME --team <team>`
|
|
130
|
+
4. Buildpacks, in this exact order (check `heroku buildpacks -a $APP_NAME` first):
|
|
131
|
+
1. `heroku buildpacks:add heroku/nodejs -a $APP_NAME`
|
|
132
|
+
2. `heroku buildpacks:add heroku/ruby -a $APP_NAME`
|
|
133
|
+
5. Add-ons (skip any that already exist per `heroku addons -a $APP_NAME`):
|
|
134
|
+
- `heroku addons:create heroku-postgresql:essential-0 -a $APP_NAME`
|
|
135
|
+
- Papertrail: run `heroku addons:plans papertrail` and pick the plan whose
|
|
136
|
+
name/description matches "Development" (case-insensitive); if no plan matches,
|
|
137
|
+
show the plan list and ask the user which to use. Then
|
|
138
|
+
`heroku addons:create papertrail:<plan> -a $APP_NAME`.
|
|
139
|
+
6. Config vars:
|
|
140
|
+
- `RAILS_MASTER_KEY`: **never read `config/master.key` (or
|
|
141
|
+
`config/credentials/production.key`) — the key must not enter the model's context.**
|
|
142
|
+
Pause and tell the user to run this in their own terminal:
|
|
143
|
+
`heroku config:set RAILS_MASTER_KEY=$(cat config/master.key) -a $APP_NAME`
|
|
144
|
+
(substitute `config/credentials/production.key` if per-environment credentials are
|
|
145
|
+
in use). Once they confirm, verify presence without exposing the value:
|
|
146
|
+
`heroku config --json -a $APP_NAME | jq 'has("RAILS_MASTER_KEY")'` — must be `true`.
|
|
147
|
+
- env-var mode only, set these yourself:
|
|
148
|
+
`heroku config:set SENTRY_DSN=<dsn> SENTRY_ENVIRONMENT=staging -a $APP_NAME`
|
|
149
|
+
7. Enable runtime dyno metadata (idempotent) so Sentry can detect releases:
|
|
150
|
+
`heroku labs:enable runtime-dyno-metadata -a $APP_NAME`. This adds `HEROKU_*`
|
|
151
|
+
env vars (e.g. `HEROKU_SLUG_COMMIT`) on the next release; without it the release
|
|
152
|
+
command logs a warning about dyno metadata.
|
|
153
|
+
8. Initial deploy from the local machine:
|
|
154
|
+
- `heroku git:remote -a $APP_NAME -r heroku-staging`
|
|
155
|
+
- `git push heroku-staging <current-branch>:main`
|
|
156
|
+
- If the build or release phase fails, read the build output / `heroku logs` and fix
|
|
157
|
+
the root cause before proceeding — do not create the GitHub environment until the
|
|
158
|
+
app deploys and boots.
|
|
159
|
+
9. Dyno formation (after the first successful deploy):
|
|
160
|
+
- `heroku ps:type web=basic -a $APP_NAME`
|
|
161
|
+
- If the Procfile has a `worker` process: `heroku ps:scale worker=1:basic -a $APP_NAME`
|
|
162
|
+
10. Verify: get the app URL from `heroku apps:info -a $APP_NAME --json` (`web_url`), then
|
|
163
|
+
curl `<web_url>/up` and confirm a 200. Save the URL as `APP_URL` (no trailing slash).
|
|
164
|
+
|
|
165
|
+
## Phase 5: GitHub environment + deploy workflow
|
|
166
|
+
|
|
167
|
+
1. Create the environment (idempotent PUT):
|
|
168
|
+
`gh api -X PUT repos/{owner}/{repo}/environments/Staging`
|
|
169
|
+
2. Set the environment variables:
|
|
170
|
+
- `gh variable set HEROKU_APP_NAME --env Staging --body "$APP_NAME"`
|
|
171
|
+
- `gh variable set HEROKU_APP_URL --env Staging --body "$APP_URL"`
|
|
172
|
+
3. Confirm `.github/workflows/deploy-staging.yml` exists (the rolemodel-rails github
|
|
173
|
+
generator installs it). If it's missing, the app skipped the github generator — tell
|
|
174
|
+
the user to run it (`rails generate rolemodel:github`) rather than hand-writing the
|
|
175
|
+
workflow. The workflow relies on the RoleModel **org-level**
|
|
176
|
+
`HEROKU_IT_SUPPORT_API_KEY` secret and `HEROKU_IT_SUPPORT_EMAIL` variable — do not
|
|
177
|
+
create per-repo copies.
|
|
178
|
+
4. Verify end-to-end: `gh workflow run deploy-staging.yml` then `gh run watch` the run.
|
|
179
|
+
A green run (including its `/up` healthcheck) is the definition of done.
|
|
180
|
+
|
|
181
|
+
## Production mode
|
|
182
|
+
|
|
183
|
+
Same phases with these differences — ask, don't assume, on every sizing choice:
|
|
184
|
+
|
|
185
|
+
- App name defaults to `<REPO_NAME>` (confirm with the user).
|
|
186
|
+
- Sentry: reuse the existing project; set `SENTRY_ENVIRONMENT=production`.
|
|
187
|
+
- Heroku tiers: ask the user for dyno type (basic/standard-1x/standard-2x/performance),
|
|
188
|
+
Postgres plan, and Papertrail plan instead of assuming the staging defaults.
|
|
189
|
+
- GitHub environment is `Production`; the workflow is
|
|
190
|
+
`.github/workflows/deploy-production.yml` (also installed by the github generator; manual
|
|
191
|
+
`workflow_dispatch` only — production never auto-deploys on push).
|
|
192
|
+
|
|
193
|
+
## Notes
|
|
194
|
+
|
|
195
|
+
- Never run destructive Heroku commands (`apps:destroy`, `addons:destroy`,
|
|
196
|
+
`pg:reset`) as part of this skill.
|
|
197
|
+
- Never read secret material into the model's context: `config/master.key`,
|
|
198
|
+
`config/credentials/*.key`, or the output of `heroku config` without `--json | jq`
|
|
199
|
+
filtering. When a secret must be set, have the user run the command themselves and
|
|
200
|
+
verify only the key's presence afterwards.
|
|
201
|
+
- If any phase was already completed on a previous run, say so and continue with the
|
|
202
|
+
next phase rather than starting over.
|
|
@@ -16,7 +16,11 @@ module Rolemodel
|
|
|
16
16
|
|
|
17
17
|
def add_config
|
|
18
18
|
copy_file '.rubocop.yml', force: true
|
|
19
|
-
|
|
19
|
+
# Custom cops live in .rubocop/cops (loaded via .rubocop.yml's require:),
|
|
20
|
+
# not lib/cops — keeping them out of Rails' autoload/eager-load paths so
|
|
21
|
+
# they don't crash production boot referencing the dev/test-only RuboCop
|
|
22
|
+
# constant.
|
|
23
|
+
directory '.rubocop/cops'
|
|
20
24
|
end
|
|
21
25
|
end
|
|
22
26
|
end
|
|
@@ -7,4 +7,4 @@
|
|
|
7
7
|
Installs [Lograge](https://github.com/roidrage/lograge).
|
|
8
8
|
|
|
9
9
|
Lograge is an opinionated gem that slims down the amount of logs that Rails generates, with the goal of reducing
|
|
10
|
-
noise that's handled by other services.
|
|
10
|
+
noise that's handled by other services.
|
|
@@ -4,7 +4,7 @@ Rails.application.configure do
|
|
|
4
4
|
config.lograge.enabled = true
|
|
5
5
|
config.lograge.custom_payload do |controller|
|
|
6
6
|
{
|
|
7
|
-
user_id: controller.current_user.try(:id)
|
|
7
|
+
user_id: controller.try(:current_user).try(:id)
|
|
8
8
|
}
|
|
9
9
|
end
|
|
10
10
|
config.lograge.custom_options = lambda do |event|
|