rolemodel-rails 2.1.0 → 2.3.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -9
  3. data/Rakefile +3 -3
  4. data/lib/generators/rolemodel/all_generator.rb +1 -0
  5. data/lib/generators/rolemodel/core_setup/README.md +27 -0
  6. data/lib/generators/rolemodel/core_setup/USAGE +12 -0
  7. data/lib/generators/rolemodel/core_setup/core_setup_generator.rb +23 -0
  8. data/lib/generators/rolemodel/github/README.md +4 -0
  9. data/lib/generators/rolemodel/github/templates/workflows/deploy-production.yml +34 -0
  10. data/lib/generators/rolemodel/github/templates/workflows/deploy-staging.yml +35 -0
  11. data/lib/generators/rolemodel/good_job/good_job_generator.rb +7 -13
  12. data/lib/generators/rolemodel/heroku/README.md +20 -1
  13. data/lib/generators/rolemodel/heroku/heroku_generator.rb +20 -0
  14. data/lib/generators/rolemodel/heroku/templates/app.json.tt +5 -2
  15. data/lib/generators/rolemodel/heroku/templates/deploy-app/SKILL.md +202 -0
  16. data/lib/generators/rolemodel/linters/eslint/eslint_generator.rb +1 -0
  17. data/lib/generators/rolemodel/linters/rubocop/rubocop_generator.rb +5 -1
  18. data/lib/generators/rolemodel/lograge/README.md +1 -1
  19. data/lib/generators/rolemodel/lograge/templates/config/initializers/lograge.rb +1 -1
  20. data/lib/generators/rolemodel/optics/base/base_generator.rb +3 -2
  21. data/lib/generators/rolemodel/react/react_generator.rb +1 -0
  22. data/lib/generators/rolemodel/readme/templates/README.md.erb +2 -4
  23. data/lib/generators/rolemodel/saas/devise/devise_generator.rb +3 -3
  24. data/lib/generators/rolemodel/sentry/README.md +25 -0
  25. data/lib/generators/rolemodel/sentry/USAGE +13 -0
  26. data/lib/generators/rolemodel/sentry/sentry_generator.rb +114 -0
  27. data/lib/generators/rolemodel/sentry/templates/app/controllers/concerns/sentry_user.rb +13 -0
  28. data/lib/generators/rolemodel/sentry/templates/app/javascript/initializers/sentry.js +34 -0
  29. data/lib/generators/rolemodel/sentry/templates/config/initializers/sentry.rb +68 -0
  30. data/lib/generators/rolemodel/simple_form/README.md +6 -0
  31. data/lib/generators/rolemodel/simple_form/USAGE +3 -0
  32. data/lib/generators/rolemodel/simple_form/simple_form_generator.rb +11 -0
  33. data/lib/generators/rolemodel/simple_form/templates/app/inputs/switch_checkbox_input.rb +1 -1
  34. data/lib/generators/rolemodel/slim/templates/app/views/application/_head.html.slim.tt +0 -4
  35. data/lib/generators/rolemodel/tailored_select/USAGE +2 -1
  36. data/lib/generators/rolemodel/tailored_select/tailored_select_generator.rb +15 -0
  37. data/lib/generators/rolemodel/testing/factory_bot/templates/support/factory_bot.rb +2 -0
  38. data/lib/generators/rolemodel/testing/jasmine_playwright/jasmine_playwright_generator.rb +2 -2
  39. data/lib/generators/rolemodel/testing/rspec/rspec_generator.rb +8 -10
  40. data/lib/generators/rolemodel/testing/rspec/templates/spec/rails_helper.rb.tt +4 -1
  41. data/lib/generators/rolemodel/testing/rspec/templates/spec/support/capybara_drivers.rb.tt +2 -0
  42. data/lib/generators/rolemodel/testing/rspec/templates/spec/support/helpers/action_cable_helper.rb +2 -0
  43. data/lib/generators/rolemodel/testing/rspec/templates/spec/support/helpers/test_element_helper.rb +2 -0
  44. data/lib/generators/rolemodel/testing/rspec/templates/spec/support/helpers.rb.tt +3 -1
  45. data/lib/generators/rolemodel/testing/vitest/vitest_generator.rb +1 -0
  46. data/lib/generators/rolemodel/turbo/all/README.md +16 -0
  47. data/lib/generators/rolemodel/turbo/all/USAGE +5 -0
  48. data/lib/generators/rolemodel/turbo/all/all_generator.rb +15 -0
  49. data/lib/generators/rolemodel/turbo/confirm/README.md +25 -0
  50. data/lib/generators/rolemodel/turbo/confirm/USAGE +5 -0
  51. data/lib/generators/rolemodel/turbo/confirm/confirm_generator.rb +35 -0
  52. data/lib/generators/rolemodel/turbo/form/README.md +8 -0
  53. data/lib/generators/rolemodel/turbo/form/USAGE +5 -0
  54. data/lib/generators/rolemodel/turbo/form/form_generator.rb +29 -0
  55. data/lib/generators/rolemodel/turbo/form/templates/app/javascript/controllers/turbo_form_controller.js +15 -0
  56. data/lib/generators/rolemodel/turbo/form/templates/spec/support/helpers/turbo_form_helper.rb +8 -0
  57. data/lib/generators/rolemodel/{ui_components → turbo}/modals/README.md +0 -2
  58. data/lib/generators/rolemodel/turbo/modals/USAGE +5 -0
  59. data/lib/generators/rolemodel/{ui_components → turbo}/modals/modals_generator.rb +9 -22
  60. data/lib/generators/rolemodel/{ui_components → turbo}/modals/templates/app/helpers/turbo_frame_link_helper.rb +2 -0
  61. data/lib/generators/rolemodel/turbo/ready/README.md +12 -0
  62. data/lib/generators/rolemodel/turbo/ready/USAGE +5 -0
  63. data/lib/generators/rolemodel/turbo/ready/ready_generator.rb +48 -0
  64. data/lib/generators/rolemodel/turbo/ready/templates/app/javascript/controllers/prevent_morph_controller.js +22 -0
  65. data/lib/generators/rolemodel/ui_components/navbar/navbar_generator.rb +11 -14
  66. data/lib/generators/rolemodel/webpack/README.md +2 -1
  67. data/lib/generators/rolemodel/webpack/templates/postcss.config.cjs +0 -3
  68. data/lib/generators/rolemodel/webpack/templates/webpack.config.js +6 -14
  69. data/lib/generators/rolemodel/webpack/webpack_generator.rb +18 -18
  70. data/lib/rolemodel/generator_base.rb +15 -0
  71. data/lib/rolemodel/version.rb +1 -1
  72. data/lib/rolemodel/yarn.rb +42 -0
  73. data/lib/rolemodel-rails.rb +0 -1
  74. metadata +58 -28
  75. data/lib/generators/rolemodel/linters/rubocop/templates/lib/cops/form_error_response.rb +0 -54
  76. data/lib/generators/rolemodel/linters/rubocop/templates/lib/cops/no_chrome_tag.rb +0 -45
  77. data/lib/generators/rolemodel/semaphore/README.md +0 -15
  78. data/lib/generators/rolemodel/semaphore/USAGE +0 -11
  79. data/lib/generators/rolemodel/semaphore/semaphore_generator.rb +0 -25
  80. data/lib/generators/rolemodel/semaphore/templates/heroku-deployment-commands.sh +0 -3
  81. data/lib/generators/rolemodel/semaphore/templates/production-deploy.yml.erb +0 -17
  82. data/lib/generators/rolemodel/semaphore/templates/semaphore.yml.erb +0 -145
  83. data/lib/generators/rolemodel/semaphore/templates/staging-deploy.yml.erb +0 -17
  84. data/lib/generators/rolemodel/ui_components/modals/USAGE +0 -8
  85. data/lib/generators/rolemodel/ui_components/modals/templates/app/javascript/controllers/morph_controller.js +0 -23
  86. data/lib/generators/rolemodel/webpack/templates/app/assets/stylesheets/application.scss +0 -5
  87. data/lib/generators/rolemodel/webpack/templates/app/javascript/initializers/honeybadger.js +0 -26
  88. /data/lib/generators/rolemodel/{simple_form → tailored_select}/templates/app/inputs/tailored_select_input.rb +0 -0
  89. /data/lib/generators/rolemodel/{ui_components/modals/templates/app/javascript/initializers/turbo_confirm.js → turbo/confirm/templates/app/javascript/initializers/turbo_confirm.js.tt} +0 -0
  90. /data/lib/generators/rolemodel/{ui_components/modals/templates/app/views/application/_confirm.html.slim → turbo/confirm/templates/app/views/application/_confirm.html.slim.tt} +0 -0
  91. /data/lib/generators/rolemodel/{ui_components → turbo}/modals/templates/app/javascript/controllers/toggle_controller.js +0 -0
  92. /data/lib/generators/rolemodel/{ui_components → turbo}/modals/templates/app/javascript/initializers/frame_missing_handler.js +0 -0
  93. /data/lib/generators/rolemodel/{ui_components → turbo}/modals/templates/app/views/layouts/modal.html.slim.tt +0 -0
  94. /data/lib/generators/rolemodel/{ui_components → turbo}/modals/templates/app/views/layouts/panel.html.slim.tt +0 -0
  95. /data/lib/generators/rolemodel/{ui_components/modals → turbo/ready}/templates/app/javascript/initializers/before_morph_handler.js +0 -0
  96. /data/lib/generators/rolemodel/ui_components/navbar/templates/app/assets/stylesheets/components/shoelace/{index.scss → index.css} +0 -0
  97. /data/lib/generators/rolemodel/ui_components/navbar/templates/app/views/{layouts → application}/_navbar.html.slim +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4af0b11896cbe75d88cf7934e0322e9c8145656e115d13888473c642aac86716
