ruby_cms 0.2.1.1 → 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 -75
  3. data/README.md +293 -158
  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 +13 -13
  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/{app/views/layouts/ruby_cms → lib/generators/ruby_cms/templates/views/layouts/admin}/_admin_sidebar.html.erb +47 -48
  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 -328
  538. metadata +606 -170
  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 -49
  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.html.erb +0 -77
  599. data/app/views/layouts/ruby_cms/minimal.html.erb +0 -181
  600. data/app/views/ruby_cms/_tailwind_safelist.html.erb +0 -2
  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 -1159
  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 -90
  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,1159 +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
- ensure_ruby_cms_tailwind_safelist_partial
496
- run "bin/rails tailwindcss:build" if File.exist?(tailwind_css)
497
- # Importmap pins are provided by the engine via `ruby_cms/config/importmap.rb`.
498
- add_importmap_pins
499
- add_stimulus_registration
500
- end
501
- end
502
-
503
- def install_ruby_ui
504
- gemfile = Rails.root.join("Gemfile")
505
- gemfile_content = File.read(gemfile)
506
- return if ruby_ui_in_gemfile?(gemfile_content)
507
-
508
- add_ruby_ui_gem
509
- rescue StandardError => e
510
- say "⚠ Task ruby_ui: Could not add: #{e.message}. " \
511
- "Run 'bundle add ruby_ui --group development --require false' manually.",
512
- :yellow
513
- nil
514
- end
515
-
516
- def run_ruby_ui_install
517
- gemfile = Rails.root.join("Gemfile")
518
- gemfile_content = File.read(gemfile)
519
- return unless ruby_ui_in_gemfile?(gemfile_content)
520
- return if ruby_ui_already_installed?
521
-
522
- install_ruby_ui_generator
523
- end
524
-
525
- no_tasks do
526
- def ruby_ui_in_gemfile?(content)
527
- content.include?("ruby_ui") || content.include?("rails_ui")
528
- end
529
-
530
- def add_ruby_ui_gem
531
- say "ℹ Task ruby_ui: Adding ruby_ui to Gemfile.", :cyan
532
- run "bundle add ruby_ui --group development --require false"
533
- run "bundle install"
534
- say "✓ Task ruby_ui: Added ruby_ui to Gemfile", :green
535
- end
536
-
537
- def ruby_ui_already_installed?
538
- ruby_ui_initializer = Rails.root.join("config/initializers/ruby_ui.rb")
539
- ruby_ui_base = Rails.root.join("app/components/ruby_ui/base.rb")
540
- if File.exist?(ruby_ui_initializer) || File.exist?(ruby_ui_base)
541
- say "ℹ Task ruby_ui:install: ruby_ui is already installed. Skipping.", :cyan
542
- return true
543
- end
544
- false
545
- end
546
-
547
- def install_ruby_ui_generator
548
- say "ℹ Task ruby_ui:install: Running ruby_ui:install generator.", :cyan
549
- try_ruby_ui_install
550
- rescue StandardError => e
551
- say "⚠ Task ruby_ui:install: Could not find generator: #{e.message}. " \
552
- "Run 'rails g ruby_ui:install' manually.",
553
- :yellow
554
- end
555
-
556
- def try_ruby_ui_install
557
- run "bin/rails generate ruby_ui:install"
558
- say "✓ Task ruby_ui:install: Installed ruby_ui", :green
559
- rescue StandardError
560
- try_rails_ui_install
561
- end
562
-
563
- def try_rails_ui_install
564
- say "ℹ Task ruby_ui:install: Using rails_ui:install (ruby_ui alias).", :cyan
565
- run "bin/rails generate rails_ui:install"
566
- say "✓ Task ruby_ui:install: Installed rails_ui", :green
567
- end
568
-
569
- def add_ruby_ui_to_application_helper
570
- helper_path = Rails.root.join("app/helpers/application_helper.rb")
571
- return unless File.exist?(helper_path)
572
-
573
- content = File.read(helper_path)
574
- return if ruby_ui_already_included?(content)
575
-
576
- inject_ruby_ui_include(helper_path)
577
- rescue StandardError => e
578
- say "⚠ Task ruby_ui/helper: Could not add RubyUI: #{e.message}. " \
579
- "Add 'include RubyUI' manually.",
580
- :yellow
581
- end
582
-
583
- def ruby_ui_already_included?(content)
584
- content.include?("include RubyUI") || content.include?("include RubyUi")
585
- end
586
-
587
- def inject_ruby_ui_include(helper_path)
588
- say "✓ Task ruby_ui/helper: Added include RubyUI to ApplicationHelper.", :green
589
- inject_into_file helper_path.to_s, after: /module ApplicationHelper\n/ do
590
- " include RubyUI\n"
591
- end
592
- end
593
-
594
- def generate_ruby_ui_components
595
- # Skip component generation - page builder removed.
596
- # Generate manually: rails g ruby_ui:component ComponentName
597
- say "ℹ Task ruby_ui/components: Skipping automatic component generation " \
598
- "(page builder removed). " \
599
- "Generate components manually if needed: rails g ruby_ui:component ComponentName",
600
- :cyan
601
- end
602
- end
603
-
604
- # Directories to skip when scanning for page templates
605
- SKIP_VIEW_DIRS = %w[layouts shared mailers components admin].freeze
606
-
607
- # NOTE: Rails generators are Thor groups; private methods can still be
608
- # treated as "tasks" unless wrapped in `no_tasks`.
609
- no_tasks do
610
- def detect_page_templates
611
- views_dir = Rails.root.join("app/views")
612
- return {} unless Dir.exist?(views_dir)
613
-
614
- pages = {}
615
- views_base = views_dir.to_s
616
- scan_for_templates(views_dir, pages, views_base)
617
- log_detected_pages(pages) if pages.any?
618
- pages
619
- rescue StandardError => e
620
- say "⚠ Task pages: Could not scan for page templates: #{e.message}.", :yellow
621
- {}
622
- end
623
-
624
- def scan_for_templates(dir_path, pages, views_base, relative_path="")
625
- # Find all template files in this directory
626
- Dir.glob(File.join(dir_path, "*.{html.erb,html.haml,html.slim}")).each do |template_file|
627
- base_name = File.basename(template_file, ".*")
628
- base_name = File.basename(base_name, ".*") # Remove .html extension
629
-
630
- # Skip partials
631
- next if base_name.start_with?("_")
632
- # Skip admin pages
633
- next if relative_path.start_with?("admin") || relative_path == "admin"
634
-
635
- # Build template path relative to app/views
636
- if relative_path.empty?
637
- template_path = base_name
638
- page_key = base_name
639
- elsif base_name == "index"
640
- page_key = relative_path.split("/").last
641
- template_path = "#{relative_path}/index"
642
- # pages/index.html.erb -> "pages" => "pages/index"
643
- else
644
- # pages/home.html.erb -> "home" => "pages/home"
645
- page_key = base_name
646
- template_path = "#{relative_path}/#{base_name}"
647
- end
648
-
649
- pages[page_key] = template_path
650
- end
651
-
652
- # Recursively scan subdirectories (skip common non-page dirs, limit depth)
653
- Dir.glob(File.join(dir_path, "*")).each do |path|
654
- next unless File.directory?(path)
655
-
656
- dir_name = File.basename(path)
657
- # Skip admin directories and common non-page directories
658
- next if SKIP_VIEW_DIRS.include?(dir_name)
659
- # Skip if we're already in an admin path
660
- next if relative_path.start_with?("admin")
661
-
662
- # Limit depth to 2 levels (e.g., app/views/pages/home is OK, but not deeper)
663
- depth = relative_path.empty? ? 1 : relative_path.split("/").length + 1
664
- next if depth > 2
665
-
666
- new_relative_path = relative_path.empty? ? dir_name : "#{relative_path}/#{dir_name}"
667
- scan_for_templates(path, pages, views_base, new_relative_path)
668
- end
669
- end
670
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
671
-
672
- def log_detected_pages(pages)
673
- say "✓ Task pages: Detected #{pages.size} page template(s): " \
674
- "#{pages.keys.join(', ')}", :green
675
- end
676
-
677
- def add_importmap_pins
678
- importmap_path = Rails.root.join("config/importmap.rb")
679
- return unless File.exist?(importmap_path)
680
-
681
- content = File.read(importmap_path)
682
- gem_js_path = calculate_gem_js_path
683
- return if importmap_already_configured?(content, gem_js_path)
684
-
685
- inject_importmap_pins(importmap_path, gem_js_path)
686
- rescue StandardError => e
687
- say "⚠ Task importmap: Could not add pins: #{e.message}. " \
688
- "Add manually to config/importmap.rb.",
689
- :yellow
690
- end
691
-
692
- def calculate_gem_js_path
693
- RubyCms::Engine.root.join("app/javascript").relative_path_from(Rails.root).to_s
694
- end
695
-
696
- def importmap_already_configured?(content, gem_js_path)
697
- pin_pattern = %(pin_all_from "#{gem_js_path}/controllers", under: "controllers")
698
- content.include?("RubyCMS Stimulus controllers") || content.include?(pin_pattern)
699
- end
700
-
701
- def inject_importmap_pins(importmap_path, gem_js_path)
702
- pin_line = %(pin_all_from "#{gem_js_path}/controllers", under: "controllers")
703
- alias_line = %(pin "ruby_cms", to: "controllers/ruby_cms/index.js", preload: true)
704
- inject_into_file importmap_path.to_s, before: /^end/ do
705
- "\n # RubyCMS Stimulus controllers\n #{pin_line}\n #{alias_line}\n"
706
- end
707
- say "✓ Task importmap: Added RubyCMS controllers to importmap.rb.", :green
708
- end
709
-
710
- def add_stimulus_registration
711
- controllers_app_path = Rails.root.join("app/javascript/controllers/application.js")
712
- return handle_missing_stimulus_file unless File.exist?(controllers_app_path)
713
-
714
- content = File.read(controllers_app_path)
715
- unless stimulus_already_exposed?(content)
716
- expose_stimulus_application(controllers_app_path,
717
- content)
718
- end
719
- import_rubycms_controllers(controllers_app_path, content)
720
- cleanup_old_registration_code
721
- end
722
-
723
- def handle_missing_stimulus_file
724
- say "⚠ Task stimulus: Could not find app/javascript/controllers/application.js. " \
725
- "RubyCMS controllers may need manual setup.",
726
- :yellow
727
- end
728
-
729
- def stimulus_already_exposed?(content)
730
- if content.include?("window.Stimulus") || content.include?("window.application")
731
- say "ℹ Task stimulus: Stimulus application is already exposed on window.", :cyan
732
- return true
733
- end
734
- false
735
- end
736
-
737
- def expose_stimulus_application(controllers_app_path, content)
738
- return unless stimulus_application_startable?(content)
739
-
740
- if stimulus_const_assignment?(content)
741
- add_stimulus_const_pattern(controllers_app_path)
742
- elsif stimulus_var_assignment?(content)
743
- add_stimulus_var_pattern(controllers_app_path)
744
- else
745
- warn_manual_stimulus_exposure
746
- end
747
- end
748
-
749
- def stimulus_application_startable?(content)
750
- content.include?("const application") && content.include?("Application.start")
751
- end
752
-
753
- def stimulus_const_assignment?(content)
754
- content.match?(/const\s+application\s*=\s*Application\.start\(\)/)
755
- end
756
-
757
- def stimulus_var_assignment?(content)
758
- content.match?(/application\s*=\s*Application\.start\(\)/)
759
- end
760
-
761
- def warn_manual_stimulus_exposure
762
- say "⚠ Task stimulus: Could not automatically expose. " \
763
- "Manually add 'window.Stimulus = application'.",
764
- :yellow
765
- end
766
-
767
- def add_stimulus_const_pattern(controllers_app_path)
768
- gsub_file controllers_app_path.to_s,
769
- /const\s+application\s*=\s*Application\.start\(\)/,
770
- "const application = Application.start()\nwindow.Stimulus = application"
771
- say_stimulus_added
772
- end
773
-
774
- def add_stimulus_var_pattern(controllers_app_path)
775
- gsub_file controllers_app_path.to_s,
776
- /application\s*=\s*Application\.start\(\)/,
777
- "application = Application.start()\nwindow.Stimulus = application"
778
- say_stimulus_added
779
- end
780
-
781
- def say_stimulus_added
782
- say "✓ Task stimulus: Added window.Stimulus = application to " \
783
- "controllers/application.js.",
784
- :green
785
- end
786
-
787
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
788
- def import_rubycms_controllers(controllers_app_path, content)
789
- # Re-read content in case it was modified by expose_stimulus_application
790
- content = File.read(controllers_app_path) if File.exist?(controllers_app_path)
791
-
792
- return if content.include?('import "ruby_cms"') || content.include?("import 'ruby_cms'")
793
- return if content.include?("registerRubyCmsControllers")
794
-
795
- # Try to add after the Stimulus import (most common pattern)
796
- stimulus_import_pattern = %r{import\s+.*@hotwired/stimulus.*$}
797
- if content.match?(stimulus_import_pattern)
798
- inject_into_file controllers_app_path.to_s,
799
- after: stimulus_import_pattern,
800
- verbose: false do
801
- "\nimport \"ruby_cms\""
802
- end
803
- say "✓ Task stimulus: Added RubyCMS controllers import.", :green
804
- return
805
- end
806
-
807
- # Try to add after any import statement
808
- first_import_pattern = /^import\s+.*$/m
809
- if content.match?(first_import_pattern)
810
- inject_into_file controllers_app_path.to_s,
811
- after: first_import_pattern,
812
- verbose: false do
813
- "\nimport \"ruby_cms\""
814
- end
815
- say "✓ Task stimulus: Added RubyCMS controllers import.", :green
816
- return
817
- end
818
-
819
- # Add at the very top if no imports found
820
- prepend_to_file controllers_app_path.to_s, "import \"ruby_cms\"\n"
821
- say "✓ Task stimulus: Added RubyCMS controllers import.", :green
822
- rescue StandardError => e
823
- say "⚠ Task stimulus: Could not add RubyCMS import: #{e.message}. " \
824
- "Add 'import \"ruby_cms\"' manually to controllers/application.js.",
825
- :yellow
826
- end
827
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
828
-
829
- def cleanup_old_registration_code
830
- js_files = find_js_files_to_check
831
- js_files.each {|js_file| cleanup_file_registration(js_file) }
832
- rescue StandardError
833
- # Silent fail - not critical
834
- end
835
-
836
- def find_js_files_to_check
837
- [
838
- Rails.root.join("app/javascript/application.js"),
839
- Rails.root.join("app/javascript/index.js"),
840
- Rails.root.join("app/javascript/entrypoints/application.js")
841
- ].select(&:exist?)
842
- end
843
-
844
- def cleanup_file_registration(js_file)
845
- content = File.read(js_file)
846
- return unless needs_cleanup?(content)
847
-
848
- new_content = remove_registration_lines(content)
849
- return if new_content == content
850
-
851
- File.write(js_file, new_content)
852
- say "✓ Task stimulus: Removed old manual registration from #{js_file.basename}. " \
853
- "Auto-registration handles this now.",
854
- :green
855
- end
856
-
857
- def needs_cleanup?(content)
858
- content.include?("registerRubyCmsControllers(application)") &&
859
- content.exclude?("if (typeof application !== \"undefined\")")
860
- end
861
-
862
- def remove_registration_lines(content)
863
- lines = content.split("\n")
864
- lines.reject do |line|
865
- line.strip.include?("registerRubyCmsControllers(application)") ||
866
- (line.strip.start_with?("import") && line.include?("registerRubyCmsControllers") &&
867
- line.include?("ruby_cms"))
868
- end.join("\n")
869
- end
870
-
871
- # Keep Tailwind generation fast without scanning the gem path:
872
- # generate a host-app safelist partial from RubyCMS classes.
873
- def ensure_ruby_cms_tailwind_safelist_partial
874
- classes = ruby_cms_tailwind_classes
875
- return if classes.empty?
876
-
877
- safelist_path = Rails.root.join("app/views/ruby_cms/_tailwind_safelist.html.erb")
878
- FileUtils.mkdir_p(safelist_path.dirname)
879
- File.write(safelist_path, build_tailwind_safelist_partial(classes))
880
- say "✓ Task tailwind/safelist: Generated app/views/ruby_cms/_tailwind_safelist.html.erb.",
881
- :green
882
- rescue StandardError => e
883
- say "⚠ Task tailwind/safelist: Could not generate safelist partial: #{e.message}.", :yellow
884
- end
885
-
886
- def ruby_cms_tailwind_classes
887
- patterns = [
888
- RubyCms::Engine.root.join("app/views/**/*.erb"),
889
- RubyCms::Engine.root.join("app/components/**/*.rb"),
890
- RubyCms::Engine.root.join("app/helpers/**/*.rb"),
891
- RubyCms::Engine.root.join("app/javascript/**/*.js")
892
- ]
893
-
894
- tokens = Set.new
895
- patterns.each do |pattern|
896
- Dir.glob(pattern.to_s).each do |path|
897
- extract_tailwind_tokens_from(File.read(path), tokens)
898
- end
899
- end
900
-
901
- tokens.to_a.sort
902
- end
903
-
904
- def extract_tailwind_tokens_from(content, tokens)
905
- class_strings(content).each do |raw|
906
- add_tailwind_tokens(raw, tokens)
907
-
908
- # Also include classes from interpolated branches:
909
- # "#{active ? 'text-blue-600' : 'text-gray-600'}"
910
- raw.scan(/['"]([^'"]+)['"]/).flatten.each do |inner|
911
- add_tailwind_tokens(inner, tokens)
912
- end
913
- end
914
- end
915
-
916
- def class_strings(content)
917
- regexes = [
918
- /class\s*=\s*"([^"]+)"/m,
919
- /class\s*=\s*'([^']+)'/m,
920
- /class:\s*"([^"]+)"/m,
921
- /class:\s*'([^']+)'/m
922
- ]
923
-
924
- regexes.flat_map { |regex| content.scan(regex).flatten.compact }
925
- end
926
-
927
- def add_tailwind_tokens(value, tokens)
928
- value
929
- .gsub(/\#\{[^}]*\}/, " ")
930
- .split(/\s+/)
931
- .map(&:strip)
932
- .reject(&:empty?)
933
- .each do |token|
934
- next unless token.match?(/\A[-!:\[\]\/.%a-zA-Z0-9_]+\z/)
935
- next unless token.match?(/[A-Za-z]/)
936
- next if token.start_with?("http", "/", "#")
937
-
938
- tokens << token
939
- end
940
- end
941
-
942
- def build_tailwind_safelist_partial(classes)
943
- grouped_classes = classes.each_slice(20).map { |group| group.join(" ") }.join("\n ")
944
-
945
- <<~ERB
946
- <%# RubyCMS Tailwind safelist (auto-generated by ruby_cms:install). %>
947
- <%# Keep this file so Tailwind picks up classes used inside the RubyCMS gem. %>
948
- <div class="hidden #{grouped_classes}"></div>
949
- ERB
950
- end
951
-
952
- # RubyCMS admin styles are precompiled to app/assets/stylesheets/ruby_cms/admin.css.
953
- # Legacy cleanup helpers kept for backward compatibility.
954
- def remove_ruby_cms_tailwind_source(tailwind_css_path)
955
- return unless valid_tailwind_source_path?(tailwind_css_path)
956
-
957
- content = File.read(tailwind_css_path)
958
- cleaned_content = remove_legacy_ruby_cms_tailwind_sources(content)
959
- return if cleaned_content == content
960
-
961
- File.write(tailwind_css_path, cleaned_content)
962
- say_tailwind_source_cleaned
963
- rescue StandardError => e
964
- say_tailwind_source_clean_failed(e)
965
- end
966
-
967
- # Remove RubyCMS content globs from Tailwind v3 config as well.
968
- def remove_ruby_cms_tailwind_content_paths
969
- config_path = Rails.root.join("config/tailwind.config.js")
970
- return unless File.exist?(config_path)
971
-
972
- content = File.read(config_path)
973
- cleaned = content.gsub(/^\s*["']?[^"'\n]*ruby_cms[^"'\n]*["']?,?\s*$\n?/i, "")
974
- return if cleaned == content
975
-
976
- File.write(config_path, cleaned)
977
- say "✓ Task tailwind/content: Removed RubyCMS paths from tailwind.config.js.", :green
978
- rescue StandardError => e
979
- say "⚠ Task tailwind/content: Could not clean tailwind.config.js: #{e.message}.", :yellow
980
- end
981
-
982
- def remove_legacy_ruby_cms_tailwind_sources(content)
983
- content
984
- .gsub(%r{^\s*/\*\s*Include RubyCMS views/components so Tailwind finds utility classes\.\s*\*/\s*$\n?}, "")
985
- .gsub(/^\s*@source\s+"[^"\n]*ruby_cms[^"\n]*";\s*$\n?/i, "")
986
- end
987
-
988
- def valid_tailwind_source_path?(tailwind_css_path)
989
- tailwind_css_path.to_s.present? && File.exist?(tailwind_css_path)
990
- end
991
-
992
- def say_tailwind_source_cleaned
993
- say "✓ Task tailwind/source: Removed RubyCMS @source paths from tailwind entry CSS.",
994
- :green
995
- end
996
-
997
- def say_tailwind_source_clean_failed(error)
998
- say "⚠ Task tailwind/source: Could not clean @source lines: #{error.message}. " \
999
- "Check tailwind/application.css manually.",
1000
- :yellow
1001
- end
1002
- end
1003
-
1004
- def run_migrate
1005
- say "ℹ Task db:migrate: Running db:migrate.", :cyan
1006
- success = run("bin/rails db:migrate")
1007
- raise "db:migrate failed" unless success
1008
-
1009
- say "✓ Task db:migrate: Completed", :green
1010
- rescue StandardError => e
1011
- say "⚠ Task db:migrate: Failed: #{e.message}. Run rails db:create db:migrate if needed.",
1012
- :yellow
1013
- end
1014
-
1015
- def run_seed_permissions
1016
- say "ℹ Task permissions: Seeding RubyCMS permissions.", :cyan
1017
- success = seed_permissions_via_open3
1018
- say_seed_permissions_outcome(success)
1019
- rescue StandardError => e
1020
- say_seed_permissions_error(e)
1021
- end
1022
-
1023
- def run_setup_admin
1024
- return if skip_setup_admin_due_to_existing_admin?
1025
- return unless setup_admin_tty?
1026
-
1027
- run_setup_admin_task
1028
- rescue StandardError => e
1029
- say_setup_admin_error(e)
1030
- end
1031
-
1032
- no_tasks do
1033
- def seed_permissions_via_open3
1034
- require "open3"
1035
-
1036
- _stdin, stdout, stderr, wait_thr = open3_seed_permissions_process
1037
- stderr_thread = stream_seed_permissions_stderr(stderr)
1038
- stdout_thread = stream_seed_permissions_stdout(stdout)
1039
-
1040
- success = wait_thr.value.success?
1041
- stderr_thread.join
1042
- stdout_thread.join
1043
- close_seed_permissions_streams(stdout, stderr)
1044
- success
1045
- end
1046
-
1047
- def open3_seed_permissions_process
1048
- Open3.popen3(*seed_permissions_command, chdir: Rails.root.to_s)
1049
- end
1050
-
1051
- def seed_permissions_command
1052
- # Use argv form to avoid invoking a shell.
1053
- %w[bin/rails ruby_cms:seed_permissions ruby_cms:import_initializer_settings]
1054
- end
1055
-
1056
- def stream_seed_permissions_stderr(stderr)
1057
- Thread.new do
1058
- stderr.each_line do |line|
1059
- $stderr.print line unless line.include?("already initialized constant")
1060
- end
1061
- end
1062
- end
1063
-
1064
- def stream_seed_permissions_stdout(stdout)
1065
- Thread.new do
1066
- stdout.each_line {|line| $stdout.print line }
1067
- end
1068
- end
1069
-
1070
- def close_seed_permissions_streams(stdout, stderr)
1071
- stdout.close
1072
- stderr.close
1073
- end
1074
-
1075
- def say_seed_permissions_outcome(success)
1076
- if success
1077
- say "✓ Task permissions: Seeded RubyCMS permissions", :green
1078
- else
1079
- say "⚠ Task permissions: Could not seed. " \
1080
- "Run 'rails ruby_cms:seed_permissions' manually.",
1081
- :yellow
1082
- end
1083
- end
1084
-
1085
- def say_seed_permissions_error(error)
1086
- say "⚠ Task permissions: Could not seed: #{error.message}. " \
1087
- "Run 'rails ruby_cms:seed_permissions' manually.",
1088
- :yellow
1089
- end
1090
-
1091
- def skip_setup_admin_due_to_existing_admin?
1092
- return false unless user_with_admin_permissions_exists?
1093
-
1094
- say "ℹ Task setup_admin: User with admin permissions already exists. " \
1095
- "Skipping setup_admin.",
1096
- :cyan
1097
- true
1098
- end
1099
-
1100
- def setup_admin_tty?
1101
- return true if $stdin.tty?
1102
-
1103
- say "ℹ Task setup_admin: Skipping interactive setup (non-TTY). " \
1104
- "Run: rails ruby_cms:setup_admin",
1105
- :yellow
1106
- false
1107
- end
1108
-
1109
- def run_setup_admin_task
1110
- say "ℹ Task setup_admin: Running setup_admin (create or pick first admin).", :cyan
1111
- run "bin/rails ruby_cms:setup_admin"
1112
- say "✓ Task setup_admin: Completed", :green
1113
- end
1114
-
1115
- def say_setup_admin_error(error)
1116
- say "⚠ Task setup_admin: Failed or skipped: #{error.message}. " \
1117
- "Run: rails ruby_cms:setup_admin",
1118
- :yellow
1119
- end
1120
-
1121
- def user_with_admin_permissions_exists? # rubocop:disable Metrics/MethodLength
1122
- return false unless defined?(::User)
1123
-
1124
- begin
1125
- # Ensure permissions exist first
1126
- RubyCms::Permission.ensure_defaults!
1127
-
1128
- # Check if any user has ALL required admin permissions
1129
- required_keys = %w[
1130
- manage_admin
1131
- manage_permissions
1132
- manage_content_blocks
1133
- manage_visitor_errors
1134
- manage_analytics
1135
- ]
1136
- required_permission_ids = RubyCms::Permission.where(key: required_keys).pluck(:id)
1137
- return false if required_permission_ids.size != required_keys.size
1138
-
1139
- # Find users who have all required permissions
1140
- user_ids_with_all_perms = RubyCms::UserPermission
1141
- .where(permission_id: required_permission_ids)
1142
- .group(:user_id)
1143
- .having("COUNT(DISTINCT permission_id) = ?", required_keys.size)
1144
- .pluck(:user_id)
1145
-
1146
- user_ids_with_all_perms.any?
1147
- rescue StandardError
1148
- # If there's an error (e.g., tables don't exist yet), assume no admin exists
1149
- false
1150
- end
1151
- end
1152
- end
1153
-
1154
- def show_next_steps
1155
- say NEXT_STEPS_MESSAGE, :green
1156
- end
1157
- end
1158
- end
1159
- end