Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3a044c7
Sentry generator
mark-kraemer Jul 3, 2026
86bb71b
Enable coreback rather than use yarn 1.22
mark-kraemer Jul 3, 2026
ec161bb
Conditionally add tailor select
mark-kraemer Jul 6, 2026
4b6026b
Additional cleanup
mark-kraemer Jul 6, 2026
160ae7e
Clean up which items are generated in the core workflow
mark-kraemer Jul 6, 2026
26b8249
generator cleanup
mark-kraemer Jul 6, 2026
0db7d0a
Remove extra templates
mark-kraemer Jul 6, 2026
610576d
Fix specs
mark-kraemer Jul 6, 2026
c7b030c
Fix specs
mark-kraemer Jul 6, 2026
51f371a
Just reference the skill from rolemodel rails
mark-kraemer Jul 6, 2026
ca0ebeb
Remove semaphore
mark-kraemer Jul 6, 2026
64f185b
Address PR #198 review feedback
mark-kraemer Jul 7, 2026
e8f5c0a
minor clean up
OutlawAndy Jul 12, 2026
7e956d2
test: pin the Rails resolution chain the generator registry relies on
OutlawAndy Jul 12, 2026
9d01c33
feat: add generator registry and recording seam in GeneratorBase
OutlawAndy Jul 12, 2026
3f52395
feat: add coupling_hook and requires_generator declaration macros
OutlawAndy Jul 12, 2026
9ddd8dc
feat: convert sentry<->webpack coupling to a hook sub-generator
OutlawAndy Jul 12, 2026
b96b276
feat: drive tailored_select's SimpleForm input off the registry
OutlawAndy Jul 12, 2026
a8b693e
feat: remove coupling flags and semaphore from composite generators
OutlawAndy Jul 12, 2026
7799718
feat: add registry seeding generator for existing apps
OutlawAndy Jul 12, 2026
8bbbb1a
docs: document registry contract, coupling behavior, remove semaphore…
OutlawAndy Jul 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ log
.history

.DS_Store

# Leaked app config from running generators at the repo root during dev/specs
/config/
41 changes: 38 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,14 @@ And then execute:

## Usage

Run all generators (useful on a new app)
Run the core generators (recommended on a new app — sets up our standard
baseline so the app is ready to push straight to Heroku)

```shell
bin/rails g rolemodel:core_setup
```

Or run every generator, including app-specific extras like React, SaaS/Devise, and GoodJob

```shell
bin/rails g rolemodel:all
Expand All @@ -64,10 +71,37 @@ You can see complete list of available generators (including those under the Rol
bin/rails g
```

## Generator Registry

Every rolemodel generator that is run records itself in your app's
`config/initializers/rolemodel_generators.rb`. This registry powers coupling
declarations between generators — for example, the sentry and webpack generators
know about each other through the registry and automatically wire the Sentry
webpack plugin when both are present.

For apps set up before the registry existed, run the seeding generator:

```shell
bin/rails g rolemodel:registry
```

The seeder probes for each generator's characteristic output files and writes
entries marked as `seeded-by-detection`. It is safe to run multiple times.

### Opting out

* **Persistent opt-out:** Set `g.rolemodel <key>: false` in the initializer.
The generator will never re-record over a `false` entry.
* **Per-invocation opt-out:** Pass `--no-<key>` to any generator that declares
a coupling (e.g. `rails g rolemodel:sentry --no-sentry-webpack`).
* **Drift recovery:** If the managed block markers are missing from the
initializer, delete the file and run `rails g rolemodel:registry` to
rebuild it.

## Generators

* [Core Setup](./lib/generators/rolemodel/core_setup)
* [Github](./lib/generators/rolemodel/github)
* [Semaphore](./lib/generators/rolemodel/semaphore)
* [Heroku](./lib/generators/rolemodel/heroku)
* [Readme](./lib/generators/rolemodel/readme)
* [Webpack](./lib/generators/rolemodel/webpack)
Expand Down Expand Up @@ -101,10 +135,11 @@ bin/rails g
* [Editors](./lib/generators/rolemodel/editors)
* [Tailored Select](./lib/generators/rolemodel/tailored_select)
* [Lograge](./lib/generators/rolemodel/lograge)
* [Registry](./lib/generators/rolemodel/registry)

