panda-core 0.7.2 → 0.7.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: abf323ca5beed58dcb17cd7f39ed76028413524b20e40cd2de8d9c5d21b97fff
4
- data.tar.gz: 992d91647d9cd2567b7d818beb2f7a2d173fb16edf8f78f222a576a1e6b9534f
3
+ metadata.gz: a2cf8c2a2e3d0ee0034e6d066e6b0091a91ac26b45175137bd20e55b428d0b27
4
+ data.tar.gz: dfce6c83389d46e65195b10ac115d6fde5ae27eb7aeaab791e102b12aa428917
5
5
  SHA512:
6
- metadata.gz: 42f8a466d633d31ee8e77650c0afcd9f768ef7990df74b185d3722e614a623f176944a610bee1ca4eee60f33edba812eda96b0b25f5e9466773c1f1920bd1213
7
- data.tar.gz: a0f9ec858050df85d6a9cac0a04dc179673ad5438d8bd6c9deeb1ee0d52c5a1d0f5c2e0af9784cc717fcd59a987efd79f9ecc55d3d2456ba842d5056acff363b
6
+ metadata.gz: 733a874eeff0435314e2fde0ec445a35b13c3f5623bb4bb711d6bc6a5709bdc9bf07699cfa7c241868f3157c0421c3993b91a1f43c7704378aae0f7e07c5f74e
7
+ data.tar.gz: 3fed89c4393a1b33ab120308a40657189719c1a9b956afb822823eefda663989a259f769535fb00a587dd9e3136d70e6c0fcb4b8ceb0dbac4cc4b8511b001ff1
@@ -31,14 +31,15 @@ module Panda
31
31
 
32
32
  def render_avatar
33
33
  has_image = resolved_user.respond_to?(:avatar_url) &&
34
- resolved_user.avatar_url.present?
34
+ resolved_user.avatar_url(size: :small).present?
35
35
 
36
36
  if has_image
37
37
  div do
38
38
  img(
39
39
  class: "inline-block w-10 h-10 rounded-full object-cover",
40
- src: resolved_user.avatar_url,
41
- alt: ""
40
+ src: resolved_user.avatar_url(size: :small),
41
+ alt: "",
42
+ loading: "lazy"
42
43
  )
43
44
  end
44
45
  else
@@ -5,8 +5,13 @@ module Panda
5
5
  class User < ApplicationRecord
6
6
  self.table_name = "panda_core_users"
7
7
 
8
- # Active Storage attachment for avatar
9
- has_one_attached :avatar
8
+ # Active Storage attachment for avatar with variants
9
+ has_one_attached :avatar do |attachable|
10
+ attachable.variant :thumb, resize_to_limit: [50, 50], preprocessed: true
11
+ attachable.variant :small, resize_to_limit: [100, 100], preprocessed: true
12
+ attachable.variant :medium, resize_to_limit: [200, 200], preprocessed: true
13
+ attachable.variant :large, resize_to_limit: [400, 400], preprocessed: true
14
+ end
10
15
 
11
16
  validates :email, presence: true, uniqueness: {case_sensitive: false}
12
17
 
@@ -90,9 +95,15 @@ module Panda
90
95
 
91
96
  # Returns the URL for the user's avatar
92
97
  # Prefers Active Storage attachment over OAuth provider URL
93
- def avatar_url
98
+ # @param size [Symbol] The variant size (:thumb, :small, :medium, :large, or nil for original)
99
+ # @return [String, nil] The avatar URL or nil if no avatar available
100
+ def avatar_url(size: nil)
94
101
  if avatar.attached?
95
- Rails.application.routes.url_helpers.rails_blob_path(avatar, only_path: true)
102
+ if size && [:thumb, :small, :medium, :large].include?(size)
103
+ Rails.application.routes.url_helpers.rails_blob_path(avatar.variant(size), only_path: true)
104
+ else
105
+ Rails.application.routes.url_helpers.rails_blob_path(avatar, only_path: true)
106
+ end
96
107
  elsif self[:image_url].present?
97
108
  # Fallback to OAuth provider URL if no avatar is attached yet
98
109
  self[:image_url]