4
- data.tar.gz: ad3a3ab92a726d9ce7333ae9db2f45762ce02d5ded8a1c8b86b0923eb685c3c0
3
+ metadata.gz: 6f574ec1763415cfd5d59b0c42e27e04cf7a105639d736dec4b73f331212014b
4
+ data.tar.gz: f558030f6438ecc3703ddc58d51ef2fc29932e022ba3e732fffad2e58bdd3653
5
5
  SHA512:
6
- metadata.gz: 0c3cbd55ba285c70505447bc34b0509b98df66093e4fe35b5ae1007cb2252cd080a5779c434a4033302096d461a63c0365ea11b6b288d488adbbc8e31267a797
7
- data.tar.gz: 41ba42965a2ab047098f450cf3c1fe3d7b69cffda6b4a634da757c6df229f4173b0b1dabc403d77493d1feb891ad9b364f221b6ef61e6c739e76663658feb91f
6
+ metadata.gz: 443ef8cc4397434dbccc3fba921dd5bd7e319ef9c92d5cb7e34b29b8dfb3537551d7b39428d07d4baa045b4caa9750cf8cd723d081ab314da3fb771795be661b
7
+ data.tar.gz: 75c1c5ed618b05e5656f87fd7af488707af8c184a97f71e1142019347e56b77c70e31c1175b321003c3ab4c05b9839c76228b7d02faa4781f6032b39bf8fdd51
data/README.md CHANGED
@@ -40,10 +40,11 @@ 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)
44
45
 