## Development

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`.
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.

## Adding new Generators

Expand Down
21 changes: 20 additions & 1 deletion lib/generators/rolemodel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,32 @@
* [Optics](./optics)
* [README](./readme)
* [SaaS](./saas)
* [Semaphore](./semaphore)
* [SimpleForm](./simple_form)
* [Slim](./slim)
* [SoftDestroyable](./soft_destroyable)
* [Source Map](./source_map)
* [Testing](./testing)
* [Webpack](./webpack)
* [Registry](./registry)

## Generator Registry

Each generator records itself in `config/initializers/rolemodel_generators.rb`
so that other generators can detect whether it has been applied. See
[Registry](./registry) for the seeding tool.

### Generator coupling

Some generators declare optional couplings:

* **sentry ↔ webpack:** Whichever is installed second wires the Sentry webpack
plugin into `webpack.config.js` via the `sentry_webpack` hook sub-generator.
Pass `--no-sentry-webpack` to suppress. Set
`g.rolemodel sentry_webpack: false` in the initializer for a persistent opt-out.
* **tailored_select ↔ simple_form:** Installing simple_form with
`--tailored-select` installs the tailored_select package and its input.
Installing tailored_select standalone installs the package without the input.
Pass `--simple-form-input` to force the input regardless.

## Helpful documentation

Expand Down
6 changes: 5 additions & 1 deletion lib/generators/rolemodel/all_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ module Rolemodel
class AllGenerator < GeneratorBase
source_root File.expand_path('templates', __dir__)

# Composite generator: it orchestrates other generators and installs nothing
# itself, so it is not recorded in the registry.
skip_registry_entry!

def run_all_the_generators
generate 'rolemodel:github'
generate 'rolemodel:semaphore'
generate 'rolemodel:heroku'
generate 'rolemodel:readme'
generate 'rolemodel:webpack'
generate 'rolemodel:sentry'
generate 'rolemodel:react'
generate 'rolemodel:slim'
generate 'rolemodel:optics:all'
Expand Down
27 changes: 27 additions & 0 deletions lib/generators/rolemodel/core_setup/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Core Setup Generator

Runs the core generators every new Rails app should have. Unlike
`rolemodel:all`, this skips the app-specific extras (React, SaaS/Devise,
GoodJob, Kaminari, etc.) so you get just the standard baseline — a new Rails
app you can push straight to Heroku right after generation.

## Prerequisites

- A freshly generated Rails app

## What you get

- [GitHub](../github) — standard GitHub configuration
- [Heroku](../heroku) — standard Heroku deployment configuration
- [Readme](../readme) — standard project README
- [Webpack](../webpack) — Webpack v5 for JS and CSS
- [Sentry](../sentry) — error monitoring for Ruby and JavaScript
- [Slim](../slim) — Slim templates
- [Optics](../optics) — Optics design system
- [Testing](../testing) — RSpec, FactoryBot, parallel_tests, TestProf
(pass `--js-runner` to include jasmine-playwright-runner)
- [SimpleForm](../simple_form) — SimpleForm with our configuration
- [Linters](../linters) — Rubocop and ESLint
- [UI Components](../ui_components) — flash, the modal pattern, & Turbo 8 support
- [Editors](../editors) — EditorConfig and recommended VSCode extensions
- [Lograge](../lograge) — condensed request logging
12 changes: 12 additions & 0 deletions lib/generators/rolemodel/core_setup/USAGE
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Description:
Runs the core generators every new Rails app should have

Sets up GitHub config, Heroku deployment, README, Webpack, Sentry, Slim,
Optics, testing (RSpec & friends), SimpleForm, linters, Turbo 8+ support,
and Lograge. The result is an app that is ready to push directly to Heroku.

Pass --js-runner to also include jasmine-playwright-runner for browser
JS testing.

Example:
rails generate rolemodel:core_setup
23 changes: 23 additions & 0 deletions lib/generators/rolemodel/core_setup/core_setup_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module Rolemodel
class CoreSetupGenerator < ::Rolemodel::GeneratorBase
# Composite generator: it orchestrates other generators and installs nothing
# itself, so it is not recorded in the registry.
skip_registry_entry!

def run_the_core_generators
generate 'rolemodel:github'
generate 'rolemodel:heroku'
generate 'rolemodel:readme'
generate 'rolemodel:webpack'
generate 'rolemodel:sentry'
generate 'rolemodel:slim'
generate 'rolemodel:optics:all'
generate 'rolemodel:testing:all'
generate 'rolemodel:simple_form'
generate 'rolemodel:linters:all'
generate 'rolemodel:ui_components:flash'
generate 'rolemodel:ui_components:modals'
generate 'rolemodel:lograge'
end
end
end
4 changes: 4 additions & 0 deletions lib/generators/rolemodel/github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ It doesn't need to be run first, but the parallel_tests generator must be run in
- CI workflow
- A sensible default `ci.yml` to get you started with Github Actions. This will run linters, model tests, and system tests.
- Along with the `ci.yml`, your `database.yml` will be modified to be able to be run in GHA.
- Deploy workflows
- `deploy-staging.yml` deploys to Heroku on every push to `main` (or manually); `deploy-production.yml` deploys manually via `workflow_dispatch`.
- 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.
- 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.
- Pull Request Template
- 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.
- Helpful for reminding collaborators to add specific details to the PR.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Deploy to Heroku Production

on:
workflow_dispatch:
inputs:
sha:
description: 'Specific SHA or Branch name (optional)'
required: false
type: string

concurrency: production-deployment

jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
timeout-minutes: 20
environment:
name: Production
url: ${{ vars.HEROKU_APP_URL }}
steps:
- uses: actions/checkout@v6

- name: Install Heroku CLI
run: |
curl https://cli-assets.heroku.com/install.sh | sh

- uses: akhileshns/heroku-deploy@v3.15.15
with:
heroku_api_key: ${{ secrets.HEROKU_IT_SUPPORT_API_KEY }}
heroku_app_name: ${{ vars.HEROKU_APP_NAME }}
heroku_email: ${{ vars.HEROKU_IT_SUPPORT_EMAIL }}
branch: ${{ inputs.sha || 'HEAD' }}
healthcheck: '${{ vars.HEROKU_APP_URL }}/up'
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Deploy to Heroku Staging

on:
workflow_dispatch:
inputs:
sha:
description: 'Specific SHA or Branch name (optional)'
required: false
type: string
push: { branches: [ staging ] }

concurrency: staging-deployment

jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
timeout-minutes: 20
environment:
name: Staging
url: ${{ vars.HEROKU_APP_URL }}
steps:
- uses: actions/checkout@v6

- name: Install Heroku CLI
run: |
curl https://cli-assets.heroku.com/install.sh | sh

- uses: akhileshns/heroku-deploy@v3.15.15
with:
heroku_api_key: ${{ secrets.HEROKU_IT_SUPPORT_API_KEY }}
heroku_app_name: ${{ vars.HEROKU_APP_NAME }}
heroku_email: ${{ vars.HEROKU_IT_SUPPORT_EMAIL }}
branch: ${{ inputs.sha || 'HEAD' }}
healthcheck: "${{ vars.HEROKU_APP_URL }}/up"
2 changes: 0 additions & 2 deletions lib/generators/rolemodel/good_job/good_job_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@ def copy_initializers

def finishing_notes
say <<~NOTES
*** Reminder to update Honeybadger gem to version 5.7.0 or later to get correct GoodJob error notifications in Honeybadger

*** Reminder to also update your job classes to include appropriate concurrency controls (enqueue_limit/perform_limit with keys)
NOTES
end
Expand Down
10 changes: 10 additions & 0 deletions lib/generators/rolemodel/heroku/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,15 @@

* Procfile configured for a server process and a release command to run migrations
* Basic app.json preconfigured with a script to initialize the database and common environment variables, addons, and buildpacks
* A pointer in `AGENTS.md` to the `deploy-app` agent skill, which ships inside the
`rolemodel-rails` gem (`lib/rolemodel/skills/deploy-app/SKILL.md`) rather than being copied
into your repo — it's one-time deployment setup, so nothing skill-related is committed to the
app. The skill cleans up the generated Gemfile (merges duplicate groups, removes comments,
alphabetizes), verifies the test suite and RuboCop pass, creates the Sentry project and wires
up the DSN, creates and deploys the Heroku staging app (buildpacks, dynos, Postgres,
Papertrail), and creates the GitHub `Staging` environment with the
`HEROKU_APP_NAME`/`HEROKU_APP_URL` variables the deploy workflow consumes. The skill is
LLM-agnostic (Agent Skills format): locate it with `bundle show rolemodel-rails` and point any
coding agent at the SKILL.md. Requires the Heroku CLI, the GitHub CLI, and the Sentry MCP server.

This is the basic config needed to deploy to Heroku.
62 changes: 62 additions & 0 deletions lib/generators/rolemodel/heroku/heroku_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,47 @@ def install_procfile
template 'Procfile'
end

def reference_deploy_app_skill
say 'Reference the deploy-app agent skill from rolemodel-rails', :green

# The deploy-app skill is one-time, run-once deployment setup. Rather than
# copying ~200 lines of instructions into every app repo, point the agent at
# the SKILL.md that ships inside the rolemodel-rails gem (already a bundled
# dependency). Nothing skill-related is committed to the generated app.
if File.exist?(File.join(destination_root, 'AGENTS.md'))
append_to_file 'AGENTS.md', agents_md_skill_entry
else
create_file 'AGENTS.md', "# Agent instructions\n#{agents_md_skill_entry}"
end
end

def pin_ruby_version_for_buildpack
say 'Pin the Ruby version in the Gemfile so the Heroku buildpack respects it.', :green

# A bare .ruby-version file is not enough: without a `ruby` directive the version
# never lands in Gemfile.lock, so the Heroku Ruby buildpack falls back to its own
# default and can install an incompatible Ruby. Tie the Gemfile to .ruby-version.
gemfile = File.join(destination_root, 'Gemfile')
return if File.exist?(gemfile) && File.read(gemfile).match?(/^\s*ruby\s/)

inject_into_file 'Gemfile', "\nruby file: '.ruby-version'\n", after: /^source .*$/
end

def use_database_url_in_production
say 'Point the production database at DATABASE_URL for Heroku.', :green

# Rails' generated production block hardcodes database/username/<APP>_DATABASE_PASSWORD,
# none of which exist on Heroku, where the Postgres add-on provides a full DATABASE_URL.
# Replace the whole block with a url-based config.
gsub_file 'config/database.yml',
/^production:\n(?:[ \t]+.*\n?)+/,
<<~YAML
production:
<<: *default
url: <%= ENV["DATABASE_URL"] %>
YAML
end

def force_ssl
say 'Require SSL for production environment.', :green

Expand Down Expand Up @@ -48,5 +89,26 @@ def create_assets_rake_tasks # rubocop:disable Metrics/MethodLength
end
RAKE
end

private

def agents_md_skill_entry
<<~MD

## Agent skills

Reusable, agent-agnostic task instructions (Agent Skills format) ship inside the
`rolemodel-rails` gem — a bundled dependency of this app — rather than being copied
into this repo. Locate the gem's skills directory with
`bundle show rolemodel-rails` (the skills live under `lib/rolemodel/skills/`), then
read the relevant `SKILL.md` and follow it directly.

* `deploy-app` (`$(bundle show rolemodel-rails)/lib/rolemodel/skills/deploy-app/SKILL.md`)
— one-time deployment setup: cleans up the generated Gemfile, verifies the test
suite and RuboCop pass, creates the Sentry project and wires up the DSN, creates
and deploys the Heroku staging app, and creates the GitHub `Staging` environment +
deploy workflow. Use when asked to set up staging, Heroku, or deployment.
MD
end
end
end
7 changes: 5 additions & 2 deletions lib/generators/rolemodel/heroku/templates/app.json.tt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
"postdeploy": "bin/rails db:seed"
},
"env": {
"HONEYBADGER_API_KEY": {
"SENTRY_DSN": {
"required": true
},
"HONEYBADGER_ENV": {
"SENTRY_ENVIRONMENT": {
"required": true,
"value": "review-app"
},
"SENTRY_AUTH_TOKEN": {
"required": false
}
},
"addons": [
Expand Down
Loading