@@ -5,6 +5,9 @@ require "open-uri"
5
5
  module Panda
6
6
  module Core
7
7
  class AttachAvatarService < Services::BaseService
8
+ MAX_FILE_SIZE = 5.megabytes
9
+ MAX_DIMENSION = 800 # Max width/height for optimization
10
+
8
11
  def initialize(user:, avatar_url:)
9
12
  @user = user
10
13
  @avatar_url = avatar_url
@@ -33,25 +36,67 @@ module Panda
33
36
  URI.open(@avatar_url, read_timeout: 10, open_timeout: 10, redirect: true) do |downloaded_file|
34
37
  # standard:enable Security/Open
35
38
  # Validate file size (max 5MB)
36
- if downloaded_file.size > 5.megabytes
39
+ if downloaded_file.size > MAX_FILE_SIZE
37
40
  raise "Avatar file too large (#{downloaded_file.size} bytes)"
38
41
  end
39
42
 
40
43
  # Determine content type and filename
41
44
  content_type = downloaded_file.content_type || "image/jpeg"
42
- extension = determine_extension(content_type)
43
- filename = "avatar_#{@user.id}_#{Time.current.to_i}#{extension}"
44
45
 
45
- # Attach the avatar
46
+ # Optimize and attach the avatar
47
+ optimized_file = optimize_image(downloaded_file, content_type)
48
+
49
+ filename = "avatar_#{@user.id}_#{Time.current.to_i}.webp"
50
+
51
+ # Attach the optimized avatar
46
52
  @user.avatar.attach(
47
- io: downloaded_file,
53
+ io: optimized_file,
48
54
  filename: filename,
49
- content_type: content_type
55
+ content_type: "image/webp"
50
56
  )
51
57
  end
52
58
  # standard:enable Security/Open
53
59
  end
54
60
 
61
+ def optimize_image(file, content_type)
62
+ processor = Panda::Core.config.avatar_image_processor
63
+ loader = load_image_processor(processor)
64
+ return file unless loader
65
+
66
+ begin
67
+ processed = loader
68
+ .source(file)
69
+ .resize_to_limit(MAX_DIMENSION, MAX_DIMENSION)
70
+ .convert("webp")
71
+ .saver(quality: 85, strip: true) # Strip metadata, 85% quality
72
+ .call
73
+
74
+ # Return File object
75
+ File.open(processed.path)
76
+ rescue => e
77
+ Rails.logger.warn("Image optimization failed (#{processor}), using original: #{e.message}")
78
+ # Fallback to original file if optimization fails
79
+ file
80
+ end
81
+ end
82
+
83
+ def load_image_processor(processor)
84
+ case processor
85
+ when :vips
86
+ require "image_processing/vips"
87
+ ImageProcessing::Vips
88
+ when :mini_magick
89
+ require "image_processing/mini_magick"
90
+ ImageProcessing::MiniMagick
91
+ else
92
+ Rails.logger.warn("Unknown image processor: #{processor}, avatar optimization disabled")
93
+ nil
94
+ end
95
+ rescue LoadError => e
96
+ Rails.logger.warn("Image processor #{processor} not available: #{e.message}")
97
+ nil
98
+ end
99
+
55
100
  def determine_extension(content_type)
56
101
  case content_type
57
102
  when /jpeg|jpg/
@@ -24,7 +24,12 @@ module Panda
24
24
  :login_page_title,
25
25
  :admin_title,
26
26
  :initial_admin_breadcrumb,
27
- :dashboard_redirect_path
27
+ :dashboard_redirect_path,
28
+ :avatar_variants,
29
+ :avatar_max_file_size,
30
+ :avatar_max_dimension,
31
+ :avatar_optimization_quality,
32
+ :avatar_image_processor
28
33
 
29
34
  def initialize
30
35
  @user_class = "Panda::Core::User"
@@ -80,6 +85,18 @@ module Panda
80
85
  @admin_title = "Panda Admin"
81
86
  @initial_admin_breadcrumb = nil # Proc that returns [label, path]
82
87
  @dashboard_redirect_path = nil # Path to redirect to after login (defaults to admin_root_path)