45
46
  ```shell
46
- bin/rails g rolemodel:all
47
+ bin/rails g rolemodel:core_setup
47
48
  ```
48
49
 
49
50
  Or run a single generator
@@ -66,6 +67,7 @@ bin/rails g
66
67
 
67
68
  ## Generators
68
69
 
70
+ * [Core Setup](./lib/generators/rolemodel/core_setup)
69
71
  * [Github](./lib/generators/rolemodel/github)
70
72
  * [Semaphore](./lib/generators/rolemodel/semaphore)
71
73
  * [Heroku](./lib/generators/rolemodel/heroku)
@@ -76,6 +78,11 @@ bin/rails g
76
78
  * [Optics](./lib/generators/rolemodel/optics)
77
79
  * [Base](./lib/generators/rolemodel/optics/base)
78
80
  * [Icons](./lib/generators/rolemodel/optics/icons)
81
+ * [Turbo](./lib/generators/rolemodel/turbo)
82
+ * [Ready](./lib/generators/rolemodel/turbo/ready)
83
+ * [Confirm](./lib/generators/rolemodel/turbo/confirm)
84
+ * [Modals](./lib/generators/rolemodel/turbo/modals)
85
+ * [Form](./lib/generators/rolemodel/turbo/form)
79
86
  * [Testing](./lib/generators/rolemodel/testing)
