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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +121 -2
  3. data/lib/generators/rolemodel/all_generator.rb +1 -0
  4. data/lib/generators/rolemodel/core_setup/README.md +27 -0
  5. data/lib/generators/rolemodel/core_setup/USAGE +12 -0
  6. data/lib/generators/rolemodel/core_setup/core_setup_generator.rb +19 -0
  7. data/lib/generators/rolemodel/github/README.md +4 -0
  8. data/lib/generators/rolemodel/github/templates/workflows/deploy-production.yml +34 -0
  9. data/lib/generators/rolemodel/github/templates/workflows/deploy-staging.yml +35 -0
  10. data/lib/generators/rolemodel/good_job/good_job_generator.rb +0 -2
  11. data/lib/generators/rolemodel/heroku/README.md +20 -1
  12. data/lib/generators/rolemodel/heroku/heroku_generator.rb +35 -0
  13. data/lib/generators/rolemodel/heroku/templates/app.json.tt +5 -2
  14. data/lib/generators/rolemodel/heroku/templates/deploy-app/SKILL.md +202 -0
  15. data/lib/generators/rolemodel/linters/eslint/eslint_generator.rb +1 -0
  16. data/lib/generators/rolemodel/linters/rubocop/rubocop_generator.rb +5 -1
  17. data/lib/generators/rolemodel/lograge/README.md +1 -1
  18. data/lib/generators/rolemodel/lograge/templates/config/initializers/lograge.rb +1 -1
  19. data/lib/generators/rolemodel/optics/base/base_generator.rb +1 -0
  20. data/lib/generators/rolemodel/react/react_generator.rb +1 -0
  21. data/lib/generators/rolemodel/readme/templates/README.md.erb +2 -4
  22. data/lib/generators/rolemodel/sentry/README.md +25 -0
  23. data/lib/generators/rolemodel/sentry/USAGE +13 -0
  24. data/lib/generators/rolemodel/sentry/sentry_generator.rb +126 -0
  25. data/lib/generators/rolemodel/sentry/templates/app/javascript/initializers/sentry.js +34 -0
  26. data/lib/generators/rolemodel/sentry/templates/config/initializers/sentry.rb +68 -0
  27. data/lib/generators/rolemodel/simple_form/README.md +6 -0
  28. data/lib/generators/rolemodel/simple_form/USAGE +3 -0
  29. data/lib/generators/rolemodel/simple_form/simple_form_generator.rb +11 -0
  30. data/lib/generators/rolemodel/simple_form/templates/app/inputs/switch_checkbox_input.rb +1 -1
  31. data/lib/generators/rolemodel/tailored_select/USAGE +2 -1
  32. data/lib/generators/rolemodel/tailored_select/tailored_select_generator.rb +15 -0
  33. data/lib/generators/rolemodel/testing/factory_bot/templates/support/factory_bot.rb +2 -0
  34. data/lib/generators/rolemodel/testing/jasmine_playwright/jasmine_playwright_generator.rb +2 -2
  35. data/lib/generators/rolemodel/testing/rspec/templates/spec/rails_helper.rb.tt +4 -1
  36. data/lib/generators/rolemodel/testing/rspec/templates/spec/support/capybara_drivers.rb.tt +2 -0
  37. data/lib/generators/rolemodel/testing/rspec/templates/spec/support/helpers/action_cable_helper.rb +2 -0
  38. data/lib/generators/rolemodel/testing/rspec/templates/spec/support/helpers/test_element_helper.rb +2 -0
  39. data/lib/generators/rolemodel/testing/rspec/templates/spec/support/helpers.rb.tt +3 -1
  40. data/lib/generators/rolemodel/testing/vitest/vitest_generator.rb +1 -0
  41. data/lib/generators/rolemodel/ui_components/modals/modals_generator.rb +1 -0
  42. data/lib/generators/rolemodel/ui_components/modals/templates/app/helpers/turbo_frame_link_helper.rb +2 -0
  43. data/lib/generators/rolemodel/ui_components/navbar/navbar_generator.rb +1 -0
  44. data/lib/generators/rolemodel/webpack/README.md +2 -1
  45. data/lib/generators/rolemodel/webpack/templates/webpack.config.js +2 -12
  46. data/lib/generators/rolemodel/webpack/webpack_generator.rb +19 -11
  47. data/lib/rolemodel/generator_base.rb +37 -0
  48. data/lib/rolemodel/utility/task_tools.rb +27 -0
  49. data/lib/rolemodel/version.rb +1 -1
  50. data/lib/rolemodel-rails.rb +1 -0
  51. metadata +13 -12
  52. data/lib/generators/rolemodel/linters/rubocop/templates/lib/cops/form_error_response.rb +0 -54
  53. data/lib/generators/rolemodel/linters/rubocop/templates/lib/cops/no_chrome_tag.rb +0 -45
  54. data/lib/generators/rolemodel/semaphore/README.md +0 -15
  55. data/lib/generators/rolemodel/semaphore/USAGE +0 -11
  56. data/lib/generators/rolemodel/semaphore/semaphore_generator.rb +0 -25
  57. data/lib/generators/rolemodel/semaphore/templates/heroku-deployment-commands.sh +0 -3
  58. data/lib/generators/rolemodel/semaphore/templates/production-deploy.yml.erb +0 -17
  59. data/lib/generators/rolemodel/semaphore/templates/semaphore.yml.erb +0 -145
  60. data/lib/generators/rolemodel/semaphore/templates/staging-deploy.yml.erb +0 -17
  61. data/lib/generators/rolemodel/webpack/templates/app/javascript/initializers/honeybadger.js +0 -26
  62. /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: 2cbd3c1b81aa62c00354cd5faa3cc5bea47c48c2ede2c86470b391372751db9c