88
+
89
+ # Avatar configuration
90
+ @avatar_variants = {
91
+ thumb: {resize_to_limit: [50, 50]},
92
+ small: {resize_to_limit: [100, 100]},
93
+ medium: {resize_to_limit: [200, 200]},
94
+ large: {resize_to_limit: [400, 400]}
95
+ }
96
+ @avatar_max_file_size = 5.megabytes
97
+ @avatar_max_dimension = 800
98
+ @avatar_optimization_quality = 85
99
+ @avatar_image_processor = :vips # or :mini_magick
83
100
  end
84
101
  end
85
102
 
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ class Engine < ::Rails::Engine
6
+ # AdminController alias configuration
7
+ module AdminControllerConfig
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ # Create AdminController alias after controllers are loaded
12
+ # This allows other gems to inherit from Panda::Core::AdminController
13
+ initializer "panda_core.admin_controller_alias", after: :load_config_initializers do
14
+ ActiveSupport.on_load(:action_controller_base) do
15
+ Panda::Core.const_set(:AdminController, Panda::Core::Admin::BaseController) unless Panda::Core.const_defined?(:AdminController)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ class Engine < ::Rails::Engine
6
+ # Autoload paths configuration
7
+ module AutoloadConfig
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ config.eager_load_namespaces << Panda::Core::Engine
12
+
13
+ # Add engine's app directories to autoload paths
14
+ # Note: Only add the root directories, not nested subdirectories
15
+ # Zeitwerk will automatically discover nested modules from these roots
16
+ config.autoload_paths += Dir[root.join("app", "models")]
17
+ config.autoload_paths += Dir[root.join("app", "controllers")]
18
+ config.autoload_paths += Dir[root.join("app", "builders")]
19
+ config.autoload_paths += Dir[root.join("app", "components")]
20
+ config.autoload_paths += Dir[root.join("app", "services")]
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ class Engine < ::Rails::Engine
6
+ # Generator configuration
7
+ module GeneratorConfig
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ config.generators do |g|
12
+ g.test_framework :rspec
13
+ g.fixture_replacement :factory_bot
14
+ g.factory_bot dir: "spec/factories"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ class Engine < ::Rails::Engine
6
+ # Importmap configuration
7
+ module ImportmapConfig
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ # Add importmap paths from the engine
12
+ initializer "panda_core.importmap", before: "importmap" do |app|
13
+ if app.config.respond_to?(:importmap)
14
+ # Create a new array if frozen
15
+ app.config.importmap.paths = app.config.importmap.paths.dup if app.config.importmap.paths.frozen?
16
+
17
+ # Add our paths
18
+ app.config.importmap.paths << root.join("config/importmap.rb")
19
+
20
+ # Handle cache sweepers similarly
21
+ if app.config.importmap.cache_sweepers.frozen?
22
+ app.config.importmap.cache_sweepers = app.config.importmap.cache_sweepers.dup
23
+ end
24
+ app.config.importmap.cache_sweepers << root.join("app/javascript")
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ class Engine < ::Rails::Engine
6
+ # Inflections configuration
7
+ module InflectionsConfig
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ # Load inflections early to ensure proper constant resolution
12
+ initializer "panda_core.inflections", before: :load_config_initializers do
13
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
14
+ inflect.acronym "CMS"
15
+ inflect.acronym "SEO"
16
+ inflect.acronym "AI"
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ class Engine < ::Rails::Engine
6
+ # Middleware configuration for static assets
7
+ module MiddlewareConfig
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ # Make files in public available to the main app (e.g. /panda-core-assets/panda-logo.png)
12
+ config.middleware.use Rack::Static,
13
+ urls: ["/panda-core-assets"],
14
+ root: Panda::Core::Engine.root.join("public"),
15
+ header_rules: [
16
+ # Disable caching in development for instant CSS updates
17
+ [:all, {"Cache-Control" => Rails.env.development? ? "no-cache, no-store, must-revalidate" : "public, max-age=31536000"}]
18
+ ]
19
+
20
+ # Make JavaScript files available for importmap
21
+ # Serve from app/javascript with proper MIME types
22
+ config.middleware.use Rack::Static,
23
+ urls: ["/panda", "/panda/core"],
24
+ root: Panda::Core::Engine.root.join("app/javascript"),
25
+ header_rules: [
26
+ [:all, {"Cache-Control" => Rails.env.development? ? "no-cache, no-store, must-revalidate" : "public, max-age=31536000",
27
+ "Content-Type" => "text/javascript; charset=utf-8"}]
28
+ ]
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ class Engine < ::Rails::Engine
6
+ # OmniAuth configuration
7
+ module OmniauthConfig
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ initializer "panda_core.omniauth" do |app|
12
+ # Load OAuth provider gems
13
+ require_relative "../oauth_providers"
14
+ Panda::Core::OAuthProviders.setup
15
+
16
+ # Mount OmniAuth at configurable admin path
17
+ app.middleware.use OmniAuth::Builder do
18
+ # Configure OmniAuth to use the configured admin path
19
+ configure do |config|
20
+ config.path_prefix = "#{Panda::Core.config.admin_path}/auth"
21
+ # POST-only for CSRF protection (CVE-2015-9284)
22
+ # All login forms use POST via form_tag method: "post"
23
+ config.allowed_request_methods = [:post]
24
+ end
25
+
26
+ Panda::Core.config.authentication_providers.each do |provider_name, settings|
27
+ # Build provider options, allowing custom path name override
28
+ provider_options = settings[:options] || {}
29
+
30
+ # If path_name is specified, use it to override the default strategy name in URLs
31
+ if settings[:path_name].present?
32
+ provider_options = provider_options.merge(name: settings[:path_name])
33
+ end
34
+
35
+ case provider_name.to_s
36
+ when "microsoft_graph"
37
+ provider :microsoft_graph, settings[:client_id], settings[:client_secret], provider_options
38
+ when "google_oauth2"
39
+ provider :google_oauth2, settings[:client_id], settings[:client_secret], provider_options
40
+ when "github"
41
+ provider :github, settings[:client_id], settings[:client_secret], provider_options
42
+ when "developer"
43
+ provider :developer if Rails.env.development?
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ class Engine < ::Rails::Engine
6
+ # Phlex configuration
7
+ module PhlexConfig
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ # Load Phlex base component after Rails application is initialized
12
+ # This ensures Rails.application.routes is available
13
+ initializer "panda_core.phlex_base", after: :load_config_initializers do
14
+ require "phlex"
15
+ require "phlex-rails"
16
+ require "literal"
17
+ require "tailwind_merge"
18
+
19
+ # Load the base component
20
+ require root.join("app/components/panda/core/base")
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ class Engine < ::Rails::Engine
6
+ # Test environment configuration
7
+ module TestConfig
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ # For testing: Don't expose engine migrations since we use "copy to host app" strategy
12
+ # In test environment, migrations should be copied to the host app
13
+ if Rails.env.test?
14
+ config.paths["db/migrate"] = []
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -19,131 +19,36 @@ ensure
19
19
  $stderr = original_stderr