80
87
  * [RSpec](./lib/generators/rolemodel/testing/rspec)
81
88
  * [Jasmine Playwright](./lib/generators/rolemodel/testing/jasmine_playwright)
@@ -93,7 +100,6 @@ bin/rails g
93
100
  * [ESLint](./lib/generators/rolemodel/linters/eslint)
94
101
  * [UI Components](./lib/generators/rolemodel/ui_components)
95
102
  * [Flash](./lib/generators/rolemodel/ui_components/flash)
96
- * [Modals](./lib/generators/rolemodel/ui_components/modals)
97
103
  * [Navbar](./lib/generators/rolemodel/ui_components/navbar)
98
104
  * [Source Map](./lib/generators/rolemodel/source_map)
99
105
  * [Kaminari](./lib/generators/rolemodel/kaminari)
@@ -215,7 +221,7 @@ DRY_RUN=true rake users:dev[_,bob@example.com]
215
221
 
216
222
  ## Development
217
223
 
218
- 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`.
224
+ 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.
219
225
 
220
226
  ## Adding new Generators
221
227
 
@@ -240,7 +246,7 @@ e.g.
240
246
 
241
247
  ```ruby
242
248
  RSpec.describe Rolemodel::MyGenerator, type: :generator do
243
- before { run_generator_against_test_app }
249
+ before { run_generators }
244
250
  end
245
251
  ```
246
252
 
@@ -250,19 +256,34 @@ e.g.
250
256
 
251
257
  ```ruby
252
258
  RSpec.describe Rolemodel::Testing::JasminePlaywrightGenerator, type: :generator do
253
- before { run_generator_against_test_app(['--github-package-token=123']) }
259
+ before { run_generators(['--github-package-token=123']) }
254
260
  end
255
261
  ```
256
262
 
257
- If the generator you're testing depends on being run after another generator, you should run that one first.
263
+ If the generator you're testing depends on being run after another generator, you may pass an array to the optional `generators` keyword argument in the order in which they should run.
258
264
 
259
265
  e.g.
260
266
 
261
267
  ```ruby
262
268
  RSpec.describe Rolemodel::MyGenerator, type: :generator do
263
269
  before do
264
- run_generator_against_test_app(generator: ::Rolemodel::PrereqGenerator)
265
- run_generator_against_test_app
270
+ run_generators(generators: [::Rolemodel::PrereqGenerator, described_class])
271
+ end
272
+ end
273
+ ```
274
+
275
+ If you're testing the standard output produced by your generator, assign the return value of `run_generators` to a variable.
276
+
277
+ e.g.
278
+
279
+ ```ruby
280
+ RSpec.describe Rolemodel::MyGenerator, type: :generator do
281
+ before do
282
+ @stdout = run_generators(generators: [::Rolemodel::PrereqGenerator, described_class])
283
+ end
284
+
285
+ it 'prints something useful' do
286
+ expect(@stdout[Rolemodel::MyGenerator]).to include('something useful')
266
287
  end
267
288
  end