4
- data.tar.gz: c9bd14d2787122907369daaf9bfa26b63d0cc882449247ae5da26af15de9586f
3
+ metadata.gz: f1cf52276aac5192b4349379e6f7792232fbdabae218e3d3ac6fbde81cd1ae01
4
+ data.tar.gz: 3d13d291529c487fb93b8d7c7816acb05e3b686fd4129173208611c0c714558d
5
5
  SHA512:
6
- metadata.gz: d79a388d4221587a777dddeddaea260f389924272c49299fed129b7f5e364df154fa09161217b0087d975168fda59ce6c75e7fd36468c0b0a5b5767cddecea28
7
- data.tar.gz: 960b18a4e997a18092a6bacd69f0833da0154a54717d41215b401f4f8c9cf08034ba8aa10f4daf0fef560a7bd54154a470d650e4427122b3db6c47ac687087ee
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 all generators (useful on a new app)
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 `npm install -g yarn`.
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
 
@@ -8,6 +8,7 @@ module Rolemodel
8
8
  generate 'rolemodel:heroku'
9
9
  generate 'rolemodel:readme'
10
10
  generate 'rolemodel:webpack'
11
+ generate 'rolemodel:sentry'
11
12
  generate 'rolemodel:react'
12
13
  generate 'rolemodel:slim'
13
14
  generate 'rolemodel:optics:all'
@@ -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
- "HONEYBADGER_API_KEY": {
7
+ "SENTRY_DSN": {
8
8
  "required": true
9
9
  },
10
- "HONEYBADGER_ENV": {
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.
@@ -18,6 +18,7 @@ module Rolemodel
18
18
  ].freeze
19
19
 
20
20
  def install_eslint
21
+ ensure_yarn
21
22
  run "yarn add --dev #{DEV_DEPENDENCIES.join(' ')}"
22
23
  end
23
24
 
@@ -16,7 +16,11 @@ module Rolemodel
16
16
 
17
17
  def add_config
18
18
  copy_file '.rubocop.yml', force: true
19
- directory 'lib/cops'
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. For instance, it removes view rendering logs, which is a duplication of Skylight.
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|