20
20
  end
21
21
 
22
+ # Load engine configuration modules
23
+ require_relative "engine/test_config"
24
+ require_relative "engine/autoload_config"
25
+ require_relative "engine/middleware_config"
26
+ require_relative "engine/importmap_config"
27
+ require_relative "engine/omniauth_config"
28
+ require_relative "engine/phlex_config"
29
+ require_relative "engine/admin_controller_config"
30
+
22
31
  module Panda
23
32
  module Core
24
33
  class Engine < ::Rails::Engine
25
34
  isolate_namespace Panda::Core
26
35
 
27
- # For testing: Don't expose engine migrations since we use "copy to host app" strategy
28
- # In test environment, migrations should be copied to the host app
29
- if Rails.env.test?
30
- config.paths["db/migrate"] = []
31
- end
32
-
33
- config.eager_load_namespaces << Panda::Core::Engine
36
+ # Include shared configuration modules
37
+ include Shared::InflectionsConfig
38
+ include Shared::GeneratorConfig
34
39
 
35
- # Add engine's app directories to autoload paths
36
- # Note: Only add the root directories, not nested subdirectories
37
- # Zeitwerk will automatically discover nested modules from these roots
38
- config.autoload_paths += Dir[root.join("app", "models")]
39
- config.autoload_paths += Dir[root.join("app", "controllers")]
40
- config.autoload_paths += Dir[root.join("app", "builders")]
41
- config.autoload_paths += Dir[root.join("app", "components")]
42
- config.autoload_paths += Dir[root.join("app", "services")]
43
-
44
- # Make files in public available to the main app (e.g. /panda-core-assets/panda-logo.png)
45
- config.middleware.use Rack::Static,
46
- urls: ["/panda-core-assets"],
47
- root: Panda::Core::Engine.root.join("public"),
48
- header_rules: [
49
- # Disable caching in development for instant CSS updates
50
- [:all, {"Cache-Control" => Rails.env.development? ? "no-cache, no-store, must-revalidate" : "public, max-age=31536000"}]
51
- ]
52
-
53
- # Make JavaScript files available for importmap
54
- # Serve from app/javascript with proper MIME types
55
- config.middleware.use Rack::Static,
56
- urls: ["/panda", "/panda/core"],
57
- root: Panda::Core::Engine.root.join("app/javascript"),
58
- header_rules: [
59
- [:all, {"Cache-Control" => Rails.env.development? ? "no-cache, no-store, must-revalidate" : "public, max-age=31536000",
60
- "Content-Type" => "text/javascript; charset=utf-8"}]
61
- ]
62
-
63
- config.generators do |g|
64
- g.test_framework :rspec
65
- g.fixture_replacement :factory_bot
66
- g.factory_bot dir: "spec/factories"
67
- end
40
+ # Include engine-specific configuration modules
41
+ include TestConfig
42
+ include AutoloadConfig
43
+ include MiddlewareConfig
44
+ include ImportmapConfig
45
+ include OmniauthConfig
46
+ include PhlexConfig
47
+ include AdminControllerConfig
68
48
 
