rolemodel-rails 0.26.0 → 1.1.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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -4
  3. data/lib/generators/rolemodel/all_generator.rb +2 -1
  4. data/lib/generators/rolemodel/editors/editors_generator.rb +1 -1
  5. data/lib/generators/rolemodel/github/README.md +14 -17
  6. data/lib/generators/rolemodel/github/USAGE +4 -1
  7. data/lib/generators/rolemodel/github/github_generator.rb +24 -18
  8. data/lib/generators/rolemodel/github/templates/CODEOWNERS +8 -0
  9. data/lib/generators/rolemodel/github/templates/dependabot.yml +87 -0
  10. data/lib/generators/rolemodel/github/templates/instructions +1 -0
  11. data/lib/generators/rolemodel/github/templates/pull_request_template.md +18 -0
  12. data/lib/generators/rolemodel/github/templates/workflows/ci.yml.tt +81 -0
  13. data/lib/generators/rolemodel/good_job/good_job_generator.rb +1 -1
  14. data/lib/generators/rolemodel/heroku/heroku_generator.rb +1 -1
  15. data/lib/generators/rolemodel/kaminari/kaminari_generator.rb +1 -1
  16. data/lib/generators/rolemodel/linters/all_generator.rb +1 -1
  17. data/lib/generators/rolemodel/linters/eslint/eslint_generator.rb +1 -1
  18. data/lib/generators/rolemodel/linters/rubocop/rubocop_generator.rb +1 -1
  19. data/lib/generators/rolemodel/lograge/lograge_generator.rb +1 -1
  20. data/lib/generators/rolemodel/mailers/mailers_generator.rb +1 -1
  21. data/lib/generators/rolemodel/mcp/README.md +13 -0
  22. data/lib/generators/rolemodel/mcp/USAGE +8 -0
  23. data/lib/generators/rolemodel/mcp/mcp_generator.rb +110 -0
  24. data/lib/generators/rolemodel/mcp/templates/app/assets/stylesheets/components/doorkeeper.css +140 -0
  25. data/lib/generators/rolemodel/mcp/templates/app/controllers/doorkeeper/base_controller.rb +7 -0
  26. data/lib/generators/rolemodel/mcp/templates/app/controllers/mcp_controller.rb.tt +91 -0
  27. data/lib/generators/rolemodel/mcp/templates/app/controllers/oauth_registrations_controller.rb +46 -0
  28. data/lib/generators/rolemodel/mcp/templates/app/controllers/well_known_controller.rb +39 -0
  29. data/lib/generators/rolemodel/mcp/templates/app/mcp/prompts/sample.rb +36 -0
  30. data/lib/generators/rolemodel/mcp/templates/app/mcp/resources/controller.rb +57 -0
  31. data/lib/generators/rolemodel/mcp/templates/app/mcp/resources/docs/SAMPLE_DOC.md +4 -0
  32. data/lib/generators/rolemodel/mcp/templates/app/mcp/resources/docs_controller.rb +46 -0
  33. data/lib/generators/rolemodel/mcp/templates/app/mcp/tools/sample.rb +42 -0
  34. data/lib/generators/rolemodel/mcp/templates/app/views/doorkeeper/authorizations/error.html.slim.tt +13 -0
  35. data/lib/generators/rolemodel/mcp/templates/app/views/doorkeeper/authorizations/new.html.slim.tt +41 -0
  36. data/lib/generators/rolemodel/mcp/templates/app/views/layouts/doorkeeper.html.slim +7 -0
  37. data/lib/generators/rolemodel/mcp/templates/config/initializers/doorkeeper.rb +537 -0
  38. data/lib/generators/rolemodel/mcp/templates/spec/mcp/prompts/sample_spec.rb +15 -0
  39. data/lib/generators/rolemodel/mcp/templates/spec/mcp/resources/controller_spec.rb +16 -0
  40. data/lib/generators/rolemodel/mcp/templates/spec/mcp/resources/docs_controller_spec.rb +55 -0
  41. data/lib/generators/rolemodel/mcp/templates/spec/mcp/tools/sample_spec.rb +15 -0
  42. data/lib/generators/rolemodel/mcp/templates/spec/requests/mcp_controller_spec.rb +84 -0
  43. data/lib/generators/rolemodel/mcp/templates/spec/requests/oauth_registrations_controller_spec.rb +62 -0
  44. data/lib/generators/rolemodel/mcp/templates/spec/requests/well_known_controller_spec.rb +30 -0
  45. data/lib/generators/rolemodel/optics/all_generator.rb +1 -1
  46. data/lib/generators/rolemodel/optics/base/base_generator.rb +2 -2
  47. data/lib/generators/rolemodel/optics/icons/icons_generator.rb +1 -1
  48. data/lib/generators/rolemodel/react/react_generator.rb +1 -1
  49. data/lib/generators/rolemodel/readme/readme_generator.rb +1 -1
  50. data/lib/generators/rolemodel/saas/all_generator.rb +1 -1
  51. data/lib/generators/rolemodel/saas/devise/devise_generator.rb +1 -1
  52. data/lib/generators/rolemodel/semaphore/semaphore_generator.rb +1 -1
  53. data/lib/generators/rolemodel/simple_form/simple_form_generator.rb +1 -1
  54. data/lib/generators/rolemodel/slim/slim_generator.rb +1 -1
  55. data/lib/generators/rolemodel/soft_destroyable/soft_destroyable_generator.rb +1 -1
  56. data/lib/generators/rolemodel/source_map/source_map_generator.rb +1 -1
  57. data/lib/generators/rolemodel/tailored_select/tailored_select_generator.rb +1 -1
  58. data/lib/generators/rolemodel/testing/all_generator.rb +1 -1
  59. data/lib/generators/rolemodel/testing/factory_bot/factory_bot_generator.rb +1 -1
  60. data/lib/generators/rolemodel/testing/jasmine_playwright/jasmine_playwright_generator.rb +1 -1
  61. data/lib/generators/rolemodel/testing/parallel_tests/parallel_tests_generator.rb +1 -1
  62. data/lib/generators/rolemodel/testing/rspec/rspec_generator.rb +1 -2
  63. data/lib/generators/rolemodel/testing/rspec/templates/rails_helper.rb +4 -0
  64. data/lib/generators/rolemodel/testing/rspec/templates/support/capybara_drivers.rb +0 -2
  65. data/lib/generators/rolemodel/testing/rspec/templates/support/helpers/capybara_helper.rb +0 -46
  66. data/lib/generators/rolemodel/testing/rspec/templates/support/helpers/playwright_helper.rb +0 -60
  67. data/lib/generators/rolemodel/testing/test_prof/test_prof_generator.rb +1 -1
  68. data/lib/generators/rolemodel/testing/vitest/vitest_generator.rb +1 -1
  69. data/lib/generators/rolemodel/ui_components/all_generator.rb +1 -1
  70. data/lib/generators/rolemodel/ui_components/flash/flash_generator.rb +1 -1
  71. data/lib/generators/rolemodel/ui_components/modals/modals_generator.rb +1 -1
  72. data/lib/generators/rolemodel/ui_components/navbar/navbar_generator.rb +1 -1
  73. data/lib/generators/rolemodel/webpack/webpack_generator.rb +1 -1
  74. data/lib/rolemodel/engine.rb +3 -1
  75. data/lib/rolemodel/generator_base.rb +17 -0
  76. data/lib/rolemodel/version.rb +1 -1
  77. data/lib/rolemodel-rails.rb +2 -0
  78. metadata +32 -7
  79. data/lib/generators/rolemodel/base_generator.rb +0 -14
  80. data/lib/generators/templates/generator/%filename%.rb.tt +0 -11
  81. data/lib/generators/templates/generator/README.md.tt +0 -11
  82. data/lib/generators/templates/generator/USAGE.tt +0 -5
  83. data/lib/generators/templates/generator_spec/%filename%_spec.rb.tt +0 -5
  84. /data/lib/{generators/rolemodel → rolemodel}/replace_content_helper.rb +0 -0
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ RSpec.describe 'MCPController', type: :request do
6
+ let(:user) { create(:user, name: 'Jane Smith', active: true, employee: true) }
7
+ let(:application) do
8
+ Doorkeeper::Application.create!(
9
+ name: 'MCP Test Client',
10
+ redirect_uri: 'https://example.com/callback',
11
+ scopes: 'mcp',
12
+ )
13
+ end
14
+
15
+ describe 'POST /mcp' do
16
+ let(:token) do
17
+ Doorkeeper::AccessToken.create!(
18
+ application: application,
19
+ resource_owner_id: user.id,
20
+ scopes: 'mcp',
21
+ expires_in: 2.hours.to_i,
22
+ )
23
+ end
24
+
25
+ let(:initialize_request) do
26
+ {
27
+ jsonrpc: '2.0',
28
+ id: 1,
29
+ method: 'initialize',
30
+ params: {
31
+ protocolVersion: '2025-11-25',
32
+ capabilities: {},
33
+ clientInfo: {
34
+ name: 'rspec',
35
+ version: '1.0',
36
+ },
37
+ },
38
+ }
39
+ end
40
+
41
+ let(:headers) do
42
+ {
43
+ 'CONTENT_TYPE' => 'application/json',
44
+ 'ACCEPT' => 'application/json, text/event-stream',
45
+ }
46
+ end
47
+
48
+ let(:authorized_headers) do
49
+ headers.merge('Authorization' => "Bearer #{token.token}")
50
+ end
51
+
52
+ it 'returns unauthorized when no bearer token is provided' do
53
+ post '/mcp', params: initialize_request.to_json, headers: headers
54
+
55
+ expect(response).to have_http_status(:unauthorized)
56
+ expect(response.headers['WWW-Authenticate']).to include('resource_metadata=')
57
+ end
58
+
59
+ it 'returns forbidden when token does not include mcp scope' do
60
+ token = Doorkeeper::AccessToken.create!(
61
+ application: application,
62
+ resource_owner_id: user.id,
63
+ scopes: 'other',
64
+ expires_in: 2.hours.to_i,
65
+ )
66
+
67
+ post '/mcp',
68
+ params: initialize_request.to_json,
69
+ headers: headers.merge('Authorization' => "Bearer #{token.token}")
70
+
71
+ expect(response).to have_http_status(:forbidden)
72
+ end
73
+
74
+ it 'returns initialize response for a valid bearer token' do
75
+ post '/mcp',
76
+ params: initialize_request.to_json,
77
+ headers: authorized_headers
78
+
79
+ expect(response).to have_http_status(:ok)
80
+ expect(response.parsed_body['result']).to be_present
81
+ expect(response.parsed_body['result']['capabilities']).to include('tools')
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ RSpec.describe 'OauthRegistrationsController', type: :request do
6
+ describe 'POST /oauth/register' do
7
+ let(:params) do
8
+ {
9
+ client_name: 'GitHub Copilot',
10
+ redirect_uris: ['http://localhost:12345/callback'],
11
+ grant_types: ['authorization_code'],
12
+ response_types: ['code'],
13
+ token_endpoint_auth_method: 'none',
14
+ scope: 'mcp',
15
+ }
16
+ end
17
+
18
+ it 'creates an OAuth application and returns client_id' do
19
+ expect { post '/oauth/register', params: params, as: :json }
20
+ .to change(Doorkeeper::Application, :count).by(1)
21
+
22
+ expect(response).to have_http_status(:created)
23
+ body = response.parsed_body
24
+ expect(body['client_id']).to be_present
25
+ expect(body['client_name']).to eq('GitHub Copilot')
26
+ expect(body['redirect_uris']).to eq(['http://localhost:12345/callback'])
27
+ expect(body['token_endpoint_auth_method']).to eq('none')
28
+ expect(body).not_to have_key('client_secret')
29
+
30
+ app = Doorkeeper::Application.last
31
+ expect(app.name).to eq('GitHub Copilot')
32
+ expect(app.redirect_uri).to eq('http://localhost:12345/callback')
33
+ expect(app.scopes).to contain_exactly('mcp')
34
+ expect(app).not_to be_confidential
35
+ end
36
+
37
+ it 'returns bad_request when redirect_uris is missing' do
38
+ post '/oauth/register', params: { client_name: 'Test' }, as: :json
39
+
40
+ expect(response).to have_http_status(:bad_request)
41
+ expect(response.parsed_body['error']).to eq('invalid_client_metadata')
42
+ end
43
+
44
+ it 'creates a confidential client when token_endpoint_auth_method is not none' do
45
+ params[:token_endpoint_auth_method] = 'client_secret_basic'
46
+ post '/oauth/register', params: params, as: :json
47
+
48
+ expect(response).to have_http_status(:created)
49
+ body = response.parsed_body
50
+ expect(body['client_secret']).to be_present
51
+ expect(body['token_endpoint_auth_method']).to eq('client_secret_basic')
52
+ end
53
+
54
+ it 'allows loopback redirect URIs for native clients' do
55
+ params[:redirect_uris] = ['http://127.0.0.1:33418/']
56
+ post '/oauth/register', params: params, as: :json
57
+
58
+ expect(response).to have_http_status(:created)
59
+ expect(response.parsed_body['redirect_uris']).to eq(['http://127.0.0.1:33418/'])
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ RSpec.describe 'WellKnownController', type: :request do
6
+ describe 'GET /.well-known/oauth-protected-resource' do
7
+ it 'returns the MCP resource and authorization server' do
8
+ get '/.well-known/oauth-protected-resource'
9
+
10
+ expect(response).to have_http_status(:ok)
11
+ body = response.parsed_body
12
+ expect(body['resource']).to end_with('/mcp')
13
+ expect(body['authorization_servers']).to be_an(Array)
14
+ end
15
+ end
16
+
17
+ describe 'GET /.well-known/oauth-authorization-server' do
18
+ it 'returns authorization server metadata with registration_endpoint' do
19
+ get '/.well-known/oauth-authorization-server'
20
+
21
+ expect(response).to have_http_status(:ok)
22
+ body = response.parsed_body
23
+ expect(body['registration_endpoint']).to end_with('/oauth/register')
24
+ expect(body['authorization_endpoint']).to end_with('/oauth/authorize')
25
+ expect(body['token_endpoint']).to end_with('/oauth/token')
26
+ expect(body['code_challenge_methods_supported']).to include('S256')
27
+ expect(body['scopes_supported']).to include('mcp')
28
+ end
29
+ end
30
+ end
@@ -1,6 +1,6 @@
1
1
  module Rolemodel
