panda-core 0.9.2 → 0.9.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: '08a72a419dd702e8cc8cde2bfea476a283a17280499bb16de53b554ebaf7406c'
4
- data.tar.gz: 4cf2468a51ed77691ab280b2cfb4b0345a2449e4bb61a36560b286cecb311cac
3
+ metadata.gz: '04695145fc8e1f8c8bdd13d1ee35d91e1a3eb470070d265463f72e97f9ddd19c'
4
+ data.tar.gz: 509bd365e3b501f2f34d0add9617f29b757dcb1b2ca6db6d65c6f1dccfc2142f
5
5
  SHA512:
6
- metadata.gz: d8ebe8e076e53a063dc0f7ab565c6b12c299a6765cdd7e9c446716a5de191070ea2da46a75167f384ab4ee5c2fa91753543625e2cbc64f58a9f962725be39042
7
- data.tar.gz: c7d7d8932c8eea8b767343e27a4ce23ac0206c40df9acd6557b5734b2684f16080a2586fc99a63d62b0b548427dd35cf4603219e2d9938f3d704d873da818a39
6
+ metadata.gz: 3187a5adef002caceeda919102eb8f281aad08058bdc53a10be6297fcc78edaeecade4cdd6ead4246d6596f4fdbd1d37c44d1a2b280bb91023de20b6dc9a75c9
7
+ data.tar.gz: 6b7ee0a2d2dc9fcfd4c35bde5217a6390d700085dfdb52417189e702c9906d446d5a8d4f89efb21db8c14bd38962a4f23a7c4748b797f7cc235db349b6d7c7c8
@@ -248,6 +248,16 @@ module Panda
248
248
  options[:src] = source.start_with?("/") ? source : "/assets/#{source}"
249
249
  content_tag(:script, "", options)
250
250
  end
251
+
252
+ def self.last_dummy_asset_report
253
+ # Only expose this in test/CI/development — never in production
254
+ return nil unless Rails.env.test? || ENV["CI"].present?
255
+
256
+ if defined?(Panda::Core::Assets::ReportRegistry) &&
257
+ Panda::Core::Assets::ReportRegistry.present?
258
+ Panda::Core::Assets::ReportRegistry.last
259
+ end
260
+ end
251
261
  end
252
262
  end
253
263
  end
@@ -58,9 +58,10 @@ module Panda
58
58
  end
59
59
 
60
60
  # Static asset middleware for serving public files and JavaScript modules
61
- initializer "panda.core.static_assets", before: :build_middleware_stack do |app|
62
- # Make files in public available to the main app (e.g. /panda-core-assets/panda-logo.png)
63
- app.config.middleware.use Rack::Static,
61
+ # Must run before Propshaft to intercept /panda/* requests
62
+ initializer "panda.core.static_assets" do |app|
63
+ # Serve public assets (CSS, images, etc.)
64
+ app.config.middleware.insert_before Propshaft::Server, Rack::Static,
64
65
  urls: ["/panda-core-assets"],
65
66
  root: Panda::Core::Engine.root.join("public"),
66
67
  header_rules: [
@@ -68,14 +69,9 @@ module Panda
68
69
  [:all, {"Cache-Control" => Rails.env.development? ? "no-cache, no-store, must-revalidate" : "public, max-age=31536000"}]
69
70
  ]
70
71
 
71
- # Make JavaScript files available for importmap
72
- # Serve from app/javascript with proper MIME types
73
- app.config.middleware.use Rack::Static,
74
- urls: ["/panda/core"],
75
- root: Panda::Core::Engine.root.join("app/javascript"),
76
- header_rules: [
77
- [:all, {"Cache-Control" => Rails.env.development? ? "no-cache, no-store, must-revalidate" : "public, max-age=31536000"}]
78
- ]
72
+ # Use ModuleRegistry's custom middleware to serve JavaScript from all registered modules
73
+ # This middleware checks all modules and serves from the first matching location
74
+ app.config.middleware.insert_before Propshaft::Server, Panda::Core::ModuleRegistry::JavaScriptMiddleware
79
75
  end
80
76
 
81
77
  # Auto-compile CSS for test/development environments
@@ -96,11 +92,7 @@ module Panda
96
92
 
97
93
  # Compile CSS with timestamp
98
94
  require "open3"
99
- require "fileutils"
100
95
 