69
49
  initializer "panda_core.config" do |app|
70
50
  # Configuration is already initialized with defaults in Configuration class
71
51
  end
72
-
73
- # Add importmap paths from the engine
74
- initializer "panda_core.importmap", before: "importmap" do |app|
75
- if app.config.respond_to?(:importmap)
76
- # Create a new array if frozen
77
- app.config.importmap.paths = app.config.importmap.paths.dup if app.config.importmap.paths.frozen?
78
-
79
- # Add our paths
80
- app.config.importmap.paths << root.join("config/importmap.rb")
81
-
82
- # Handle cache sweepers similarly
83
- if app.config.importmap.cache_sweepers.frozen?
84
- app.config.importmap.cache_sweepers = app.config.importmap.cache_sweepers.dup
85
- end
86
- app.config.importmap.cache_sweepers << root.join("app/javascript")
87
- end
88
- end
89
-
90
- initializer "panda_core.omniauth" do |app|
91
- # Load OAuth provider gems
92
- require_relative "oauth_providers"
93
- Panda::Core::OAuthProviders.setup
94
-
95
- # Mount OmniAuth at configurable admin path
96
- app.middleware.use OmniAuth::Builder do
97
- # Configure OmniAuth to use the configured admin path
98
- configure do |config|
99
- config.path_prefix = "#{Panda::Core.config.admin_path}/auth"
100
- # POST-only for CSRF protection (CVE-2015-9284)
101
- # All login forms use POST via form_tag method: "post"
102
- config.allowed_request_methods = [:post]
103
- end
104
-
105
- Panda::Core.config.authentication_providers.each do |provider_name, settings|
106
- # Build provider options, allowing custom path name override
107
- provider_options = settings[:options] || {}
108
-
109
- # If path_name is specified, use it to override the default strategy name in URLs
110
- if settings[:path_name].present?
111
- provider_options = provider_options.merge(name: settings[:path_name])
112
- end
113
-
114
- case provider_name.to_s
115
- when "microsoft_graph"
116
- provider :microsoft_graph, settings[:client_id], settings[:client_secret], provider_options
117
- when "google_oauth2"
118
- provider :google_oauth2, settings[:client_id], settings[:client_secret], provider_options
119
- when "github"
120
- provider :github, settings[:client_id], settings[:client_secret], provider_options
121
- when "developer"
122
- provider :developer if Rails.env.development?
123
- end
124
- end
125
- end
126
- end
127
-
128
- # Load Phlex base component after Rails application is initialized
129
- # This ensures Rails.application.routes is available
130
- initializer "panda_core.phlex_base", after: :load_config_initializers do
131
- require "phlex"
132
- require "phlex-rails"
133
- require "literal"
134
- require "tailwind_merge"
135
-
136
- # Load the base component
137
- require root.join("app/components/panda/core/base")
138
- end
139
-
140
- # Create AdminController alias after controllers are loaded
141
- # This allows other gems to inherit from Panda::Core::AdminController
142
- initializer "panda_core.admin_controller_alias", after: :load_config_initializers do
143
- ActiveSupport.on_load(:action_controller_base) do
144
- Panda::Core.const_set(:AdminController, Panda::Core::Admin::BaseController) unless Panda::Core.const_defined?(:AdminController)
145
- end
146
- end
147
52
  end
