ruby_cms 0.2.1.0 → 1.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 (647) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -72
  3. data/README.md +308 -153
  4. data/Rakefile +0 -18
  5. data/SYNC_RULES.md +84 -0
  6. data/exe/ruby_cms +13 -18
  7. data/lib/generators/ruby_cms/admin_page_generator.rb +116 -60
  8. data/lib/generators/ruby_cms/templates/admin.html.erb +35 -17
  9. data/lib/generators/ruby_cms/templates/admin_page/_admin_table_content.html.erb.tt +25 -0
  10. data/lib/generators/ruby_cms/templates/admin_page/_form.html.erb.tt +36 -0
  11. data/lib/generators/ruby_cms/templates/admin_page/_row.html.erb.tt +20 -0
  12. data/lib/generators/ruby_cms/templates/admin_page/controller.rb.tt +74 -2
  13. data/lib/generators/ruby_cms/templates/admin_page/edit.html.erb.tt +14 -0
  14. data/lib/generators/ruby_cms/templates/admin_page/index.html.erb.tt +21 -10
  15. data/lib/generators/ruby_cms/templates/admin_page/new.html.erb.tt +14 -0
  16. data/lib/generators/ruby_cms/templates/assets/tailwind/application.css +225 -0
  17. data/lib/generators/ruby_cms/templates/components/ruby_ui/accordion/accordion.rb +17 -0
  18. data/lib/generators/ruby_cms/templates/components/ruby_ui/accordion/accordion_content.rb +21 -0
  19. data/lib/generators/ruby_cms/templates/components/ruby_ui/accordion/accordion_default_content.rb +17 -0
  20. data/lib/generators/ruby_cms/templates/components/ruby_ui/accordion/accordion_default_trigger.rb +19 -0
  21. data/lib/generators/ruby_cms/templates/components/ruby_ui/accordion/accordion_icon.rb +38 -0
  22. data/lib/generators/ruby_cms/templates/components/ruby_ui/accordion/accordion_item.rb +28 -0
  23. data/lib/generators/ruby_cms/templates/components/ruby_ui/accordion/accordion_trigger.rb +17 -0
  24. data/lib/generators/ruby_cms/templates/components/ruby_ui/admin_page/admin_page.rb +31 -0
  25. data/lib/generators/ruby_cms/templates/components/ruby_ui/admin_page/admin_page_header.rb +65 -0
  26. data/lib/generators/ruby_cms/templates/components/ruby_ui/admin_page/admin_resource_card.rb +28 -0
  27. data/lib/generators/ruby_cms/templates/components/ruby_ui/admin_page/admin_table_content.rb +20 -0
  28. data/lib/generators/ruby_cms/templates/components/ruby_ui/alert/alert.rb +36 -0
  29. data/lib/generators/ruby_cms/templates/components/ruby_ui/alert/alert_description.rb +17 -0
  30. data/lib/generators/ruby_cms/templates/components/ruby_ui/alert/alert_title.rb +17 -0
  31. data/lib/generators/ruby_cms/templates/components/ruby_ui/aspect_ratio/aspect_ratio.rb +33 -0
  32. data/lib/generators/ruby_cms/templates/components/ruby_ui/avatar/avatar.rb +31 -0
  33. data/lib/generators/ruby_cms/templates/components/ruby_ui/avatar/avatar_fallback.rb +17 -0
  34. data/lib/generators/ruby_cms/templates/components/ruby_ui/avatar/avatar_image.rb +26 -0
  35. data/lib/generators/ruby_cms/templates/components/ruby_ui/badge/badge.rb +60 -0
  36. data/lib/generators/ruby_cms/templates/components/ruby_ui/base.rb +29 -0
  37. data/lib/generators/ruby_cms/templates/components/ruby_ui/box/box.rb +15 -0
  38. data/lib/generators/ruby_cms/templates/components/ruby_ui/breadcrumb/breadcrumb.rb +17 -0
  39. data/lib/generators/ruby_cms/templates/components/ruby_ui/breadcrumb/breadcrumb_ellipsis.rb +39 -0
  40. data/lib/generators/ruby_cms/templates/components/ruby_ui/breadcrumb/breadcrumb_item.rb +17 -0
  41. data/lib/generators/ruby_cms/templates/components/ruby_ui/breadcrumb/breadcrumb_link.rb +22 -0
  42. data/lib/generators/ruby_cms/templates/components/ruby_ui/breadcrumb/breadcrumb_list.rb +17 -0
  43. data/lib/generators/ruby_cms/templates/components/ruby_ui/breadcrumb/breadcrumb_page.rb +19 -0
  44. data/lib/generators/ruby_cms/templates/components/ruby_ui/breadcrumb/breadcrumb_separator.rb +38 -0
  45. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_button.rb +82 -0
  46. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table.rb +133 -0
  47. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table_actions.rb +104 -0
  48. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table_body.rb +17 -0
  49. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table_checkbox_cell.rb +40 -0
  50. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table_checkbox_head.rb +34 -0
  51. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table_delete_modal.rb +43 -0
  52. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table_header.rb +42 -0
  53. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table_pagination.rb +115 -0
  54. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_action_table_row.rb +77 -0
  55. data/lib/generators/ruby_cms/templates/components/ruby_ui/bulk_action_table/bulk_actions.rb +86 -0
  56. data/lib/generators/ruby_cms/templates/components/ruby_ui/button/button.rb +113 -0
  57. data/lib/generators/ruby_cms/templates/components/ruby_ui/calendar/calendar.rb +39 -0
  58. data/lib/generators/ruby_cms/templates/components/ruby_ui/calendar/calendar_body.rb +19 -0
  59. data/lib/generators/ruby_cms/templates/components/ruby_ui/calendar/calendar_days.rb +104 -0
  60. data/lib/generators/ruby_cms/templates/components/ruby_ui/calendar/calendar_header.rb +17 -0
  61. data/lib/generators/ruby_cms/templates/components/ruby_ui/calendar/calendar_next.rb +43 -0
  62. data/lib/generators/ruby_cms/templates/components/ruby_ui/calendar/calendar_prev.rb +43 -0
  63. data/lib/generators/ruby_cms/templates/components/ruby_ui/calendar/calendar_title.rb +27 -0
  64. data/lib/generators/ruby_cms/templates/components/ruby_ui/calendar/calendar_weekdays.rb +33 -0
  65. data/lib/generators/ruby_cms/templates/components/ruby_ui/card/card.rb +17 -0
  66. data/lib/generators/ruby_cms/templates/components/ruby_ui/card/card_content.rb +17 -0
  67. data/lib/generators/ruby_cms/templates/components/ruby_ui/card/card_description.rb +17 -0
  68. data/lib/generators/ruby_cms/templates/components/ruby_ui/card/card_footer.rb +17 -0
  69. data/lib/generators/ruby_cms/templates/components/ruby_ui/card/card_header.rb +17 -0
  70. data/lib/generators/ruby_cms/templates/components/ruby_ui/card/card_title.rb +17 -0
  71. data/lib/generators/ruby_cms/templates/components/ruby_ui/carousel/carousel.rb +44 -0
  72. data/lib/generators/ruby_cms/templates/components/ruby_ui/carousel/carousel_content.rb +23 -0
  73. data/lib/generators/ruby_cms/templates/components/ruby_ui/carousel/carousel_item.rb +23 -0
  74. data/lib/generators/ruby_cms/templates/components/ruby_ui/carousel/carousel_next.rb +48 -0
  75. data/lib/generators/ruby_cms/templates/components/ruby_ui/carousel/carousel_previous.rb +49 -0
  76. data/lib/generators/ruby_cms/templates/components/ruby_ui/checkbox/checkbox.rb +29 -0
  77. data/lib/generators/ruby_cms/templates/components/ruby_ui/checkbox/checkbox_group.rb +20 -0
  78. data/lib/generators/ruby_cms/templates/components/ruby_ui/clipboard/clipboard.rb +42 -0
  79. data/lib/generators/ruby_cms/templates/components/ruby_ui/clipboard/clipboard_popover.rb +40 -0
  80. data/lib/generators/ruby_cms/templates/components/ruby_ui/clipboard/clipboard_source.rb +19 -0
  81. data/lib/generators/ruby_cms/templates/components/ruby_ui/clipboard/clipboard_trigger.rb +20 -0
  82. data/lib/generators/ruby_cms/templates/components/ruby_ui/codeblock/codeblock.rb +102 -0
  83. data/lib/generators/ruby_cms/templates/components/ruby_ui/collapsible/collapsible.rb +25 -0
  84. data/lib/generators/ruby_cms/templates/components/ruby_ui/collapsible/collapsible_content.rb +18 -0
  85. data/lib/generators/ruby_cms/templates/components/ruby_ui/collapsible/collapsible_trigger.rb +19 -0
  86. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox.rb +26 -0
  87. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_checkbox.rb +25 -0
  88. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_empty_state.rb +21 -0
  89. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_item.rb +25 -0
  90. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_list.rb +18 -0
  91. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_list_group.rb +20 -0
  92. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_popover.rb +30 -0
  93. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_radio.rb +26 -0
  94. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_search_input.rb +53 -0
  95. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_toggle_all_checkbox.rb +25 -0
  96. data/lib/generators/ruby_cms/templates/components/ruby_ui/combobox/combobox_trigger.rb +57 -0
  97. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command.rb +9 -0
  98. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command_dialog.rb +17 -0
  99. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command_dialog_content.rb +48 -0
  100. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command_dialog_trigger.rb +29 -0
  101. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command_empty.rb +19 -0
  102. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command_group.rb +40 -0
  103. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command_input.rb +56 -0
  104. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command_item.rb +32 -0
  105. data/lib/generators/ruby_cms/templates/components/ruby_ui/command/command_list.rb +17 -0
  106. data/lib/generators/ruby_cms/templates/components/ruby_ui/container/container.rb +33 -0
  107. data/lib/generators/ruby_cms/templates/components/ruby_ui/container/container_section.rb +31 -0
  108. data/lib/generators/ruby_cms/templates/components/ruby_ui/context_menu/context_menu.rb +26 -0
  109. data/lib/generators/ruby_cms/templates/components/ruby_ui/context_menu/context_menu_content.rb +25 -0
  110. data/lib/generators/ruby_cms/templates/components/ruby_ui/context_menu/context_menu_item.rb +66 -0
  111. data/lib/generators/ruby_cms/templates/components/ruby_ui/context_menu/context_menu_label.rb +24 -0
  112. data/lib/generators/ruby_cms/templates/components/ruby_ui/context_menu/context_menu_separator.rb +19 -0
  113. data/lib/generators/ruby_cms/templates/components/ruby_ui/context_menu/context_menu_trigger.rb +20 -0
  114. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table.rb +29 -0
  115. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_bulk_actions.rb +18 -0
  116. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_column_toggle.rb +62 -0
  117. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_expand_toggle.rb +53 -0
  118. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_form.rb +39 -0
  119. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_kaminari_adapter.rb +17 -0
  120. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_manual_adapter.rb +17 -0
  121. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_pagination.rb +100 -0
  122. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_pagination_bar.rb +15 -0
  123. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_pagy_adapter.rb +17 -0
  124. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_per_page_select.rb +35 -0
  125. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_row_checkbox.rb +30 -0
  126. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_search.rb +57 -0
  127. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_select_all_checkbox.rb +21 -0
  128. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_selection_summary.rb +25 -0
  129. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_sort_head.rb +112 -0
  130. data/lib/generators/ruby_cms/templates/components/ruby_ui/data_table/data_table_toolbar.rb +15 -0
  131. data/lib/generators/ruby_cms/templates/components/ruby_ui/dialog/dialog.rb +25 -0
  132. data/lib/generators/ruby_cms/templates/components/ruby_ui/dialog/dialog_content.rb +78 -0
  133. data/lib/generators/ruby_cms/templates/components/ruby_ui/dialog/dialog_description.rb +17 -0
  134. data/lib/generators/ruby_cms/templates/components/ruby_ui/dialog/dialog_footer.rb +17 -0
  135. data/lib/generators/ruby_cms/templates/components/ruby_ui/dialog/dialog_header.rb +17 -0
  136. data/lib/generators/ruby_cms/templates/components/ruby_ui/dialog/dialog_middle.rb +17 -0
  137. data/lib/generators/ruby_cms/templates/components/ruby_ui/dialog/dialog_title.rb +17 -0
  138. data/lib/generators/ruby_cms/templates/components/ruby_ui/dialog/dialog_trigger.rb +20 -0
  139. data/lib/generators/ruby_cms/templates/components/ruby_ui/dropdown_menu/dropdown_menu.rb +35 -0
  140. data/lib/generators/ruby_cms/templates/components/ruby_ui/dropdown_menu/dropdown_menu_content.rb +44 -0
  141. data/lib/generators/ruby_cms/templates/components/ruby_ui/dropdown_menu/dropdown_menu_item.rb +28 -0
  142. data/lib/generators/ruby_cms/templates/components/ruby_ui/dropdown_menu/dropdown_menu_label.rb +17 -0
  143. data/lib/generators/ruby_cms/templates/components/ruby_ui/dropdown_menu/dropdown_menu_separator.rb +19 -0
  144. data/lib/generators/ruby_cms/templates/components/ruby_ui/dropdown_menu/dropdown_menu_trigger.rb +18 -0
  145. data/lib/generators/ruby_cms/templates/components/ruby_ui/empty_state/empty_state.rb +190 -0
  146. data/lib/generators/ruby_cms/templates/components/ruby_ui/form/form.rb +15 -0
  147. data/lib/generators/ruby_cms/templates/components/ruby_ui/form/form_field.rb +20 -0
  148. data/lib/generators/ruby_cms/templates/components/ruby_ui/form/form_field_error.rb +20 -0
  149. data/lib/generators/ruby_cms/templates/components/ruby_ui/form/form_field_hint.rb +15 -0
  150. data/lib/generators/ruby_cms/templates/components/ruby_ui/form/form_field_label.rb +15 -0
  151. data/lib/generators/ruby_cms/templates/components/ruby_ui/hover_card/hover_card.rb +27 -0
  152. data/lib/generators/ruby_cms/templates/components/ruby_ui/hover_card/hover_card_content.rb +22 -0
  153. data/lib/generators/ruby_cms/templates/components/ruby_ui/hover_card/hover_card_trigger.rb +20 -0
  154. data/lib/generators/ruby_cms/templates/components/ruby_ui/input/input.rb +34 -0
  155. data/lib/generators/ruby_cms/templates/components/ruby_ui/link/link.rb +106 -0
  156. data/lib/generators/ruby_cms/templates/components/ruby_ui/masked_input/masked_input.rb +15 -0
  157. data/lib/generators/ruby_cms/templates/components/ruby_ui/modal/modal.rb +26 -0
  158. data/lib/generators/ruby_cms/templates/components/ruby_ui/modal/modal_action.rb +17 -0
  159. data/lib/generators/ruby_cms/templates/components/ruby_ui/modal/modal_cancel.rb +20 -0
  160. data/lib/generators/ruby_cms/templates/components/ruby_ui/modal/modal_content.rb +68 -0
  161. data/lib/generators/ruby_cms/templates/components/ruby_ui/modal/modal_description.rb +17 -0
  162. data/lib/generators/ruby_cms/templates/components/ruby_ui/modal/modal_footer.rb +17 -0
  163. data/lib/generators/ruby_cms/templates/components/ruby_ui/modal/modal_header.rb +17 -0
  164. data/lib/generators/ruby_cms/templates/components/ruby_ui/modal/modal_title.rb +17 -0
  165. data/lib/generators/ruby_cms/templates/components/ruby_ui/native_select/native_select.rb +39 -0
  166. data/lib/generators/ruby_cms/templates/components/ruby_ui/native_select/native_select_group.rb +15 -0
  167. data/lib/generators/ruby_cms/templates/components/ruby_ui/native_select/native_select_icon.rb +39 -0
  168. data/lib/generators/ruby_cms/templates/components/ruby_ui/native_select/native_select_option.rb +15 -0
  169. data/lib/generators/ruby_cms/templates/components/ruby_ui/pagination/pagination.rb +19 -0
  170. data/lib/generators/ruby_cms/templates/components/ruby_ui/pagination/pagination_content.rb +17 -0
  171. data/lib/generators/ruby_cms/templates/components/ruby_ui/pagination/pagination_ellipsis.rb +42 -0
  172. data/lib/generators/ruby_cms/templates/components/ruby_ui/pagination/pagination_item.rb +28 -0
  173. data/lib/generators/ruby_cms/templates/components/ruby_ui/popover/popover.rb +26 -0
  174. data/lib/generators/ruby_cms/templates/components/ruby_ui/popover/popover_content.rb +27 -0
  175. data/lib/generators/ruby_cms/templates/components/ruby_ui/popover/popover_trigger.rb +20 -0
  176. data/lib/generators/ruby_cms/templates/components/ruby_ui/progress/progress.rb +37 -0
  177. data/lib/generators/ruby_cms/templates/components/ruby_ui/radio_button/radio_button.rb +25 -0
  178. data/lib/generators/ruby_cms/templates/components/ruby_ui/select/select.rb +23 -0
  179. data/lib/generators/ruby_cms/templates/components/ruby_ui/select/select_content.rb +32 -0
  180. data/lib/generators/ruby_cms/templates/components/ruby_ui/select/select_group.rb +15 -0
  181. data/lib/generators/ruby_cms/templates/components/ruby_ui/select/select_input.rb +22 -0
  182. data/lib/generators/ruby_cms/templates/components/ruby_ui/select/select_item.rb +52 -0
  183. data/lib/generators/ruby_cms/templates/components/ruby_ui/select/select_label.rb +17 -0
  184. data/lib/generators/ruby_cms/templates/components/ruby_ui/select/select_trigger.rb +54 -0
  185. data/lib/generators/ruby_cms/templates/components/ruby_ui/select/select_value.rb +27 -0
  186. data/lib/generators/ruby_cms/templates/components/ruby_ui/separator/separator.rb +38 -0
  187. data/lib/generators/ruby_cms/templates/components/ruby_ui/sheet/sheet.rb +17 -0
  188. data/lib/generators/ruby_cms/templates/components/ruby_ui/sheet/sheet_content.rb +77 -0
  189. data/lib/generators/ruby_cms/templates/components/ruby_ui/sheet/sheet_description.rb +17 -0
  190. data/lib/generators/ruby_cms/templates/components/ruby_ui/sheet/sheet_footer.rb +17 -0
  191. data/lib/generators/ruby_cms/templates/components/ruby_ui/sheet/sheet_header.rb +17 -0
  192. data/lib/generators/ruby_cms/templates/components/ruby_ui/sheet/sheet_middle.rb +17 -0
  193. data/lib/generators/ruby_cms/templates/components/ruby_ui/sheet/sheet_title.rb +17 -0
  194. data/lib/generators/ruby_cms/templates/components/ruby_ui/sheet/sheet_trigger.rb +17 -0
  195. data/lib/generators/ruby_cms/templates/components/ruby_ui/shortcut_key/shortcut_key.rb +17 -0
  196. data/lib/generators/ruby_cms/templates/components/ruby_ui/skeleton/skeleton.rb +17 -0
  197. data/lib/generators/ruby_cms/templates/components/ruby_ui/switch/switch.rb +24 -0
  198. data/lib/generators/ruby_cms/templates/components/ruby_ui/table/table.rb +19 -0
  199. data/lib/generators/ruby_cms/templates/components/ruby_ui/table/table_body.rb +17 -0
  200. data/lib/generators/ruby_cms/templates/components/ruby_ui/table/table_caption.rb +17 -0
  201. data/lib/generators/ruby_cms/templates/components/ruby_ui/table/table_cell.rb +17 -0
  202. data/lib/generators/ruby_cms/templates/components/ruby_ui/table/table_footer.rb +17 -0
  203. data/lib/generators/ruby_cms/templates/components/ruby_ui/table/table_head.rb +17 -0
  204. data/lib/generators/ruby_cms/templates/components/ruby_ui/table/table_header.rb +17 -0
  205. data/lib/generators/ruby_cms/templates/components/ruby_ui/table/table_row.rb +17 -0
  206. data/lib/generators/ruby_cms/templates/components/ruby_ui/tabs/tabs.rb +25 -0
  207. data/lib/generators/ruby_cms/templates/components/ruby_ui/tabs/tabs_content.rb +26 -0
  208. data/lib/generators/ruby_cms/templates/components/ruby_ui/tabs/tabs_list.rb +17 -0
  209. data/lib/generators/ruby_cms/templates/components/ruby_ui/tabs/tabs_trigger.rb +28 -0
  210. data/lib/generators/ruby_cms/templates/components/ruby_ui/textarea/textarea.rb +26 -0
  211. data/lib/generators/ruby_cms/templates/components/ruby_ui/theme_toggle/set_dark_mode.rb +16 -0
  212. data/lib/generators/ruby_cms/templates/components/ruby_ui/theme_toggle/set_light_mode.rb +16 -0
  213. data/lib/generators/ruby_cms/templates/components/ruby_ui/theme_toggle/theme_toggle.rb +9 -0
  214. data/lib/generators/ruby_cms/templates/components/ruby_ui/tooltip/tooltip.rb +26 -0
  215. data/lib/generators/ruby_cms/templates/components/ruby_ui/tooltip/tooltip_content.rb +26 -0
  216. data/lib/generators/ruby_cms/templates/components/ruby_ui/tooltip/tooltip_trigger.rb +19 -0
  217. data/lib/generators/ruby_cms/templates/components/ruby_ui/typography/heading.rb +60 -0
  218. data/lib/generators/ruby_cms/templates/components/ruby_ui/typography/inline_code.rb +17 -0
  219. data/lib/generators/ruby_cms/templates/components/ruby_ui/typography/inline_link.rb +22 -0
  220. data/lib/generators/ruby_cms/templates/components/ruby_ui/typography/text.rb +53 -0
  221. data/lib/generators/ruby_cms/templates/components/ruby_ui/typography/typography_blockquote.rb +17 -0
  222. data/lib/generators/ruby_cms/templates/{ruby_cms.rb → config/initializers/admin.rb} +15 -9
  223. data/lib/generators/ruby_cms/templates/config/initializers/admin_dashboard.rb +43 -0
  224. data/lib/generators/ruby_cms/templates/config/initializers/pagy.rb +7 -0
  225. data/lib/generators/ruby_cms/templates/config/initializers/ruby_cms_core.rb +25 -0
  226. data/lib/generators/ruby_cms/templates/config/initializers/ruby_cms_custom_settings.rb +35 -0
  227. data/lib/generators/ruby_cms/templates/config/initializers/webauthn.rb +28 -0
  228. data/lib/generators/ruby_cms/templates/config/locales/en.yml +277 -0
  229. data/lib/generators/ruby_cms/templates/config/locales/nl.yml +329 -0
  230. data/lib/generators/ruby_cms/templates/controllers/admin/analytics_controller.rb +211 -0
  231. data/lib/generators/ruby_cms/templates/controllers/admin/application_controller.rb +186 -0
  232. data/lib/generators/ruby_cms/templates/controllers/admin/audit_log_entries_controller.rb +69 -0
  233. data/lib/generators/ruby_cms/templates/controllers/admin/commands_controller.rb +146 -0
  234. data/lib/generators/ruby_cms/templates/controllers/admin/content_block_versions_controller.rb +96 -0
  235. data/lib/generators/ruby_cms/templates/controllers/admin/content_blocks_controller.rb +405 -0
  236. data/lib/generators/ruby_cms/templates/controllers/admin/dashboard_controller.rb +114 -0
  237. data/lib/generators/ruby_cms/templates/controllers/admin/invitations_controller.rb +64 -0
  238. data/lib/generators/ruby_cms/templates/controllers/admin/locale_controller.rb +19 -0
  239. data/lib/generators/ruby_cms/templates/controllers/admin/media_assets_controller.rb +186 -0
  240. data/lib/generators/ruby_cms/templates/controllers/admin/notifications_controller.rb +37 -0
  241. data/lib/generators/ruby_cms/templates/controllers/admin/passkey_credentials_controller.rb +17 -0
  242. data/lib/generators/ruby_cms/templates/controllers/admin/passkey_registrations_controller.rb +50 -0
  243. data/lib/generators/ruby_cms/templates/controllers/admin/permissions_controller.rb +132 -0
  244. data/lib/generators/ruby_cms/templates/controllers/admin/redirects_controller.rb +175 -0
  245. data/lib/generators/ruby_cms/templates/controllers/admin/security_controller.rb +146 -0
  246. data/lib/generators/ruby_cms/templates/controllers/admin/settings_controller.rb +298 -0
  247. data/lib/generators/ruby_cms/templates/controllers/admin/system_health_controller.rb +11 -0
  248. data/lib/generators/ruby_cms/templates/controllers/admin/trash_controller.rb +61 -0
  249. data/lib/generators/ruby_cms/templates/controllers/admin/user_permissions_controller.rb +91 -0
  250. data/lib/generators/ruby_cms/templates/controllers/admin/users_controller.rb +220 -0
  251. data/lib/generators/ruby_cms/templates/controllers/admin/visitor_errors_controller.rb +114 -0
  252. data/lib/generators/ruby_cms/templates/controllers/admin/visual_editor_controller.rb +384 -0
  253. data/lib/generators/ruby_cms/templates/controllers/concerns/.keep +0 -0
  254. data/lib/generators/ruby_cms/templates/controllers/concerns/admin_bulk_actions.rb +59 -0
  255. data/lib/generators/ruby_cms/templates/controllers/concerns/admin_pagination.rb +106 -0
  256. data/lib/generators/ruby_cms/templates/controllers/concerns/admin_turbo_table.rb +46 -0
  257. data/lib/generators/ruby_cms/templates/controllers/concerns/audit_loggable.rb +28 -0
  258. data/lib/generators/ruby_cms/templates/controllers/concerns/authentication.rb +63 -0
  259. data/lib/generators/ruby_cms/templates/controllers/concerns/page_tracking.rb +45 -0
  260. data/lib/generators/ruby_cms/templates/controllers/concerns/security_monitoring.rb +102 -0
  261. data/lib/generators/ruby_cms/templates/controllers/concerns/sudo_mode.rb +29 -0
  262. data/lib/generators/ruby_cms/templates/controllers/concerns/visitor_error_capture.rb +35 -0
  263. data/lib/generators/ruby_cms/templates/controllers/errors_controller.rb +36 -0
  264. data/lib/generators/ruby_cms/templates/controllers/passkey_sessions_controller.rb +69 -0
  265. data/lib/generators/ruby_cms/templates/db/migrate/20251121085414_create_email_blocklists.rb +12 -0
  266. data/lib/generators/ruby_cms/templates/db/migrate/20251121161828_create_ip_blocklists.rb +12 -0
  267. data/lib/generators/ruby_cms/templates/db/migrate/20251205120000_add_analytics_indices.rb +48 -0
  268. data/lib/generators/ruby_cms/templates/db/migrate/20251216064753_backfill_security_fields_in_ahoy_events.rb +49 -0
  269. data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260125000001_create_ruby_cms_permissions.rb +2 -2
  270. data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260125000002_create_ruby_cms_user_permissions.rb +2 -2
  271. data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260125000003_create_ruby_cms_content_blocks.rb +3 -3
  272. data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260125000010_add_indexes_to_ruby_cms_tables.rb +1 -1
  273. data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260127000001_add_locale_to_ruby_cms_content_blocks.rb +8 -6
  274. data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260129000001_create_ruby_cms_visitor_errors.rb +4 -4
  275. data/lib/generators/ruby_cms/templates/db/migrate/20260130000001_add_referer_and_query_to_ruby_cms_visitor_errors.rb +10 -0
  276. data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260130000002_create_ruby_cms_preferences.rb +2 -2
  277. data/lib/generators/ruby_cms/templates/db/migrate/20260130000003_add_category_to_ruby_cms_preferences.rb +10 -0
  278. data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260328000001_create_content_block_versions.rb +2 -2
  279. data/lib/generators/ruby_cms/templates/db/migrate/20260525120000_create_audit_log_entries.rb +24 -0
  280. data/lib/generators/ruby_cms/templates/db/migrate/20260525130000_create_redirects.rb +28 -0
  281. data/lib/generators/ruby_cms/templates/db/migrate/20260525140000_create_media_assets.rb +25 -0
  282. data/lib/generators/ruby_cms/templates/db/migrate/20260525150000_create_invitations.rb +22 -0
  283. data/lib/generators/ruby_cms/templates/db/migrate/20260525160000_create_command_runs.rb +22 -0
  284. data/lib/generators/ruby_cms/templates/db/migrate/20260525190000_add_state_to_visitor_errors.rb +20 -0
  285. data/lib/generators/ruby_cms/templates/db/migrate/20260527165017_add_discarded_at_to_visitor_errors.rb +10 -0
  286. data/lib/generators/ruby_cms/templates/db/migrate/20260527165728_add_discarded_at_to_cms_tables.rb +12 -0
  287. data/lib/generators/ruby_cms/templates/db/migrate/20260605151935_create_passkey_credentials.rb +15 -0
  288. data/lib/generators/ruby_cms/templates/db/migrate/20260605151936_add_webauthn_id_to_users.rb +22 -0
  289. data/lib/generators/ruby_cms/templates/db/migrate/20260606000001_add_authenticated_at_to_sessions.rb +14 -0
  290. data/lib/generators/ruby_cms/templates/helpers/admin/admin_page_helper.rb +9 -0
  291. data/lib/generators/ruby_cms/templates/helpers/admin/dashboard_helper.rb +16 -0
  292. data/lib/generators/ruby_cms/templates/helpers/admin/media_assets_helper.rb +38 -0
  293. data/lib/generators/ruby_cms/templates/helpers/admin/relative_time_helper.rb +37 -0
  294. data/lib/generators/ruby_cms/templates/helpers/admin/ui_helper.rb +48 -0
  295. data/lib/generators/ruby_cms/templates/helpers/cms_application_helpers.rb +70 -0
  296. data/lib/generators/ruby_cms/templates/helpers/content_blocks_helper.rb +382 -0
  297. data/lib/generators/ruby_cms/templates/helpers/settings_helper.rb +192 -0
  298. data/lib/generators/ruby_cms/templates/javascript/admin.js +6 -0
  299. data/{app/javascript/controllers/ruby_cms → lib/generators/ruby_cms/templates/javascript/controllers/admin}/admin_commands_controller.js +2 -2
  300. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/autosearch_controller.js +18 -0
  301. data/{app/javascript/controllers/ruby_cms → lib/generators/ruby_cms/templates/javascript/controllers/admin}/clickable_row_controller.js +11 -0
  302. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/clipboard_controller.js +34 -0
  303. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/command_palette_controller.js +189 -0
  304. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/commands_controller.js +300 -0
  305. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/content_block_history_controller.js +167 -0
  306. data/{app/javascript/controllers/ruby_cms → lib/generators/ruby_cms/templates/javascript/controllers/admin}/flash_messages_controller.js +1 -1
  307. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/inline_rich_editor_controller.js +157 -0
  308. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/media_selection_controller.js +76 -0
  309. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/media_uploader_controller.js +23 -0
  310. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/mobile_menu_controller.js +125 -0
  311. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/nav_order_sortable_controller.js +296 -0
  312. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/nav_shortcuts_controller.js +47 -0
  313. data/{app/javascript/controllers/ruby_cms → lib/generators/ruby_cms/templates/javascript/controllers/admin}/page_preview_controller.js +64 -3
  314. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/permission_matrix_controller.js +49 -0
  315. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/search_shortcut_controller.js +24 -0
  316. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/settings_rail_search_controller.js +17 -0
  317. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/shortcuts_overlay_controller.js +39 -0
  318. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/sidebar_collapse_controller.js +135 -0
  319. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/sidebar_nav_persist_controller.js +48 -0
  320. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/sidebar_toggle_trigger_controller.js +10 -0
  321. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/topbar_controller.js +88 -0
  322. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/visual_editor_controller.js +721 -0
  323. data/lib/generators/ruby_cms/templates/javascript/controllers/admin/visual_editor_page_select_controller.js +174 -0
  324. data/lib/generators/ruby_cms/templates/javascript/controllers/application.js +10 -0
  325. data/lib/generators/ruby_cms/templates/javascript/controllers/index.js +2 -0
  326. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/accordion_controller.js +97 -0
  327. data/{app/javascript/controllers/ruby_cms → lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui}/bulk_action_table_controller.js +86 -233
  328. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/calendar_controller.js +249 -0
  329. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/calendar_input_controller.js +8 -0
  330. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/carousel_controller.js +60 -0
  331. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/checkbox_group_controller.js +21 -0
  332. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/clipboard_controller.js +54 -0
  333. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/collapsible_controller.js +47 -0
  334. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/combobox_controller.js +190 -0
  335. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/command_controller.js +138 -0
  336. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/context_menu_controller.js +144 -0
  337. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/data_table_column_visibility_controller.js +14 -0
  338. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/data_table_controller.js +109 -0
  339. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/data_table_search_controller.js +62 -0
  340. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/dialog_controller.js +32 -0
  341. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/dropdown_menu_controller.js +149 -0
  342. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/form_field_controller.js +61 -0
  343. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/hover_card_controller.js +153 -0
  344. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/masked_input_controller.js +9 -0
  345. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/modal_controller.js +45 -0
  346. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/popover_controller.js +107 -0
  347. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/select_controller.js +192 -0
  348. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/select_item_controller.js +11 -0
  349. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/sheet_content_controller.js +7 -0
  350. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/sheet_controller.js +9 -0
  351. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/tabs_controller.js +67 -0
  352. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/theme_toggle_controller.js +30 -0
  353. data/lib/generators/ruby_cms/templates/javascript/controllers/ruby_ui/tooltip_controller.js +38 -0
  354. data/lib/generators/ruby_cms/templates/javascript/controllers/webauthn_controller.js +153 -0
  355. data/lib/generators/ruby_cms/templates/lib/ruby_cms/cli.rb +170 -0
  356. data/lib/generators/ruby_cms/templates/lib/ruby_cms/commands_registry.rb +76 -0
  357. data/lib/{ruby_cms → generators/ruby_cms/templates/lib/ruby_cms}/content_blocks_grouping.rb +2 -6
  358. data/lib/{ruby_cms → generators/ruby_cms/templates/lib/ruby_cms}/content_blocks_sync.rb +6 -8
  359. data/lib/{ruby_cms → generators/ruby_cms/templates/lib/ruby_cms}/dashboard_blocks.rb +5 -5
  360. data/lib/{ruby_cms → generators/ruby_cms/templates/lib/ruby_cms}/icons.rb +41 -10
  361. data/lib/{ruby_cms → generators/ruby_cms/templates/lib/ruby_cms}/settings.rb +60 -10
  362. data/lib/{ruby_cms → generators/ruby_cms/templates/lib/ruby_cms}/settings_registry.rb +79 -3
  363. data/lib/generators/ruby_cms/templates/lib/ruby_cms.rb +342 -0
  364. data/lib/{tasks → generators/ruby_cms/templates/lib/tasks}/admin.rake +16 -16
  365. data/lib/generators/ruby_cms/templates/lib/tasks/ruby_cms.rake +158 -0
  366. data/lib/generators/ruby_cms/templates/models/audit_log_entry.rb +56 -0
  367. data/lib/generators/ruby_cms/templates/models/command_run.rb +29 -0
  368. data/lib/generators/ruby_cms/templates/models/concerns/content_block/searchable.rb +18 -0
  369. data/{app → lib/generators/ruby_cms/templates}/models/concerns/content_block/versionable.rb +1 -1
  370. data/lib/generators/ruby_cms/templates/models/concerns/ransackable.rb +32 -0
  371. data/{app → lib/generators/ruby_cms/templates}/models/content_block.rb +13 -5
  372. data/lib/generators/ruby_cms/templates/models/email_blocklist.rb +21 -0
  373. data/lib/generators/ruby_cms/templates/models/invitation.rb +86 -0
  374. data/lib/generators/ruby_cms/templates/models/ip_blocklist.rb +34 -0
  375. data/lib/generators/ruby_cms/templates/models/media_asset.rb +90 -0
  376. data/lib/generators/ruby_cms/templates/models/passkey_credential.rb +6 -0
  377. data/lib/generators/ruby_cms/templates/models/permission.rb +71 -0
  378. data/lib/generators/ruby_cms/templates/models/permittable.rb +69 -0
  379. data/lib/generators/ruby_cms/templates/models/preference.rb +109 -0
  380. data/lib/generators/ruby_cms/templates/models/redirect.rb +100 -0
  381. data/lib/generators/ruby_cms/templates/models/user_permission.rb +25 -0
  382. data/lib/generators/ruby_cms/templates/models/visitor_error.rb +157 -0
  383. data/lib/generators/ruby_cms/templates/nav/analytics.rb +3 -0
  384. data/lib/generators/ruby_cms/templates/nav/audit_log.rb +3 -0
  385. data/lib/generators/ruby_cms/templates/nav/commands.rb +3 -0
  386. data/lib/generators/ruby_cms/templates/nav/content_blocks.rb +3 -0
  387. data/lib/generators/ruby_cms/templates/nav/core.rb +13 -0
  388. data/lib/generators/ruby_cms/templates/nav/media.rb +3 -0
  389. data/lib/generators/ruby_cms/templates/nav/permissions.rb +3 -0
  390. data/lib/generators/ruby_cms/templates/nav/redirects.rb +3 -0
  391. data/lib/generators/ruby_cms/templates/nav/security.rb +4 -0
  392. data/lib/generators/ruby_cms/templates/nav/system_health.rb +3 -0
  393. data/lib/generators/ruby_cms/templates/nav/trash.rb +3 -0
  394. data/lib/generators/ruby_cms/templates/nav/users.rb +3 -0
  395. data/lib/generators/ruby_cms/templates/nav/visual_editor.rb +3 -0
  396. data/lib/generators/ruby_cms/templates/routes/analytics.rb +5 -0
  397. data/lib/generators/ruby_cms/templates/routes/audit_log.rb +3 -0
  398. data/lib/generators/ruby_cms/templates/routes/auth.public.rb +4 -0
  399. data/lib/generators/ruby_cms/templates/routes/auth.rb +6 -0
  400. data/lib/generators/ruby_cms/templates/routes/commands.rb +4 -0
  401. data/lib/generators/ruby_cms/templates/routes/content_blocks.rb +12 -0
  402. data/lib/generators/ruby_cms/templates/routes/core.public.rb +5 -0
  403. data/lib/generators/ruby_cms/templates/routes/core.rb +16 -0
  404. data/lib/generators/ruby_cms/templates/routes/media.rb +9 -0
  405. data/lib/generators/ruby_cms/templates/routes/passkeys.public.rb +4 -0
  406. data/lib/generators/ruby_cms/templates/routes/passkeys.rb +5 -0
  407. data/lib/generators/ruby_cms/templates/routes/permissions.rb +5 -0
  408. data/lib/generators/ruby_cms/templates/routes/redirects.rb +10 -0
  409. data/lib/generators/ruby_cms/templates/routes/security.rb +20 -0
  410. data/lib/generators/ruby_cms/templates/routes/system_health.rb +3 -0
  411. data/lib/generators/ruby_cms/templates/routes/trash.rb +5 -0
  412. data/lib/generators/ruby_cms/templates/routes/users.rb +10 -0
  413. data/lib/generators/ruby_cms/templates/routes/visual_editor.rb +5 -0
  414. data/lib/generators/ruby_cms/templates/services/admin/health_check.rb +362 -0
  415. data/lib/generators/ruby_cms/templates/services/analytics/report.rb +503 -0
  416. data/lib/generators/ruby_cms/templates/services/analytics_service.rb +305 -0
  417. data/lib/generators/ruby_cms/templates/services/audit_log.rb +60 -0
  418. data/lib/generators/ruby_cms/templates/services/command_runner.rb +79 -0
  419. data/lib/generators/ruby_cms/templates/services/security_service.rb +203 -0
  420. data/lib/generators/ruby_cms/templates/services/security_tracker.rb +90 -0
  421. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/index.html.erb +61 -50
  422. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/page_details.html.erb +15 -10
  423. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_browser_device.html.erb +2 -2
  424. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_conversions.html.erb +3 -3
  425. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_daily_activity_chart.html.erb +18 -17
  426. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_exit_pages.html.erb +4 -4
  427. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_hourly_activity_chart.html.erb +18 -17
  428. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_landing_pages.html.erb +3 -3
  429. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_os_stats.html.erb +2 -2
  430. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_popular_pages.html.erb +4 -4
  431. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_recent_activity.html.erb +4 -4
  432. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_top_referrers.html.erb +3 -3
  433. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_top_visitors.html.erb +4 -4
  434. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_utm_sources.html.erb +2 -2
  435. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/visitor_details.html.erb +15 -10
  436. data/lib/generators/ruby_cms/templates/views/admin/audit_log_entries/_admin_table_content.html.erb +55 -0
  437. data/lib/generators/ruby_cms/templates/views/admin/audit_log_entries/_row.html.erb +84 -0
  438. data/lib/generators/ruby_cms/templates/views/admin/audit_log_entries/index.html.erb +33 -0
  439. data/lib/generators/ruby_cms/templates/views/admin/audit_log_entries/show.html.erb +89 -0
  440. data/lib/generators/ruby_cms/templates/views/admin/commands/index.html.erb +393 -0
  441. data/lib/generators/ruby_cms/templates/views/admin/content_block_versions/index.html.erb +63 -0
  442. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/content_block_versions/show.html.erb +2 -2
  443. data/lib/generators/ruby_cms/templates/views/admin/content_blocks/_audits.html.erb +49 -0
  444. data/lib/generators/ruby_cms/templates/views/admin/content_blocks/_form.html.erb +79 -0
  445. data/lib/generators/ruby_cms/templates/views/admin/content_blocks/_rich_field.html.erb +44 -0
  446. data/lib/generators/ruby_cms/templates/views/admin/content_blocks/_row.html.erb +60 -0
  447. data/lib/generators/ruby_cms/templates/views/admin/content_blocks/index.html.erb +84 -0
  448. data/lib/generators/ruby_cms/templates/views/admin/content_blocks/new.html.erb +18 -0
  449. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/content_blocks/show.html.erb +67 -61
  450. data/lib/generators/ruby_cms/templates/views/admin/dashboard/_gmail_token_status.html.erb +69 -0
  451. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/dashboard/blocks/_analytics_overview.html.erb +11 -11
  452. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/dashboard/blocks/_content_blocks_stats.html.erb +3 -3
  453. data/lib/generators/ruby_cms/templates/views/admin/dashboard/blocks/_notifications.html.erb +50 -0
  454. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/dashboard/blocks/_permissions_stats.html.erb +3 -3
  455. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/dashboard/blocks/_quick_actions.html.erb +15 -15
  456. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/dashboard/blocks/_recent_errors.html.erb +8 -8
  457. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/dashboard/blocks/_users_stats.html.erb +3 -3
  458. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/dashboard/blocks/_visitor_errors_stats.html.erb +3 -3
  459. data/lib/generators/ruby_cms/templates/views/admin/dashboard/index.html.erb +34 -0
  460. data/lib/generators/ruby_cms/templates/views/admin/media_assets/_admin_table_content.html.erb +107 -0
  461. data/lib/generators/ruby_cms/templates/views/admin/media_assets/_form.html.erb +33 -0
  462. data/lib/generators/ruby_cms/templates/views/admin/media_assets/_grid.html.erb +58 -0
  463. data/lib/generators/ruby_cms/templates/views/admin/media_assets/_list.html.erb +43 -0
  464. data/lib/generators/ruby_cms/templates/views/admin/media_assets/edit.html.erb +13 -0
  465. data/lib/generators/ruby_cms/templates/views/admin/media_assets/index.html.erb +36 -0
  466. data/lib/generators/ruby_cms/templates/views/admin/media_assets/show.html.erb +97 -0
  467. data/lib/generators/ruby_cms/templates/views/admin/passkey_credentials/index.html.erb +30 -0
  468. data/lib/generators/ruby_cms/templates/views/admin/passkey_registrations/new.html.erb +23 -0
  469. data/lib/generators/ruby_cms/templates/views/admin/permissions/_admin_table_content.html.erb +45 -0
  470. data/lib/generators/ruby_cms/templates/views/admin/permissions/_matrix.html.erb +75 -0
  471. data/lib/generators/ruby_cms/templates/views/admin/permissions/_row.html.erb +62 -0
  472. data/lib/generators/ruby_cms/templates/views/admin/permissions/index.html.erb +65 -0
  473. data/lib/generators/ruby_cms/templates/views/admin/redirects/_admin_table_content.html.erb +77 -0
  474. data/lib/generators/ruby_cms/templates/views/admin/redirects/_form.html.erb +97 -0
  475. data/lib/generators/ruby_cms/templates/views/admin/redirects/_row.html.erb +82 -0
  476. data/lib/generators/ruby_cms/templates/views/admin/redirects/edit.html.erb +21 -0
  477. data/lib/generators/ruby_cms/templates/views/admin/redirects/index.html.erb +40 -0
  478. data/lib/generators/ruby_cms/templates/views/admin/redirects/new.html.erb +12 -0
  479. data/lib/generators/ruby_cms/templates/views/admin/security/index.html.erb +88 -0
  480. data/lib/generators/ruby_cms/templates/views/admin/security/ip_blocklist_content.html.erb +3 -0
  481. data/lib/generators/ruby_cms/templates/views/admin/security/partials/_events_by_type.html.erb +24 -0
  482. data/lib/generators/ruby_cms/templates/views/admin/security/partials/_ip_blocklist.html.erb +37 -0
  483. data/lib/generators/ruby_cms/templates/views/admin/security/partials/_ip_blocklist_content.html.erb +41 -0
  484. data/lib/generators/ruby_cms/templates/views/admin/security/partials/_ip_management.html.erb +11 -0
  485. data/lib/generators/ruby_cms/templates/views/admin/security/partials/_recent_events.html.erb +59 -0
  486. data/lib/generators/ruby_cms/templates/views/admin/security/partials/_top_threat_ips.html.erb +21 -0
  487. data/lib/generators/ruby_cms/templates/views/admin/settings/index.html.erb +509 -0
  488. data/lib/generators/ruby_cms/templates/views/admin/shared/_add_button.html.erb +15 -0
  489. data/lib/generators/ruby_cms/templates/views/admin/shared/_data_table.html.erb +255 -0
  490. data/lib/generators/ruby_cms/templates/views/admin/shared/_turbo_pagination.html.erb +53 -0
  491. data/lib/generators/ruby_cms/templates/views/admin/system_health/index.html.erb +248 -0
  492. data/lib/generators/ruby_cms/templates/views/admin/trash/index.html.erb +58 -0
  493. data/lib/generators/ruby_cms/templates/views/admin/user_permissions/_row.html.erb +29 -0
  494. data/lib/generators/ruby_cms/templates/views/admin/user_permissions/index.html.erb +78 -0
  495. data/lib/generators/ruby_cms/templates/views/admin/users/_admin_table_content.html.erb +50 -0
  496. data/lib/generators/ruby_cms/templates/views/admin/users/_invite_form.html.erb +43 -0
  497. data/lib/generators/ruby_cms/templates/views/admin/users/_invites_panel.html.erb +91 -0
  498. data/lib/generators/ruby_cms/templates/views/admin/users/_row.html.erb +109 -0
  499. data/lib/generators/ruby_cms/templates/views/admin/users/index.html.erb +43 -0
  500. data/lib/generators/ruby_cms/templates/views/admin/users/show.html.erb +343 -0
  501. data/lib/generators/ruby_cms/templates/views/admin/visitor_errors/_admin_table_content.html.erb +61 -0
  502. data/lib/generators/ruby_cms/templates/views/admin/visitor_errors/_row.html.erb +90 -0
  503. data/lib/generators/ruby_cms/templates/views/admin/visitor_errors/index.html.erb +18 -0
  504. data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/visitor_errors/show.html.erb +4 -4
  505. data/lib/generators/ruby_cms/templates/views/admin/visual_editor/index.html.erb +383 -0
  506. data/{app/views/layouts/ruby_cms → lib/generators/ruby_cms/templates/views/layouts/admin}/_admin_flash_messages.html.erb +3 -3
  507. data/lib/generators/ruby_cms/templates/views/layouts/admin/_admin_sidebar.html.erb +239 -0
  508. data/lib/generators/ruby_cms/templates/views/layouts/admin/_command_palette.html.erb +65 -0
  509. data/lib/generators/ruby_cms/templates/views/layouts/admin/_shortcuts_overlay.html.erb +81 -0
  510. data/lib/generators/ruby_cms/templates/views/layouts/admin/_topbar_widgets.html.erb +9 -0
  511. data/lib/generators/ruby_cms/templates/views/layouts/admin/minimal.html.erb +71 -0
  512. data/lib/generators/ruby_cms/templates/views/layouts/admin/topbar/_bell.html.erb +81 -0
  513. data/lib/generators/ruby_cms/templates/views/layouts/admin/topbar/_profile_menu.html.erb +86 -0
  514. data/lib/generators/ruby_cms/templates/views/layouts/admin/topbar/_search_trigger.html.erb +9 -0
  515. data/lib/generators/ruby_cms/templates/views/layouts/admin/topbar/_shortcuts_button.html.erb +8 -0
  516. data/lib/generators/ruby_cms/templates/views/shared/_maintenance_banner.html.erb +10 -0
  517. data/lib/ruby_cms/app_wiring.rb +56 -0
  518. data/lib/ruby_cms/auth_wiring.rb +64 -0
  519. data/lib/ruby_cms/cli.rb +181 -121
  520. data/lib/ruby_cms/excludes.rb +41 -0
  521. data/lib/ruby_cms/file_installer.rb +41 -0
  522. data/lib/ruby_cms/gem_setup.rb +84 -0
  523. data/lib/ruby_cms/helper_wiring.rb +32 -0
  524. data/lib/ruby_cms/importmap_wiring.rb +41 -0
  525. data/lib/ruby_cms/installer.rb +128 -0
  526. data/lib/ruby_cms/lockfile.rb +49 -0
  527. data/lib/ruby_cms/manifest.rb +88 -0
  528. data/lib/ruby_cms/manifest_data.rb +233 -0
  529. data/lib/ruby_cms/migration_installer.rb +64 -0
  530. data/lib/ruby_cms/nav_assembler.rb +43 -0
  531. data/lib/ruby_cms/passkey_wiring.rb +42 -0
  532. data/lib/ruby_cms/path_map.rb +26 -0
  533. data/lib/ruby_cms/routes_assembler.rb +103 -0
  534. data/lib/ruby_cms/syncer.rb +81 -0
  535. data/lib/ruby_cms/updater.rb +76 -0
  536. data/lib/ruby_cms/version.rb +1 -1
  537. data/lib/ruby_cms.rb +4 -245
  538. metadata +606 -169
  539. data/.cursor/dhh.mdc +0 -698
  540. data/app/components/ruby_cms/admin/admin_page/admin_table_content.rb +0 -32
  541. data/app/components/ruby_cms/admin/admin_page.rb +0 -345
  542. data/app/components/ruby_cms/admin/admin_page_header.rb +0 -79
  543. data/app/components/ruby_cms/admin/admin_resource_card.rb +0 -55
  544. data/app/components/ruby_cms/admin/base_component.rb +0 -78
  545. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table.rb +0 -157
  546. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_actions.rb +0 -127
  547. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_body.rb +0 -15
  548. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_checkbox_cell.rb +0 -43
  549. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_checkbox_head.rb +0 -35
  550. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_delete_modal.rb +0 -174
  551. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_header.rb +0 -59
  552. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_header_bar.rb +0 -169
  553. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_pagination.rb +0 -192
  554. data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_row.rb +0 -96
  555. data/app/components/ruby_cms/admin/bulk_action_table/bulk_actions.rb +0 -196
  556. data/app/controllers/concerns/ruby_cms/admin_pagination.rb +0 -120
  557. data/app/controllers/concerns/ruby_cms/admin_turbo_table.rb +0 -68
  558. data/app/controllers/concerns/ruby_cms/page_tracking.rb +0 -67
  559. data/app/controllers/concerns/ruby_cms/visitor_error_capture.rb +0 -39
  560. data/app/controllers/ruby_cms/admin/analytics_controller.rb +0 -213
  561. data/app/controllers/ruby_cms/admin/base_controller.rb +0 -132
  562. data/app/controllers/ruby_cms/admin/commands_controller.rb +0 -122
  563. data/app/controllers/ruby_cms/admin/content_block_versions_controller.rb +0 -62
  564. data/app/controllers/ruby_cms/admin/content_blocks_controller.rb +0 -391
  565. data/app/controllers/ruby_cms/admin/dashboard_controller.rb +0 -89
  566. data/app/controllers/ruby_cms/admin/locale_controller.rb +0 -21
  567. data/app/controllers/ruby_cms/admin/permissions_controller.rb +0 -66
  568. data/app/controllers/ruby_cms/admin/settings_controller.rb +0 -223
  569. data/app/controllers/ruby_cms/admin/user_permissions_controller.rb +0 -77
  570. data/app/controllers/ruby_cms/admin/users_controller.rb +0 -107
  571. data/app/controllers/ruby_cms/admin/visitor_errors_controller.rb +0 -89
  572. data/app/controllers/ruby_cms/admin/visual_editor_controller.rb +0 -345
  573. data/app/controllers/ruby_cms/errors_controller.rb +0 -35
  574. data/app/helpers/ruby_cms/admin/admin_page_helper.rb +0 -21
  575. data/app/helpers/ruby_cms/admin/bulk_action_table_helper.rb +0 -159
  576. data/app/helpers/ruby_cms/admin/dashboard_helper.rb +0 -20
  577. data/app/helpers/ruby_cms/application_helper.rb +0 -41
  578. data/app/helpers/ruby_cms/bulk_action_table_helper.rb +0 -151
  579. data/app/helpers/ruby_cms/content_blocks_helper.rb +0 -399
  580. data/app/helpers/ruby_cms/settings_helper.rb +0 -172
  581. data/app/javascript/controllers/ruby_cms/content_block_history_controller.js +0 -91
  582. data/app/javascript/controllers/ruby_cms/index.js +0 -117
  583. data/app/javascript/controllers/ruby_cms/mobile_menu_controller.js +0 -55
  584. data/app/javascript/controllers/ruby_cms/nav_order_sortable_controller.js +0 -192
  585. data/app/javascript/controllers/ruby_cms/visual_editor_controller.js +0 -325
  586. data/app/models/concerns/content_block/searchable.rb +0 -22
  587. data/app/models/ruby_cms/content_block.rb +0 -8
  588. data/app/models/ruby_cms/permission.rb +0 -57
  589. data/app/models/ruby_cms/permittable.rb +0 -37
  590. data/app/models/ruby_cms/preference.rb +0 -111
  591. data/app/models/ruby_cms/user_permission.rb +0 -12
  592. data/app/models/ruby_cms/visitor_error.rb +0 -109
  593. data/app/services/ruby_cms/analytics/report.rb +0 -512
  594. data/app/services/ruby_cms/command_runner.rb +0 -42
  595. data/app/services/ruby_cms/security_tracker.rb +0 -92
  596. data/app/views/admin/content_block_versions/index.html.erb +0 -52
  597. data/app/views/admin/content_block_versions/show.html.erb +0 -37
  598. data/app/views/layouts/ruby_cms/_admin_sidebar.html.erb +0 -122
  599. data/app/views/layouts/ruby_cms/admin.html.erb +0 -77
  600. data/app/views/layouts/ruby_cms/minimal.html.erb +0 -181
  601. data/app/views/ruby_cms/admin/commands/index.html.erb +0 -104
  602. data/app/views/ruby_cms/admin/content_block_versions/index.html.erb +0 -52
  603. data/app/views/ruby_cms/admin/content_blocks/_form.html.erb +0 -92
  604. data/app/views/ruby_cms/admin/content_blocks/_row.html.erb +0 -25
  605. data/app/views/ruby_cms/admin/content_blocks/index.html.erb +0 -62
  606. data/app/views/ruby_cms/admin/content_blocks/new.html.erb +0 -10
  607. data/app/views/ruby_cms/admin/dashboard/index.html.erb +0 -31
  608. data/app/views/ruby_cms/admin/permissions/_row.html.erb +0 -11
  609. data/app/views/ruby_cms/admin/permissions/index.html.erb +0 -62
  610. data/app/views/ruby_cms/admin/settings/index.html.erb +0 -268
  611. data/app/views/ruby_cms/admin/shared/_bulk_action_table_index.html.erb +0 -56
  612. data/app/views/ruby_cms/admin/user_permissions/index.html.erb +0 -85
  613. data/app/views/ruby_cms/admin/users/_row.html.erb +0 -17
  614. data/app/views/ruby_cms/admin/users/index.html.erb +0 -70
  615. data/app/views/ruby_cms/admin/visitor_errors/_row.html.erb +0 -35
  616. data/app/views/ruby_cms/admin/visitor_errors/index.html.erb +0 -57
  617. data/app/views/ruby_cms/admin/visual_editor/index.html.erb +0 -157
  618. data/app/views/ruby_cms/errors/not_found.html.erb +0 -92
  619. data/config/database.yml +0 -6
  620. data/config/importmap.rb +0 -40
  621. data/config/locales/en.yml +0 -198
  622. data/config/locales/nl.yml +0 -156
  623. data/config/routes.rb +0 -76
  624. data/db/migrate/20260130000001_add_referer_and_query_to_ruby_cms_visitor_errors.rb +0 -8
  625. data/db/migrate/20260130000003_add_category_to_ruby_cms_preferences.rb +0 -8
  626. data/docs/assets/ruby_cms_logo.png +0 -0
  627. data/lib/generators/ruby_cms/install_generator.rb +0 -1080
  628. data/lib/ruby_cms/app_integration.rb +0 -82
  629. data/lib/ruby_cms/commands_registry.rb +0 -40
  630. data/lib/ruby_cms/css_compiler.rb +0 -35
  631. data/lib/ruby_cms/engine/admin_permissions.rb +0 -69
  632. data/lib/ruby_cms/engine/content_blocks_tasks.rb +0 -66
  633. data/lib/ruby_cms/engine/css.rb +0 -14
  634. data/lib/ruby_cms/engine/dashboard_registration.rb +0 -66
  635. data/lib/ruby_cms/engine/navigation_registration.rb +0 -89
  636. data/lib/ruby_cms/engine.rb +0 -273
  637. /data/{app → lib/generators/ruby_cms/templates}/assets/images/ruby_cms/logo.png +0 -0
  638. /data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260211000001_add_ruby_cms_analytics_fields_to_ahoy_events.rb +0 -0
  639. /data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260212000001_use_unprefixed_cms_tables.rb +0 -0
  640. /data/{db → lib/generators/ruby_cms/templates/db}/migrate/20260409000001_add_analytics_performance_indexes.rb +0 -0
  641. /data/{app/javascript/controllers/ruby_cms → lib/generators/ruby_cms/templates/javascript/controllers/admin}/auto_save_preference_controller.js +0 -0
  642. /data/{app/javascript/controllers/ruby_cms → lib/generators/ruby_cms/templates/javascript/controllers/admin}/locale_tabs_controller.js +0 -0
  643. /data/{app/javascript/controllers/ruby_cms → lib/generators/ruby_cms/templates/javascript/controllers/admin}/toggle_controller.js +0 -0
  644. /data/{app → lib/generators/ruby_cms/templates}/models/concerns/content_block/publishable.rb +0 -0
  645. /data/{app → lib/generators/ruby_cms/templates}/models/content_block_version.rb +0 -0
  646. /data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_back_button.html.erb +0 -0
  647. /data/{app/views/ruby_cms → lib/generators/ruby_cms/templates/views}/admin/analytics/partials/_security_alert.html.erb +0 -0