101
- FileUtils.mkdir_p(assets_dir)
102
-
103
- # Get content paths from ModuleRegistry
104
96
  content_paths = Panda::Core::ModuleRegistry.tailwind_content_paths
105
97
  content_flags = content_paths.map { |path| "--content '#{path}'" }.join(" ")
106
98
 
@@ -125,3 +117,14 @@ module Panda
125
117
  end
126
118
  end
127
119
  end
120
+
121
+ # Register Core module with ModuleRegistry for JavaScript serving
122
+ Panda::Core::ModuleRegistry.register(
123
+ gem_name: "panda-core",
124
+ engine: "Panda::Core::Engine",
125
+ paths: {
126
+ views: "app/views/panda/core/**/*.erb",
127
+ components: "app/components/panda/core/**/*.rb"
128
+ # JavaScript paths are auto-discovered from config/importmap.rb
129
+ }
130
+ )
@@ -277,6 +277,99 @@ module Panda
277
277
  nil
278
278
  end
279
279
  end
280
+
281
+ # Custom Rack middleware to serve JavaScript modules from all registered Panda modules
282
+ #
283
+ # This middleware checks all registered modules' app/javascript/panda directories
284
+ # and serves the first matching file. This solves the problem of multiple Rack::Static
285
+ # instances blocking each other.
286
+ #
287
+ class JavaScriptMiddleware
288
+ def initialize(app)
289
+ @app = app
290
+ end
291
+
292
+ def call(env)
293
+ request = Rack::Request.new(env)
294
+ path = request.path_info
295
+
296
+ # Only handle /panda/core/* and /panda/cms/* style JavaScript module requests
297
+ # Skip paths like /panda-core-assets/* (public assets handled by Rack::Static)
298
+ return @app.call(env) unless path.start_with?("/panda/")
299
+
300
+ # Strip /panda/ prefix to get relative path
301
+ # e.g., "/panda/cms/application.js" -> "cms/application.js"
302
+ relative_path = path.sub(%r{^/panda/}, "")
303
+ return @app.call(env) if relative_path.empty?
304
+
305
+ # Try to find the file in registered modules
306
+ file_path = find_javascript_file(relative_path)
307
+
308
+ if file_path && File.file?(file_path)
309
+ puts "[JavaScriptMiddleware] Serving: #{path} from #{file_path}" if ENV["RSPEC_DEBUG"]
310
+ serve_file(file_path, env)
311
+ else
312
+ puts "[JavaScriptMiddleware] Not found: #{path} (tried #{relative_path})" if ENV["RSPEC_DEBUG"]
313
+ @app.call(env)
314
+ end
315
+ rescue => e
316
+ # On error, log and pass to next middleware
317
+ puts "[JavaScriptMiddleware] Error: #{e.message}" if ENV["RSPEC_DEBUG"]
318
+ Rails.logger.error("[ModuleRegistry::JavaScriptMiddleware] Error: #{e.message}\n#{e.backtrace.join("\n")}") if defined?(Rails.logger)
319
+ @app.call(env)
320
+ end
321
+
322
+ private
323
+
324
+ def find_javascript_file(relative_path)
325
+ # Check each registered module's JavaScript directory
326
+ ModuleRegistry.modules.each do |gem_name, info|
327
+ next unless ModuleRegistry.send(:engine_available?, info[:engine])
328
+
329
+ root = ModuleRegistry.send(:engine_root, info[:engine])
330
+ next unless root
331
+
332
+ # Check in app/javascript/panda/
333
+ candidate = root.join("app/javascript/panda", relative_path)
334
+ return candidate.to_s if candidate.exist? && candidate.file?
335
+ end
336
+
337
+ nil
338
+ end
339
+
340
+ def serve_file(file_path, env)
341
+ # Read file content
342
+ content = File.read(file_path)
343
+
344
+ # Determine content type
345
+ content_type = case File.extname(file_path)
346
+ when ".js"
347
+ "application/javascript; charset=utf-8"
348
+ when ".json"
349
+ "application/json; charset=utf-8"
350
+ else
351
+ "text/plain; charset=utf-8"
352
+ end
353
+
354
+ # Determine cache control
355
+ cache_control = if Rails.env.development? || Rails.env.test?
356
+ "no-cache, no-store, must-revalidate"
357
+ else
358
+ "public, max-age=31536000"
359
+ end
360
+
361
+ # Return response
362
+ [
363
+ 200,
364
+ {
365
+ "Content-Type" => content_type,
366
+ "Content-Length" => content.bytesize.to_s,
367
+ "Cache-Control" => cache_control
368
+ },
369
+ [content]
370
+ ]
371
+ end
372
+ end
280
373
  end
