ruby_raider 3.0.1 → 3.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 (189) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/e2e_tests.yml +0 -17
  3. data/.github/workflows/system_tests.yml +0 -22
  4. data/README.md +8 -62
  5. data/lib/commands/scaffolding_commands.rb +1 -192
  6. data/lib/commands/utility_commands.rb +0 -43
  7. data/lib/generators/automation/templates/login.tt +1 -9
  8. data/lib/generators/automation/templates/page.tt +1 -7
  9. data/lib/generators/automation/templates/partials/initialize_selector.tt +1 -3
  10. data/lib/generators/automation/templates/partials/visit_method.tt +2 -6
  11. data/lib/generators/common_generator.rb +0 -6
  12. data/lib/generators/cucumber/cucumber_generator.rb +0 -24
  13. data/lib/generators/cucumber/templates/accessibility_steps.tt +2 -6
  14. data/lib/generators/cucumber/templates/cucumber.tt +0 -7
  15. data/lib/generators/cucumber/templates/env.tt +1 -3
  16. data/lib/generators/cucumber/templates/partials/appium_env.tt +1 -6
  17. data/lib/generators/cucumber/templates/partials/selenium_env.tt +0 -5
  18. data/lib/generators/cucumber/templates/partials/watir_env.tt +0 -5
  19. data/lib/generators/cucumber/templates/partials/web_steps.tt +4 -4
  20. data/lib/generators/cucumber/templates/world.tt +1 -3
  21. data/lib/generators/generator.rb +2 -46
  22. data/lib/generators/helper_generator.rb +4 -42
  23. data/lib/generators/infrastructure/templates/github.tt +2 -2
  24. data/lib/generators/infrastructure/templates/github_appium.tt +2 -6
  25. data/lib/generators/invoke_generators.rb +4 -25
  26. data/lib/generators/menu_generator.rb +10 -100
  27. data/lib/generators/rspec/rspec_generator.rb +0 -11
  28. data/lib/generators/rspec/templates/accessibility_spec.tt +2 -6
  29. data/lib/generators/rspec/templates/spec.tt +6 -8
  30. data/lib/generators/templates/common/gemfile.tt +0 -26
  31. data/lib/generators/templates/common/git_ignore.tt +0 -2
  32. data/lib/generators/templates/common/partials/mobile_config.tt +0 -4
  33. data/lib/generators/templates/common/partials/web_config.tt +0 -4
  34. data/lib/generators/templates/common/rakefile.tt +0 -9
  35. data/lib/generators/templates/common/read_me.tt +4 -10
  36. data/lib/generators/templates/helpers/allure_helper.tt +0 -10
  37. data/lib/generators/templates/helpers/partials/debug_diagnostics.tt +1 -3
  38. data/lib/generators/templates/helpers/partials/debug_start.tt +1 -3
  39. data/lib/generators/templates/helpers/partials/quit_driver.tt +1 -3
  40. data/lib/generators/templates/helpers/partials/screenshot.tt +1 -3
  41. data/lib/generators/templates/helpers/spec_helper.tt +2 -39
  42. data/lib/ruby_raider.rb +2 -50
  43. data/lib/scaffolding/project_detector.rb +2 -9
  44. data/lib/scaffolding/scaffolding.rb +0 -109
  45. data/lib/scaffolding/templates/component.tt +1 -4
  46. data/lib/scaffolding/templates/page_object.tt +0 -10
  47. data/lib/scaffolding/templates/spec.tt +2 -6
  48. data/lib/scaffolding/templates/steps.tt +0 -2
  49. data/lib/utilities/utilities.rb +23 -29
  50. data/lib/version +1 -1
  51. data/sig/commands/scaffolding_commands.rbs +0 -12
  52. data/sig/commands/utility_commands.rbs +0 -6
  53. data/sig/generators/cucumber/cucumber_generator.rbs +0 -4
  54. data/sig/generators/generator.rbs +0 -11
  55. data/sig/generators/helper_generator.rbs +1 -5
  56. data/sig/generators/invoke_generators.rbs +1 -2
  57. data/sig/generators/menu_generator.rbs +2 -6
  58. data/sig/generators/rspec/rspec_generator.rbs +0 -2
  59. data/sig/ruby_raider.rbs +1 -3
  60. data/sig/scaffolding/project_detector.rbs +0 -1
  61. data/sig/scaffolding/scaffolding.rbs +0 -23
  62. data/sig/utilities/utilities.rbs +0 -4
  63. data/spec/commands/raider_commands_spec.rb +0 -61
  64. data/spec/integration/commands/browser_update_after_creation_spec.rb +123 -0
  65. data/spec/integration/commands/scaffolding_commands_spec.rb +0 -20
  66. data/spec/integration/commands/utility_commands_spec.rb +5 -5
  67. data/spec/integration/content/ci_content_spec.rb +6 -61
  68. data/spec/integration/content/common_content_spec.rb +0 -64
  69. data/spec/integration/content/config_content_spec.rb +1 -51
  70. data/spec/integration/content/content_helper.rb +2 -2
  71. data/spec/integration/content/gemfile_content_spec.rb +0 -71
  72. data/spec/integration/content/helper_content_spec.rb +4 -240
  73. data/spec/integration/content/page_content_spec.rb +0 -89
  74. data/spec/integration/content/syntax_validation_spec.rb +2 -2
  75. data/spec/integration/content/test_content_spec.rb +0 -119
  76. data/spec/integration/end_to_end_features_spec.rb +13 -411
  77. data/spec/integration/end_to_end_spec.rb +0 -96
  78. data/spec/integration/generators/axe_addon_spec.rb +2 -52
  79. data/spec/integration/generators/common_generator_spec.rb +1 -13
  80. data/spec/integration/generators/config_features_spec.rb +3 -81
  81. data/spec/integration/generators/debug_helper_spec.rb +0 -20
  82. data/spec/integration/generators/github_generator_spec.rb +5 -15
  83. data/spec/integration/generators/helpers_generator_spec.rb +0 -37
  84. data/spec/integration/scaffolding_e2e_spec.rb +2 -237
  85. data/spec/integration/settings_helper.rb +1 -3
  86. data/spec/integration/spec_helper.rb +6 -11
  87. data/spec/menus/menu_generator_spec.rb +4 -107
  88. data/spec/scaffolding/scaffold_project_detector_spec.rb +4 -26
  89. data/spec/scaffolding/scaffolding_features_spec.rb +0 -183
  90. data/spec/system/selenium_spec.rb +1 -4
  91. data/spec/system/support/system_test_helper.rb +0 -1
  92. data/spec/system/watir_spec.rb +1 -4
  93. data/spec/utilities/headless_config_spec.rb +0 -7
  94. metadata +3 -97
  95. data/lib/adopter/adopt_menu.rb +0 -146
  96. data/lib/adopter/converters/base_converter.rb +0 -84
  97. data/lib/adopter/converters/identity_converter.rb +0 -53
  98. data/lib/adopter/migration_plan.rb +0 -74
  99. data/lib/adopter/migrator.rb +0 -96
  100. data/lib/adopter/plan_builder.rb +0 -275
  101. data/lib/adopter/project_analyzer.rb +0 -252
  102. data/lib/adopter/project_detector.rb +0 -157
  103. data/lib/generators/cucumber/templates/partials/capybara_env.tt +0 -38
  104. data/lib/generators/cucumber/templates/partials/capybara_world.tt +0 -6
  105. data/lib/generators/cucumber/templates/performance_feature.tt +0 -5
  106. data/lib/generators/cucumber/templates/performance_steps.tt +0 -17
  107. data/lib/generators/cucumber/templates/visual_feature.tt +0 -5
  108. data/lib/generators/cucumber/templates/visual_steps.tt +0 -19
  109. data/lib/generators/infrastructure/gitlab_generator.rb +0 -11
  110. data/lib/generators/infrastructure/templates/gitlab.tt +0 -46
  111. data/lib/generators/minitest/minitest_generator.rb +0 -35
  112. data/lib/generators/minitest/templates/accessibility_test.tt +0 -26
  113. data/lib/generators/minitest/templates/performance_test.tt +0 -18
  114. data/lib/generators/minitest/templates/test.tt +0 -64
  115. data/lib/generators/minitest/templates/visual_test.tt +0 -23
  116. data/lib/generators/rspec/templates/performance_spec.tt +0 -18
  117. data/lib/generators/rspec/templates/visual_spec.tt +0 -20
  118. data/lib/generators/templates/helpers/capybara_helper.tt +0 -32
  119. data/lib/generators/templates/helpers/performance_helper.tt +0 -57
  120. data/lib/generators/templates/helpers/visual_helper.tt +0 -58
  121. data/lib/llm/client.rb +0 -79
  122. data/lib/llm/config.rb +0 -57
  123. data/lib/llm/prompts.rb +0 -84
  124. data/lib/llm/provider.rb +0 -27
  125. data/lib/llm/providers/anthropic_provider.rb +0 -43
  126. data/lib/llm/providers/ollama_provider.rb +0 -56
  127. data/lib/llm/providers/openai_provider.rb +0 -42
  128. data/lib/llm/response_parser.rb +0 -67
  129. data/lib/plugin/plugin.rb +0 -111
  130. data/lib/plugin/plugin_exposer.rb +0 -55
  131. data/lib/scaffolding/crud_generator.rb +0 -94
  132. data/lib/scaffolding/dry_run_presenter.rb +0 -16
  133. data/lib/scaffolding/page_introspector.rb +0 -45
  134. data/lib/scaffolding/scaffold_menu.rb +0 -103
  135. data/lib/scaffolding/templates/page_from_url.tt +0 -75
  136. data/lib/scaffolding/templates/spec_from_page.tt +0 -31
  137. data/lib/scaffolding/templates/spec_from_url.tt +0 -46
  138. data/lib/scaffolding/url_analyzer.rb +0 -179
  139. data/sig/adopter/adopt_menu.rbs +0 -25
  140. data/sig/adopter/converters/base_converter.rbs +0 -23
  141. data/sig/adopter/converters/identity_converter.rbs +0 -16
  142. data/sig/adopter/migration_plan.rbs +0 -34
  143. data/sig/adopter/migrator.rbs +0 -21
  144. data/sig/adopter/plan_builder.rbs +0 -38
  145. data/sig/adopter/project_analyzer.rbs +0 -39
  146. data/sig/adopter/project_detector.rbs +0 -26
  147. data/sig/generators/infrastructure/gitlab_generator.rbs +0 -4
  148. data/sig/generators/minitest/minitest_generator.rbs +0 -8
  149. data/sig/llm/client.rbs +0 -15
  150. data/sig/llm/config.rbs +0 -20
  151. data/sig/llm/prompts.rbs +0 -8
  152. data/sig/llm/provider.rbs +0 -12
  153. data/sig/llm/providers/anthropic_provider.rbs +0 -16
  154. data/sig/llm/providers/ollama_provider.rbs +0 -18
  155. data/sig/llm/providers/openai_provider.rbs +0 -16
  156. data/sig/llm/response_parser.rbs +0 -13
  157. data/sig/plugin/plugin.rbs +0 -24
  158. data/sig/plugin/plugin_exposer.rbs +0 -20
  159. data/sig/scaffolding/crud_generator.rbs +0 -16
  160. data/sig/scaffolding/dry_run_presenter.rbs +0 -4
  161. data/sig/scaffolding/page_introspector.rbs +0 -14
  162. data/sig/scaffolding/scaffold_menu.rbs +0 -18
  163. data/sig/scaffolding/url_analyzer.rbs +0 -28
  164. data/spec/adopter/adopt_menu_spec.rb +0 -176
  165. data/spec/adopter/converters/identity_converter_spec.rb +0 -145
  166. data/spec/adopter/migration_plan_spec.rb +0 -113
  167. data/spec/adopter/migrator_spec.rb +0 -277
  168. data/spec/adopter/plan_builder_spec.rb +0 -298
  169. data/spec/adopter/project_analyzer_spec.rb +0 -337
  170. data/spec/adopter/project_detector_spec.rb +0 -295
  171. data/spec/generators/generator_spec.rb +0 -23
  172. data/spec/integration/content/reporter_content_spec.rb +0 -236
  173. data/spec/integration/content/skip_flags_content_spec.rb +0 -206
  174. data/spec/integration/generators/gitlab_generator_spec.rb +0 -38
  175. data/spec/integration/generators/lighthouse_addon_spec.rb +0 -132
  176. data/spec/integration/generators/minitest_generator_spec.rb +0 -64
  177. data/spec/integration/generators/reporter_spec.rb +0 -159
  178. data/spec/integration/generators/skip_flags_spec.rb +0 -134
  179. data/spec/integration/generators/visual_addon_spec.rb +0 -148
  180. data/spec/llm/client_spec.rb +0 -79
  181. data/spec/llm/config_spec.rb +0 -92
  182. data/spec/llm/prompts_spec.rb +0 -49
  183. data/spec/llm/response_parser_spec.rb +0 -92
  184. data/spec/menus/adopter_adopt_menu_spec.rb +0 -97
  185. data/spec/scaffolding/page_introspector_spec.rb +0 -82
  186. data/spec/scaffolding/url_analyzer_spec.rb +0 -110
  187. data/spec/system/adopt_matrix_spec.rb +0 -537
  188. data/spec/system/adopt_spec.rb +0 -225
  189. data/spec/system/capybara_spec.rb +0 -42