@@ -1,1080 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "fileutils"
4
-
5
- module RubyCms
6
- module Generators
7
- class InstallGenerator < ::Rails::Generators::Base
8
- source_root File.expand_path("templates", __dir__)
9
-
10
- NEXT_STEPS_MESSAGE = <<~TEXT
11
-
12
- ✓ RubyCMS install complete.
13
-
14
- Next steps (if not already done):
15
- - rails db:migrate
16
- - rails ruby_cms:seed_permissions (includes manage_visitor_errors and manage_analytics)
17
- - rails ruby_cms:setup_admin (or: rails ruby_cms:grant_manage_admin email=you@example.com)
18
- - To seed content blocks from YAML: add content under content_blocks in config/locales/<locale>.yml, then run rails ruby_cms:content_blocks:seed (or call it from db/seeds.rb).
19
-
20
- Notes:
21
- - If the host uses /admin already, remove or change those routes.
22
- - Avoid root to: redirect("/admin") — use a real root or ruby_cms.unauthorized_redirect_path.
23
- - Review config/initializers/ruby_cms.rb (session, CSP).
24
- - RubyCMS admin styles are compiled once on install to app/assets/stylesheets/ruby_cms/admin.css.
25
- - Visit /admin (sign in as the admin you configured).
26
-
27
- Tracking:
28
- - Visitor errors: Automatically captured via ApplicationController (see /admin/visitor_errors)
29
- - Page views (Ahoy): Include RubyCms::PageTracking in your public controllers to track page views
30
- Example: class PagesController < ApplicationController; include RubyCms::PageTracking; end
31
- - Conversions: Call ahoy.track RubyCms::Analytics::Report::EVENT_CONVERSION, goal: "contact_form"
32
- from any controller action (e.g. after a successful form submit). Goal names are free-form strings.
33
- - Bot filtering: Set config.ruby_cms.analytics_visit_scope to exclude IPs/bots (see config/initializers/ruby_cms.rb)
34
- - Analytics: View visit/event data in Ahoy tables (ahoy_visits, ahoy_events)
35
- TEXT
36
-
37
- def run_authentication
38
- user_path = Rails.root.join("app/models/user.rb")
39
- return if File.exist?(user_path)
40
-
41
- say "ℹ Task authentication: User model not found. " \
42
- "Running 'rails g authentication' (Rails 8+).", :cyan
43
- @authentication_attempted = true
44
- run "bin/rails generate authentication"
45
- run "bundle install"
46
- rescue StandardError => e
47
- say "⚠ Could not run 'rails g authentication': #{e.message}.", :yellow
48
- say " On Rails 8+, run 'rails g authentication' and 'bundle install' manually.", :yellow
49
- end
50
-
51
- def verify_auth
52
- verify_user_model
53
- verify_session_model
54
- verify_application_controller
55
- end
56
-
57
- def verify_user_model
58
- return if defined?(::User)
59
-
60
- message = if @authentication_attempted
61
- "Run 'rails db:migrate' if the authentication generator succeeded."
62
- else
63
- "User model not found. Run 'rails g authentication' before using /admin."
64
- end
65
- say "ℹ Task authentication: #{message}", :yellow
66
- end
67
-
68
- def verify_session_model
69
- return if defined?(::Session)
70
-
71
- say "ℹ Task authentication: Session model not found. " \
72
- "The host app should provide authentication.",
73
- :yellow
74
- end
75
-
76
- def verify_application_controller
77
- ac_path = Rails.root.join("app/controllers/application_controller.rb")
78
- return unless ac_path.exist?
79
-
80
- content = File.read(ac_path)
81
- return if content.include?("include Authentication")
82
- return if @authentication_warning_shown
83
-
84
- say "ℹ Task authentication: ApplicationController does not include " \
85
- "Authentication. Ensure /admin is protected.",
86
- :yellow
87
- @authentication_warning_shown = true
88
- end
89
-
90
- def create_initializer
91
- @detected_pages = detect_page_templates
92
- template "ruby_cms.rb", "config/initializers/ruby_cms.rb"
93
- end
94
-
95
- def mount_engine
96
- route 'mount RubyCms::Engine => "/"'
97
- end
98
-
99
- def add_catch_all_route
100
- routes_path = Rails.root.join("config/routes.rb")
101
- return unless routes_path.exist?
102
-
103
- content = File.read(routes_path)
104
- return if content.include?("ruby_cms/errors#not_found")
105
-
106
- # Add catch-all route at the end of the routes block (before final 'end')
107
- catch_all = <<~ROUTE
108
-
109
- # RubyCMS: Catch-all route for 404 error tracking (must be LAST)
110
- match "*path", to: "ruby_cms/errors#not_found", via: :all,
111
- constraints: ->(req) { !req.path.start_with?("/rails/", "/assets/") }
112
- ROUTE
113
-
114
- # Insert before the last 'end' in the file
115
- gsub_file routes_path, /(\nend)\s*\z/ do
116
- "#{catch_all}end\n"
117
- end
118
- say "✓ Catch-all route: Added for 404 error tracking", :green
119
- rescue StandardError => e
120
- say "⚠ Catch-all route: Could not add automatically: #{e.message}. " \
121
- "Add manually at the END of routes.rb:\n " \
122
- 'match "*path", to: "ruby_cms/errors#not_found", via: :all, ' \
123
- 'constraints: ->(req) { !req.path.start_with?("/rails/", "/assets/") }',
124
- :yellow
125
- end
126
-
127
- def add_permittable_to_user
128
- user_path = Rails.root.join("app/models/user.rb")
129
- unless File.exist?(user_path)
130
- say "Skipping User: app/models/user.rb not found.", :yellow
131
- return
132
- end
133
-
134
- return if File.read(user_path).include?("RubyCms::Permittable")
135
-
136
- inject_into_file user_path, after: /class User .*\n/ do
137
- " include RubyCms::Permittable\n"
138
- end
139
- end
140
-
141
- def add_current_user_to_authentication
142
- auth_path = Rails.root.join("app/controllers/concerns/authentication.rb")
143
- return unless auth_path.exist?
144
- return if File.read(auth_path).include?("def current_user")
145
-
146
- gsub_file auth_path, " helper_method :authenticated?\n",
147
- " helper_method :authenticated?, :current_user\n"
148
- inject_into_file auth_path, after: " private\n" do
149
- " def current_user\n Current.user\n end\n\n"
150
- end
151
- end
152
-
153
- def add_visitor_error_capture
154
- ac_path = Rails.root.join("app/controllers/application_controller.rb")
155
- return unless ac_path.exist?
156
-
157
- content = File.read(ac_path)
158
- return if content.include?("RubyCms::VisitorErrorCapture")
159
-
160
- to_inject = " include RubyCms::VisitorErrorCapture\n"
161
- to_inject += " rescue_from StandardError, with: :handle_visitor_error\n" \
162
- unless content.include?("rescue_from StandardError")
163
-
164
- inject_into_file ac_path, after: /class ApplicationController.*\n/ do
165
- to_inject
166
- end
167
- say "✓ Visitor error capture: Added to ApplicationController", :green
168
- rescue StandardError => e
169
- say "⚠ Visitor error capture: Could not add to ApplicationController: #{e.message}. " \
170
- "Add manually: include RubyCms::VisitorErrorCapture and rescue_from StandardError, with: :handle_visitor_error",
171
- :yellow
172
- end
173
-
174
- def add_page_tracking_to_home_controller
175
- home_path = Rails.root.join("app/controllers/home_controller.rb")
176
- return unless home_path.exist?
177
-
178
- content = File.read(home_path)
179
- return if content.include?("RubyCms::PageTracking")
180
-
181
- inject_into_file home_path, after: /class HomeController.*\n/ do
182
- " include RubyCms::PageTracking\n"
183
- end
184
-
185
- say "✓ Page tracking: Added RubyCms::PageTracking to HomeController", :green
186
- rescue StandardError => e
187
- say "⚠ Page tracking: Could not add to HomeController: #{e.message}. " \
188
- "Add manually: include RubyCms::PageTracking",
189
- :yellow
190
- end
191
-
192
- def copy_fallback_css
193
- src_dir = RubyCms::Engine.root.join("app/assets/stylesheets/ruby_cms")
194
- dest_dir = Rails.root.join("app/assets/stylesheets/ruby_cms")
195
-
196
- return unless src_dir.exist?
197
-
198
- FileUtils.mkdir_p(dest_dir)
199
- copy_admin_css(dest_dir)
200
- # Don't copy component files - only the compiled admin.css is needed
201
- # copy_components_css(src_dir, dest_dir)
202
- say "✓ Task css/copy: Combined component CSS into " \
203
- "app/assets/stylesheets/ruby_cms/admin.css", :green
204
- rescue StandardError => e
205
- say "⚠ Task css/copy: Could not copy CSS files: #{e.message}.", :yellow
206
- end
207
-
208
- def create_admin_layout
209
- layout_path = Rails.root.join("app/views/layouts/admin.html.erb")
210
- return if File.exist?(layout_path)
211
-
212
- template "admin.html.erb", layout_path.to_s
213
- say "✓ Layout admin: Created app/views/layouts/admin.html.erb", :green
214
- rescue StandardError => e
215
- say "⚠ Layout admin: Could not create admin.html.erb: #{e.message}. " \
216
- "Create it manually using the RubyCMS template.", :yellow
217
- end
218
- no_tasks do
219
- def copy_admin_css(dest_dir)
220
- admin_css_dest = dest_dir.join("admin.css")
221
- precompiled_admin_css = RubyCms::Engine.root.join("app/assets/stylesheets/ruby_cms/admin.css")
222
- if File.exist?(precompiled_admin_css) && File.size(precompiled_admin_css).to_i.positive?
223
- FileUtils.cp(precompiled_admin_css, admin_css_dest)
224
- return
225
- end
226
-
227
- RubyCms::Engine.compile_admin_css(admin_css_dest)
228
- return if File.size(admin_css_dest).to_i > 64
229
-
230
- say "⚠ Task css/copy: Generated admin.css is very small. " \
231
- "Ensure RubyCMS ships precompiled CSS at app/assets/stylesheets/ruby_cms/admin.css.",
232
- :yellow
233
- end
234
-
235
- def copy_components_css(src_dir, dest_dir)
236
- components_src_dir = src_dir.join("components")
237
- components_dest_dir = dest_dir.join("components")
238
- return unless components_src_dir.exist? && components_src_dir.directory?
239
-
240
- FileUtils.mkdir_p(components_dest_dir)
241
- Dir.glob(components_src_dir.join("*.css")).each do |src_file|
242
- copy_component_css_file(src_file, components_dest_dir)
243
- end
244
- end
245
-
246
- def copy_component_css_file(src_file, dest_dir)
247
- filename = File.basename(src_file)
248
- dest_file = dest_dir.join(filename)
249
- return if dest_file.exist? && File.mtime(src_file) <= File.mtime(dest_file)
250
-
251
- FileUtils.cp(src_file, dest_file, preserve: true)
252
- end
253
- end
254
-
255
- def install_ahoy
256
- if ahoy_already_installed?
257
- say "ℹ Task ahoy: Existing Ahoy setup detected (tables or migrations). Skipping ahoy:install.",
258
- :cyan
259
- configure_ahoy_server_side_only
260
- return
261
- end
262
-
263
- say "ℹ Task ahoy: Installing Ahoy for visit/event tracking.", :cyan
264
- run "bin/rails generate ahoy:install"
265
- add_ahoy_security_fields_migration
266
- configure_ahoy_server_side_only
267
- say "✓ Task ahoy: Installed Ahoy (visits, events, tracking)", :green
268
- rescue StandardError => e
269
- say "⚠ Task ahoy: Could not install: #{e.message}. " \
270
- "Run 'rails g ahoy:install' manually.",
271
- :yellow
272
- end
273
-
274
- def install_action_text
275
- migrate_dir = Rails.root.join("db/migrate")
276
- return unless migrate_dir.directory?
277
-
278
- if action_text_already_installed?(migrate_dir)
279
- say "ℹ Task action_text: Existing Action Text setup detected. Skipping action_text:install.",
280
- :cyan
281
- else
282
- say "ℹ Task action_text: Installing Action Text for rich text/image content blocks.", :cyan
283
- run "bin/rails action_text:install"
284
- say "✓ Task action_text: Installed Action Text", :green
285
- end
286
-
287
- configure_action_text_assets
288
- rescue StandardError => e
289
- say "⚠ Task action_text: Could not install: #{e.message}. Rich text will be disabled.",
290
- :yellow
291
- end
292
-
293
- no_tasks do
294
- def ahoy_already_installed?
295
- return true if ahoy_tables_exist?
296
-
297
- migrate_dir = Rails.root.join("db/migrate")
298
- return false unless migrate_dir.directory?
299
-
300
- Dir.glob(migrate_dir.join("*.rb").to_s).any? do |f|
301
- content = File.read(f)
302
- content.include?("ahoy_visits") || content.include?("ahoy_events")
303
- end
304
- end
305
-
306
- def ahoy_tables_exist?
307
- return false unless defined?(ActiveRecord::Base)
308
-
309
- # Calling connection lazily establishes a connection when possible,
310
- # so we can detect existing Ahoy tables even before AR reports connected?.
311
- c = ActiveRecord::Base.connection
312
- c.data_source_exists?("ahoy_visits") || c.data_source_exists?("ahoy_events")
313
- rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::NoDatabaseError,
314
- ActiveRecord::StatementInvalid
315
- false
316
- end
317
-
318
- def add_ahoy_security_fields_migration
319
- run "bin/rails generate migration AddRubyCmsFieldsToAhoyEvents"
320
- migration_file = Rails.root.glob("db/migrate/*add_ruby_cms*fields*.rb").max_by(&:basename)
321
- return unless migration_file
322
-
323
- migration_file = migration_file.to_s
324
- content = <<~RUBY
325
- class AddRubyCmsFieldsToAhoyEvents < ActiveRecord::Migration[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]
326
- def change
327
- return unless table_exists?(:ahoy_events)
328
-
329
- add_column :ahoy_events, :page_name, :string unless column_exists?(:ahoy_events, :page_name)
330
- add_column :ahoy_events, :ip_address, :string unless column_exists?(:ahoy_events, :ip_address)
331
- add_column :ahoy_events, :request_path, :string unless column_exists?(:ahoy_events, :request_path)
332
- add_column :ahoy_events, :user_agent, :text unless column_exists?(:ahoy_events, :user_agent)
333
- add_column :ahoy_events, :description, :text unless column_exists?(:ahoy_events, :description)
334
-
335
- add_index :ahoy_events, :page_name, if_not_exists: true
336
- add_index :ahoy_events, :ip_address, if_not_exists: true
337
- add_index :ahoy_events, :request_path, if_not_exists: true
338
- add_index :ahoy_events, [:name, :page_name], if_not_exists: true
339
- add_index :ahoy_events, [:name, :request_path], if_not_exists: true
340
- end
341
- end
342
- RUBY
343
- File.write(migration_file, content)
344
- end
345
-
346
- def configure_ahoy_server_side_only
347
- ahoy_path = Rails.root.join("config/initializers/ahoy.rb")
348
- content = if ahoy_path.exist?
349
- File.read(ahoy_path)
350
- else
351
- <<~RUBY
352
- # Configure Ahoy
353
- RUBY
354
- end
355
-
356
- # Ensure Ahoy is loaded before any references (fixes NameError when
357
- # ahoy_matey loads after initializers)
358
- content = %(require "ahoy_matey"\n\n#{content}) unless content.include?('require "ahoy_matey"')
359
-
360
- # Ensure a default Ahoy store class exists.
361
- unless content.match?(/class\s+Ahoy::Store\s*<\s*Ahoy::DatabaseStore/)
362
- content += <<~RUBY
363
-
364
- class Ahoy::Store < Ahoy::DatabaseStore
365
- end
366
- RUBY
367
- end
368
-
369
- if content.include?("Ahoy.api = false")
370
- File.write(ahoy_path, content)
371
- return
372
- end
373
-
374
- append = "\n\n# RubyCMS: server-side tracking only (no JavaScript)\nAhoy.api = false\nAhoy.geocode = false\n"
375
- File.write(ahoy_path, "#{content}#{append}")
376
- end
377
-
378
- def action_text_already_installed?(migrate_dir)
379
- return true if active_storage_tables_exist? || action_text_tables_exist?
380
-
381
- existing = Dir.glob(migrate_dir.join("*.rb").to_s).join("\n")
382
- existing.include?("create_action_text_rich_texts") ||
383
- existing.include?("create_active_storage_tables")
384
- end
385
-
386
- def active_storage_tables_exist?
387
- return false unless defined?(ActiveRecord::Base) && ActiveRecord::Base.connected?
388
-
389
- c = ActiveRecord::Base.connection
390
- c.data_source_exists?("active_storage_blobs") &&
391
- c.data_source_exists?("active_storage_attachments")
392
- rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::NoDatabaseError
393
- false
394
- end
395
-
396
- def action_text_tables_exist?
397
- return false unless defined?(ActiveRecord::Base) && ActiveRecord::Base.connected?
398
-
399
- ActiveRecord::Base.connection.data_source_exists?("action_text_rich_texts")
400
- rescue ActiveRecord::ConnectionNotEstablished, ActiveRecord::NoDatabaseError
401
- false
402
- end
403
-
404
- def configure_action_text_assets
405
- add_action_text_importmap_pins
406
- add_action_text_imports_to_admin_entrypoint
407
- end
408
-
409
- def add_action_text_importmap_pins
410
- importmap_path = Rails.root.join("config/importmap.rb")
411
- return unless File.exist?(importmap_path)
412
-
413
- content = File.read(importmap_path)
414
- lines_to_add = []
415
- lines_to_add << %(pin "trix") unless content.include?('pin "trix"')
416
- lines_to_add << %(pin "@rails/actiontext", to: "actiontext.esm.js") unless content.include?('pin "@rails/actiontext"')
417
- return if lines_to_add.empty?
418
-
419
- inject_into_file importmap_path.to_s, before: /^end/ do
420
- "\n # RubyCMS rich text (Action Text + Trix)\n #{lines_to_add.join("\n ")}\n"
421
- end
422
- say "✓ Task action_text/importmap: Added trix/actiontext pins to importmap.rb.", :green
423
- rescue StandardError => e
424
- say "⚠ Task action_text/importmap: Could not add pins: #{e.message}. " \
425
- "Add pins for trix and @rails/actiontext manually.",
426
- :yellow
427
- end
428
-
429
- def add_action_text_imports_to_admin_entrypoint
430
- admin_js_path = Rails.root.join("app/javascript/admin.js")
431
- content = File.exist?(admin_js_path) ? File.read(admin_js_path) : ""
432
-
433
- lines_to_add = []
434
- lines_to_add << %(import "trix") unless content.include?('import "trix"') || content.include?("import 'trix'")
435
- unless content.include?('import "@rails/actiontext"') ||
436
- content.include?("import '@rails/actiontext'")
437
- lines_to_add << %(import "@rails/actiontext")
438
- end
439
- return if lines_to_add.empty?
440
-
441
- if File.exist?(admin_js_path)
442
- append_to_file admin_js_path.to_s, "\n#{lines_to_add.join("\n")}\n"
443
- say "✓ Task action_text/js: Added Action Text imports to app/javascript/admin.js.", :green
444
- else
445
- content = <<~JS
446
- // RubyCMS admin entrypoint
447
- import "trix"
448
- import "@rails/actiontext"
449
- JS
450
- File.write(admin_js_path, content)
451
- say "✓ Task action_text/js: Created app/javascript/admin.js with Action Text imports.", :green
452
- end
453
- rescue StandardError => e
454
- say "⚠ Task action_text/js: Could not update admin.js: #{e.message}. " \
455
- "Import trix and @rails/actiontext manually.",
456
- :yellow
457
- end
458
- end
459
-
460
- def install_tailwind
461
- gemfile = Rails.root.join("Gemfile")
462
- tailwind_css = detect_tailwind_entry_css_path
463
-
464
- install_tailwind_if_needed(gemfile, tailwind_css)
465
- configure_tailwind(tailwind_css)
466
- rescue StandardError => e
467
- say "⚠ Task tailwind: Could not install: #{e.message}. Add tailwindcss-rails manually.",
468
- :yellow
469
- end
470
-
471
- no_tasks do
472
- def detect_tailwind_entry_css_path
473
- candidates = [
474
- Rails.root.join("app/assets/tailwind/application.css"),
475
- Rails.root.join("app/assets/stylesheets/application.tailwind.css"),
476
- Rails.root.join("app/assets/stylesheets/tailwind.css")
477
- ]
478
- candidates.find(&:exist?) || candidates.first
479
- end
480
-
481
- def install_tailwind_if_needed(gemfile, tailwind_css)
482
- return if File.exist?(tailwind_css)
483
-
484
- if File.read(gemfile).include?("tailwindcss-rails")
485
- say "ℹ Task tailwind: Tailwind CSS gem found; running tailwindcss:install.", :cyan
486
- else
487
- say "ℹ Task tailwind: Adding tailwindcss-rails for admin styling.", :cyan
488
- run "bundle add tailwindcss-rails"
489
- end
490
- run "bin/rails tailwindcss:install"
491
- say "✓ Task tailwind: Installed Tailwind CSS", :green
492
- end
493
-
494
- def configure_tailwind(tailwind_css)
495
- remove_ruby_cms_tailwind_source(tailwind_css)
496
- remove_ruby_cms_tailwind_content_paths
497
- run "bin/rails tailwindcss:build" if File.exist?(tailwind_css)
498
- # Importmap pins are provided by the engine via `ruby_cms/config/importmap.rb`.
499
- add_importmap_pins
500
- add_stimulus_registration
501
- end
502
- end
503
-
504
- def install_ruby_ui
505
- gemfile = Rails.root.join("Gemfile")
506
- gemfile_content = File.read(gemfile)
507
- return if ruby_ui_in_gemfile?(gemfile_content)
508
-
509
- add_ruby_ui_gem
510
- rescue StandardError => e
511
- say "⚠ Task ruby_ui: Could not add: #{e.message}. " \
512
- "Run 'bundle add ruby_ui --group development --require false' manually.",
513
- :yellow
514
- nil
515
- end
516
-
517
- def run_ruby_ui_install
518
- gemfile = Rails.root.join("Gemfile")
519
- gemfile_content = File.read(gemfile)
520
- return unless ruby_ui_in_gemfile?(gemfile_content)
521
- return if ruby_ui_already_installed?
522
-
523
- install_ruby_ui_generator
524
- end
525
-
526
- no_tasks do
527
- def ruby_ui_in_gemfile?(content)
528
- content.include?("ruby_ui") || content.include?("rails_ui")
529
- end
530
-
531
- def add_ruby_ui_gem
532
- say "ℹ Task ruby_ui: Adding ruby_ui to Gemfile.", :cyan
533
- run "bundle add ruby_ui --group development --require false"
534
- run "bundle install"
535
- say "✓ Task ruby_ui: Added ruby_ui to Gemfile", :green
536
- end
537
-
538
- def ruby_ui_already_installed?
539
- ruby_ui_initializer = Rails.root.join("config/initializers/ruby_ui.rb")
540
- ruby_ui_base = Rails.root.join("app/components/ruby_ui/base.rb")
541
- if File.exist?(ruby_ui_initializer) || File.exist?(ruby_ui_base)
542
- say "ℹ Task ruby_ui:install: ruby_ui is already installed. Skipping.", :cyan
543
- return true
544
- end
545
- false
546
- end
547
-
548
- def install_ruby_ui_generator
549
- say "ℹ Task ruby_ui:install: Running ruby_ui:install generator.", :cyan
550
- try_ruby_ui_install
551
- rescue StandardError => e
552
- say "⚠ Task ruby_ui:install: Could not find generator: #{e.message}. " \
553
- "Run 'rails g ruby_ui:install' manually.",
554
- :yellow
555
- end
556
-
557
- def try_ruby_ui_install
558
- run "bin/rails generate ruby_ui:install"
559
- say "✓ Task ruby_ui:install: Installed ruby_ui", :green
560
- rescue StandardError
561
- try_rails_ui_install
562
- end
563
-
564
- def try_rails_ui_install
565
- say "ℹ Task ruby_ui:install: Using rails_ui:install (ruby_ui alias).", :cyan
566
- run "bin/rails generate rails_ui:install"
567
- say "✓ Task ruby_ui:install: Installed rails_ui", :green
568
- end
569
-
570
- def add_ruby_ui_to_application_helper
571
- helper_path = Rails.root.join("app/helpers/application_helper.rb")
572
- return unless File.exist?(helper_path)
573
-
574
- content = File.read(helper_path)
575
- return if ruby_ui_already_included?(content)
576
-
577
- inject_ruby_ui_include(helper_path)
578
- rescue StandardError => e
579
- say "⚠ Task ruby_ui/helper: Could not add RubyUI: #{e.message}. " \
580
- "Add 'include RubyUI' manually.",
581
- :yellow
582
- end
583
-
584
- def ruby_ui_already_included?(content)
585
- content.include?("include RubyUI") || content.include?("include RubyUi")
586
- end
587
-
588
- def inject_ruby_ui_include(helper_path)
589
- say "✓ Task ruby_ui/helper: Added include RubyUI to ApplicationHelper.", :green
590
- inject_into_file helper_path.to_s, after: /module ApplicationHelper\n/ do
591
- " include RubyUI\n"
592
- end
593
- end
594
-
595
- def generate_ruby_ui_components
596
- # Skip component generation - page builder removed.
597
- # Generate manually: rails g ruby_ui:component ComponentName
598
- say "ℹ Task ruby_ui/components: Skipping automatic component generation " \
599
- "(page builder removed). " \
600
- "Generate components manually if needed: rails g ruby_ui:component ComponentName",
601
- :cyan
602
- end
603
- end
604
-
605
- # Directories to skip when scanning for page templates
606
- SKIP_VIEW_DIRS = %w[layouts shared mailers components admin].freeze
607
-
608
- # NOTE: Rails generators are Thor groups; private methods can still be
609
- # treated as "tasks" unless wrapped in `no_tasks`.
610
- no_tasks do
611
- def detect_page_templates
612
- views_dir = Rails.root.join("app/views")
613
- return {} unless Dir.exist?(views_dir)
614
-
615
- pages = {}
616
- views_base = views_dir.to_s
617
- scan_for_templates(views_dir, pages, views_base)
618
- log_detected_pages(pages) if pages.any?
619
- pages
620
- rescue StandardError => e
621
- say "⚠ Task pages: Could not scan for page templates: #{e.message}.", :yellow
622
- {}
623
- end
624
-
625
- def scan_for_templates(dir_path, pages, views_base, relative_path="")
626
- # Find all template files in this directory
627
- Dir.glob(File.join(dir_path, "*.{html.erb,html.haml,html.slim}")).each do |template_file|
628
- base_name = File.basename(template_file, ".*")
629
- base_name = File.basename(base_name, ".*") # Remove .html extension
630
-
631
- # Skip partials
632
- next if base_name.start_with?("_")
633
- # Skip admin pages
634
- next if relative_path.start_with?("admin") || relative_path == "admin"
635
-
636
- # Build template path relative to app/views
637
- if relative_path.empty?
638
- template_path = base_name
639
- page_key = base_name
640
- elsif base_name == "index"
641
- page_key = relative_path.split("/").last
642
- template_path = "#{relative_path}/index"
643
- # pages/index.html.erb -> "pages" => "pages/index"
644
- else
645
- # pages/home.html.erb -> "home" => "pages/home"
646
- page_key = base_name
647
- template_path = "#{relative_path}/#{base_name}"
648
- end
649
-
650
- pages[page_key] = template_path
651
- end
652
-
653
- # Recursively scan subdirectories (skip common non-page dirs, limit depth)
654
- Dir.glob(File.join(dir_path, "*")).each do |path|
655
- next unless File.directory?(path)
656
-
657
- dir_name = File.basename(path)
658
- # Skip admin directories and common non-page directories
659
- next if SKIP_VIEW_DIRS.include?(dir_name)
660
- # Skip if we're already in an admin path
661
- next if relative_path.start_with?("admin")
662
-
663
- # Limit depth to 2 levels (e.g., app/views/pages/home is OK, but not deeper)
664
- depth = relative_path.empty? ? 1 : relative_path.split("/").length + 1
665
- next if depth > 2
666
-
667
- new_relative_path = relative_path.empty? ? dir_name : "#{relative_path}/#{dir_name}"
668
- scan_for_templates(path, pages, views_base, new_relative_path)
669
- end
670
- end
671
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
672
-
673
- def log_detected_pages(pages)
674
- say "✓ Task pages: Detected #{pages.size} page template(s): " \
675
- "#{pages.keys.join(', ')}", :green
676
- end
677
-
678
- def add_importmap_pins
679
- importmap_path = Rails.root.join("config/importmap.rb")
680
- return unless File.exist?(importmap_path)
681
-
682
- content = File.read(importmap_path)
683
- gem_js_path = calculate_gem_js_path
684
- return if importmap_already_configured?(content, gem_js_path)
685
-
686
- inject_importmap_pins(importmap_path, gem_js_path)
687
- rescue StandardError => e
688
- say "⚠ Task importmap: Could not add pins: #{e.message}. " \
689
- "Add manually to config/importmap.rb.",
690
- :yellow
691
- end
692
-
693
- def calculate_gem_js_path
694
- RubyCms::Engine.root.join("app/javascript").relative_path_from(Rails.root).to_s
695
- end
696
-
697
- def importmap_already_configured?(content, gem_js_path)
698
- pin_pattern = %(pin_all_from "#{gem_js_path}/controllers", under: "controllers")
699
- content.include?("RubyCMS Stimulus controllers") || content.include?(pin_pattern)
700
- end
701
-
702
- def inject_importmap_pins(importmap_path, gem_js_path)
703
- pin_line = %(pin_all_from "#{gem_js_path}/controllers", under: "controllers")
704
- alias_line = %(pin "ruby_cms", to: "controllers/ruby_cms/index.js", preload: true)
705
- inject_into_file importmap_path.to_s, before: /^end/ do
706
- "\n # RubyCMS Stimulus controllers\n #{pin_line}\n #{alias_line}\n"
707
- end
708
- say "✓ Task importmap: Added RubyCMS controllers to importmap.rb.", :green
709
- end
710
-
711
- def add_stimulus_registration
712
- controllers_app_path = Rails.root.join("app/javascript/controllers/application.js")
713
- return handle_missing_stimulus_file unless File.exist?(controllers_app_path)
714
-
715
- content = File.read(controllers_app_path)
716
- unless stimulus_already_exposed?(content)
717
- expose_stimulus_application(controllers_app_path,
718
- content)
719
- end
720
- import_rubycms_controllers(controllers_app_path, content)
721
- cleanup_old_registration_code
722
- end
723
-
724
- def handle_missing_stimulus_file
725
- say "⚠ Task stimulus: Could not find app/javascript/controllers/application.js. " \
726
- "RubyCMS controllers may need manual setup.",
727
- :yellow
728
- end
729
-
730
- def stimulus_already_exposed?(content)
731
- if content.include?("window.Stimulus") || content.include?("window.application")
732
- say "ℹ Task stimulus: Stimulus application is already exposed on window.", :cyan
733
- return true
734
- end
735
- false
736
- end
737
-
738
- def expose_stimulus_application(controllers_app_path, content)
739
- return unless stimulus_application_startable?(content)
740
-
741
- if stimulus_const_assignment?(content)
742
- add_stimulus_const_pattern(controllers_app_path)
743
- elsif stimulus_var_assignment?(content)
744
- add_stimulus_var_pattern(controllers_app_path)
745
- else
746
- warn_manual_stimulus_exposure
747
- end
748
- end
749
-
750
- def stimulus_application_startable?(content)
751
- content.include?("const application") && content.include?("Application.start")
752
- end
753
-
754
- def stimulus_const_assignment?(content)
755
- content.match?(/const\s+application\s*=\s*Application\.start\(\)/)
756
- end
757
-
758
- def stimulus_var_assignment?(content)
759
- content.match?(/application\s*=\s*Application\.start\(\)/)
760
- end
761
-
762
- def warn_manual_stimulus_exposure
763
- say "⚠ Task stimulus: Could not automatically expose. " \
764
- "Manually add 'window.Stimulus = application'.",
765
- :yellow
766
- end
767
-
768
- def add_stimulus_const_pattern(controllers_app_path)
769
- gsub_file controllers_app_path.to_s,
770
- /const\s+application\s*=\s*Application\.start\(\)/,
771
- "const application = Application.start()\nwindow.Stimulus = application"
772
- say_stimulus_added
773
- end
774
-
775
- def add_stimulus_var_pattern(controllers_app_path)
776
- gsub_file controllers_app_path.to_s,
777
- /application\s*=\s*Application\.start\(\)/,
778
- "application = Application.start()\nwindow.Stimulus = application"
779
- say_stimulus_added
780
- end
781
-
782
- def say_stimulus_added
783
- say "✓ Task stimulus: Added window.Stimulus = application to " \
784
- "controllers/application.js.",
785
- :green
786
- end
787
-
788
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
789
- def import_rubycms_controllers(controllers_app_path, content)
790
- # Re-read content in case it was modified by expose_stimulus_application
791
- content = File.read(controllers_app_path) if File.exist?(controllers_app_path)
792
-
793
- return if content.include?('import "ruby_cms"') || content.include?("import 'ruby_cms'")
794
- return if content.include?("registerRubyCmsControllers")
795
-
796
- # Try to add after the Stimulus import (most common pattern)
797
- stimulus_import_pattern = %r{import\s+.*@hotwired/stimulus.*$}
798
- if content.match?(stimulus_import_pattern)
799
- inject_into_file controllers_app_path.to_s,
800
- after: stimulus_import_pattern,
801
- verbose: false do
802
- "\nimport \"ruby_cms\""
803
- end
804
- say "✓ Task stimulus: Added RubyCMS controllers import.", :green
805
- return
806
- end
807
-
808
- # Try to add after any import statement
809
- first_import_pattern = /^import\s+.*$/m
810
- if content.match?(first_import_pattern)
811
- inject_into_file controllers_app_path.to_s,
812
- after: first_import_pattern,
813
- verbose: false do
814
- "\nimport \"ruby_cms\""
815
- end
816
- say "✓ Task stimulus: Added RubyCMS controllers import.", :green
817
- return
818
- end
819
-
820
- # Add at the very top if no imports found
821
- prepend_to_file controllers_app_path.to_s, "import \"ruby_cms\"\n"
822
- say "✓ Task stimulus: Added RubyCMS controllers import.", :green
823
- rescue StandardError => e
824
- say "⚠ Task stimulus: Could not add RubyCMS import: #{e.message}. " \
825
- "Add 'import \"ruby_cms\"' manually to controllers/application.js.",
826
- :yellow
827
- end
828
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
829
-
830
- def cleanup_old_registration_code
831
- js_files = find_js_files_to_check
832
- js_files.each {|js_file| cleanup_file_registration(js_file) }
833
- rescue StandardError
834
- # Silent fail - not critical
835
- end
836
-
837
- def find_js_files_to_check
838
- [
839
- Rails.root.join("app/javascript/application.js"),
840
- Rails.root.join("app/javascript/index.js"),
841
- Rails.root.join("app/javascript/entrypoints/application.js")
842
- ].select(&:exist?)
843
- end
844
-
845
- def cleanup_file_registration(js_file)
846
- content = File.read(js_file)
847
- return unless needs_cleanup?(content)
848
-
849
- new_content = remove_registration_lines(content)
850
- return if new_content == content
851
-
852
- File.write(js_file, new_content)
853
- say "✓ Task stimulus: Removed old manual registration from #{js_file.basename}. " \
854
- "Auto-registration handles this now.",
855
- :green
856
- end
857
-
858
- def needs_cleanup?(content)
859
- content.include?("registerRubyCmsControllers(application)") &&
860
- content.exclude?("if (typeof application !== \"undefined\")")
861
- end
862
-
863
- def remove_registration_lines(content)
864
- lines = content.split("\n")
865
- lines.reject do |line|
866
- line.strip.include?("registerRubyCmsControllers(application)") ||
867
- (line.strip.start_with?("import") && line.include?("registerRubyCmsControllers") &&
868
- line.include?("ruby_cms"))
869
- end.join("\n")
870
- end
871
-
872
- # RubyCMS admin styles are precompiled to app/assets/stylesheets/ruby_cms/admin.css.
873
- # Remove RubyCMS-specific Tailwind source globs to keep host-app builds fast.
874
- # Not a generator task.
875
- def remove_ruby_cms_tailwind_source(tailwind_css_path)
876
- return unless valid_tailwind_source_path?(tailwind_css_path)
877
-
878
- content = File.read(tailwind_css_path)
879
- cleaned_content = remove_legacy_ruby_cms_tailwind_sources(content)
880
- return if cleaned_content == content
881
-
882
- File.write(tailwind_css_path, cleaned_content)
883
- say_tailwind_source_cleaned
884
- rescue StandardError => e
885
- say_tailwind_source_clean_failed(e)
886
- end
887
-
888
- # Remove RubyCMS content globs from Tailwind v3 config as well.
889
- def remove_ruby_cms_tailwind_content_paths
890
- config_path = Rails.root.join("config/tailwind.config.js")
891
- return unless File.exist?(config_path)
892
-
893
- content = File.read(config_path)
894
- cleaned = content.gsub(/^\s*["']?[^"'\n]*ruby_cms[^"'\n]*["']?,?\s*$\n?/i, "")
895
- return if cleaned == content
896
-
897
- File.write(config_path, cleaned)
898
- say "✓ Task tailwind/content: Removed RubyCMS paths from tailwind.config.js.", :green
899
- rescue StandardError => e
900
- say "⚠ Task tailwind/content: Could not clean tailwind.config.js: #{e.message}.", :yellow
901
- end
902
-
903
- def remove_legacy_ruby_cms_tailwind_sources(content)
904
- content
905
- .gsub(%r{^\s*/\*\s*Include RubyCMS views/components so Tailwind finds utility classes\.\s*\*/\s*$\n?}, "")
906
- .gsub(/^\s*@source\s+"[^"\n]*ruby_cms[^"\n]*";\s*$\n?/i, "")
907
- end
908
-
909
- def valid_tailwind_source_path?(tailwind_css_path)
910
- tailwind_css_path.to_s.present? && File.exist?(tailwind_css_path)
911
- end
912
-
913
- def say_tailwind_source_cleaned
914
- say "✓ Task tailwind/source: Removed RubyCMS @source paths from tailwind entry CSS.",
915
- :green
916
- end
917
-
918
- def say_tailwind_source_clean_failed(error)
919
- say "⚠ Task tailwind/source: Could not clean @source lines: #{error.message}. " \
920
- "Check tailwind/application.css manually.",
921
- :yellow
922
- end
923
- end
924
-
925
- def run_migrate
926
- say "ℹ Task db:migrate: Running db:migrate.", :cyan
927
- success = run("bin/rails db:migrate")
928
- raise "db:migrate failed" unless success
929
-
930
- say "✓ Task db:migrate: Completed", :green
931
- rescue StandardError => e
932
- say "⚠ Task db:migrate: Failed: #{e.message}. Run rails db:create db:migrate if needed.",
933
- :yellow
934
- end
935
-
936
- def run_seed_permissions
937
- say "ℹ Task permissions: Seeding RubyCMS permissions.", :cyan
938
- success = seed_permissions_via_open3
939
- say_seed_permissions_outcome(success)
940
- rescue StandardError => e
941
- say_seed_permissions_error(e)
942
- end
943
-
944
- def run_setup_admin
945
- return if skip_setup_admin_due_to_existing_admin?
946
- return unless setup_admin_tty?
947
-
948
- run_setup_admin_task
949
- rescue StandardError => e
950
- say_setup_admin_error(e)
951
- end
952
-
953
- no_tasks do
954
- def seed_permissions_via_open3
955
- require "open3"
956
-
957
- _stdin, stdout, stderr, wait_thr = open3_seed_permissions_process
958
- stderr_thread = stream_seed_permissions_stderr(stderr)
959
- stdout_thread = stream_seed_permissions_stdout(stdout)
960
-
961
- success = wait_thr.value.success?
962
- stderr_thread.join
963
- stdout_thread.join
964
- close_seed_permissions_streams(stdout, stderr)
965
- success
966
- end
967
-
968
- def open3_seed_permissions_process
969
- Open3.popen3(*seed_permissions_command, chdir: Rails.root.to_s)
970
- end
971
-
972
- def seed_permissions_command
973
- # Use argv form to avoid invoking a shell.
974
- %w[bin/rails ruby_cms:seed_permissions ruby_cms:import_initializer_settings]
975
- end
976
-
977
- def stream_seed_permissions_stderr(stderr)
978
- Thread.new do
979
- stderr.each_line do |line|
980
- $stderr.print line unless line.include?("already initialized constant")
981
- end
982
- end
983
- end
984
-
985
- def stream_seed_permissions_stdout(stdout)
986
- Thread.new do
987
- stdout.each_line {|line| $stdout.print line }
988
- end
989
- end
990
-
991
- def close_seed_permissions_streams(stdout, stderr)
992
- stdout.close
993
- stderr.close
994
- end
995
-
996
- def say_seed_permissions_outcome(success)
997
- if success
998
- say "✓ Task permissions: Seeded RubyCMS permissions", :green
999
- else
1000
- say "⚠ Task permissions: Could not seed. " \
1001
- "Run 'rails ruby_cms:seed_permissions' manually.",
1002
- :yellow
1003
- end
1004
- end
1005
-
1006
- def say_seed_permissions_error(error)
1007
- say "⚠ Task permissions: Could not seed: #{error.message}. " \
1008
- "Run 'rails ruby_cms:seed_permissions' manually.",
1009
- :yellow
1010
- end
1011
-
1012
- def skip_setup_admin_due_to_existing_admin?
1013
- return false unless user_with_admin_permissions_exists?
1014
-
1015
- say "ℹ Task setup_admin: User with admin permissions already exists. " \
1016
- "Skipping setup_admin.",
1017
- :cyan
1018
- true
1019
- end
1020
-
1021
- def setup_admin_tty?
1022
- return true if $stdin.tty?
1023
-
1024
- say "ℹ Task setup_admin: Skipping interactive setup (non-TTY). " \
1025
- "Run: rails ruby_cms:setup_admin",
1026
- :yellow
1027
- false
1028
- end
1029
-
1030
- def run_setup_admin_task
1031
- say "ℹ Task setup_admin: Running setup_admin (create or pick first admin).", :cyan
1032
- run "bin/rails ruby_cms:setup_admin"
1033
- say "✓ Task setup_admin: Completed", :green
1034
- end
1035
-
1036
- def say_setup_admin_error(error)
1037
- say "⚠ Task setup_admin: Failed or skipped: #{error.message}. " \
1038
- "Run: rails ruby_cms:setup_admin",
1039
- :yellow
1040
- end
1041
-
1042
- def user_with_admin_permissions_exists? # rubocop:disable Metrics/MethodLength
1043
- return false unless defined?(::User)
1044
-
1045
- begin
1046
- # Ensure permissions exist first
1047
- RubyCms::Permission.ensure_defaults!
1048
-
1049
- # Check if any user has ALL required admin permissions
1050
- required_keys = %w[
1051
- manage_admin
1052
- manage_permissions
1053
- manage_content_blocks
1054
- manage_visitor_errors
1055
- manage_analytics
1056
- ]
1057
- required_permission_ids = RubyCms::Permission.where(key: required_keys).pluck(:id)
1058
- return false if required_permission_ids.size != required_keys.size
1059
-
1060
- # Find users who have all required permissions
1061
- user_ids_with_all_perms = RubyCms::UserPermission
1062
- .where(permission_id: required_permission_ids)
1063
- .group(:user_id)
1064
- .having("COUNT(DISTINCT permission_id) = ?", required_keys.size)
1065
- .pluck(:user_id)
1066
-
1067
- user_ids_with_all_perms.any?
1068
- rescue StandardError
1069
- # If there's an error (e.g., tables don't exist yet), assume no admin exists
1070
- false
1071
- end
1072
- end
1073
- end
1074
-
1075
- def show_next_steps
1076
- say NEXT_STEPS_MESSAGE, :green
1077
- end
1078
- end
1079
- end
1080
- end