281
374
  end
282
375
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Capture and log browser console output for debugging CI failures
4
+ module BrowserConsoleLogger
5
+ def self.included(base)
6
+ base.after do |example|
7
+ # Only log console in CI when test fails
8
+ next unless ENV["CI"] && example.exception
9
+
10
+ if respond_to?(:page) && page.driver.is_a?(Capybara::Cuprite::Driver)
11
+ begin
12
+ console_logs = page.driver.browser.console_messages
13
+
14
+ if console_logs.any?
15
+ puts "\n" + "=" * 80
16
+ puts "BROWSER CONSOLE OUTPUT (#{console_logs.length} messages)"
17
+ puts "=" * 80
18
+
19
+ console_logs.each_with_index do |msg, index|
20
+ type_icon = case msg["type"]
21
+ when "error" then "❌"
22
+ when "warning" then "⚠️"
23
+ when "info" then "ℹ️"
24
+ else "📝"
25
+ end
26
+
27
+ puts "#{index + 1}. [#{msg["type"].upcase}] #{type_icon}"
28
+ puts " Message: #{msg["message"]}"
29
+ puts " Source: #{msg["source"]}" if msg["source"]
30
+ puts " Line: #{msg["line"]}" if msg["line"]
31
+ puts ""
32
+ end
33
+
34
+ puts "=" * 80
35
+ else
36
+ puts "\n⚠️ No console messages captured (browser may not have started)"
37
+ end
38
+ rescue => e
39
+ puts "\n⚠️ Failed to capture console logs: #{e.message}"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ # Include in all system tests
47
+ RSpec.configure do |config|
48
+ config.include BrowserConsoleLogger, type: :system
49
+ end
@@ -32,6 +32,8 @@ Capybara.singleton_class.prepend(Module.new do
32
32
  end)
33
33
 
34
34
  # Configure server host and port
35
+ # This is loaded for all environments
36
+ # → you do not want fixed ports there
35
37
  Capybara.server_host = "127.0.0.1"
36
38
  Capybara.server_port = ENV["CAPYBARA_PORT"]&.to_i # Let Capybara choose if not specified
37
39
 
@@ -15,11 +15,17 @@ RSpec.configure do |config|
15
15
  config.before(:suite) do
16
16
  Capybara.server = :puma_ci
17
17
  Capybara.default_max_wait_time = Integer(ENV.fetch("CAPYBARA_MAX_WAIT_TIME", 5))
18
+
19
+ port = Integer(ENV.fetch("CAPYBARA_PORT", 3001))
18
20
  Capybara.server_host = "127.0.0.1"
21
+ Capybara.server_port = port
22
+ Capybara.app_host = "http://127.0.0.1:#{port}"
19
23
  Capybara.always_include_port = true
20
24
 
21
25
  puts "[CI Config] Capybara.server = #{Capybara.server.inspect}"
26
+ puts "[CI Config] Capybara.app_host = #{Capybara.app_host.inspect}"
22
27
  puts "[CI Config] Capybara.server_host = #{Capybara.server_host.inspect}"
28
+ puts "[CI Config] Capybara.server_port = #{Capybara.server_port.inspect}"
23
29
  puts "[CI Config] Capybara.max_wait = #{Capybara.default_max_wait_time}s"
24
30
  end
25
31
  end
@@ -35,8 +41,8 @@ Capybara.register_server :puma_ci do |app, port, host|
35
41
  Port: port,
36
42
  Threads: "#{min_threads}:#{max_threads}",
37
43
  Workers: 0,
38
- Silent: false,
39
- Verbose: true,
44
+ Silent: !ENV["RSPEC_DEBUG"],
45
+ Verbose: ENV["RSPEC_DEBUG"],
40
46
  PreloadApp: false
41
47
  }
42
48
 
@@ -19,15 +19,18 @@ module Panda
19
19
  module CupriteSetup
20
20
  # Base Cuprite options shared across all drivers
21
21
  def self.base_options