268
289
  ```
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task default: :spec
@@ -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,23 @@
1
+ module Rolemodel
2
+ class CoreSetupGenerator < ::Rolemodel::GeneratorBase
3
+ class_option :skip_deployable, type: :boolean, default: false, desc: 'skip generators for app deployment (Heroku, Sentry, Lograge)'
4
+
5
+ def run_the_core_generators
6
+ generate 'rolemodel:slim'
7
+ generate 'rolemodel:webpack'
8
+ generate 'rolemodel:optics:all'
9
+ generate 'rolemodel:simple_form'
10
+ generate 'rolemodel:testing:all'
11
+ generate 'rolemodel:turbo:all'
12
+ generate 'rolemodel:ui_components:flash'
13
+ generate 'rolemodel:github'
14
+ generate 'rolemodel:readme'
15
+ generate 'rolemodel:linters:all'
16
+ return if options.skip_deployable?
17
+
18
+ generate 'rolemodel:heroku'
19
+ generate 'rolemodel:sentry'
20
+ generate 'rolemodel:lograge'
21
+ end
22
+ end
23
+ 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"
@@ -16,13 +16,11 @@ module Rolemodel
16
16
  def configure_good_job
17
17
  say 'Add GoodJob configuration'
18
18
 
19
- inject_into_file 'config/application.rb', after: "class Application < Rails::Application\n" do
20
- optimize_indentation <<~'RUBY', 4
21
- # Add GoodJob ActiveJob queue adapter
22
- config.active_job.queue_adapter = :good_job
19
+ inject_into_class 'config/application.rb', 'Application', optimize_indentation(<<~'RUBY', 4)
20
+ # Add GoodJob ActiveJob queue adapter
21
+ config.active_job.queue_adapter = :good_job
23
22
 
24
- RUBY
25
- end
23
+ RUBY
26
24
 
27
25
  gsub_file 'config/environments/production.rb', 'config.active_job.queue_adapter', '# config.active_job.queue_adapter'
28
26
  end
@@ -30,7 +28,7 @@ module Rolemodel
30
28
  def configure_good_job_routes
31
29
  say 'Add GoodJob routes'
32
30
 
33
- route_text = <<~RUBY
31
+ route <<~RUBY
34
32
  # Example but update per the authorization strategy of app
35
33
  namespace :admin do
36
34
  # authenticate :user, ->(user) { user.admin? } do
@@ -38,7 +36,6 @@ module Rolemodel
38
36
  # end
39
37
  end
40
38
  RUBY
41
- route route_text
42
39
  end
43
40
 
44
41
  def configure_procfile
@@ -58,14 +55,11 @@ module Rolemodel
58
55
  def copy_initializers
59
56
  say 'Add GoodJob initializers'
60
57
 
61
- copy_file 'config/initializers/active_job.rb'
62
- copy_file 'config/initializers/good_job.rb'
58
+ directory 'config/initializers'
63
59
  end
64
60
 
65
61
  def finishing_notes
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
-
62
+ say <<~NOTES, :green
69
63
  *** Reminder to also update your job classes to include appropriate concurrency controls (enqueue_limit/perform_limit with keys)
70
64
  NOTES
71
65
  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,18 @@ 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
+
18
30
  def force_ssl
19
31
  say 'Require SSL for production environment.', :green
20
32
 
@@ -48,5 +60,13 @@ module Rolemodel
48
60
  end
49
61
  RAKE
50
62
  end
63
+
64
+ def show_deploy_app_skill_path
65
+ # The deploy-app skill is one-time deployment setup, so it ships inside the
66
+ # rolemodel-rails gem instead of being copied into the app. Point the user
67
+ # at it so they can hand it to a coding agent when they're ready to deploy.
68
+ say "\nWhen you're ready to deploy, point your coding agent at the deploy-app skill:", :green
69
+ say File.expand_path('templates/deploy-app/SKILL.md', __dir__), :yellow
70
+ end
51
71
  end
52
72
  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|