data/lib/llm/prompts.rb DELETED
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Llm
4
- # Centralized prompt templates for LLM-enhanced code generation.
5
- # All prompts instruct the model to return JSON matching specific schemas.
6
- module Prompts
7
- module_function
8
-
9
- def analyze_page(html, url)
10
- <<~PROMPT
11
- Analyze this HTML page and extract all interactive elements that would be useful for a page object model in UI test automation.
12
-
13
- URL: #{url}
14
-
15
- HTML:
16
- #{html[0..8000]}
17
-
18
- Return a JSON object with this exact structure:
19
- {
20
- "elements": [
21
- {
22
- "name": "descriptive_snake_case_name",
23
- "type": "input|select|textarea|button|submit|link",
24
- "locator": {"type": "id|name|css|xpath", "value": "the_locator"},
25
- "purpose": "brief description of what this element does",
26
- "input_type": "text|email|password|etc (only for inputs)",
27
- "text": "visible text (only for buttons/links)"
28
- }
29
- ]
30
- }
31
-
32
- Guidelines:
33
- - Use semantic, descriptive names (e.g., "email_field" not "input_1", "submit_login" not "button_1")
34
- - Prefer ID locators, then name, then CSS, then XPath
35
- - Skip hidden inputs and purely decorative elements
36
- - Include up to 5 important links
37
- - The "purpose" field should be a brief, clear description
38
-
39
- Return ONLY the JSON object, no other text.
40
- PROMPT
41
- end
42
-
43
- def generate_test_scenarios(class_name, methods, automation, framework)
44
- methods_desc = methods.map do |m|
45
- params = m[:params].empty? ? '' : "(#{m[:params].join(', ')})"
46
- " - #{m[:name]}#{params}"
47
- end.join("\n")
48
-
49
- <<~PROMPT
50
- Generate meaningful test scenarios for this page object class used in UI test automation.
51
-
52
- Class: #{class_name}
53
- Automation: #{automation}
54
- Framework: #{framework}
55
- Public methods:
56
- #{methods_desc}
57
-
58
- Return a JSON object with this exact structure:
59
- {
60
- "scenarios": [
61
- {
62
- "method": "method_name",
63
- "description": "human-readable test description",
64
- "assertion_hint": "what to assert after calling this method"
65
- }
66
- ]
67
- }
68
-
69
- Guidelines:
70
- - Generate one scenario per method
71
- - Descriptions should read naturally (e.g., "fills in the login form with valid credentials")
72
- - Assertion hints should be specific (e.g., "expect page to redirect to dashboard")
73
- - Consider the class name for context about what page this is
74
-
75
- Return ONLY the JSON object, no other text.
76
- PROMPT
77
- end
78
-
79
- def system_prompt
80
- 'You are a UI test automation expert. You generate clean, idiomatic Ruby code ' \
81
- 'for page object models and test specs. Always return valid JSON when asked for JSON.'
82
- end
83
- end
84
- end
data/lib/llm/provider.rb DELETED
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Llm
4
- # Abstract base class for LLM providers
5
- class Provider
6
- def complete(prompt, system_prompt: nil)
7
- raise NotImplementedError, "#{self.class}#complete must be implemented"
8
- end
9
-
10
- def available?
11
- raise NotImplementedError, "#{self.class}#available? must be implemented"
12
- end
13
-
14
- def name
15
- self.class.name.split('::').last.sub('Provider', '').downcase
16
- end
17
-
18
- private
19
-
20
- def build_messages(prompt, system_prompt)
21
- messages = []
22
- messages << { role: 'system', content: system_prompt } if system_prompt
23
- messages << { role: 'user', content: prompt }
24
- messages
25
- end
26
- end
27
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../provider'
4
-
5
- module Llm
6
- module Providers
7
- class AnthropicProvider < Provider
8
- DEFAULT_MODEL = 'claude-sonnet-4-20250514'
9
-
10
- def initialize(api_key:, model: nil)
11
- super()
12
- @api_key = api_key
13
- @model = model || DEFAULT_MODEL
14
- end
15
-
16
- def complete(prompt, system_prompt: nil)
17
- client = build_client
18
- params = { model: @model, max_tokens: 4096, messages: [{ role: 'user', content: prompt }] }
19
- params[:system] = system_prompt if system_prompt
20
- response = client.messages(parameters: params)
21
- response.dig('content', 0, 'text')
22
- rescue StandardError => e
23
- warn "[Ruby Raider] Anthropic error: #{e.message}"
24
- nil
25
- end
26
-
27
- def available?
28
- require 'anthropic'
29
- !@api_key.nil? && !@api_key.empty?
30
- rescue LoadError
31
- warn '[Ruby Raider] Install anthropic gem: gem install anthropic-rb'
32
- false
33
- end
34
-
35
- private
36
-
37
- def build_client
38
- require 'anthropic'
39
- Anthropic::Client.new(access_token: @api_key)
40
- end
41
- end
42
- end
43
- end
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'net/http'
4
- require 'json'
5
- require 'uri'
6
- require_relative '../provider'
7
-
8
- module Llm
9
- module Providers
10
- class OllamaProvider < Provider
11
- DEFAULT_MODEL = 'llama3.2'
12
- DEFAULT_URL = 'http://localhost:11434'
13
- TIMEOUT = 60
14
-
15
- def initialize(model: nil, url: nil)
16
- super()
17
- @model = model || DEFAULT_MODEL
18
- @base_url = url || DEFAULT_URL
19
- end
20
-
21
- def complete(prompt, system_prompt: nil)
22
- uri = URI("#{@base_url}/api/generate")
23
- body = { model: @model, prompt:, stream: false }
24
- body[:system] = system_prompt if system_prompt
25
-
26
- response = post_request(uri, body)
27
- return nil unless response.is_a?(Net::HTTPSuccess)
28
-
29
- JSON.parse(response.body)['response']
30
- rescue StandardError => e
31
- warn "[Ruby Raider] Ollama error: #{e.message}"
32
- nil
33
- end
34
-
35
- def available?
36
- uri = URI("#{@base_url}/api/tags")
37
- response = Net::HTTP.get_response(uri)
38
- response.is_a?(Net::HTTPSuccess)
39
- rescue StandardError
40
- warn '[Ruby Raider] Ollama not reachable. Start it with: ollama serve'
41
- false
42
- end
43
-
44
- private
45
-
46
- def post_request(uri, body)
47
- http = Net::HTTP.new(uri.host, uri.port)
48
- http.read_timeout = TIMEOUT
49
- http.open_timeout = 10
50
- request = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
51
- request.body = JSON.generate(body)
52
- http.request(request)
53
- end
54
- end
55
- end
56
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../provider'
4
-
5
- module Llm
6
- module Providers
7
- class OpenaiProvider < Provider
8
- DEFAULT_MODEL = 'gpt-4o-mini'
9
-
10
- def initialize(api_key:, model: nil)
11
- super()
12
- @api_key = api_key
13
- @model = model || DEFAULT_MODEL
14
- end
15
-
16
- def complete(prompt, system_prompt: nil)
17
- client = build_client
18
- messages = build_messages(prompt, system_prompt)
19
- response = client.chat(parameters: { model: @model, messages:, temperature: 0.2 })
20
- response.dig('choices', 0, 'message', 'content')
21
- rescue StandardError => e
22
- warn "[Ruby Raider] OpenAI error: #{e.message}"
23
- nil
24
- end
25
-
26
- def available?
27
- require 'openai'
28
- !@api_key.nil? && !@api_key.empty?
29
- rescue LoadError
30
- warn '[Ruby Raider] Install ruby-openai gem: gem install ruby-openai'
31
- false
32
- end
33
-
34
- private
35
-
36
- def build_client
37
- require 'openai'
38
- OpenAI::Client.new(access_token: @api_key)
39
- end
40
- end
41
- end
42
- end
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- module Llm
6
- # Extracts JSON from LLM responses, handling markdown wrapping and malformed output.
7
- # Returns nil on parse failure — callers should fall back to non-AI path.
8
- module ResponseParser
9
- module_function
10
-
11
- def parse_json(response)
12
- return nil if response.nil? || response.strip.empty?
13
-
14
- json_str = extract_json(response)
15
- result = JSON.parse(json_str, symbolize_names: true)
16
- result.is_a?(Hash) ? result : nil
17
- rescue JSON::ParserError
18
- nil
19
- end
20
-
21
- def extract_elements(response)
22
- parsed = parse_json(response)
23
- return nil unless parsed && parsed[:elements].is_a?(Array)
24
-
25
- parsed[:elements].map { |el| normalize_element(el) }.compact
26
- end
27
-
28
- def extract_scenarios(response)
29
- parsed = parse_json(response)
30
- return nil unless parsed && parsed[:scenarios].is_a?(Array)
31
-
32
- parsed[:scenarios]
33
- end
34
-
35
- def normalize_element(element)
36
- return nil unless element[:name] && element[:type] && element[:locator]
37
-
38
- locator = element[:locator]
39
- locator = { type: locator[:type]&.to_sym, value: locator[:value] } if locator.is_a?(Hash)
40
-
41
- {
42
- name: element[:name].to_s.gsub(/[^a-z0-9_]/i, '_').downcase,
43
- type: element[:type].to_sym,
44
- locator:,
45
- purpose: element[:purpose],
46
- input_type: element[:input_type],
47
- text: element[:text]
48
- }.compact
49
- end
50
-
51
- def extract_json(text)
52
- # Try raw JSON first
53
- stripped = text.strip
54
- return stripped if stripped.start_with?('{')
55
-
56
- # Try markdown code block
57
- match = text.match(/```(?:json)?\s*\n?(.*?)\n?\s*```/m)
58
- return match[1].strip if match
59
-
60
- # Try finding first { ... } block
61
- brace_match = text.match(/(\{.*\})/m)
62
- return brace_match[1] if brace_match
63
-
64
- text
65
- end
66
- end
67
- end
data/lib/plugin/plugin.rb DELETED
@@ -1,111 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'yaml'
4
- require_relative 'plugin_exposer'
5
-
6
- module RubyRaider
7
- module Plugin
8
- class << self
9
- def add_plugin(plugin_name)
10
- return gemfile_guard unless File.exist?('Gemfile')
11
- return pp 'The plugin was not found' unless available?(plugin_name)
12
- return pp 'The plugin is already installed' if installed?(plugin_name)
13
-
14
- pp "Adding #{plugin_name}..."
15
- add_plugin_to_gemfile(plugin_name)
16
- invalidate_gemfile_cache
17
- system('bundle install')
18
- PluginExposer.expose_commands(plugin_name)
19
- pp "The plugin #{plugin_name} is added"
20
- end
21
-
22
- def delete_plugin(plugin_name)
23
- return gemfile_guard unless File.exist?('Gemfile')
24
- return 'The plugin is not installed' unless installed_plugins.include?(plugin_name)
25
-
26
- pp "Deleting #{plugin_name}..."
27
- remove_plugin_from_gemfile(plugin_name)
28
- invalidate_gemfile_cache
29
- PluginExposer.remove_command(plugin_name)
30
- system('bundle install')
31
- pp "The plugin #{plugin_name} is deleted"
32
- end
33
-
34
- def installed_plugins
35
- return gemfile_guard unless File.exist?('Gemfile')
36
-
37
- cached_gemfile_lines.filter_map do |line|
38
- stripped = line.sub('gem ', '').strip.delete("'")
39
- stripped if plugins.include?(stripped)
40
- end
41
- end
42
-
43
- def installed?(plugin_name)
44
- installed_plugins.include?(plugin_name)
45
- end
46
-
47
- def available?(plugin_name)
48
- plugins.include?(plugin_name)
49
- end
50
-
51
- def camelize(str)
52
- str.split('_').collect(&:capitalize).join
53
- end
54
-
55
- def plugins
56
- ['great_axe']
57
- end
58
-
59
- private
60
-
61
- # Read Gemfile once and cache until explicitly invalidated
62
- def cached_gemfile_lines
63
- @cached_gemfile_lines ||= File.readlines('Gemfile')
64
- end
65
-
66
- def invalidate_gemfile_cache
67
- @cached_gemfile_lines = nil
68
- end
69
-
70
- def add_plugin_to_gemfile(plugin_name)
71
- return gemfile_guard unless File.exist?('Gemfile')
72
-
73
- lines = cached_gemfile_lines
74
- has_comment = lines.any? { |l| l.include?('Ruby Raider Plugins') }
75
- has_plugin = lines.any? { |l| l.include?(plugin_name) }
76
-
77
- File.open('Gemfile', 'a') do |file|
78
- file.puts "\n# Ruby Raider Plugins\n" unless has_comment
79
- file.puts "gem '#{plugin_name}'" unless has_plugin
80
- end
81
- end
82
-
83
- def remove_plugin_from_gemfile(plugin_name)
84
- output_lines = remove_plugins_and_comments(plugin_name)
85
- update_gemfile(output_lines)
86
- end
87
-
88
- def last_plugin?
89
- installed_plugins.count == 1
90
- end
91
-
92
- def remove_plugins_and_comments(plugin_name)
93
- cached_gemfile_lines.reject do |line|
94
- line.include?(plugin_name) || line.include?('Ruby Raider Plugins') && last_plugin?
95
- end
96
- end
97
-
98
- def update_gemfile(output_lines)
99
- return gemfile_guard unless File.exist?('Gemfile')
100
-
101
- File.open('Gemfile', 'w') do |file|
102
- output_lines.each { |line| file.puts line }
103
- end
104
- end
105
-
106
- def gemfile_guard
107
- pp 'There is no Gemfile, please create one to install plugins'
108
- end
109
- end
110
- end
111
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../plugin/plugin'
4
-
5
- module RubyRaider
6
- module PluginExposer
7
- class << self
8
- FILE_PATH = File.expand_path('../commands/loaded_commands.rb', __dir__)
9
- def expose_commands(plugin_name)
10
- commands = read_loaded_commands
11
- return pp 'The plugin is already installed' if commands.any? { |l| l.include?(plugin_name) }
12
-
13
- has_subcommands = commands.any? { |l| l.include?('subcommand') }
14
-
15
- File.open(FILE_PATH, 'w') do |file|
16
- commands.each do |line|
17
- file.puts line
18
- file.puts "require '#{plugin_name}'" if line.include?("require 'thor'")
19
- if line.strip == 'class LoadedCommands < Thor' && !has_subcommands
20
- file.puts formatted_command_without_space(plugin_name)
21
- elsif line.strip == 'class LoadedCommands < Thor' && has_subcommands
22
- file.puts formatted_command_with_space(plugin_name)
23
- end
24
- end
25
- end
26
- end
27
-
28
- def remove_command(plugin_name)
29
- commands = read_loaded_commands
30
- return pp 'The plugin is not installed' unless commands.any? { |l| l.include?(plugin_name) }
31
-
32
- output_lines = commands.reject { |line| line.include?(plugin_name) }
33
-
34
- File.open(FILE_PATH, 'w') do |file|
35
- output_lines.each { |line| file.puts line }
36
- end
37
- end
38
-
39
- private
40
-
41
- def read_loaded_commands
42
- File.readlines(FILE_PATH)
43
- end
44
-
45
- def formatted_command_without_space(plugin_name)
46
- "desc '#{plugin_name}', 'Provides access to all the commands for #{plugin_name}'\n" \
47
- "subcommand '#{plugin_name}', #{Plugin.camelize(plugin_name)}::PluginCommands"
48
- end
49
-
50
- def formatted_command_with_space(plugin_name)
51
- "\n#{formatted_command_without_space(plugin_name)}"
52
- end
53
- end
54
- end
55
- end
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'name_normalizer'
4
-
5
- class CrudGenerator
6
- ACTIONS = %w[list create detail edit].freeze
7
-
8
- def initialize(base_name, scaffolding_class, config_loader)
9
- @base_name = NameNormalizer.normalize(base_name)
10
- @scaffolding_class = scaffolding_class
11
- @config_loader = config_loader
12
- end
13
-
14
- def generate
15
- generated = []
16
- ACTIONS.each do |action|
17
- name = "#{@base_name}_#{action}"
18
- generate_page(name)
19
- generate_test(name)
20
- generated << name
21
- end
22
- generate_model
23
- generated
24
- end
25
-
26
- def planned_files
27
- files = []
28
- ACTIONS.each do |action|
29
- name = "#{@base_name}_#{action}"
30
- files << "page_objects/pages/#{name}.rb"
31
- files << test_path(name)
32
- end
33
- files << "models/data/#{@base_name}.yml"
34
- files
35
- end
36
-
37
- private
38
-
39
- def generate_page(name)
40
- path = @config_loader.call('page')
41
- @scaffolding_class.new([name, path]).generate_page
42
- end
43
-
44
- def generate_test(name)
45
- if Dir.exist?('features')
46
- path = @config_loader.call('feature')
47
- @scaffolding_class.new([name, path]).generate_feature
48
- path = @config_loader.call('steps')
49
- @scaffolding_class.new([name, path]).generate_steps
50
- elsif Dir.exist?('test')
51
- path = @config_loader.call('spec')
52
- @scaffolding_class.new([name, path]).generate_spec
53
- else
54
- path = @config_loader.call('spec')
55
- @scaffolding_class.new([name, path]).generate_spec
56
- end
57
- end
58
-
59
- def generate_model
60
- model_path = "models/data/#{@base_name}.yml"
61
- return if File.exist?(model_path)
62
-
63
- FileUtils.mkdir_p(File.dirname(model_path))
64
- File.write(model_path, model_content)
65
- end
66
-
67
- def model_content
68
- <<~YAML
69
- # Data model for #{@base_name}
70
- # Used with ModelFactory for test data generation
71
- default:
72
- name: 'Test #{@base_name.capitalize}'
73
- email: 'test@example.com'
74
-
75
- valid:
76
- name: 'Valid #{@base_name.capitalize}'
77
- email: 'valid@example.com'
78
-
79
- invalid:
80
- name: ''
81
- email: 'invalid'
82
- YAML
83
- end
84
-
85
- def test_path(name)
86
- if Dir.exist?('features')
87
- "features/#{name}.feature"
88
- elsif Dir.exist?('test')
89
- "test/test_#{name}.rb"
90
- else
91
- "spec/#{name}_page_spec.rb"
92
- end
93
- end
94
- end
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DryRunPresenter
4
- module_function
5
-
6
- def preview(planned_files)
7
- return [] if planned_files.empty?
8
-
9
- puts '[dry-run] Would create:'
10
- planned_files.each do |file|
11
- puts " #{file}"
12
- end
13
- puts ''
14
- planned_files
15
- end
16
- end
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'ripper'
4
-
5
- class PageIntrospector
6
- SKIP_METHODS = %w[initialize url to_s inspect].freeze
7
-
8
- attr_reader :class_name, :methods
9
-
10
- def initialize(file_path)
11
- @source = File.read(file_path)
12
- @class_name = extract_class_name
13
- @methods = extract_public_methods
14
- end
15
-
16
- private
17
-
18
- def extract_class_name
19
- match = @source.match(/class\s+(\w+)/)
20
- match ? match[1] : 'UnknownPage'
21
- end
22
-
23
- def extract_public_methods
24
- in_private = false
25
- results = []
26
-
27
- @source.each_line do |line|
28
- stripped = line.strip
29
- in_private = true if stripped.match?(/^\s*(private|protected)\s*$/)
30
- next if in_private
31
-
32
- match = stripped.match(/^\s*def\s+(\w+)(\(([^)]*)\))?/)
33
- next unless match
34
-
35
- method_name = match[1]
36
- next if SKIP_METHODS.include?(method_name)
37
- next if method_name.start_with?('_')
38
-
39
- params = match[3]&.split(',')&.map(&:strip) || []
40
- results << { name: method_name, params: }
41
- end
42
-
43
- results
44
- end
45
- end