2
2
  module Optics
3
- class AllGenerator < Rolemodel::BaseGenerator
3
+ class AllGenerator < Rolemodel::GeneratorBase
4
4
  source_root File.expand_path('templates', __dir__)
5
5
 
6
6
  def run_all_the_generators
@@ -1,6 +1,6 @@
1
1
  module Rolemodel
2
2
  module Optics
3
- class BaseGenerator < Rolemodel::BaseGenerator
3
+ class BaseGenerator < Rolemodel::GeneratorBase
4
4
  source_root File.expand_path('templates', __dir__)
5
5
 
6
6
  def add_optics_package
@@ -12,7 +12,7 @@ module Rolemodel
12
12
  def copy_templates
13
13
  say 'importing stylesheet', :green
14
14
 
15
- prepend_to_file 'app/assets/stylesheets/application.scss', <<~SCSS
15
+ prepend_to_file Dir.glob('app/assets/stylesheets/application.*').first, <<~SCSS
16
16
  @import '@rolemodel/optics/dist/css/optics';
17
17
  SCSS
18
18
  end
@@ -3,7 +3,7 @@
3
3
  module Rolemodel
4
4
  module Optics
5
5
  # Generates the icon helper and icon builders for the chosen icon library
6
- class IconsGenerator < Rolemodel::BaseGenerator
6
+ class IconsGenerator < Rolemodel::GeneratorBase
7
7
  SUPPORTED_LIBRARIES = HashWithIndifferentAccess.new(
8
8
  material: 'filled, size, weight, emphasis, additional_classes, color, hover_text',
9
9
  phosphor: 'duotone, filled, size, weight, additional_classes, color, hover_text',
@@ -1,5 +1,5 @@
1
1
  module Rolemodel
2
- class ReactGenerator < BaseGenerator
2
+ class ReactGenerator < GeneratorBase
3
3
  source_root File.expand_path('templates', __dir__)
4
4
 
5
5
  def add_npm_packages
@@ -1,5 +1,5 @@
1
1
  module Rolemodel
2
- class ReadmeGenerator < BaseGenerator
2
+ class ReadmeGenerator < GeneratorBase
3
3
  source_root File.expand_path('templates', __dir__)
4
4
 
5
5
  def install_readme
@@ -1,6 +1,6 @@
1
1
  module Rolemodel
2
2
  module Saas
3
- class AllGenerator < BaseGenerator
3
+ class AllGenerator < GeneratorBase
4
4
  source_root File.expand_path('templates', __dir__)
5
5
 
6
6
  def run_all_the_generators
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rolemodel
4
4
  module Saas
5
- class DeviseGenerator < BaseGenerator
5
+ class DeviseGenerator < GeneratorBase
6
6
  source_root File.expand_path('templates', __dir__)
7
7
 
8
8
  def add_organization
@@ -1,5 +1,5 @@
1
1
  module Rolemodel
2
- class SemaphoreGenerator < BaseGenerator
2
+ class SemaphoreGenerator < GeneratorBase
3
3
  source_root File.expand_path('templates', __dir__)
4
4
 
5
5
  def create_base_semaphore_config
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rolemodel
4
- class SimpleFormGenerator < BaseGenerator
4
+ class SimpleFormGenerator < GeneratorBase
5
5
  source_root File.expand_path('templates', __dir__)
6
6
 
7
7
  def add_gem
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rolemodel
4
- class SlimGenerator < BaseGenerator
4
+ class SlimGenerator < GeneratorBase
5
5
  source_root File.expand_path('templates', __dir__)
6
6
 
7
7
  def add_slim
@@ -1,5 +1,5 @@
1
1
  module Rolemodel
2
- class SoftDestroyableGenerator < BaseGenerator
2
+ class SoftDestroyableGenerator < GeneratorBase
3
3
  source_root File.expand_path('templates', __dir__)
4
4
 
5
5
  def add_concern
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rolemodel
4
- class SourceMapGenerator < BaseGenerator
4
+ class SourceMapGenerator < GeneratorBase
5
5
  source_root File.expand_path('templates', __dir__)
6
6
 
7
7
  def inject_config_for_production
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rolemodel
4
- class TailoredSelectGenerator < BaseGenerator
4
+ class TailoredSelectGenerator < GeneratorBase
5
5
  source_root File.expand_path('templates', __dir__)
6
6
 
7
7
  def add_tailored_select_package
@@ -1,6 +1,6 @@
1
1
  module Rolemodel
2
2
  module Testing
3
- class AllGenerator < BaseGenerator
3
+ class AllGenerator < GeneratorBase
4
4
  source_root File.expand_path('templates', __dir__)
5
5
 
6
6
  def run_all_the_generators
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rolemodel
4
4
  module Testing
5
- class FactoryBotGenerator < BaseGenerator
5
+ class FactoryBotGenerator < GeneratorBase
6
6
  source_root File.expand_path('templates', __dir__)
7
7
 
8
8
  def install_factory_bot_rails
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rolemodel
4
4
  module Testing
5
- class JasminePlaywrightGenerator < BaseGenerator
5
+ class JasminePlaywrightGenerator < GeneratorBase
6
6
  include ReplaceContentHelper
7
7
  source_root File.expand_path('templates', __dir__)
8
8
  class_option :github_package_token, type: :string, default: ENV['GITHUB_PACKAGES_TOKEN'], desc: 'GitHub Packages token with access to @rolemodel packages'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rolemodel
4
4
  module Testing
5
- class ParallelTestsGenerator < BaseGenerator # rubocop:disable Style/Documentation
5
+ class ParallelTestsGenerator < GeneratorBase # rubocop:disable Style/Documentation
6
6
  source_root File.expand_path('templates', __dir__)
7
7
 
8
8
  def install_parallel_tests
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rolemodel
4
4
  module Testing
5
- class RspecGenerator < BaseGenerator
5
+ class RspecGenerator < GeneratorBase
6
6
  source_root File.expand_path('templates', __dir__)
7
7
 
8
8
  def install_rspec
@@ -15,7 +15,6 @@ module Rolemodel
15
15
  gem 'capybara-playwright-driver'
16
16
  gem 'marsh_grass'
17
17
  gem 'pry'
18
- gem 'webdrivers'
19
18
  end
20
19
  run_bundle
21
20
  end
@@ -9,6 +9,10 @@ abort("The Rails environment is running in production mode!") if Rails.env.produ
9
9
  require 'rspec/rails'
10
10
  # Add additional requires below this line. Rails is not loaded until this point!
11
11
 
12
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
13
+ inflect.acronym "MCP"
14
+ end
15
+
12
16
  # Requires supporting ruby files with custom matchers and macros, etc, in
13
17
  # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
14
18
  # run as spec files by default. This means that files in spec/support that end
@@ -47,5 +47,3 @@ Capybara.server = :puma, { Silent: true }
47
47
 
48
48
  # try to remove any potential for parallel tests to conflict
49
49
  Capybara.threadsafe = true
50
-
51
- Webdrivers.cache_time = 24.hours.to_i
@@ -1,51 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Update the link_or_button selector so that we can use `click_on` with gc-menu-item
4
- Capybara.modify_selector(:link_or_button) do
5
- label 'link or button'
6
-
7
- xpath do |locator, **options|
8
- %i[link button].map do |selector|
9
- expression_for(selector, locator, **options)
10
- end.reduce(:union)
11
- end
12
- end
13
-
14
- module LoadStateWaitingLogic
15
- module_function
16
-
17
- def add_wait_for_load_state(method_names)
18
- method_names.each do |method_name|
19
- define_method(method_name) do |*args, **kwargs, &block|
20
- super(*args, **kwargs, &block).tap do
21
- return unless CapybaraHelper.supports_javascript?
22
-
23
- Capybara.page.driver.with_playwright_page do |page|
24
- page.wait_for_load_state(state: 'networkidle')
25
- end
26
- end
27
- end
28
- end
29
- end
30
- end
31
-
32
- module LoadStateWaiter
33
- include LoadStateWaitingLogic
34
-
35
- INTERACTIVE_METHODS = %i[choose check uncheck select fill_in].freeze
36
- LoadStateWaitingLogic.add_wait_for_load_state(INTERACTIVE_METHODS)
37
- end
38
-
39
- module ElementLoadStateWaiter
40
- include LoadStateWaitingLogic
41
-
42
- INTERACTIVE_METHODS = %i[send_keys click select].freeze
43
- LoadStateWaitingLogic.add_wait_for_load_state(INTERACTIVE_METHODS)
44
- end
45
-
46
- Capybara::Node::Actions.prepend(LoadStateWaiter)
47
- Capybara::Node::Element.prepend(ElementLoadStateWaiter)
48
-
49
3
  module CapybaraHelper
50
4
  TIMEOUT = 10
51
5
 
@@ -22,64 +22,4 @@ module PlaywrightHelper
22
22
 
23
23
  puts "Screenshot saved to \e[4;96m#{directory}/#{filename}.png\e[0m" if print_message
24
24
  end
25
-
26
- def self.scope_stack
27
- @scope_stack ||= []
28
- end
29
-
30
- def current_scope
31
- PlaywrightHelper.scope_stack.last || pw_page
32
- end
33
-
34
- def within(selector, **, &block)
35
- if supports_javascript?
36
- begin
37
- pw_locator = if selector.is_a?(Capybara::Node::Element)
38
- pw_page.locator("xpath=#{selector.path}")
39
- else
40
- pw_page.locator(selector)
41
- end
42
-
43
- PlaywrightHelper.scope_stack.push(pw_locator)
44
-
45
- super
46
- ensure
47
- PlaywrightHelper.scope_stack.pop
48
- end
49
- else
50
- super
51
- end
52
- end
53
-
54
- def click_on(text = nil, **args)
55
- if supports_javascript? && text.present?
56
- scope = current_scope
57
- locator = nil
58
-
59
- %w[button link gc-menu-item summary].each do |tag|
60
- loc = scope.get_by_role(tag).get_by_text(text)
61
- loc = scope.get_by_role(tag).get_by_text(text, exact: true) if loc.count > 1
62
- loc = scope.locator(tag).get_by_text(text) unless loc.count == 1
63
- locator = loc if loc.count == 1
64
- break if locator.present?
65
- end
66
-
67
- locator = scope.get_by_text(text, exact: true) if locator.blank?
68
-
69
- raise "No element matching text: '#{text}'" if locator.count < 1
70
-
71
- if locator.count > 1
72
- raise "Multiple elements matching text: '#{text}'" unless args[:match] == :first
73
-
74
- result = locator.first.click
75
- else
76
- result = locator.click
77
- end
78
-
79
- pw_page.wait_for_load_state(state: 'networkidle')
80
- result
81
- else
82
- Capybara.click_on(text, **args)
83
- end
84
- end
85
25
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rolemodel
4
4
  module Testing
5
- class TestProfGenerator < BaseGenerator
5
+ class TestProfGenerator < GeneratorBase
6
6
  def install_test_prof
7
7
  gem_group :test do
8
8
  gem 'test-prof'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rolemodel
4
4
  module Testing
5
- class VitestGenerator < BaseGenerator
5
+ class VitestGenerator < GeneratorBase
6
6
  include ReplaceContentHelper
7
7
  source_root File.expand_path('templates', __dir__)
8
8
 
@@ -1,6 +1,6 @@
1
1
  module Rolemodel
2
2
  module UiComponents
3
- class AllGenerator < BaseGenerator
3
+ class AllGenerator < GeneratorBase
4
4
  source_root File.expand_path('templates', __dir__)
5
5
 
6
6
  def run_all_the_generators
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rolemodel
4
4
  module UiComponents
5
- class FlashGenerator < BaseGenerator
5
+ class FlashGenerator < GeneratorBase
6
6
  source_root File.expand_path('templates', __dir__)
7
7
 
8
8
  def generate_files
@@ -1,6 +1,6 @@
1
1
  module Rolemodel
2
2
  module UiComponents
3
- class ModalsGenerator < BaseGenerator
3
+ class ModalsGenerator < GeneratorBase
4
4
  source_root File.expand_path('templates', __dir__)
5
5
  class_option :panels, type: :boolean, default: false, desc: 'Include RoleModel Panel Setup'
6
6
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rolemodel
4
4
  module UiComponents
5
- class NavbarGenerator < BaseGenerator
5
+ class NavbarGenerator < GeneratorBase
6
6
  source_root File.expand_path('templates', __dir__)
7
7
 
8
8
  def copy_navbar_files
@@ -1,5 +1,5 @@
1
1
  module Rolemodel
2
- class WebpackGenerator < BaseGenerator
2
+ class WebpackGenerator < GeneratorBase
3
3
  source_root File.expand_path('templates', __dir__)
4
4
 
5
5
  DEV_DEPS = %w[
@@ -2,8 +2,10 @@
2
2
 
3
3
  module Rolemodel
4
4
  class Engine < ::Rails::Engine
5
+ require_relative 'generator_base'
6
+
5
7
  generators do
6
- require 'generators/rolemodel/base_generator'
8
+ require 'generators/rolemodel/all_generator'
7
9
  end
8
10
  end
9
11
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/bundle_helper'
5
+ require_relative 'replace_content_helper'
6
+
7
+ module Rolemodel
8
+ class GeneratorBase < ::Rails::Generators::Base
9
+ include ::Rails::Generators::BundleHelper, ReplaceContentHelper
10
+
11
+ private
12
+ # based on https://github.com/rails/rails/blob/main/railties/lib/rails/generators/app_base.rb#L713
13
+ def run_bundle
14
+ bundle_command("install --quiet", "BUNDLE_IGNORE_MESSAGES" => "1")
15
+ end
16
+ end
17
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rolemodel
4
- VERSION = '0.26.0'
4
+ VERSION = '1.1.0'
5
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rolemodel
2
4
  NODE_VERSION = '24.12.0'
3
5
  RUBY_VERSION = '4.0.1'