ruby_raider 1.1.4 → 3.0.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 (278) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/e2e_tests.yml +58 -0
  3. data/.github/workflows/integration.yml +4 -6
  4. data/.github/workflows/reek.yml +6 -5
  5. data/.github/workflows/release.yml +175 -0
  6. data/.github/workflows/rubocop.yml +7 -6
  7. data/.github/workflows/steep.yml +21 -0
  8. data/.github/workflows/system_tests.yml +83 -0
  9. data/.gitignore +1 -1
  10. data/.reek.yml +46 -4
  11. data/.rubocop.yml +24 -0
  12. data/.ruby-version +1 -1
  13. data/README.md +140 -77
  14. data/RELEASE.md +412 -0
  15. data/RELEASE_QUICK_GUIDE.md +77 -0
  16. data/Steepfile +22 -0
  17. data/assets/ruby_raider_logo.svg +51 -0
  18. data/bin/release +186 -0
  19. data/lib/adopter/adopt_menu.rb +146 -0
  20. data/lib/adopter/converters/base_converter.rb +84 -0
  21. data/lib/adopter/converters/identity_converter.rb +53 -0
  22. data/lib/adopter/migration_plan.rb +74 -0
  23. data/lib/adopter/migrator.rb +96 -0
  24. data/lib/adopter/plan_builder.rb +275 -0
  25. data/lib/adopter/project_analyzer.rb +252 -0
  26. data/lib/adopter/project_detector.rb +157 -0
  27. data/lib/commands/adopt_commands.rb +42 -0
  28. data/lib/commands/plugin_commands.rb +0 -2
  29. data/lib/commands/scaffolding_commands.rb +220 -37
  30. data/lib/commands/utility_commands.rb +82 -2
  31. data/lib/generators/automation/automation_generator.rb +0 -7
  32. data/lib/generators/automation/templates/account.tt +9 -5
  33. data/lib/generators/automation/templates/appium_caps.tt +60 -6
  34. data/lib/generators/automation/templates/home.tt +4 -4
  35. data/lib/generators/automation/templates/login.tt +61 -4
  36. data/lib/generators/automation/templates/page.tt +13 -7
  37. data/lib/generators/automation/templates/partials/element.tt +1 -1
  38. data/lib/generators/automation/templates/partials/home_page_selector.tt +4 -4
  39. data/lib/generators/automation/templates/partials/initialize_selector.tt +3 -8
  40. data/lib/generators/automation/templates/partials/pdp_page_selector.tt +4 -4
  41. data/lib/generators/automation/templates/partials/url_methods.tt +0 -1
  42. data/lib/generators/automation/templates/partials/visit_method.tt +11 -1
  43. data/lib/generators/automation/templates/pdp.tt +1 -1
  44. data/lib/generators/common_generator.rb +12 -0
  45. data/lib/generators/cucumber/cucumber_generator.rb +36 -0
  46. data/lib/generators/cucumber/templates/accessibility_feature.tt +5 -0
  47. data/lib/generators/cucumber/templates/accessibility_steps.tt +21 -0
  48. data/lib/generators/cucumber/templates/cucumber.tt +8 -1
  49. data/lib/generators/cucumber/templates/env.tt +6 -4
  50. data/lib/generators/cucumber/templates/feature.tt +0 -4
  51. data/lib/generators/cucumber/templates/partials/appium_env.tt +5 -0
  52. data/lib/generators/cucumber/templates/partials/capybara_env.tt +38 -0
  53. data/lib/generators/cucumber/templates/partials/capybara_world.tt +6 -0
  54. data/lib/generators/cucumber/templates/partials/driver_world.tt +1 -4
  55. data/lib/generators/cucumber/templates/partials/mobile_steps.tt +2 -2
  56. data/lib/generators/cucumber/templates/partials/selenium_env.tt +22 -35
  57. data/lib/generators/cucumber/templates/partials/watir_env.tt +20 -1
  58. data/lib/generators/cucumber/templates/partials/web_steps.tt +10 -15
  59. data/lib/generators/cucumber/templates/performance_feature.tt +5 -0
  60. data/lib/generators/cucumber/templates/performance_steps.tt +17 -0
  61. data/lib/generators/cucumber/templates/steps.tt +2 -2
  62. data/lib/generators/cucumber/templates/visual_feature.tt +5 -0
  63. data/lib/generators/cucumber/templates/visual_steps.tt +19 -0
  64. data/lib/generators/cucumber/templates/world.tt +5 -3
  65. data/lib/generators/generator.rb +50 -7
  66. data/lib/generators/helper_generator.rb +39 -9
  67. data/lib/generators/infrastructure/github_generator.rb +6 -0
  68. data/lib/generators/infrastructure/templates/github.tt +12 -8
  69. data/lib/generators/infrastructure/templates/github_appium.tt +108 -0
  70. data/lib/generators/infrastructure/templates/gitlab.tt +6 -3
  71. data/lib/generators/invoke_generators.rb +43 -9
  72. data/lib/generators/menu_generator.rb +122 -11
  73. data/lib/generators/minitest/minitest_generator.rb +35 -0
  74. data/lib/generators/minitest/templates/accessibility_test.tt +26 -0
  75. data/lib/generators/minitest/templates/performance_test.tt +18 -0
  76. data/lib/generators/minitest/templates/test.tt +64 -0
  77. data/lib/generators/minitest/templates/visual_test.tt +23 -0
  78. data/lib/generators/rspec/rspec_generator.rb +16 -4
  79. data/lib/generators/rspec/templates/accessibility_spec.tt +25 -0
  80. data/lib/generators/rspec/templates/performance_spec.tt +18 -0
  81. data/lib/generators/rspec/templates/spec.tt +13 -41
  82. data/lib/generators/rspec/templates/visual_spec.tt +20 -0
  83. data/lib/generators/template_renderer/partial_cache.rb +126 -0
  84. data/lib/generators/template_renderer/partial_resolver.rb +110 -0
  85. data/lib/generators/template_renderer/template_error.rb +50 -0
  86. data/lib/generators/template_renderer.rb +106 -0
  87. data/lib/generators/templates/common/config.tt +2 -2
  88. data/lib/generators/templates/common/gemfile.tt +36 -9
  89. data/lib/generators/templates/common/git_ignore.tt +6 -1
  90. data/lib/generators/templates/common/partials/mobile_config.tt +5 -1
  91. data/lib/generators/templates/common/partials/web_config.tt +17 -8
  92. data/lib/generators/templates/common/rakefile.tt +36 -0
  93. data/lib/generators/templates/common/read_me.tt +43 -91
  94. data/lib/generators/templates/common/rspec.tt +3 -0
  95. data/lib/generators/templates/common/ruby_version.tt +1 -0
  96. data/lib/generators/templates/helpers/allure_helper.tt +13 -2
  97. data/lib/generators/templates/helpers/browser_helper.tt +13 -2
  98. data/lib/generators/templates/helpers/capybara_helper.tt +32 -0
  99. data/lib/generators/templates/helpers/debug_helper.tt +190 -0
  100. data/lib/generators/templates/helpers/driver_helper.tt +3 -11
  101. data/lib/generators/templates/helpers/partials/allure_imports.tt +3 -1
  102. data/lib/generators/templates/helpers/partials/allure_requirements.tt +3 -1
  103. data/lib/generators/templates/helpers/partials/appium_driver.tt +44 -0
  104. data/lib/generators/templates/helpers/partials/browserstack_config.tt +13 -0
  105. data/lib/generators/templates/helpers/partials/debug_diagnostics.tt +7 -0
  106. data/lib/generators/templates/helpers/partials/debug_start.tt +7 -0
  107. data/lib/generators/templates/helpers/partials/driver_and_options.tt +5 -115
  108. data/lib/generators/templates/helpers/partials/quit_driver.tt +3 -1
  109. data/lib/generators/templates/helpers/partials/screenshot.tt +3 -1
  110. data/lib/generators/templates/helpers/partials/selenium_driver.tt +26 -0
  111. data/lib/generators/templates/helpers/partials/video_start.tt +9 -0
  112. data/lib/generators/templates/helpers/partials/video_stop.tt +4 -0
  113. data/lib/generators/templates/helpers/performance_helper.tt +57 -0
  114. data/lib/generators/templates/helpers/spec_helper.tt +72 -10
  115. data/lib/generators/templates/helpers/test_helper.tt +94 -0
  116. data/lib/generators/templates/helpers/video_helper.tt +270 -0
  117. data/lib/generators/templates/helpers/visual_helper.tt +39 -46
  118. data/lib/llm/client.rb +79 -0
  119. data/lib/llm/config.rb +57 -0
  120. data/lib/llm/prompts.rb +84 -0
  121. data/lib/llm/provider.rb +27 -0
  122. data/lib/llm/providers/anthropic_provider.rb +43 -0
  123. data/lib/llm/providers/ollama_provider.rb +56 -0
  124. data/lib/llm/providers/openai_provider.rb +42 -0
  125. data/lib/llm/response_parser.rb +67 -0
  126. data/lib/plugin/plugin.rb +22 -20
  127. data/lib/plugin/plugin_exposer.rb +16 -38
  128. data/lib/ruby_raider.rb +51 -11
  129. data/lib/scaffolding/crud_generator.rb +94 -0
  130. data/lib/scaffolding/dry_run_presenter.rb +16 -0
  131. data/lib/scaffolding/name_normalizer.rb +63 -0
  132. data/lib/scaffolding/page_introspector.rb +45 -0
  133. data/lib/scaffolding/project_detector.rb +72 -0
  134. data/lib/scaffolding/scaffold_menu.rb +103 -0
  135. data/lib/scaffolding/scaffolding.rb +158 -11
  136. data/lib/scaffolding/templates/component.tt +30 -0
  137. data/lib/scaffolding/templates/feature.tt +4 -4
  138. data/lib/scaffolding/templates/helper.tt +15 -1
  139. data/lib/scaffolding/templates/page_from_url.tt +75 -0
  140. data/lib/scaffolding/templates/page_object.tt +50 -1
  141. data/lib/scaffolding/templates/spec.tt +33 -2
  142. data/lib/scaffolding/templates/spec_from_page.tt +31 -0
  143. data/lib/scaffolding/templates/spec_from_url.tt +46 -0
  144. data/lib/scaffolding/templates/steps.tt +17 -5
  145. data/lib/scaffolding/url_analyzer.rb +179 -0
  146. data/lib/utilities/desktop_downloader.rb +177 -0
  147. data/lib/utilities/logo.rb +83 -0
  148. data/lib/utilities/utilities.rb +53 -20
  149. data/lib/version +1 -1
  150. data/ruby_raider.gemspec +1 -0
  151. data/sig/adopter/adopt_menu.rbs +25 -0
  152. data/sig/adopter/converters/base_converter.rbs +23 -0
  153. data/sig/adopter/converters/identity_converter.rbs +16 -0
  154. data/sig/adopter/migration_plan.rbs +34 -0
  155. data/sig/adopter/migrator.rbs +21 -0
  156. data/sig/adopter/plan_builder.rbs +38 -0
  157. data/sig/adopter/project_analyzer.rbs +39 -0
  158. data/sig/adopter/project_detector.rbs +26 -0
  159. data/sig/commands/adopt_commands.rbs +8 -0
  160. data/sig/commands/loaded_commands.rbs +5 -0
  161. data/sig/commands/plugin_commands.rbs +9 -0
  162. data/sig/commands/scaffolding_commands.rbs +28 -0
  163. data/sig/commands/utility_commands.rbs +21 -0
  164. data/sig/generators/automation/automation_generator.rbs +20 -0
  165. data/sig/generators/common_generator.rbs +12 -0
  166. data/sig/generators/cucumber/cucumber_generator.rbs +16 -0
  167. data/sig/generators/generator.rbs +40 -0
  168. data/sig/generators/helper_generator.rbs +18 -0
  169. data/sig/generators/infrastructure/github_generator.rbs +5 -0
  170. data/sig/generators/infrastructure/gitlab_generator.rbs +4 -0
  171. data/sig/generators/invoke_generators.rbs +10 -0
  172. data/sig/generators/menu_generator.rbs +29 -0
  173. data/sig/generators/minitest/minitest_generator.rbs +8 -0
  174. data/sig/generators/rspec/rspec_generator.rbs +8 -0
  175. data/sig/generators/template_renderer/partial_cache.rbs +20 -0
  176. data/sig/generators/template_renderer/partial_resolver.rbs +20 -0
  177. data/sig/generators/template_renderer/template_error.rbs +19 -0
  178. data/sig/generators/template_renderer.rbs +10 -0
  179. data/sig/llm/client.rbs +15 -0
  180. data/sig/llm/config.rbs +20 -0
  181. data/sig/llm/prompts.rbs +8 -0
  182. data/sig/llm/provider.rbs +12 -0
  183. data/sig/llm/providers/anthropic_provider.rbs +16 -0
  184. data/sig/llm/providers/ollama_provider.rbs +18 -0
  185. data/sig/llm/providers/openai_provider.rbs +16 -0
  186. data/sig/llm/response_parser.rbs +13 -0
  187. data/sig/plugin/plugin.rbs +24 -0
  188. data/sig/plugin/plugin_exposer.rbs +20 -0
  189. data/sig/ruby_raider.rbs +15 -0
  190. data/sig/scaffolding/crud_generator.rbs +16 -0
  191. data/sig/scaffolding/dry_run_presenter.rbs +4 -0
  192. data/sig/scaffolding/name_normalizer.rbs +17 -0
  193. data/sig/scaffolding/page_introspector.rbs +14 -0
  194. data/sig/scaffolding/project_detector.rbs +14 -0
  195. data/sig/scaffolding/scaffold_menu.rbs +18 -0
  196. data/sig/scaffolding/scaffolding.rbs +55 -0
  197. data/sig/scaffolding/url_analyzer.rbs +28 -0
  198. data/sig/utilities/desktop_downloader.rbs +23 -0
  199. data/sig/utilities/logger.rbs +13 -0
  200. data/sig/utilities/logo.rbs +16 -0
  201. data/sig/utilities/utilities.rbs +30 -0
  202. data/sig/vendor/thor.rbs +34 -0
  203. data/sig/vendor/tty_prompt.rbs +15 -0
  204. data/spec/adopter/adopt_menu_spec.rb +176 -0
  205. data/spec/adopter/converters/identity_converter_spec.rb +145 -0
  206. data/spec/adopter/migration_plan_spec.rb +113 -0
  207. data/spec/adopter/migrator_spec.rb +277 -0
  208. data/spec/adopter/plan_builder_spec.rb +298 -0
  209. data/spec/adopter/project_analyzer_spec.rb +337 -0
  210. data/spec/adopter/project_detector_spec.rb +295 -0
  211. data/spec/commands/raider_commands_spec.rb +129 -0
  212. data/spec/generators/fixtures/templates/test.tt +1 -0
  213. data/spec/generators/fixtures/templates/test_partial.tt +1 -0
  214. data/spec/generators/generator_spec.rb +23 -0
  215. data/spec/generators/template_renderer_spec.rb +298 -0
  216. data/spec/integration/commands/scaffolding_commands_spec.rb +2 -2
  217. data/spec/integration/commands/utility_commands_spec.rb +24 -4
  218. data/spec/integration/content/ci_content_spec.rb +119 -0
  219. data/spec/integration/content/common_content_spec.rb +288 -0
  220. data/spec/integration/content/config_content_spec.rb +175 -0
  221. data/spec/integration/content/content_helper.rb +32 -0
  222. data/spec/integration/content/gemfile_content_spec.rb +209 -0
  223. data/spec/integration/content/helper_content_spec.rb +485 -0
  224. data/spec/integration/content/page_content_spec.rb +259 -0
  225. data/spec/integration/content/reporter_content_spec.rb +236 -0
  226. data/spec/integration/content/skip_flags_content_spec.rb +206 -0
  227. data/spec/integration/content/syntax_validation_spec.rb +30 -0
  228. data/spec/integration/content/test_content_spec.rb +266 -0
  229. data/spec/integration/end_to_end_features_spec.rb +690 -0
  230. data/spec/integration/end_to_end_spec.rb +361 -0
  231. data/spec/integration/generators/automation_generator_spec.rb +9 -21
  232. data/spec/integration/generators/axe_addon_spec.rb +150 -0
  233. data/spec/integration/generators/common_generator_spec.rb +48 -49
  234. data/spec/integration/generators/config_features_spec.rb +155 -0
  235. data/spec/integration/generators/cucumber_generator_spec.rb +7 -7
  236. data/spec/integration/generators/debug_helper_spec.rb +68 -0
  237. data/spec/integration/generators/github_generator_spec.rb +8 -8
  238. data/spec/integration/generators/gitlab_generator_spec.rb +8 -8
  239. data/spec/integration/generators/helpers_generator_spec.rb +70 -44
  240. data/spec/integration/generators/lighthouse_addon_spec.rb +132 -0
  241. data/spec/integration/generators/minitest_generator_spec.rb +64 -0
  242. data/spec/integration/generators/reporter_spec.rb +159 -0
  243. data/spec/integration/generators/rspec_generator_spec.rb +7 -7
  244. data/spec/integration/generators/skip_flags_spec.rb +134 -0
  245. data/spec/integration/generators/visual_addon_spec.rb +148 -0
  246. data/spec/integration/settings_helper.rb +1 -4
  247. data/spec/integration/spec_helper.rb +46 -11
  248. data/spec/llm/client_spec.rb +79 -0
  249. data/spec/llm/config_spec.rb +92 -0
  250. data/spec/llm/prompts_spec.rb +49 -0
  251. data/spec/llm/response_parser_spec.rb +92 -0
  252. data/spec/menus/adopter_adopt_menu_spec.rb +97 -0
  253. data/spec/menus/menu_generator_spec.rb +263 -0
  254. data/spec/scaffolding/name_normalizer_spec.rb +113 -0
  255. data/spec/scaffolding/page_introspector_spec.rb +82 -0
  256. data/spec/scaffolding/scaffold_project_detector_spec.rb +104 -0
  257. data/spec/scaffolding/scaffolding_features_spec.rb +311 -0
  258. data/spec/scaffolding/url_analyzer_spec.rb +110 -0
  259. data/spec/system/adopt_matrix_spec.rb +537 -0
  260. data/spec/system/adopt_spec.rb +225 -0
  261. data/spec/system/capybara_spec.rb +42 -0
  262. data/spec/system/selenium_spec.rb +19 -17
  263. data/spec/system/support/system_test_helper.rb +33 -0
  264. data/spec/system/watir_spec.rb +19 -17
  265. data/spec/utilities/desktop_downloader_spec.rb +92 -0
  266. metadata +193 -18
  267. data/.github/workflows/push_gem.yml +0 -37
  268. data/.github/workflows/selenium.yml +0 -22
  269. data/.github/workflows/watir.yml +0 -22
  270. data/lib/generators/automation/templates/partials/android_caps.tt +0 -17
  271. data/lib/generators/automation/templates/partials/cross_platform_caps.tt +0 -25
  272. data/lib/generators/automation/templates/partials/ios_caps.tt +0 -18
  273. data/lib/generators/automation/templates/partials/selenium_account.tt +0 -9
  274. data/lib/generators/automation/templates/partials/selenium_login.tt +0 -34
  275. data/lib/generators/automation/templates/partials/watir_account.tt +0 -7
  276. data/lib/generators/automation/templates/partials/watir_login.tt +0 -32
  277. data/lib/generators/automation/templates/visual_options.tt +0 -16
  278. data/lib/generators/templates/helpers/visual_spec_helper.tt +0 -35
