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.
- checksums.yaml +4 -4
- data/.github/workflows/e2e_tests.yml +0 -17
- data/.github/workflows/system_tests.yml +0 -22
- data/README.md +8 -62
- data/lib/commands/scaffolding_commands.rb +1 -192
- data/lib/commands/utility_commands.rb +0 -43
- data/lib/generators/automation/templates/login.tt +1 -9
- data/lib/generators/automation/templates/page.tt +1 -7
- data/lib/generators/automation/templates/partials/initialize_selector.tt +1 -3
- data/lib/generators/automation/templates/partials/visit_method.tt +2 -6
- data/lib/generators/common_generator.rb +0 -6
- data/lib/generators/cucumber/cucumber_generator.rb +0 -24
- data/lib/generators/cucumber/templates/accessibility_steps.tt +2 -6
- data/lib/generators/cucumber/templates/cucumber.tt +0 -7
- data/lib/generators/cucumber/templates/env.tt +1 -3
- data/lib/generators/cucumber/templates/partials/appium_env.tt +1 -6
- data/lib/generators/cucumber/templates/partials/selenium_env.tt +0 -5
- data/lib/generators/cucumber/templates/partials/watir_env.tt +0 -5
- data/lib/generators/cucumber/templates/partials/web_steps.tt +4 -4
- data/lib/generators/cucumber/templates/world.tt +1 -3
- data/lib/generators/generator.rb +2 -46
- data/lib/generators/helper_generator.rb +4 -42
- data/lib/generators/infrastructure/templates/github.tt +2 -2
- data/lib/generators/infrastructure/templates/github_appium.tt +2 -6
- data/lib/generators/invoke_generators.rb +4 -25
- data/lib/generators/menu_generator.rb +10 -100
- data/lib/generators/rspec/rspec_generator.rb +0 -11
- data/lib/generators/rspec/templates/accessibility_spec.tt +2 -6
- data/lib/generators/rspec/templates/spec.tt +6 -8
- data/lib/generators/templates/common/gemfile.tt +0 -26
- data/lib/generators/templates/common/git_ignore.tt +0 -2
- data/lib/generators/templates/common/partials/mobile_config.tt +0 -4
- data/lib/generators/templates/common/partials/web_config.tt +0 -4
- data/lib/generators/templates/common/rakefile.tt +0 -9
- data/lib/generators/templates/common/read_me.tt +4 -10
- data/lib/generators/templates/helpers/allure_helper.tt +0 -10
- data/lib/generators/templates/helpers/partials/debug_diagnostics.tt +1 -3
- data/lib/generators/templates/helpers/partials/debug_start.tt +1 -3
- data/lib/generators/templates/helpers/partials/quit_driver.tt +1 -3
- data/lib/generators/templates/helpers/partials/screenshot.tt +1 -3
- data/lib/generators/templates/helpers/spec_helper.tt +2 -39
- data/lib/ruby_raider.rb +2 -50
- data/lib/scaffolding/project_detector.rb +2 -9
- data/lib/scaffolding/scaffolding.rb +0 -109
- data/lib/scaffolding/templates/component.tt +1 -4
- data/lib/scaffolding/templates/page_object.tt +0 -10
- data/lib/scaffolding/templates/spec.tt +2 -6
- data/lib/scaffolding/templates/steps.tt +0 -2
- data/lib/utilities/utilities.rb +23 -29
- data/lib/version +1 -1
- data/sig/commands/scaffolding_commands.rbs +0 -12
- data/sig/commands/utility_commands.rbs +0 -6
- data/sig/generators/cucumber/cucumber_generator.rbs +0 -4
- data/sig/generators/generator.rbs +0 -11
- data/sig/generators/helper_generator.rbs +1 -5
- data/sig/generators/invoke_generators.rbs +1 -2
- data/sig/generators/menu_generator.rbs +2 -6
- data/sig/generators/rspec/rspec_generator.rbs +0 -2
- data/sig/ruby_raider.rbs +1 -3
- data/sig/scaffolding/project_detector.rbs +0 -1
- data/sig/scaffolding/scaffolding.rbs +0 -23
- data/sig/utilities/utilities.rbs +0 -4
- data/spec/commands/raider_commands_spec.rb +0 -61
- data/spec/integration/commands/browser_update_after_creation_spec.rb +123 -0
- data/spec/integration/commands/scaffolding_commands_spec.rb +0 -20
- data/spec/integration/commands/utility_commands_spec.rb +5 -5
- data/spec/integration/content/ci_content_spec.rb +6 -61
- data/spec/integration/content/common_content_spec.rb +0 -64
- data/spec/integration/content/config_content_spec.rb +1 -51
- data/spec/integration/content/content_helper.rb +2 -2
- data/spec/integration/content/gemfile_content_spec.rb +0 -71
- data/spec/integration/content/helper_content_spec.rb +4 -240
- data/spec/integration/content/page_content_spec.rb +0 -89
- data/spec/integration/content/syntax_validation_spec.rb +2 -2
- data/spec/integration/content/test_content_spec.rb +0 -119
- data/spec/integration/end_to_end_features_spec.rb +13 -411
- data/spec/integration/end_to_end_spec.rb +0 -96
- data/spec/integration/generators/axe_addon_spec.rb +2 -52
- data/spec/integration/generators/common_generator_spec.rb +1 -13
- data/spec/integration/generators/config_features_spec.rb +3 -81
- data/spec/integration/generators/debug_helper_spec.rb +0 -20
- data/spec/integration/generators/github_generator_spec.rb +5 -15
- data/spec/integration/generators/helpers_generator_spec.rb +0 -37
- data/spec/integration/scaffolding_e2e_spec.rb +2 -237
- data/spec/integration/settings_helper.rb +1 -3
- data/spec/integration/spec_helper.rb +6 -11
- data/spec/menus/menu_generator_spec.rb +4 -107
- data/spec/scaffolding/scaffold_project_detector_spec.rb +4 -26
- data/spec/scaffolding/scaffolding_features_spec.rb +0 -183
- data/spec/system/selenium_spec.rb +1 -4
- data/spec/system/support/system_test_helper.rb +0 -1
- data/spec/system/watir_spec.rb +1 -4
- data/spec/utilities/headless_config_spec.rb +0 -7
- metadata +3 -97
- data/lib/adopter/adopt_menu.rb +0 -146
- data/lib/adopter/converters/base_converter.rb +0 -84
- data/lib/adopter/converters/identity_converter.rb +0 -53
- data/lib/adopter/migration_plan.rb +0 -74
- data/lib/adopter/migrator.rb +0 -96
- data/lib/adopter/plan_builder.rb +0 -275
- data/lib/adopter/project_analyzer.rb +0 -252
- data/lib/adopter/project_detector.rb +0 -157
- data/lib/generators/cucumber/templates/partials/capybara_env.tt +0 -38
- data/lib/generators/cucumber/templates/partials/capybara_world.tt +0 -6
- data/lib/generators/cucumber/templates/performance_feature.tt +0 -5
- data/lib/generators/cucumber/templates/performance_steps.tt +0 -17
- data/lib/generators/cucumber/templates/visual_feature.tt +0 -5
- data/lib/generators/cucumber/templates/visual_steps.tt +0 -19
- data/lib/generators/infrastructure/gitlab_generator.rb +0 -11
- data/lib/generators/infrastructure/templates/gitlab.tt +0 -46
- data/lib/generators/minitest/minitest_generator.rb +0 -35
- data/lib/generators/minitest/templates/accessibility_test.tt +0 -26
- data/lib/generators/minitest/templates/performance_test.tt +0 -18
- data/lib/generators/minitest/templates/test.tt +0 -64
- data/lib/generators/minitest/templates/visual_test.tt +0 -23
- data/lib/generators/rspec/templates/performance_spec.tt +0 -18
- data/lib/generators/rspec/templates/visual_spec.tt +0 -20
- data/lib/generators/templates/helpers/capybara_helper.tt +0 -32
- data/lib/generators/templates/helpers/performance_helper.tt +0 -57
- data/lib/generators/templates/helpers/visual_helper.tt +0 -58
- data/lib/llm/client.rb +0 -79
- data/lib/llm/config.rb +0 -57
- data/lib/llm/prompts.rb +0 -84
- data/lib/llm/provider.rb +0 -27
- data/lib/llm/providers/anthropic_provider.rb +0 -43
- data/lib/llm/providers/ollama_provider.rb +0 -56
- data/lib/llm/providers/openai_provider.rb +0 -42
- data/lib/llm/response_parser.rb +0 -67
- data/lib/plugin/plugin.rb +0 -111
- data/lib/plugin/plugin_exposer.rb +0 -55
- data/lib/scaffolding/crud_generator.rb +0 -94
- data/lib/scaffolding/dry_run_presenter.rb +0 -16
- data/lib/scaffolding/page_introspector.rb +0 -45
- data/lib/scaffolding/scaffold_menu.rb +0 -103
- data/lib/scaffolding/templates/page_from_url.tt +0 -75
- data/lib/scaffolding/templates/spec_from_page.tt +0 -31
- data/lib/scaffolding/templates/spec_from_url.tt +0 -46
- data/lib/scaffolding/url_analyzer.rb +0 -179
- data/sig/adopter/adopt_menu.rbs +0 -25
- data/sig/adopter/converters/base_converter.rbs +0 -23
- data/sig/adopter/converters/identity_converter.rbs +0 -16
- data/sig/adopter/migration_plan.rbs +0 -34
- data/sig/adopter/migrator.rbs +0 -21
- data/sig/adopter/plan_builder.rbs +0 -38
- data/sig/adopter/project_analyzer.rbs +0 -39
- data/sig/adopter/project_detector.rbs +0 -26
- data/sig/generators/infrastructure/gitlab_generator.rbs +0 -4
- data/sig/generators/minitest/minitest_generator.rbs +0 -8
- data/sig/llm/client.rbs +0 -15
- data/sig/llm/config.rbs +0 -20
- data/sig/llm/prompts.rbs +0 -8
- data/sig/llm/provider.rbs +0 -12
- data/sig/llm/providers/anthropic_provider.rbs +0 -16
- data/sig/llm/providers/ollama_provider.rbs +0 -18
- data/sig/llm/providers/openai_provider.rbs +0 -16
- data/sig/llm/response_parser.rbs +0 -13
- data/sig/plugin/plugin.rbs +0 -24
- data/sig/plugin/plugin_exposer.rbs +0 -20
- data/sig/scaffolding/crud_generator.rbs +0 -16
- data/sig/scaffolding/dry_run_presenter.rbs +0 -4
- data/sig/scaffolding/page_introspector.rbs +0 -14
- data/sig/scaffolding/scaffold_menu.rbs +0 -18
- data/sig/scaffolding/url_analyzer.rbs +0 -28
- data/spec/adopter/adopt_menu_spec.rb +0 -176
- data/spec/adopter/converters/identity_converter_spec.rb +0 -145
- data/spec/adopter/migration_plan_spec.rb +0 -113
- data/spec/adopter/migrator_spec.rb +0 -277
- data/spec/adopter/plan_builder_spec.rb +0 -298
- data/spec/adopter/project_analyzer_spec.rb +0 -337
- data/spec/adopter/project_detector_spec.rb +0 -295
- data/spec/generators/generator_spec.rb +0 -23
- data/spec/integration/content/reporter_content_spec.rb +0 -236
- data/spec/integration/content/skip_flags_content_spec.rb +0 -206
- data/spec/integration/generators/gitlab_generator_spec.rb +0 -38
- data/spec/integration/generators/lighthouse_addon_spec.rb +0 -132
- data/spec/integration/generators/minitest_generator_spec.rb +0 -64
- data/spec/integration/generators/reporter_spec.rb +0 -159
- data/spec/integration/generators/skip_flags_spec.rb +0 -134
- data/spec/integration/generators/visual_addon_spec.rb +0 -148
- data/spec/llm/client_spec.rb +0 -79
- data/spec/llm/config_spec.rb +0 -92
- data/spec/llm/prompts_spec.rb +0 -49
- data/spec/llm/response_parser_spec.rb +0 -92
- data/spec/menus/adopter_adopt_menu_spec.rb +0 -97
- data/spec/scaffolding/page_introspector_spec.rb +0 -82
- data/spec/scaffolding/url_analyzer_spec.rb +0 -110
- data/spec/system/adopt_matrix_spec.rb +0 -537
- data/spec/system/adopt_spec.rb +0 -225
- 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
|
data/lib/llm/response_parser.rb
DELETED
|
@@ -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
|