22
+ default_timeout = 2
23
+ default_process_timeout = 2
24
+
22
25
  {
23
26
  window_size: [1440, 1000],
24
27
  inspector: ENV["INSPECTOR"].in?(%w[y 1 yes true]),
25
28
  headless: !ENV["HEADLESS"].in?(%w[n 0 no false]),
26
29
  slowmo: ENV["SLOWMO"]&.to_f || 0,
27
- timeout: ENV["CUPRITE_TIMEOUT"]&.to_i || 2,
30
+ timeout: ENV["CUPRITE_TIMEOUT"]&.to_i || default_timeout,
28
31
  js_errors: true, # IMPORTANT: Report JavaScript errors as test failures
29
32
  ignore_default_browser_options: false,
30
- process_timeout: ENV["CUPRITE_PROCESS_TIMEOUT"]&.to_i || 2,
33
+ process_timeout: ENV["CUPRITE_PROCESS_TIMEOUT"]&.to_i || default_process_timeout,
31
34
  wait_for_network_idle: false, # Don't wait for all network requests
32
35
  pending_connection_errors: false, # Don't fail on pending external connections
33
36
  browser_options: {
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Panda
4
4
  module Core
5
- VERSION = "0.9.2"
5
+ # Version 0.9.4 - CI fixes
6
+ VERSION = "0.9.4"
6
7
  end
7
8
  end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: panda-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.9.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Otaina Limited
8
8
  - James Inman
9
+ autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 1980-01-02 00:00:00.000000000 Z
12
+ date: 2025-11-15 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: image_processing
@@ -193,7 +194,21 @@ dependencies:
193
194
  - !ruby/object:Gem::Version
194
195
  version: '0'
195
196
  - !ruby/object:Gem::Dependency
196
- name: rspec-rails
197
+ name: webrick
198
+ requirement: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - ">="
201
+ - !ruby/object:Gem::Version
202
+ version: '0'
203
+ type: :runtime
204
+ prerelease: false
205
+ version_requirements: !ruby/object:Gem::Requirement
206
+ requirements:
207
+ - - ">="
208
+ - !ruby/object:Gem::Version
209
+ version: '0'
210
+ - !ruby/object:Gem::Dependency
211
+ name: benchmark
197
212
  requirement: !ruby/object:Gem::Requirement
198
213
  requirements:
199
214
  - - ">="
@@ -207,7 +222,7 @@ dependencies:
207
222
  - !ruby/object:Gem::Version
208
223
  version: '0'
209
224
  - !ruby/object:Gem::Dependency
210
- name: rails-controller-testing
225
+ name: rspec-rails
211
226
  requirement: !ruby/object:Gem::Requirement
212
227
  requirements:
213
228
  - - ">="
@@ -221,7 +236,7 @@ dependencies:
221
236
  - !ruby/object:Gem::Version
222
237
  version: '0'
223
238
  - !ruby/object:Gem::Dependency
224
- name: capybara
239
+ name: rails-controller-testing
225
240
  requirement: !ruby/object:Gem::Requirement
226
241
  requirements:
227
242
  - - ">="
@@ -235,7 +250,7 @@ dependencies:
235
250
  - !ruby/object:Gem::Version
236
251
  version: '0'
237
252
  - !ruby/object:Gem::Dependency
238
- name: cuprite
253
+ name: capybara
239
254
  requirement: !ruby/object:Gem::Requirement
240
255
  requirements:
241
256
  - - ">="
@@ -249,7 +264,7 @@ dependencies:
249
264
  - !ruby/object:Gem::Version
250
265
  version: '0'
251
266
  - !ruby/object:Gem::Dependency
252
- name: database_cleaner-active_record
267
+ name: cuprite
253
268
  requirement: !ruby/object:Gem::Requirement
254
269
  requirements:
255
270
  - - ">="
@@ -263,7 +278,7 @@ dependencies:
263
278
  - !ruby/object:Gem::Version
264
279
  version: '0'
265
280
  - !ruby/object:Gem::Dependency
266
- name: shoulda-matchers
281
+ name: database_cleaner-active_record
267
282
  requirement: !ruby/object:Gem::Requirement
268
283
  requirements:
269
284
  - - ">="
@@ -277,7 +292,7 @@ dependencies:
277
292
  - !ruby/object:Gem::Version
278
293
  version: '0'
279
294
  - !ruby/object:Gem::Dependency
280
- name: simplecov
295
+ name: shoulda-matchers
281
296
  requirement: !ruby/object:Gem::Requirement
282
297
  requirements:
283
298
  - - ">="
@@ -291,7 +306,7 @@ dependencies:
291
306
  - !ruby/object:Gem::Version
292
307
  version: '0'
293
308
  - !ruby/object:Gem::Dependency
294
- name: simplecov-json
309
+ name: simplecov
295
310
  requirement: !ruby/object:Gem::Requirement
296
311
  requirements:
297
312
  - - ">="
@@ -305,7 +320,7 @@ dependencies:
305
320
  - !ruby/object:Gem::Version
306
321
  version: '0'
307
322
  - !ruby/object:Gem::Dependency
308
- name: standard
323
+ name: simplecov-json
309
324
  requirement: !ruby/object:Gem::Requirement
310
325
  requirements:
311
326
  - - ">="
@@ -319,7 +334,7 @@ dependencies:
319
334
  - !ruby/object:Gem::Version
320
335
  version: '0'
321
336
  - !ruby/object:Gem::Dependency
322
- name: brakeman
337
+ name: standard
323
338
  requirement: !ruby/object:Gem::Requirement
324
339
  requirements:
325
340
  - - ">="
@@ -333,7 +348,7 @@ dependencies:
333
348
  - !ruby/object:Gem::Version
334
349
  version: '0'
335
350
  - !ruby/object:Gem::Dependency
336
- name: bundler-audit
351
+ name: brakeman
337
352
  requirement: !ruby/object:Gem::Requirement
338
353
  requirements:
339
354
  - - ">="
@@ -347,7 +362,7 @@ dependencies:
347
362
  - !ruby/object:Gem::Version
348
363
  version: '0'
349
364
  - !ruby/object:Gem::Dependency
350
- name: yamllint
365
+ name: bundler-audit
351
366
  requirement: !ruby/object:Gem::Requirement
352
367
  requirements:
353
368
  - - ">="
@@ -501,20 +516,19 @@ files:
501
516
  - lib/panda/core/testing/rails_helper.rb
502
517
  - lib/panda/core/testing/support/authentication_helpers.rb
503
518
  - lib/panda/core/testing/support/authentication_test_helpers.rb
504
- - lib/panda/core/testing/support/generator_spec_helper.rb
505
519
  - lib/panda/core/testing/support/html_helpers.rb
506
520
  - lib/panda/core/testing/support/omniauth_setup.rb
507
521
  - lib/panda/core/testing/support/service_stubs.rb
508
522
  - lib/panda/core/testing/support/setup.rb
509
523
  - lib/panda/core/testing/support/system/better_system_tests.rb
510
- - lib/panda/core/testing/support/system/capybara_setup.rb
524
+ - lib/panda/core/testing/support/system/browser_console_logger.rb
525
+ - lib/panda/core/testing/support/system/capybara_config.rb
511
526
  - lib/panda/core/testing/support/system/ci_capybara_config.rb
512
527
  - lib/panda/core/testing/support/system/cuprite_helpers.rb
513
528
  - lib/panda/core/testing/support/system/cuprite_setup.rb
514
529
  - lib/panda/core/testing/support/system/database_connection_helpers.rb
515
530
  - lib/panda/core/testing/support/system/system_test_helpers.rb
516
531
  - lib/panda/core/version.rb
517
- - lib/tasks/assets.rake
518
532
  - lib/tasks/panda/core/migrations.rake
519
533
  - lib/tasks/panda_core.rake
520
534
  - lib/tasks/panda_core_tasks.rake
@@ -529,6 +543,7 @@ metadata:
529
543
  homepage_uri: https://github.com/tastybamboo/panda-core
530
544
  source_code_uri: https://github.com/tastybamboo/panda-core
531
545
  changelog_uri: https://github.com/tastybamboo/panda-core/blob/main/CHANGELOG.md
546
+ post_install_message:
532
547
  rdoc_options: []
533
548
  require_paths:
534
549
  - lib
@@ -543,7 +558,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
543
558
  - !ruby/object:Gem::Version
544
559
  version: '0'
545
560
  requirements: []
546
- rubygems_version: 3.6.9
561
+ rubygems_version: 3.5.22
562
+ signing_key:
547
563
  specification_version: 4
548
564
  summary: Core libraries and development tools for Tasty Bamboo projects
549
565
  test_files: []
@@ -1,97 +0,0 @@
1
- require "rails/generators"
2
-
3
- module GeneratorSpecHelper
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- before(:each) do
8
- prepare_destination
9
- @original_stdout = $stdout
10
- $stdout = File.new(File::NULL, "w")
11
- end
12
-
13
- after(:each) do
14
- FileUtils.rm_rf(destination_root)
15
- $stdout = @original_stdout
16
- end
17
- end
18
-
19
- def destination_root
20
- @destination_root ||= File.expand_path("../../tmp/generators", __dir__)
21
- end
22
-
23
- def prepare_destination
24
- FileUtils.rm_rf(destination_root)
25
- FileUtils.mkdir_p(destination_root)
26
- end
27
-
28
- def run_generator(args = [])
29
- args = Array(args)
30
- # Use the generator namespace instead of class name
31
- generator_name = described_class.namespace
32
- Rails::Generators.invoke(generator_name, args, destination_root: destination_root)
33
- end
34
-
35
- def generator
36
- @generator ||= described_class.new([], destination_root: destination_root)
37
- end
38
-
39
- def file_exists?(path)
40
- File.exist?(File.join(destination_root, path))
41
- end
42
-
43
- def read_file(path)
44
- File.read(File.join(destination_root, path))
45
- end
46
-
47
- private
48
-
49
- def capture(stream)
50
- stream = stream.to_s
51
- captured_stream = StringIO.new
52
-
53
- # Map stream names to their global variables to avoid eval
54
- streams = {
55
- "stdout" => $stdout,
56
- "stderr" => $stderr,
57
- "stdin" => $stdin
58
- }
59
-
60
- original_stream = streams[stream]
61
- case stream
62
- when "stdout"
63
- $stdout = captured_stream
64
- when "stderr"
65
- $stderr = captured_stream
66
- when "stdin"
67
- $stdin = captured_stream
68
- else
69
- raise ArgumentError, "Unsupported stream: #{stream}"
70
- end
71
-
72
- yield
73
- captured_stream.string
74
- ensure
75
- case stream
76
- when "stdout"
77
- $stdout = original_stream
78
- when "stderr"
79
- $stderr = original_stream
80
- when "stdin"
81
- $stdin = original_stream
82
- end
83
- end
84
- end
85
-
86
- RSpec.configure do |config|
87
- config.include GeneratorSpecHelper, type: :generator
88
-
89
- # Ensure generator tests have a clean environment
90
- config.before(:each, type: :generator) do
91
- prepare_destination
92
- end
93
-
94
- config.after(:each, type: :generator) do
95
- FileUtils.rm_rf(destination_root) if defined?(destination_root)
96
- end
97
- end
@@ -1,159 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #
4
- # Panda Core Asset Tasks
5
- #
6
-
7
- namespace :panda do
8
- namespace :core do
9
- namespace :assets do
10
- # =========================================================
11
- # 1) ENGINE CSS COMPILATION (unchanged, just cleaned)
12
- # =========================================================
13
-
14
- desc "Compile Panda Core CSS assets (development mode)"
15
- task :compile do
16
- puts "🐼 Compiling Panda Core CSS assets..."
17
-
18
- output_dir = panda_engine_root.join("public", "panda-core-assets")
19
- FileUtils.mkdir_p(output_dir)
20
-
21
- compile_css_development(output_dir)
22
-
23
- puts "🎉 CSS compiled → #{output_dir}"
24
- end
25
-
26
- desc "Compile and version Panda Core CSS assets for release"
27
- task :release do
28
- require_relative "../../panda/core/version"
29
-
30
- version = Panda::Core::VERSION
31
- output_dir = panda_engine_root.join("public", "panda-core-assets")
32
- FileUtils.mkdir_p(output_dir)
33
-
34
- compile_css_release(output_dir, version)
35
-
36
- puts "🎉 Release assets compiled"
37
- end
38
-
39
- # =========================================================
40
- # CSS INTERNAL HELPERS
41
- # =========================================================
42
-
43
- def panda_engine_root
44
- Panda::Core::Engine.root
45
- end
46
-
47
- def compile_css_development(output_dir)
48
- input = panda_engine_root.join("app/assets/tailwind/application.css")
49
- output = output_dir.join("panda-core.css")
50
-
51
- content = Panda::Core::ModuleRegistry.tailwind_content_paths
52
- flags = content.map { |p| "--content '#{p}'" }.join(" ")
53
-
54
- cmd = "bundle exec tailwindcss -i #{input} -o #{output} #{flags} --minify"
55
- abort("❌ CSS compile failed") unless system(cmd)
56
-
57
- puts " ✓ #{output.basename} (#{File.size(output)} bytes)"
58
- end
59
-
60
- def compile_css_release(output_dir, version)
61
- input = panda_engine_root.join("app/assets/tailwind/application.css")
62
- file = output_dir.join("panda-core-#{version}.css")
63
-
64
- content = Panda::Core::ModuleRegistry.tailwind_content_paths
65
- flags = content.map { |p| "--content '#{p}'" }.join(" ")
66
-
67
- cmd = "bundle exec tailwindcss -i #{input} -o #{file} #{flags} --minify"
68
- abort("❌ CSS release compile failed") unless system(cmd)
69
-
70
- symlink = output_dir.join("panda-core.css")
71
- FileUtils.rm_f(symlink)
72
- FileUtils.ln_sf(file.basename, symlink)
73
-
74
- puts " ✓ Versioned file + symlink created"
75
- end
76
-
77
- # =========================================================
78
- # 2) DUMMY APP PREP (FOR SYSTEM TESTS + CI)
79
- # =========================================================
80
-
81
- desc "Compile Panda Core + dummy app assets into spec/dummy/public/assets"
82
- task compile_dummy: :environment do
83
- dummy_root = find_dummy_root
84
- assets_root = dummy_root.join("public/assets")
85
-
86
- puts "🚧 Compiling dummy assets..."
87
- puts " → #{assets_root}"
88
- FileUtils.mkdir_p(assets_root)
89
-
90
- Dir.chdir(dummy_root) do
91
- # IMPORTANT: this is now the correct task name
92
- unless system("bundle exec rake panda:core:assets:compile")
93
- abort("❌ panda:core:assets:compile failed in dummy app")
94
- end
95
-
96
- # Propshaft (Rails 8)
97
- abort("❌ assets:precompile failed") \
98
- unless system("bundle exec rake assets:precompile RAILS_ENV=#{Rails.env}")
99
- end
100
-
101
- puts "✅ Dummy assets compiled"
102
- end
103
-
104
- desc "Generate importmap.json for the dummy app"
105
- task generate_dummy_importmap: :environment do
106
- dummy_root = find_dummy_root
107
- output = dummy_root.join("public/assets/importmap.json")
108
-
109
- puts "🗺️ Generating importmap.json..."
110
- FileUtils.mkdir_p(output.dirname)
111
-
112
- Dir.chdir(dummy_root) do
113
- json = Rails.application.importmap.to_json(
114
- resolver: ActionController::Base.helpers
115
- )
116
- File.write(output, JSON.pretty_generate(json))
117
- end
118
-
119
- puts " ✓ importmap.json written"
120
- end
121
-
122
- desc "Verify dummy assets for CI (fail-fast)"
123
- task verify_dummy: :environment do
124
- dummy_root = find_dummy_root
125
- assets = dummy_root.join("public/assets")
126
- manifest = assets.join(".manifest.json")
127
- importmap = assets.join("importmap.json")
128
-
129
- abort("❌ Missing #{assets}") unless assets.exist?
130
- abort("❌ Missing #{manifest}") unless manifest.exist?
131
- abort("❌ Missing #{importmap}") unless importmap.exist?
132
-
133
- begin
134
- parsed = JSON.parse(File.read(manifest))
135
- abort("❌ Empty .manifest.json") if parsed.empty?
136
- rescue
137
- abort("❌ Invalid .manifest.json")
138
- end
139
-
140
- puts "✅ Dummy assets verified"
141
- end
142
-
143
- # =========================================================
144
- # INTERNAL UTILITIES
145
- # =========================================================
146
-
147
- def find_dummy_root
148
- root = Rails.root
149
-
150
- return root if root.basename.to_s == "dummy"
151
-
152
- candidate = root.join("spec/dummy")
153
- return candidate if candidate.exist?
154
-
155
- abort("❌ Cannot find dummy root — expected #{candidate}")
156
- end
157
- end
158
- end
159
- end