@@ -0,0 +1,77 @@
1
+ # Quick Release Guide
2
+
3
+ ## TL;DR - One Command Release
4
+
5
+ ```bash
6
+ # Bug fixes: 1.1.4 -> 1.1.5
7
+ bin/release patch
8
+
9
+ # New features: 1.1.4 -> 1.2.0
10
+ bin/release minor
11
+
12
+ # Breaking changes: 1.1.4 -> 2.0.0
13
+ bin/release major
14
+ ```
15
+
16
+ **Everything else is automated!**
17
+
18
+ ---
19
+
20
+ ## What Happens
21
+
22
+ 1. ✅ Script validates & tests locally
23
+ 2. ✅ Updates version in `lib/version`
24
+ 3. ✅ Creates git commit + tag
25
+ 4. ✅ Pushes to GitHub
26
+ 5. ✅ GitHub Actions automatically:
27
+ - Runs tests
28
+ - Builds gem
29
+ - Publishes to RubyGems
30
+ - Creates GitHub Release
31
+
32
+ **Duration:** ~3 minutes total
33
+
34
+ ---
35
+
36
+ ## Before Releasing
37
+
38
+ Ensure:
39
+ - [ ] All changes merged to master
40
+ - [ ] Tests passing: `bundle exec rspec`
41
+ - [ ] Linters passing: `bundle exec rubocop && bundle exec reek`
42
+
43
+ ---
44
+
45
+ ## Monitor Progress
46
+
47
+ **GitHub Actions:**
48
+ https://github.com/RubyRaider/ruby_raider/actions
49
+
50
+ **GitHub Releases:**
51
+ https://github.com/RubyRaider/ruby_raider/releases
52
+
53
+ **RubyGems:**
54
+ https://rubygems.org/gems/ruby_raider
55
+
56
+ ---
57
+
58
+ ## First-Time Setup
59
+
60
+ Add RubyGems API key to GitHub Secrets:
61
+
62
+ 1. Get API key:
63
+ ```bash
64
+ gem signin
65
+ # Visit https://rubygems.org/profile/edit
66
+ # Copy API key
67
+ ```
68
+
69
+ 2. Add to GitHub:
70
+ - Go to: Settings → Secrets → Actions
71
+ - Add `RUBYGEMS_API_KEY` secret
72
+
73
+ ---
74
+
75
+ ## Full Documentation
76
+
77
+ See [RELEASE.md](RELEASE.md) for complete documentation.
data/Steepfile ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ target :lib do
4
+ signature 'sig'
5
+
6
+ check 'lib'
7
+
8
+ # External gem stubs (not typed)
9
+ library 'yaml'
10
+ library 'fileutils'
11
+ library 'erb'
12
+ library 'open3'
13
+ library 'timeout'
14
+ library 'uri'
15
+ library 'net-http'
16
+ library 'json'
17
+ library 'logger'
18
+ library 'forwardable'
19
+
20
+ # Configure strictness — start lenient, tighten over time
21
+ configure_code_diagnostics(Steep::Diagnostic::Ruby.lenient)
22
+ end
@@ -0,0 +1,51 @@
1
+ <svg width="512" height="512" viewBox="100 100 480 480" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <!-- LEFT HORN -->
3
+ <polygon points="220,248 218,218 168,155" fill="#d4a017"/>
4
+ <polygon points="220,248 168,155 205,230" fill="#e8c840" opacity="0.7"/>
5
+ <polygon points="218,218 168,155 205,230" fill="#b8860b" opacity="0.8"/>
6
+ <line x1="205" y1="230" x2="168" y2="155" stroke="#f5e680" stroke-width="1" opacity="0.4"/>
7
+ <polygon points="222,250 218,218 215,218 219,250" fill="#c9960c" opacity="0.6"/>
8
+ <!-- RIGHT HORN -->
9
+ <polygon points="460,248 462,218 512,155" fill="#d4a017"/>
10
+ <polygon points="460,248 512,155 475,230" fill="#e8c840" opacity="0.7"/>
11
+ <polygon points="462,218 512,155 475,230" fill="#b8860b" opacity="0.8"/>
12
+ <line x1="475" y1="230" x2="512" y2="155" stroke="#f5e680" stroke-width="1" opacity="0.4"/>
13
+ <polygon points="458,250 462,218 465,218 461,250" fill="#c9960c" opacity="0.6"/>
14
+ <!-- PAVILION (chin/jaw) -->
15
+ <polygon points="340,530 250,380 230,340 450,340 430,380" fill="#4d0c1a"/>
16
+ <polygon points="340,530 250,380 290,380" fill="#5c1220"/>
17
+ <polygon points="340,530 290,380 340,370" fill="#6a1525"/>
18
+ <polygon points="340,530 340,370 390,380" fill="#5c1220"/>
19
+ <polygon points="340,530 390,380 430,380" fill="#4d0c1a"/>
20
+ <!-- GIRDLE (visor band) -->
21
+ <rect x="220" y="330" width="240" height="16" rx="2" fill="#2a2a40" opacity="0.85"/>
22
+ <rect x="220" y="330" width="240" height="16" rx="2" fill="none" stroke="#555555" stroke-width="0.5" opacity="0.4"/>
23
+ <g fill="#888888" opacity="0.6">
24
+ <circle cx="255" cy="338" r="3"/><circle cx="285" cy="338" r="3"/>
25
+ <circle cx="315" cy="338" r="3"/><circle cx="345" cy="338" r="3"/>
26
+ <circle cx="375" cy="338" r="3"/><circle cx="405" cy="338" r="3"/>
27
+ </g>
28
+ <g fill="#bbbbbb" opacity="0.3">
29
+ <circle cx="254" cy="337" r="1.3"/><circle cx="284" cy="337" r="1.3"/>
30
+ <circle cx="314" cy="337" r="1.3"/><circle cx="344" cy="337" r="1.3"/>
31
+ <circle cx="374" cy="337" r="1.3"/><circle cx="404" cy="337" r="1.3"/>
32
+ </g>
33
+ <!-- CROWN FACETS -->
34
+ <polygon points="340,130 460,250 460,330 220,330 220,250" fill="#7b1c2a"/>
35
+ <polygon points="340,130 410,195 340,250 270,195" fill="#d4374b"/>
36
+ <polygon points="340,140 390,190 340,235 290,190" fill="#e85a6e" opacity="0.6"/>
37
+ <polygon points="220,250 270,195 220,330" fill="#6a1525"/>
38
+ <polygon points="460,250 410,195 460,330" fill="#6a1525"/>
39
+ <polygon points="270,195 340,250 220,330 220,250" fill="#8c2234" opacity="0.8"/>
40
+ <polygon points="410,195 340,250 460,330 460,250" fill="#8c2234" opacity="0.8"/>
41
+ <!-- TABLE (top facet) -->
42
+ <polygon points="340,155 385,185 340,215 295,185" fill="#ff7088" opacity="0.45"/>
43
+ <!-- Crown highlight -->
44
+ <line x1="280" y1="200" x2="340" y2="140" stroke="#ff9db0" stroke-width="1" opacity="0.3"/>
45
+ <line x1="340" y1="140" x2="400" y2="200" stroke="#ff9db0" stroke-width="1" opacity="0.2"/>
46
+ <!-- EYES -->
47
+ <ellipse cx="300" cy="308" rx="12" ry="8" fill="#1a0a10"/>
48
+ <circle cx="296" cy="305" r="2" fill="#ffffff" opacity="0.6"/>
49
+ <ellipse cx="380" cy="308" rx="12" ry="8" fill="#1a0a10"/>
50
+ <circle cx="376" cy="305" r="2" fill="#ffffff" opacity="0.6"/>
51
+ </svg>
data/bin/release ADDED
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Automated release script for Ruby Raider
5
+ # Usage: bin/release [major|minor|patch]
6
+
7
+ require 'fileutils'
8
+
9
+ class ReleaseManager
10
+ VERSION_FILE = 'lib/version'
11
+ GEMSPEC_FILE = 'ruby_raider.gemspec'
12
+
13
+ def initialize(bump_type)
14
+ @bump_type = bump_type
15
+ @current_version = read_version
16
+ end
17
+
18
+ def run
19
+ validate_clean_repo
20
+ validate_branch
21
+ validate_tests
22
+
23
+ new_version = calculate_new_version
24
+ confirm_release(new_version)
25
+
26
+ update_version(new_version)
27
+ commit_and_tag(new_version)
28
+ push_release
29
+
30
+ success_message(new_version)
31
+ end
32
+
33
+ private
34
+
35
+ def read_version
36
+ File.read(VERSION_FILE).strip
37
+ end
38
+
39
+ def validate_clean_repo
40
+ status = `git status --porcelain`.strip
41
+ return if status.empty?
42
+
43
+ puts '❌ Error: Working directory is not clean'
44
+ puts 'Please commit or stash your changes first:'
45
+ puts status
46
+ exit 1
47
+ end
48
+
49
+ def validate_branch
50
+ branch = `git branch --show-current`.strip
51
+ return unless branch != 'master' && branch != 'main'
52
+
53
+ puts "⚠️ Warning: You're on branch '#{branch}', not master/main"
54
+ print 'Continue anyway? [y/N] '
55
+ exit 1 unless gets.strip.downcase == 'y'
56
+ end
57
+
58
+ def validate_tests
59
+ puts '🧪 Running tests...'
60
+
61
+ # Run unit tests
62
+ unless system('bundle exec rspec spec/generators/ --format progress')
63
+ puts '❌ Unit tests failed'
64
+ exit 1
65
+ end
66
+
67
+ # Run integration tests (skip slow e2e tests)
68
+ unless system('bundle exec rspec spec/integration/ --tag ~slow --format progress')
69
+ puts '❌ Integration tests failed'
70
+ exit 1
71
+ end
72
+
73
+ # Run linters
74
+ unless system('bundle exec rubocop')
75
+ puts '❌ RuboCop failed'
76
+ exit 1
77
+ end
78
+
79
+ unless system('bundle exec reek')
80
+ puts '❌ Reek failed'
81
+ exit 1
82
+ end
83
+
84
+ puts '✓ All tests and linters passed'
85
+ end
86
+
87
+ def calculate_new_version
88
+ major, minor, patch = @current_version.split('.').map(&:to_i)
89
+
90
+ case @bump_type
91
+ when 'major'
92
+ "#{major + 1}.0.0"
93
+ when 'minor'
94
+ "#{major}.#{minor + 1}.0"
95
+ when 'patch'
96
+ "#{major}.#{minor}.#{patch + 1}"
97
+ else
98
+ puts "❌ Error: Invalid bump type '#{@bump_type}'"
99
+ puts 'Usage: bin/release [major|minor|patch]'
100
+ exit 1
101
+ end
102
+ end
103
+
104
+ def confirm_release(new_version)
105
+ puts "\n📦 Release Summary"
106
+ puts " Current version: #{@current_version}"
107
+ puts " New version: #{new_version}"
108
+ puts " Bump type: #{@bump_type}"
109
+ puts "\nThis will:"
110
+ puts " 1. Update lib/version to #{new_version}"
111
+ puts ' 2. Commit changes'
112
+ puts " 3. Create git tag v#{new_version}"
113
+ puts ' 4. Push to GitHub (triggers automated release)'
114
+ puts ' 5. Publish gem to RubyGems (via GitHub Actions)'
115
+ puts ' 6. Create GitHub release with changelog'
116
+
117
+ print "\nProceed with release? [y/N] "
118
+ exit 0 unless gets.strip.downcase == 'y'
119
+ end
120
+
121
+ def update_version(new_version)
122
+ puts "\n📝 Updating version to #{new_version}..."
123
+ File.write(VERSION_FILE, "#{new_version}\n")
124
+ puts "✓ Updated #{VERSION_FILE}"
125
+ end
126
+
127
+ def commit_and_tag(new_version)
128
+ puts "\n📌 Creating commit and tag..."
129
+
130
+ # Commit version change
131
+ system("git add #{VERSION_FILE}")
132
+ system("git commit -m 'Bump version to #{new_version}'")
133
+
134
+ # Create annotated tag
135
+ system("git tag -a v#{new_version} -m 'Release version #{new_version}'")
136
+
137
+ puts "✓ Created commit and tag v#{new_version}"
138
+ end
139
+
140
+ def push_release
141
+ puts "\n🚀 Pushing to GitHub..."
142
+
143
+ # Push commit
144
+ unless system('git push origin HEAD')
145
+ puts '❌ Failed to push commits'
146
+ exit 1
147
+ end
148
+
149
+ # Push tag (this triggers the release workflow)
150
+ unless system('git push origin --tags')
151
+ puts '❌ Failed to push tags'
152
+ exit 1
153
+ end
154
+
155
+ puts '✓ Pushed to GitHub'
156
+ end
157
+
158
+ def success_message(new_version)
159
+ puts "\n#{'=' * 60}"
160
+ puts "🎉 Release #{new_version} initiated successfully!"
161
+ puts '=' * 60
162
+ puts "\nThe GitHub Actions workflow is now:"
163
+ puts ' 1. Running tests'
164
+ puts ' 2. Building the gem'
165
+ puts ' 3. Publishing to RubyGems'
166
+ puts ' 4. Creating GitHub release'
167
+ puts "\nMonitor progress at:"
168
+ puts ' https://github.com/RubyRaider/ruby_raider/actions'
169
+ puts "\nRelease will be available at:"
170
+ puts " https://github.com/RubyRaider/ruby_raider/releases/tag/v#{new_version}"
171
+ puts " https://rubygems.org/gems/ruby_raider/versions/#{new_version}"
172
+ puts "\n✨ Thank you for contributing to Ruby Raider!"
173
+ end
174
+ end
175
+
176
+ # Main execution
177
+ if ARGV.length != 1
178
+ puts 'Usage: bin/release [major|minor|patch]'
179
+ puts "\nExamples:"
180
+ puts ' bin/release patch # 1.1.4 -> 1.1.5 (bug fixes)'
181
+ puts ' bin/release minor # 1.1.4 -> 1.2.0 (new features)'
182
+ puts ' bin/release major # 1.1.4 -> 2.0.0 (breaking changes)'
183
+ exit 1
184
+ end
185
+
186
+ ReleaseManager.new(ARGV[0]).run
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tty-prompt'
4
+ require_relative 'project_analyzer'
5
+ require_relative 'plan_builder'
6
+ require_relative 'migrator'
7
+
8
+ module Adopter
9
+ class AdoptMenu
10
+ WEB_AUTOMATIONS = %w[selenium capybara watir].freeze
11
+ TEST_FRAMEWORKS = %w[rspec cucumber minitest].freeze
12
+ CI_PLATFORMS = %w[github gitlab].freeze
13
+
14
+ def initialize
15
+ @prompt = TTY::Prompt.new
16
+ end
17
+
18
+ def run
19
+ source_path = ask_source_path
20
+ detection = preview_detection(source_path)
21
+ target_automation = ask_target_automation(detection[:automation])
22
+ target_framework = ask_target_framework(detection[:framework])
23
+ output_path = ask_output_path
24
+ ci_platform = ask_ci_platform(detection[:ci_platform])
25
+
26
+ execute_adoption(
27
+ source_path:,
28
+ output_path:,
29
+ target_automation:,
30
+ target_framework:,
31
+ ci_platform:
32
+ )
33
+ end
34
+
35
+ # Programmatic entry point for raider_desktop and CLI --parameters
36
+ def self.adopt(params)
37
+ validate_params!(params)
38
+
39
+ analysis = ProjectAnalyzer.new(params[:source_path]).analyze
40
+ plan = PlanBuilder.new(analysis, params).build
41
+ results = Migrator.new(plan).execute
42
+
43
+ { plan:, results: }
44
+ end
45
+
46
+ def self.validate_params!(params)
47
+ raise ArgumentError, 'source_path is required' unless params[:source_path]
48
+ raise ArgumentError, 'output_path is required' unless params[:output_path]
49
+ raise ArgumentError, 'target_automation is required' unless params[:target_automation]
50
+ raise ArgumentError, 'target_framework is required' unless params[:target_framework]
51
+
52
+ raise ArgumentError, "target_automation must be one of: #{WEB_AUTOMATIONS.join(', ')}" unless WEB_AUTOMATIONS.include?(params[:target_automation])
53
+
54
+ return if TEST_FRAMEWORKS.include?(params[:target_framework])
55
+
56
+ raise ArgumentError, "target_framework must be one of: #{TEST_FRAMEWORKS.join(', ')}"
57
+ end
58
+
59
+ private
60
+
61
+ def ask_source_path
62
+ path = @prompt.ask('Enter the path to your existing test project:') do |q|
63
+ q.required true
64
+ q.validate ->(input) { Dir.exist?(input) }
65
+ q.messages[:valid?] = 'Directory does not exist: %<value>s'
66
+ end
67
+ File.expand_path(path)
68
+ end
69
+
70
+ def preview_detection(source_path)
71
+ detection = ProjectDetector.detect(source_path)
72
+ @prompt.say("\nDetected project settings:")
73
+ @prompt.say(" Automation: #{detection[:automation] || 'unknown'}")
74
+ @prompt.say(" Framework: #{detection[:framework] || 'unknown'}")
75
+ @prompt.say(" Browser: #{detection[:browser] || 'not detected'}")
76
+ @prompt.say(" URL: #{detection[:url] || 'not detected'}")
77
+ @prompt.say(" CI: #{detection[:ci_platform] || 'none'}")
78
+ @prompt.say('')
79
+ detection
80
+ end
81
+
82
+ def ask_target_automation(detected)
83
+ default = WEB_AUTOMATIONS.include?(detected) ? detected : nil
84
+ @prompt.select('Select target automation framework:', WEB_AUTOMATIONS.map(&:capitalize),
85
+ default: default&.capitalize) do |menu|
86
+ menu.choice :Quit, -> { exit }
87
+ end.downcase
88
+ end
89
+
90
+ def ask_target_framework(detected)
91
+ default = TEST_FRAMEWORKS.include?(detected) ? detected : nil
92
+ @prompt.select('Select target test framework:', TEST_FRAMEWORKS.map(&:capitalize),
93
+ default: default&.capitalize) do |menu|
94
+ menu.choice :Quit, -> { exit }
95
+ end.downcase
96
+ end
97
+
98
+ def ask_output_path
99
+ @prompt.ask('Enter the output path for the new Raider project:') do |q|
100
+ q.required true
101
+ end
102
+ end
103
+
104
+ def ask_ci_platform(detected)
105
+ choices = [{ name: 'Github Actions', value: 'github' },
106
+ { name: 'Gitlab CI/CD', value: 'gitlab' },
107
+ { name: 'No CI', value: nil }]
108
+ default = case detected
109
+ when 'github' then 'Github Actions'
110
+ when 'gitlab' then 'Gitlab CI/CD'
111
+ end
112
+ @prompt.select('Configure CI/CD?', choices, default:)
113
+ end
114
+
115
+ def execute_adoption(params)
116
+ @prompt.say("\nAdopting project...")
117
+ result = self.class.adopt(params)
118
+ print_results(result[:plan], result[:results])
119
+ rescue MobileProjectError => e
120
+ @prompt.error(e.message)
121
+ rescue StandardError => e
122
+ @prompt.error("Adoption failed: #{e.message}")
123
+ end
124
+
125
+ def print_results(plan, results)
126
+ @prompt.say("\nAdoption complete!")
127
+ @prompt.say(" Pages converted: #{results[:pages]}")
128
+ @prompt.say(" Tests converted: #{results[:tests]}")
129
+ @prompt.say(" Features copied: #{results[:features]}")
130
+ @prompt.say(" Steps converted: #{results[:steps]}")
131
+
132
+ unless results[:errors].empty?
133
+ @prompt.warn("\nErrors:")
134
+ results[:errors].each { |e| @prompt.warn(" - #{e}") }
135
+ end
136
+
137
+ unless plan.warnings.empty?
138
+ @prompt.warn("\nWarnings:")
139
+ plan.warnings.each { |w| @prompt.warn(" - #{w}") }
140
+ end
141
+
142
+ @prompt.say("\nOutput: #{plan.output_path}")
143
+ @prompt.say("Run: cd #{plan.output_path} && bundle install")
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Adopter
4
+ module Converters
5
+ class BaseConverter
6
+ RAIDER_BASE_CLASSES = %w[Page BasePage AbstractPage].freeze
7
+
8
+ def convert_page(content, _page_info)
9
+ content
10
+ end
11
+
12
+ def convert_test(content, _test_info)
13
+ content
14
+ end
15
+
16
+ private
17
+
18
+ def ensure_frozen_string_literal(content)
19
+ return content if content.include?('frozen_string_literal')
20
+
21
+ "# frozen_string_literal: true\n\n#{content}"
22
+ end
23
+
24
+ def update_base_class(content, target_base = 'Page')
25
+ content.gsub(/class\s+(\w+)\s*<\s*([\w:]+)/) do
26
+ class_name = ::Regexp.last_match(1)
27
+ current_base = ::Regexp.last_match(2)
28
+ if raider_compatible_base?(current_base)
29
+ "class #{class_name} < #{target_base}"
30
+ else
31
+ "class #{class_name} < #{current_base}"
32
+ end
33
+ end
34
+ end
35
+
36
+ def raider_compatible_base?(base_class)
37
+ RAIDER_BASE_CLASSES.include?(base_class) || base_class.include?('Page')
38
+ end
39
+
40
+ def update_require_paths(content, target_automation)
41
+ result = content.dup
42
+ result = update_page_requires(result)
43
+ update_helper_requires(result, target_automation)
44
+ end
45
+
46
+ def update_page_requires(content)
47
+ content.gsub(%r{require_relative\s+['"]\.*/(?:pages?|page_objects)/(\w+)['"]}) do
48
+ page_name = ::Regexp.last_match(1)
49
+ "require_relative '../page_objects/pages/#{page_name}'"
50
+ end
51
+ end
52
+
53
+ def update_helper_requires(content, target_automation)
54
+ case target_automation
55
+ when 'capybara'
56
+ content.gsub(%r{require_relative\s+['"]\.*/(?:helpers?|support)/(?:driver|browser)_?\w*['"]},
57
+ "require_relative '../helpers/capybara_helper'")
58
+ when 'watir'
59
+ content.gsub(%r{require_relative\s+['"]\.*/(?:helpers?|support)/(?:driver|capybara)_?\w*['"]},
60
+ "require_relative '../helpers/browser_helper'")
61
+ else
62
+ content.gsub(%r{require_relative\s+['"]\.*/(?:helpers?|support)/(?:browser|capybara)_?\w*['"]},
63
+ "require_relative '../helpers/driver_helper'")
64
+ end
65
+ end
66
+
67
+ def remove_driver_arg(content)
68
+ content.gsub(/(\w+)\.new\(\s*(?:driver|browser|page)\s*\)/, '\1.new')
69
+ end
70
+
71
+ def swap_driver_arg(content, target_arg)
72
+ content.gsub(/(\w+)\.new\(\s*(?:driver|browser|page)\s*\)/, "\\1.new(#{target_arg})")
73
+ end
74
+
75
+ def driver_arg_for(target_automation)
76
+ case target_automation
77
+ when 'watir' then 'browser'
78
+ when 'capybara' then nil
79
+ else 'driver'
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_converter'
4
+
5
+ module Adopter
6
+ module Converters
7
+ class IdentityConverter < BaseConverter
8
+ def initialize(target_automation)
9
+ super()
10
+ @target_automation = target_automation
11
+ end
12
+
13
+ def convert_page(content, _page_info)
14
+ result = content.dup
15
+ result = ensure_frozen_string_literal(result)
16
+ result = update_base_class(result)
17
+ result = add_page_require(result)
18
+ update_page_instantiation(result)
19
+ end
20
+
21
+ def convert_test(content, _test_info)
22
+ result = content.dup
23
+ result = ensure_frozen_string_literal(result)
24
+ result = update_require_paths(result, @target_automation)
25
+ update_page_instantiation(result)
26
+ end
27
+
28
+ def convert_step(content)
29
+ result = content.dup
30
+ result = ensure_frozen_string_literal(result)
31
+ result = update_require_paths(result, @target_automation)
32
+ update_page_instantiation(result)
33
+ end
34
+
35
+ private
36
+
37
+ def add_page_require(content)
38
+ return content if content.include?("require_relative '../abstract/page'")
39
+
40
+ content.sub(/^(class\s)/, "require_relative '../abstract/page'\n\n\\1")
41
+ end
42
+
43
+ def update_page_instantiation(content)
44
+ arg = driver_arg_for(@target_automation)
45
+ if arg
46
+ swap_driver_arg(content, arg)
47
+ else
48
+ remove_driver_arg(content)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Adopter
4
+ class MigrationPlan
5
+ attr_reader :source_path, :output_path, :target_automation, :target_framework,
6
+ :ci_platform, :skeleton_structure, :converted_pages, :converted_tests,
7
+ :converted_features, :converted_steps, :gemfile_additions,
8
+ :config_overrides, :warnings, :manual_actions
9
+
10
+ def initialize(attrs = {})
11
+ @source_path = attrs[:source_path]
12
+ @output_path = attrs[:output_path]
13
+ @target_automation = attrs[:target_automation]
14
+ @target_framework = attrs[:target_framework]
15
+ @ci_platform = attrs[:ci_platform]
16
+ @skeleton_structure = attrs[:skeleton_structure] || {}
17
+ @converted_pages = attrs[:converted_pages] || []
18
+ @converted_tests = attrs[:converted_tests] || []
19
+ @converted_features = attrs[:converted_features] || []
20
+ @converted_steps = attrs[:converted_steps] || []
21
+ @gemfile_additions = attrs[:gemfile_additions] || []
22
+ @config_overrides = attrs[:config_overrides] || {}
23
+ @warnings = attrs[:warnings] || []
24
+ @manual_actions = attrs[:manual_actions] || []
25
+ end
26
+
27
+ def to_h
28
+ {
29
+ source_path:,
30
+ output_path:,
31
+ target_automation:,
32
+ target_framework:,
33
+ ci_platform:,
34
+ skeleton_structure:,
35
+ converted_pages: converted_pages.map(&:to_h),
36
+ converted_tests: converted_tests.map(&:to_h),
37
+ converted_features: converted_features.map(&:to_h),
38
+ converted_steps: converted_steps.map(&:to_h),
39
+ gemfile_additions:,
40
+ config_overrides:,
41
+ warnings:,
42
+ manual_actions:
43
+ }
44
+ end
45
+
46
+ def to_json(*args)
47
+ require 'json'
48
+ to_h.to_json(*args)
49
+ end
50
+
51
+ def summary
52
+ {
53
+ pages: converted_pages.length,
54
+ tests: converted_tests.length,
55
+ features: converted_features.length,
56
+ steps: converted_steps.length,
57
+ custom_gems: gemfile_additions.length,
58
+ warnings: warnings.length,
59
+ manual_actions: manual_actions.length
60
+ }
61
+ end
62
+ end
63
+
64
+ ConvertedFile = Struct.new(:output_path, :content, :source_file, :conversion_notes, keyword_init: true) do
65
+ def to_h
66
+ {
67
+ output_path:,
68
+ content:,
69
+ source_file:,
70
+ conversion_notes:
71
+ }
72
+ end
73
+ end
74
+ end