148
53
  end
149
54
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ module Shared
6
+ # Shared generator configuration for all panda gems
7
+ # This ensures consistent generator behavior across the ecosystem
8
+ module GeneratorConfig
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ config.generators do |g|
13
+ g.orm :active_record, primary_key_type: :uuid
14
+ g.test_framework :rspec, fixture: true
15
+ g.fixture_replacement nil
16
+ g.view_specs false
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ module Shared
6
+ # Shared inflections configuration for all panda gems
7
+ # Ensures consistent constant naming across the ecosystem
8
+ module InflectionsConfig
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ # Load inflections early to ensure proper constant resolution
13
+ initializer "panda.inflections", before: :load_config_initializers do
14
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
15
+ inflect.acronym "CMS"
16
+ inflect.acronym "SEO"
17
+ inflect.acronym "AI"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Panda
4
4
  module Core
5
- VERSION = "0.7.2"
5
+ VERSION = "0.7.4"
6
6
  end
7
7
  end
data/lib/panda/core.rb CHANGED
@@ -15,4 +15,6 @@ require_relative "core/configuration"
15
15
  require_relative "core/asset_loader"
16
16
  require_relative "core/debug"
17
17
  require_relative "core/services/base_service"
18
+ require_relative "core/shared/inflections_config"
19
+ require_relative "core/shared/generator_config"
18
20
  require_relative "core/engine" if defined?(Rails)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: panda-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 0.7.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Otaina Limited
@@ -10,6 +10,20 @@ bindir: bin
10
10
  cert_chain: []
11
11
  date: 1980-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: image_processing
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: importmap-rails
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -461,6 +475,15 @@ files:
461
475
  - lib/panda/core/configuration.rb
462
476
  - lib/panda/core/debug.rb
463
477
  - lib/panda/core/engine.rb
478
+ - lib/panda/core/engine/admin_controller_config.rb
479
+ - lib/panda/core/engine/autoload_config.rb
480
+ - lib/panda/core/engine/generator_config.rb
481
+ - lib/panda/core/engine/importmap_config.rb
482
+ - lib/panda/core/engine/inflections_config.rb
483
+ - lib/panda/core/engine/middleware_config.rb
484
+ - lib/panda/core/engine/omniauth_config.rb
485
+ - lib/panda/core/engine/phlex_config.rb
486
+ - lib/panda/core/engine/test_config.rb
464
487
  - lib/panda/core/media.rb
465
488
  - lib/panda/core/notifications.rb
466
489
  - lib/panda/core/oauth_providers.rb
@@ -468,6 +491,8 @@ files:
468
491
  - lib/panda/core/rake_tasks.rb
469
492
  - lib/panda/core/seo.rb
470
493
  - lib/panda/core/services/base_service.rb
494
+ - lib/panda/core/shared/generator_config.rb
495
+ - lib/panda/core/shared/inflections_config.rb
471
496
  - lib/panda/core/sluggable.rb
472
497
  - lib/panda/core/subscribers/authentication_subscriber.rb
473
498
  - lib/panda/core/testing/rails_helper.rb