maglevcms 3.0.0.beta3 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +16 -53
- data/Rakefile +3 -1
- data/app/assets/builds/maglev/tailwind.css +424 -84
- data/app/assets/config/maglev_manifest.js +3 -2
- data/app/assets/javascripts/maglev/client/dom-operations.js +5 -5
- data/app/assets/javascripts/maglev/client/iframe-decorator.js +24 -0
- data/app/assets/javascripts/maglev/client/incoming-messages.js +13 -3
- data/app/assets/javascripts/maglev/client/index.js +3 -2
- data/app/assets/javascripts/maglev/client/utils.js +22 -0
- data/app/assets/javascripts/maglev/editor/controllers/app/forms/section_form_controller.js +4 -3
- data/app/assets/javascripts/maglev/editor/controllers/app/forms/style_form_controller.js +2 -1
- data/app/assets/javascripts/maglev/editor/controllers/app/page_preview_controller.js +96 -5
- data/app/assets/javascripts/maglev/editor/controllers/app/preview_notification_center_controller.js +105 -23
- data/app/assets/javascripts/maglev/editor/controllers/app/setting_controller.js +3 -2
- data/app/assets/javascripts/maglev/editor/controllers/shared/copy_to_clipboard_controller.js +2 -1
- data/app/assets/javascripts/maglev/editor/controllers/utils.js +22 -0
- data/app/assets/javascripts/maglev/editor/index.js +2 -1
- data/app/assets/javascripts/maglev/editor/patches/turbo_stream_patch.js +3 -3
- data/app/assets/stylesheets/maglev/tailwind.css.erb +8 -1
- data/app/components/maglev/content/link.rb +1 -1
- data/app/components/maglev/editor/settings/link/link_component.rb +7 -1
- data/app/components/maglev/section_component.rb +11 -1
- data/app/components/maglev/uikit/app_layout/sidebar/link_component.html.erb +1 -1
- data/app/components/maglev/uikit/app_layout/sidebar/link_component.rb +35 -5
- data/app/components/maglev/uikit/app_layout/sidebar_component.html.erb +3 -4
- data/app/components/maglev/uikit/app_layout/topbar_component.html.erb +1 -1
- data/app/components/maglev/uikit/app_layout/topbar_component.rb +1 -1
- data/app/components/maglev/uikit/button_group_component/button_group_component.html.erb +5 -0
- data/app/components/maglev/uikit/button_group_component.rb +70 -0
- data/app/components/maglev/uikit/device_toggler_component.rb +10 -1
- data/app/components/maglev/uikit/dropdown_component/dropdown_component.html.erb +2 -2
- data/app/components/maglev/uikit/dropdown_component.rb +6 -1
- data/app/components/maglev/uikit/form/color_field_component.html.erb +1 -1
- data/app/components/maglev/uikit/form/combobox_component.html.erb +1 -0
- data/app/components/maglev/uikit/form/link_component.rb +5 -1
- data/app/components/maglev/uikit/form/richtext_controller.js +5 -4
- data/app/components/maglev/uikit/form/search_form_component.html.erb +1 -0
- data/app/components/maglev/uikit/icon_component.rb +3 -0
- data/app/components/maglev/uikit/image_library/uploader_controller.js +3 -2
- data/app/components/maglev/uikit/list/list_item_component.html.erb +36 -19
- data/app/components/maglev/uikit/list/list_item_component.rb +19 -5
- data/app/components/maglev/uikit/locale_switcher_component/locale_switcher_component.html.erb +6 -10
- data/app/components/maglev/uikit/menu_dropdown_component/menu_dropdown_component.html.erb +6 -2
- data/app/components/maglev/uikit/menu_dropdown_component.rb +244 -7
- data/app/components/maglev/uikit/page_actions_dropdown_component/page_actions_dropdown_component.html.erb +39 -46
- data/app/components/maglev/uikit/pagination_component/pagination_component.html.erb +9 -12
- data/app/components/maglev/uikit/pagination_component.rb +6 -1
- data/app/components/maglev/uikit/section_toolbar/bottom_component.html.erb +1 -1
- data/app/components/maglev/uikit/tabs_component/tabs_component.html.erb +7 -4
- data/app/components/maglev/uikit/tabs_component.rb +23 -4
- data/app/components/maglev/uikit/well/simple_well_component.html.erb +15 -0
- data/app/components/maglev/uikit/well/simple_well_component.rb +13 -0
- data/app/controllers/concerns/maglev/editor/preview_urls_concern.rb +32 -0
- data/app/controllers/concerns/maglev/editor/turbo_concern.rb +29 -0
- data/app/controllers/concerns/maglev/flash_i18n_concern.rb +1 -0
- data/app/controllers/maglev/application_controller.rb +1 -1
- data/app/controllers/maglev/assets/active_storage_proxy_controller.rb +2 -1
- data/app/controllers/maglev/editor/assets_controller.rb +1 -1
- data/app/controllers/maglev/editor/base_controller.rb +6 -32
- data/app/controllers/maglev/editor/pages/clone_controller.rb +22 -0
- data/app/controllers/maglev/editor/pages/discard_draft_controller.rb +17 -0
- data/app/controllers/maglev/editor/pages_controller.rb +26 -7
- data/app/controllers/maglev/editor/section_blocks_controller.rb +13 -9
- data/app/controllers/maglev/editor/sections_controller.rb +26 -7
- data/app/controllers/maglev/published_page_preview_controller.rb +4 -0
- data/app/helpers/maglev/application_helper.rb +6 -6
- data/app/helpers/maglev/editor/section_blocks_helper.rb +2 -2
- data/app/models/maglev/page/publishable_concern.rb +69 -0
- data/app/models/maglev/page.rb +2 -13
- data/app/models/maglev/section/block.rb +4 -0
- data/app/models/maglev/section/content_concern.rb +3 -1
- data/app/models/maglev/section.rb +21 -1
- data/app/models/maglev/sections_content_store.rb +1 -3
- data/app/services/concerns/maglev/content/helpers_concern.rb +5 -8
- data/app/services/maglev/app_container.rb +4 -2
- data/app/services/maglev/discard_page_draft_service.rb +45 -0
- data/app/services/maglev/fetch_section_screenshot_url.rb +12 -1
- data/app/services/maglev/has_unpublished_changes.rb +21 -0
- data/app/services/maglev/publish_service.rb +15 -2
- data/app/views/layouts/maglev/editor/_sidebar.html.erb +6 -1
- data/app/views/layouts/maglev/editor/application.html.erb +5 -0
- data/app/views/layouts/maglev/editor/topbar/_page_info.html.erb +1 -1
- data/app/views/layouts/maglev/editor/topbar/_publish_button.html.erb +32 -10
- data/app/views/maglev/editor/assets/_list.html.erb +2 -1
- data/app/views/maglev/editor/assets/index.html.erb +1 -0
- data/app/views/maglev/editor/home/index.html.erb +1 -1
- data/app/views/maglev/editor/links/edit/_email.html.erb +1 -1
- data/app/views/maglev/editor/links/edit/_url.html.erb +1 -1
- data/app/views/maglev/editor/pages/_list.html.erb +25 -21
- data/app/views/maglev/editor/pages/_preview.html.erb +6 -6
- data/app/views/maglev/editor/pages/_preview_empty_message.html.erb +13 -10
- data/app/views/maglev/editor/pages/discard_draft/create.turbo_stream.erb +16 -0
- data/app/views/maglev/editor/pages/index.html.erb +8 -1
- data/app/views/maglev/editor/section_blocks/_form.html.erb +5 -13
- data/app/views/maglev/editor/section_blocks/_form_with_tabs.html.erb +15 -0
- data/app/views/maglev/editor/section_blocks/_new.html.erb +1 -1
- data/app/views/maglev/editor/section_blocks/edit.html.erb +2 -2
- data/app/views/maglev/editor/section_blocks/index/_list.html.erb +2 -2
- data/app/views/maglev/editor/section_blocks/index/_tree.html.erb +1 -1
- data/app/views/maglev/editor/section_blocks/update.turbo_stream.erb +1 -1
- data/app/views/maglev/editor/sections/_form.html.erb +6 -20
- data/app/views/maglev/editor/sections/_form_with_tabs.html.erb +21 -0
- data/app/views/maglev/editor/sections/_list.html.erb +3 -3
- data/app/views/maglev/editor/sections/edit.html.erb +4 -4
- data/app/views/maglev/editor/sections/index.html.erb +1 -1
- data/app/views/maglev/editor/sections/new.html.erb +19 -2
- data/app/views/maglev/editor/sections/theme/_empty_list.html.erb +3 -0
- data/app/views/maglev/editor/sections/theme/_list.html.erb +35 -0
- data/app/views/maglev/editor/sections/theme/_screenshot_placeholder.html.erb +6 -0
- data/app/views/maglev/editor/sections/theme/_search.html.erb +22 -0
- data/app/views/maglev/editor/sections/update.turbo_stream.erb +1 -1
- data/app/views/maglev/editor/shared/_button_label.html.erb +4 -4
- data/app/views/maglev/editor/style/edit.html.erb +1 -0
- data/config/editor_importmap.rb +13 -13
- data/config/locales/editor.ar.yml +12 -4
- data/config/locales/editor.en.yml +31 -23
- data/config/locales/editor.es.yml +12 -4
- data/config/locales/editor.fr.yml +12 -4
- data/config/locales/editor.pt-BR.yml +12 -4
- data/config/routes/maglev/assets.rb +4 -0
- data/config/routes/maglev/editor.rb +38 -0
- data/config/routes/maglev/preview.rb +8 -0
- data/config/routes/maglev/public_preview.rb +6 -0
- data/config/routes.rb +8 -47
- data/db/migrate/20211013210954_translate_section_content.rb +1 -0
- data/db/migrate/20260114112058_add_published_payload_to_pages.rb +14 -0
- data/exe/tailwind-cli +1 -1
- data/lib/generators/maglev/install_generator.rb +9 -7
- data/lib/generators/maglev/templates/install/config/initializers/maglev.rb +10 -3
- data/lib/maglev/active_storage/serving_blob.rb +29 -0
- data/lib/maglev/active_storage.rb +2 -0
- data/lib/maglev/config.rb +22 -3
- data/lib/maglev/engine.rb +14 -10
- data/lib/maglev/version.rb +1 -1
- data/lib/maglev.rb +18 -3
- data/lib/tasks/db_test_all.rake +290 -0
- metadata +46 -19
- data/app/controllers/maglev/editor/page_clone_controller.rb +0 -20
- data/app/views/maglev/editor/sections/_theme_list.html.erb +0 -32
- /data/vendor/javascript/{@floating-ui--core.js → maglev/@floating-ui--core.js} +0 -0
- /data/vendor/javascript/{@floating-ui--dom.js → maglev/@floating-ui--dom.js} +0 -0
- /data/vendor/javascript/{@floating-ui--utils--dom.js → maglev/@floating-ui--utils--dom.js} +0 -0
- /data/vendor/javascript/{@floating-ui--utils.js → maglev/@floating-ui--utils.js} +0 -0
- /data/vendor/javascript/{@hotwired--stimulus.js → maglev/@hotwired--stimulus.js} +0 -0
- /data/vendor/javascript/{@hotwired--turbo-rails.js → maglev/@hotwired--turbo-rails.js} +0 -0
- /data/vendor/javascript/{@hotwired--turbo.js → maglev/@hotwired--turbo.js} +0 -0
- /data/vendor/javascript/{@rails--actioncable--src.js → maglev/@rails--actioncable--src.js} +0 -0
- /data/vendor/javascript/{@rails--request.js.js → maglev/@rails--request.js.js} +0 -0
- /data/vendor/javascript/{@shopify--draggable.js → maglev/@shopify--draggable.js} +0 -0
- /data/vendor/javascript/{el-transition.js → maglev/el-transition.js} +0 -0
- /data/vendor/javascript/{stimulus-use.js → maglev/stimulus-use.js} +0 -0
- /data/vendor/javascript/{tiptap.bundle.js → maglev/tiptap.bundle.js} +0 -0
data/config/routes.rb
CHANGED
|
@@ -1,58 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# rubocop:disable Metrics/BlockLength
|
|
4
3
|
Maglev::Engine.routes.draw do
|
|
5
|
-
# convenient route to create the site in development mode from the Maglev UI
|
|
6
|
-
resource :site, only: %i[create], controller: :site if Rails.env.local?
|
|
7
|
-
|
|
8
4
|
# Editor
|
|
9
5
|
namespace :editor do
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
get 'leave', to: 'home#destroy', as: :leave
|
|
13
|
-
|
|
14
|
-
resources :assets, only: %i[index create destroy]
|
|
15
|
-
resources :icons, only: %i[index]
|
|
16
|
-
resource :link, only: %i[edit update]
|
|
17
|
-
|
|
18
|
-
# combobox routes
|
|
19
|
-
namespace :combobox do
|
|
20
|
-
resources :pages, only: :index
|
|
21
|
-
resources :collection_items, only: :index
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# always keep the scope of the current page and locale in the url
|
|
25
|
-
scope ':locale/:page_id' do
|
|
26
|
-
root to: 'home#index', as: :real_root
|
|
27
|
-
|
|
28
|
-
resources :pages do
|
|
29
|
-
resource :clone, controller: :page_clone, only: :create
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
resources :sections do
|
|
33
|
-
put :sort, on: :collection
|
|
34
|
-
resources :blocks, controller: :section_blocks do
|
|
35
|
-
put :sort, on: :collection
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
resource :style, controller: :style, only: %i[edit update]
|
|
40
|
-
|
|
41
|
-
resource :publication, controller: :publication, only: :create
|
|
42
|
-
end
|
|
6
|
+
draw 'maglev/editor'
|
|
43
7
|
end
|
|
44
8
|
|
|
45
|
-
root to: 'editor/home#index'
|
|
46
|
-
|
|
47
9
|
# Preview
|
|
48
|
-
|
|
49
|
-
defaults: { path: 'index', rendering_mode: :editor },
|
|
50
|
-
constraints: Maglev::PreviewConstraint.new,
|
|
51
|
-
as: :site_preview
|
|
52
|
-
post 'preview/(*path)', to: 'page_preview#create', defaults: { path: 'index', rendering_mode: :editor }
|
|
10
|
+
draw 'maglev/preview'
|
|
53
11
|
|
|
54
12
|
# Public Assets
|
|
55
|
-
|
|
56
|
-
|
|
13
|
+
draw 'maglev/assets'
|
|
14
|
+
|
|
15
|
+
# convenient route to create the site in development mode from the Maglev UI
|
|
16
|
+
resource :site, only: %i[create], controller: :site if Rails.env.local?
|
|
17
|
+
|
|
18
|
+
root to: 'editor/home#index'
|
|
57
19
|
end
|
|
58
|
-
# rubocop:enable Metrics/BlockLength
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
class TranslateSectionContent < ActiveRecord::Migration[6.0]
|
|
2
|
+
include Maglev::Migration
|
|
2
3
|
def change
|
|
3
4
|
remove_column :maglev_sites, :sections, :jsonb, default: [] if column_exists?(:maglev_sites, :sections)
|
|
4
5
|
remove_column :maglev_pages, :sections, :jsonb, default: [] if column_exists?(:maglev_pages, :sections)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class AddPublishedPayloadToPages < ActiveRecord::Migration[6.0]
|
|
2
|
+
include Maglev::Migration
|
|
3
|
+
def change
|
|
4
|
+
change_table :maglev_pages do |t|
|
|
5
|
+
if t.respond_to? :jsonb
|
|
6
|
+
t.jsonb :published_payload, default: {}
|
|
7
|
+
elsif mysql?
|
|
8
|
+
t.json :published_payload # MySQL doesn't support default values for json columns
|
|
9
|
+
else
|
|
10
|
+
t.json :published_payload, default: {}
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
data/exe/tailwind-cli
CHANGED
|
@@ -23,9 +23,11 @@ module Maglev
|
|
|
23
23
|
def mount_engine
|
|
24
24
|
inject_into_file 'config/routes.rb', before: /^end/ do
|
|
25
25
|
<<-CODE
|
|
26
|
+
# Mount Maglev engine (mainly used for the content editor)
|
|
26
27
|
mount Maglev::Engine => '/maglev'
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
|
|
29
|
+
# Handle "/:path" + "/sitemap" requests by Maglev engine
|
|
30
|
+
draw 'maglev/public_preview'
|
|
29
31
|
CODE
|
|
30
32
|
end
|
|
31
33
|
end
|
|
@@ -34,13 +36,13 @@ module Maglev
|
|
|
34
36
|
$stdout.puts <<~INFO
|
|
35
37
|
Done! 🎉
|
|
36
38
|
|
|
37
|
-
You can now tweak config/initializers/maglev.rb
|
|
38
|
-
You can also modify your theme (
|
|
39
|
-
and generate new sections with rails g maglev:section
|
|
39
|
+
You can now tweak the settings in `config/initializers/maglev.rb`.
|
|
40
|
+
You can also modify your theme (`app/theme` and `app/views/theme`)
|
|
41
|
+
and generate new sections with `rails g maglev:section`.
|
|
40
42
|
|
|
41
|
-
👉 The next step is to create a site
|
|
43
|
+
👉 The next step is to create a site with `rails maglev:create_site`.
|
|
42
44
|
|
|
43
|
-
🚨 Don't forget to
|
|
45
|
+
🚨 Don't forget to run the last command in production as well!
|
|
44
46
|
INFO
|
|
45
47
|
end
|
|
46
48
|
end
|
|
@@ -47,9 +47,10 @@ Maglev.configure do |config|
|
|
|
47
47
|
# config.is_authenticated = :editor_allowed? # name of any protected method from your Rails application controller
|
|
48
48
|
# config.is_authenticated = ->(site) { current_user&.role == 'editor' }
|
|
49
49
|
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
# Base class for Maglev::ApplicationController (inherit auth, layout, etc. from your app).
|
|
51
|
+
# Set this in an initializer that runs before Maglev controllers are loaded
|
|
52
|
+
# if you use something other than ApplicationController.
|
|
53
|
+
# config.parent_controller = 'ApplicationController'
|
|
53
54
|
|
|
54
55
|
# Uploader engine (:active_storage is the default one)
|
|
55
56
|
# Checkout https://github.com/MarsBased/maglevcms-shrine for an example of a custom uploader
|
|
@@ -67,6 +68,12 @@ Maglev.configure do |config|
|
|
|
67
68
|
# }
|
|
68
69
|
# }
|
|
69
70
|
|
|
71
|
+
# Pagination configuration
|
|
72
|
+
# config.pagination = {
|
|
73
|
+
# pages: -1 # -1 means no pagination
|
|
74
|
+
# assets: 16 # number of assets displayed per page in the assets modal
|
|
75
|
+
# }
|
|
76
|
+
|
|
70
77
|
# Let your content editors references existing pages of your application but
|
|
71
78
|
# not served/rendered by Maglev.
|
|
72
79
|
# config.static_pages = [
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maglev
|
|
4
|
+
module ActiveStorage
|
|
5
|
+
# Since assets are uploaed by editors, we can assume they are always safe to serve.
|
|
6
|
+
# Wraps ActiveStorage::Blob only for Maglev's proxy: Active Storage maps image/svg+xml to
|
|
7
|
+
# application/octet-stream globally; we serve SVG as image/svg+xml without changing the host app's config.
|
|
8
|
+
class ServingBlob < SimpleDelegator
|
|
9
|
+
def content_type_for_serving
|
|
10
|
+
return 'image/svg+xml' if svg?
|
|
11
|
+
|
|
12
|
+
__getobj__.content_type_for_serving
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def forced_disposition_for_serving
|
|
16
|
+
return nil if svg?
|
|
17
|
+
|
|
18
|
+
__getobj__.forced_disposition_for_serving
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def svg?
|
|
24
|
+
filename.to_s.downcase.end_with?('.svg') ||
|
|
25
|
+
content_type.to_s == 'image/svg+xml'
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/maglev/config.rb
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Maglev
|
|
4
|
+
DEFAULT_PAGINATION = { pages: -1, assets: 16 }.freeze
|
|
5
|
+
|
|
4
6
|
Config = Struct.new(:primary_color, :title, :favicon, :logo, :back_action,
|
|
5
7
|
:site_publishable, :uploader, :preview_host, :asset_host, :services,
|
|
6
8
|
:collections, :is_authenticated, :ui_locale, :default_site_locales,
|
|
7
|
-
:static_pages, :reserved_paths,
|
|
8
|
-
:admin_username, :admin_password,
|
|
9
|
-
:tailwindcss_folders)
|
|
9
|
+
:static_pages, :reserved_paths, :parent_controller,
|
|
10
|
+
:admin_username, :admin_password, # legacy config
|
|
11
|
+
:tailwindcss_folders, :pagination) do
|
|
12
|
+
# custom setter + helpers for the pagination config
|
|
13
|
+
def pagination=(value)
|
|
14
|
+
self[:pagination] = if value.is_a?(Hash)
|
|
15
|
+
::Maglev::DEFAULT_PAGINATION.merge(value)
|
|
16
|
+
else
|
|
17
|
+
value
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def pagination?(key)
|
|
22
|
+
self[:pagination][key].present? && self[:pagination][key] != -1
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def per_page(key)
|
|
26
|
+
self[:pagination][key]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
10
29
|
end
|
data/lib/maglev/engine.rb
CHANGED
|
@@ -15,7 +15,13 @@ module Maglev
|
|
|
15
15
|
g.factory_bot dir: 'spec/factories'
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
initializer 'maglev.i18n' do |_app|
|
|
19
|
+
Rails.application.config.i18n.load_path += Dir["#{config.root}/config/locales/**/*.yml"]
|
|
20
|
+
end
|
|
21
|
+
|
|
18
22
|
initializer 'maglev.theme_reloader' do |app|
|
|
23
|
+
next unless Maglev.theme_reloader_enabled?
|
|
24
|
+
|
|
19
25
|
require_relative './theme_filesystem_loader'
|
|
20
26
|
theme_path = Rails.root.join('app/theme')
|
|
21
27
|
theme_reloader = app.config.file_watcher.new([], { theme_path.to_s => ['.yml', 'yml'] }) do
|
|
@@ -37,10 +43,6 @@ module Maglev
|
|
|
37
43
|
end
|
|
38
44
|
end
|
|
39
45
|
|
|
40
|
-
initializer 'maglev.i18n' do |_app|
|
|
41
|
-
Rails.application.config.i18n.load_path += Dir["#{config.root}/config/locales/**/*.yml"]
|
|
42
|
-
end
|
|
43
|
-
|
|
44
46
|
def self.importmaps
|
|
45
47
|
@importmaps ||= {
|
|
46
48
|
editor: ::Importmap::Map.new,
|
|
@@ -49,13 +51,15 @@ module Maglev
|
|
|
49
51
|
end
|
|
50
52
|
|
|
51
53
|
initializer 'maglev.assets' do |app|
|
|
52
|
-
app.config.assets
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
if app.config.respond_to?(:assets) && app.config.assets.respond_to?(:paths)
|
|
55
|
+
app.config.assets.paths << Engine.root.join('app/assets/builds')
|
|
56
|
+
app.config.assets.paths << Engine.root.join('app/components')
|
|
57
|
+
app.config.assets.paths << Engine.root.join('app/assets/javascripts')
|
|
58
|
+
app.config.assets.paths << Engine.root.join('vendor/javascript')
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
# required by Sprockets (if used by the main app)
|
|
61
|
+
app.config.assets.precompile += %w[maglev_manifest] if defined?(Sprockets)
|
|
62
|
+
end
|
|
59
63
|
end
|
|
60
64
|
|
|
61
65
|
initializer 'maglev.importmap', after: 'importmap' do |app|
|
data/lib/maglev/version.rb
CHANGED
data/lib/maglev.rb
CHANGED
|
@@ -18,7 +18,7 @@ module Maglev
|
|
|
18
18
|
ServiceContext = Struct.new(:rendering_mode, :controller, :site, keyword_init: true)
|
|
19
19
|
|
|
20
20
|
class << self
|
|
21
|
-
attr_accessor :local_themes
|
|
21
|
+
attr_accessor :local_themes, :theme_reloader_disabled
|
|
22
22
|
|
|
23
23
|
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
24
24
|
def config
|
|
@@ -36,11 +36,13 @@ module Maglev
|
|
|
36
36
|
c.services = {}
|
|
37
37
|
c.default_site_locales = [{ label: 'English', prefix: 'en' }]
|
|
38
38
|
c.is_authenticated = ->(_site) { !Rails.env.production? }
|
|
39
|
-
c.admin_username = nil
|
|
40
|
-
c.admin_password = nil
|
|
39
|
+
c.admin_username = nil # legacy config
|
|
40
|
+
c.admin_password = nil # legacy config
|
|
41
41
|
c.static_pages = []
|
|
42
42
|
c.reserved_paths = []
|
|
43
|
+
c.parent_controller = 'ApplicationController'
|
|
43
44
|
c.tailwindcss_folders = []
|
|
45
|
+
c.pagination = ::Maglev::DEFAULT_PAGINATION.dup
|
|
44
46
|
end
|
|
45
47
|
end
|
|
46
48
|
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
|
@@ -53,6 +55,10 @@ module Maglev
|
|
|
53
55
|
end
|
|
54
56
|
end
|
|
55
57
|
|
|
58
|
+
def parent_controller_class
|
|
59
|
+
(config.parent_controller.presence || 'ApplicationController').constantize
|
|
60
|
+
end
|
|
61
|
+
|
|
56
62
|
def uploader
|
|
57
63
|
case config.uploader
|
|
58
64
|
when Symbol then const_get("::Maglev::#{config.uploader.to_s.classify}")
|
|
@@ -78,6 +84,15 @@ module Maglev
|
|
|
78
84
|
@uuid_as_primary_key = config.options[config.orm][:primary_key_type] == :uuid
|
|
79
85
|
end
|
|
80
86
|
|
|
87
|
+
# disable the theme reloader which is required by the Maglev SaaS plugin
|
|
88
|
+
def disable_theme_reloader
|
|
89
|
+
@theme_reloader_disabled = true
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def theme_reloader_enabled?
|
|
93
|
+
!@theme_reloader_disabled
|
|
94
|
+
end
|
|
95
|
+
|
|
81
96
|
def config_klass
|
|
82
97
|
::Maglev::Config
|
|
83
98
|
end
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Database Migration Testing Automation
|
|
4
|
+
#
|
|
5
|
+
# This Rake task automates the process of testing migrations against multiple
|
|
6
|
+
# databases and Rails versions, generating schema files for each combination.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# # Test all databases with all Rails versions
|
|
10
|
+
# bundle exec rake db:test_all
|
|
11
|
+
#
|
|
12
|
+
# # Test all databases with current Gemfile
|
|
13
|
+
# bundle exec rake db:test:all
|
|
14
|
+
#
|
|
15
|
+
# # Test specific database with current Gemfile
|
|
16
|
+
# bundle exec rake db:test:pg
|
|
17
|
+
# bundle exec rake db:test:sqlite
|
|
18
|
+
# bundle exec rake db:test:mysql
|
|
19
|
+
# bundle exec rake db:test:mariadb
|
|
20
|
+
#
|
|
21
|
+
# # Test with specific Rails version
|
|
22
|
+
# BUNDLE_GEMFILE=Gemfile.rails_7_2 bundle exec rake db:test:all
|
|
23
|
+
#
|
|
24
|
+
# Environment variables:
|
|
25
|
+
# CONTINUE_ON_ERROR=1 - Continue testing other databases even if one fails
|
|
26
|
+
# DEBUG=1 - Show detailed error backtraces
|
|
27
|
+
#
|
|
28
|
+
# The task will:
|
|
29
|
+
# 1. Backup schema.rb (PG schema) before switching databases
|
|
30
|
+
# 2. Restore appropriate schema file for each database
|
|
31
|
+
# 3. Run migrations
|
|
32
|
+
# 4. Generate new schema files
|
|
33
|
+
# 5. Save schemas with appropriate prefixes (schema.sqlite.rb, etc.)
|
|
34
|
+
# 6. Run tests
|
|
35
|
+
# 7. Restore schema.rb to PG version (for Git)
|
|
36
|
+
|
|
37
|
+
require 'fileutils'
|
|
38
|
+
require 'open3'
|
|
39
|
+
|
|
40
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Naming/VariableNumber, Metrics/CyclomaticComplexity
|
|
41
|
+
|
|
42
|
+
DATABASES = {
|
|
43
|
+
pg: {
|
|
44
|
+
env: {
|
|
45
|
+
MAGLEV_APP_DATABASE_USERNAME: ENV.fetch('MAGLEV_APP_DATABASE_USERNAME', ''),
|
|
46
|
+
MAGLEV_APP_DATABASE_PASSWORD: ENV.fetch('MAGLEV_APP_DATABASE_PASSWORD', '')
|
|
47
|
+
},
|
|
48
|
+
schema_file: 'schema.rb',
|
|
49
|
+
backup_file: 'schema.pg.rb'
|
|
50
|
+
},
|
|
51
|
+
sqlite: {
|
|
52
|
+
env: { USE_SQLITE: '1' },
|
|
53
|
+
schema_file: 'schema.sqlite.rb',
|
|
54
|
+
backup_file: 'schema.pg.rb'
|
|
55
|
+
},
|
|
56
|
+
mysql: {
|
|
57
|
+
env: {
|
|
58
|
+
USE_MYSQL: '1',
|
|
59
|
+
MAGLEV_APP_DATABASE_HOST: ENV.fetch('MAGLEV_APP_DATABASE_HOST', '127.0.0.1'),
|
|
60
|
+
MAGLEV_APP_DATABASE_USERNAME: ENV.fetch('MAGLEV_APP_DATABASE_USERNAME', 'start'),
|
|
61
|
+
MAGLEV_APP_DATABASE_PASSWORD: ENV.fetch('MAGLEV_APP_DATABASE_PASSWORD', 'start')
|
|
62
|
+
},
|
|
63
|
+
schema_file: 'schema.mysql.rb',
|
|
64
|
+
backup_file: 'schema.pg.rb'
|
|
65
|
+
},
|
|
66
|
+
mariadb: {
|
|
67
|
+
env: {
|
|
68
|
+
USE_MYSQL: '1',
|
|
69
|
+
MAGLEV_APP_DATABASE_HOST: ENV.fetch('MAGLEV_APP_DATABASE_HOST', '127.0.0.1'),
|
|
70
|
+
MAGLEV_APP_DATABASE_PORT: ENV.fetch('MAGLEV_APP_DATABASE_PORT', '3307'),
|
|
71
|
+
MAGLEV_APP_DATABASE_USERNAME: ENV.fetch('MAGLEV_APP_DATABASE_USERNAME', 'start'),
|
|
72
|
+
MAGLEV_APP_DATABASE_PASSWORD: ENV.fetch('MAGLEV_APP_DATABASE_PASSWORD', 'start')
|
|
73
|
+
},
|
|
74
|
+
schema_file: 'schema.mariadb.rb',
|
|
75
|
+
backup_file: 'schema.pg.rb'
|
|
76
|
+
}
|
|
77
|
+
}.freeze
|
|
78
|
+
|
|
79
|
+
GEMFILES = {
|
|
80
|
+
Gemfile: 'spec/dummy',
|
|
81
|
+
"Gemfile.rails_7_2": 'spec/legacy_dummy'
|
|
82
|
+
}.with_indifferent_access.freeze
|
|
83
|
+
|
|
84
|
+
namespace :db do
|
|
85
|
+
desc 'Test all databases with all Rails versions'
|
|
86
|
+
task test_all: :environment do
|
|
87
|
+
all_passed = true
|
|
88
|
+
|
|
89
|
+
GEMFILES.each do |gemfile, dummy_path|
|
|
90
|
+
next unless File.exist?(gemfile)
|
|
91
|
+
|
|
92
|
+
puts "\n#{'=' * 70}"
|
|
93
|
+
puts "Testing with #{gemfile} (#{dummy_path})"
|
|
94
|
+
puts '=' * 70
|
|
95
|
+
|
|
96
|
+
# Ensure bundle is installed for this Gemfile
|
|
97
|
+
unless ensure_bundle_installed(gemfile)
|
|
98
|
+
puts "❌ Failed to install bundle for #{gemfile}"
|
|
99
|
+
all_passed = false
|
|
100
|
+
next
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
DATABASES.each_key do |db_name|
|
|
104
|
+
success = test_database(db_name, gemfile, dummy_path)
|
|
105
|
+
all_passed = false unless success
|
|
106
|
+
|
|
107
|
+
abort "Failed testing #{db_name} with #{gemfile}" if !ENV['CONTINUE_ON_ERROR'] && !success
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
unless all_passed
|
|
112
|
+
puts "\n❌ Some tests failed. Use CONTINUE_ON_ERROR=1 to continue despite errors."
|
|
113
|
+
exit 1
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
puts "\n✅ All tests passed!"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
namespace :test do
|
|
120
|
+
DATABASES.each_key do |db_name|
|
|
121
|
+
desc "Test #{db_name} database with current Gemfile"
|
|
122
|
+
task db_name.to_sym => :environment do
|
|
123
|
+
gemfile = File.basename(ENV['BUNDLE_GEMFILE'] || 'Gemfile')
|
|
124
|
+
dummy_path = GEMFILES[gemfile] || GEMFILES['Gemfile']
|
|
125
|
+
success = test_database(db_name, gemfile, dummy_path)
|
|
126
|
+
exit 1 unless success
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
desc 'Test all databases with current Gemfile'
|
|
131
|
+
task all: DATABASES.keys.map(&:to_sym) do
|
|
132
|
+
puts "\n✅ All database tests passed!"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def test_database(db_name, gemfile, dummy_path)
|
|
137
|
+
puts "\n#{'-' * 70}"
|
|
138
|
+
puts "Testing #{db_name} with #{gemfile}"
|
|
139
|
+
puts '-' * 70
|
|
140
|
+
|
|
141
|
+
db_config = DATABASES[db_name]
|
|
142
|
+
|
|
143
|
+
root_dir = File.expand_path('../..', __dir__)
|
|
144
|
+
|
|
145
|
+
db_dir = File.join(root_dir, dummy_path, 'db')
|
|
146
|
+
schema_rb = File.join(db_dir, 'schema.rb')
|
|
147
|
+
schema_backup = File.join(db_dir, db_config[:backup_file])
|
|
148
|
+
schema_target = File.join(db_dir, db_config[:schema_file])
|
|
149
|
+
|
|
150
|
+
# Setup environment
|
|
151
|
+
env = {
|
|
152
|
+
BUNDLE_GEMFILE: File.join(root_dir, gemfile),
|
|
153
|
+
RAILS_ENV: 'test'
|
|
154
|
+
}.merge(db_config[:env].reject { |_k, v| v.blank? })
|
|
155
|
+
|
|
156
|
+
begin
|
|
157
|
+
# Step 1: Backup current schema.rb (the PG schema) before switching databases
|
|
158
|
+
if File.exist?(schema_rb) && !File.exist?(schema_backup)
|
|
159
|
+
FileUtils.cp(schema_rb, schema_backup)
|
|
160
|
+
puts "✓ Backed up schema.rb to #{db_config[:backup_file]}"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Step 2: Restore appropriate schema file
|
|
164
|
+
if db_name == :pg
|
|
165
|
+
# For PG, restore from backup if exists, otherwise keep current schema.rb
|
|
166
|
+
if File.exist?(schema_backup)
|
|
167
|
+
FileUtils.cp(schema_backup, schema_rb)
|
|
168
|
+
puts "✓ Restored schema from #{db_config[:backup_file]}"
|
|
169
|
+
end
|
|
170
|
+
elsif File.exist?(schema_target)
|
|
171
|
+
# For other DBs, copy the specific schema file to schema.rb (if it exists)
|
|
172
|
+
FileUtils.cp(schema_target, schema_rb)
|
|
173
|
+
puts "✓ Restored schema from #{db_config[:schema_file]}"
|
|
174
|
+
elsif File.exist?(schema_backup)
|
|
175
|
+
# Fallback to PG schema if specific schema doesn't exist
|
|
176
|
+
FileUtils.cp(schema_backup, schema_rb)
|
|
177
|
+
puts "✓ Restored schema from #{db_config[:backup_file]} (fallback)"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Step 3: Run migrations
|
|
181
|
+
puts 'Running migrations...'
|
|
182
|
+
unless run_command('./bin/rails db:migrate', env, root_dir)
|
|
183
|
+
puts "❌ Migrations failed for #{db_name}"
|
|
184
|
+
return false
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Step 4: Generate schema
|
|
188
|
+
puts 'Generating schema...'
|
|
189
|
+
unless run_command('bin/rails db:schema:dump', env, root_dir)
|
|
190
|
+
puts "❌ Schema generation failed for #{db_name}"
|
|
191
|
+
return false
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Step 5: Save generated schema to the appropriate file
|
|
195
|
+
if db_name == :pg
|
|
196
|
+
# For PG, keep as schema.rb (the committed version) and also backup to schema.pg.rb
|
|
197
|
+
if File.exist?(schema_rb)
|
|
198
|
+
FileUtils.cp(schema_rb, schema_backup)
|
|
199
|
+
puts "✓ Saved schema to #{db_config[:backup_file]}"
|
|
200
|
+
end
|
|
201
|
+
elsif File.exist?(schema_rb)
|
|
202
|
+
# For other DBs, copy generated schema.rb to the specific schema file
|
|
203
|
+
FileUtils.cp(schema_rb, schema_target)
|
|
204
|
+
puts "✓ Saved schema to #{db_config[:schema_file]}"
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Step 6: Run tests
|
|
208
|
+
puts 'Running tests...'
|
|
209
|
+
unless run_command('bundle exec rspec', env, root_dir)
|
|
210
|
+
puts "❌ Tests failed for #{db_name}"
|
|
211
|
+
return false
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
puts "✅ #{db_name} tests passed!"
|
|
215
|
+
true
|
|
216
|
+
rescue StandardError => e
|
|
217
|
+
puts "❌ Error testing #{db_name}: #{e.message}"
|
|
218
|
+
puts e.backtrace.first(5).join("\n") if ENV['DEBUG']
|
|
219
|
+
false
|
|
220
|
+
ensure
|
|
221
|
+
# Step 7: Restore schema.rb from PG backup (for Git)
|
|
222
|
+
if db_name != 'pg' && File.exist?(schema_backup)
|
|
223
|
+
FileUtils.cp(schema_backup, schema_rb)
|
|
224
|
+
puts '✓ Restored schema.rb from PG backup (for Git)'
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def ensure_bundle_installed(gemfile)
|
|
230
|
+
root_dir = File.expand_path('../..', __dir__)
|
|
231
|
+
gemfile_path = File.join(root_dir, gemfile)
|
|
232
|
+
|
|
233
|
+
env = {
|
|
234
|
+
BUNDLE_GEMFILE: gemfile_path
|
|
235
|
+
}
|
|
236
|
+
full_env = ENV.to_h.merge(env).deep_stringify_keys
|
|
237
|
+
|
|
238
|
+
puts "Checking bundle for #{gemfile}..."
|
|
239
|
+
stdout, stderr, status = Open3.capture3(full_env, 'bundle check', chdir: root_dir)
|
|
240
|
+
|
|
241
|
+
if status.success?
|
|
242
|
+
puts "✓ Bundle is up to date for #{gemfile}"
|
|
243
|
+
true
|
|
244
|
+
else
|
|
245
|
+
puts "Installing bundle for #{gemfile}..."
|
|
246
|
+
stdout, stderr, status = Open3.capture3(full_env, 'bundle install', chdir: root_dir)
|
|
247
|
+
|
|
248
|
+
if status.success?
|
|
249
|
+
puts "✓ Bundle installed for #{gemfile}"
|
|
250
|
+
true
|
|
251
|
+
else
|
|
252
|
+
puts "❌ Failed to install bundle for #{gemfile}"
|
|
253
|
+
puts stderr unless stderr.empty?
|
|
254
|
+
puts stdout unless stdout.empty?
|
|
255
|
+
false
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def run_command(command, env, cwd)
|
|
261
|
+
full_env = ENV.to_h.merge(env).deep_stringify_keys
|
|
262
|
+
|
|
263
|
+
stdout, stderr, status = Open3.capture3(full_env, command, chdir: cwd)
|
|
264
|
+
|
|
265
|
+
unless status.success?
|
|
266
|
+
puts "\n#{'=' * 70}"
|
|
267
|
+
puts "Command failed: #{command}"
|
|
268
|
+
puts "Working directory: #{cwd}"
|
|
269
|
+
puts('=' * 70)
|
|
270
|
+
|
|
271
|
+
unless stdout.empty?
|
|
272
|
+
puts "\nSTDOUT:"
|
|
273
|
+
puts stdout
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
unless stderr.empty?
|
|
277
|
+
puts "\nSTDERR:"
|
|
278
|
+
puts stderr
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
puts "\nExit status: #{status.exitstatus}"
|
|
282
|
+
puts "#{'=' * 70}\n"
|
|
283
|
+
return false
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
true
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Naming/VariableNumber, Metrics/CyclomaticComplexity
|