spree_dashboard 5.6.0.rc1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 00ab70dc5556a8ef8e044c41b7b1b420b99cdfb1677e743832886c4aba4f07e7
4
+ data.tar.gz: f29e4be23eaa1f634cdb29cda7c971c5397aa22f13bbce6e4285a73b8f9fe60f
5
+ SHA512:
6
+ metadata.gz: 2853926c77a77f4a9f58efb12fc9ccf189a7dd7000596cb712ccc56c1f7471fb6e205ce35e545f53b2b23d5e77d7e8b6e81b8697f858a7c101b55f4da0af02a8
7
+ data.tar.gz: ce2e2d80e83f35e8f6fd9815a409549818818601b807039065e1d4ddd3798421852eeab6c8fcf944933b3dbf53c4d064fff9294a356c2419aaf4ab8da2b860b2
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # spree_dashboard
2
+
3
+ Hosts the [Spree React Dashboard](https://spreecommerce.org/docs/developer/dashboard/overview) from your Spree server — the **single-node topology**: the dashboard and the Admin API share one origin, so there are no CORS entries to maintain and auth cookies stay `SameSite=Lax`.
4
+
5
+ > **Developer Preview.** In Spree 6 this gem becomes the default admin delivery. The path is permanent: the dashboard lives at `/dashboard`, and `/admin` stays reserved for the classic `spree_admin`.
6
+
7
+ ## What it does
8
+
9
+ Serves a built dashboard (`vite build` output) at **`/dashboard`** with SPA semantics: every dashboard route falls back to `index.html` (no-cache), Vite's content-hashed `assets/` get immutable caching, and paths are traversal-guarded. The bundle is public client code, so there's no authentication on the route — the SPA authenticates its API calls itself.
10
+
11
+ ## Setup
12
+
13
+ ```ruby
14
+ # Gemfile
15
+ gem 'spree_dashboard'
16
+ ```
17
+
18
+ Point it at a built dashboard, either via env (what the official Docker image and the Render Blueprint use):
19
+
20
+ ```bash
21
+ SPREE_DASHBOARD_DIST_PATH=/rails/dashboard
22
+ ```
23
+
24
+ or an initializer:
25
+
26
+ ```ruby
27
+ # config/initializers/spree.rb
28
+ Spree::Dashboard.dist_path = Rails.root.join('dashboard/dist').to_s
29
+ ```
30
+
31
+ Unset, `/dashboard` responds 404.
32
+
33
+ The bundle must be built for sub-path mounting with a relative API base: `VITE_BASE_PATH=/dashboard/ pnpm build` (and `VITE_SPREE_API_URL` unset) — see the [deployment docs](https://spreecommerce.org/docs/developer/dashboard/deployment) for the full picture, including baking it into a Docker image.
34
+
35
+ ## Testing
36
+
37
+ ```bash
38
+ bundle install
39
+ bundle exec rake test_app # generates the dummy app (skip if present)
40
+ bundle exec rspec
41
+ ```
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rspec/core/rake_task'
5
+ require 'spree/testing_support/common_rake'
6
+
7
+ RSpec::Core::RakeTask.new
8
+
9
+ task default: :spec
10
+
11
+ desc "Generates a dummy app for testing"
12
+ task :test_app do
13
+ ENV['LIB_NAME'] = 'spree/dashboard'
14
+ Rake::Task['common:test_app'].invoke
15
+ end
@@ -0,0 +1,78 @@
1
+ module Spree
2
+ module Dashboard
3
+ # Serves a built React Dashboard (`vite build` output) at /dashboard — the
4
+ # single-node topology: the SPA and the Admin API share one origin, so no
5
+ # CORS entries and Lax cookies. The dist directory comes from
6
+ # `Spree::Dashboard.dist_path` (the official Docker image sets it via
7
+ # SPREE_DASHBOARD_DIST_PATH); when unset, /dashboard 404s.
8
+ #
9
+ # SPA semantics: real files are served with long-lived caching (Vite's
10
+ # `assets/` are content-hashed, so they're immutable); every other path
11
+ # falls back to index.html with no-cache so deploys take effect on the
12
+ # next navigation. No authentication — the bundle is public client code;
13
+ # the SPA authenticates its API calls itself.
14
+ class AppController < ActionController::Base
15
+ # GET-only file serving mutates nothing, but enable forgery protection
16
+ # anyway so the controller stays safe if an action is ever added.
17
+ protect_from_forgery with: :exception
18
+ # `verify_same_origin_request` blocks JavaScript responses to plain GET
19
+ # requests — protection against JSONP-style data leaks from dynamically
20
+ # generated JS. The Vite bundle is static public code that the SPA's own
21
+ # <script> tags must load, which is exactly that request shape.
22
+ skip_after_action :verify_same_origin_request
23
+
24
+ def show
25
+ root = dist_root
26
+ return head :not_found unless root
27
+
28
+ if (file = resolve_file(root, params[:path].to_s))
29
+ response.headers['Cache-Control'] = cache_control_for(params[:path].to_s)
30
+ send_file file, disposition: 'inline'
31
+ elsif (index = root.join('index.html')).file?
32
+ response.headers['Cache-Control'] = 'no-cache'
33
+ send_file index, type: 'text/html', disposition: 'inline'
34
+ else
35
+ head :not_found
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def dist_root
42
+ path = Spree::Dashboard.dist_path
43
+ return if path.blank?
44
+
45
+ root = Pathname.new(path).expand_path
46
+ root if root.directory?
47
+ end
48
+
49
+ # Resolve a request path to a real file inside the dist directory.
50
+ # Defense in depth against traversal: reject null bytes, absolute
51
+ # paths, and any `.`/`..` segment before touching the filesystem, then
52
+ # verify the expanded path still lives under the dist root (which also
53
+ # covers symlinks pointing outside it).
54
+ def resolve_file(root, relative_path)
55
+ return if relative_path.blank? || relative_path.include?("\0")
56
+
57
+ relative = Pathname.new(relative_path)
58
+ return if relative.absolute?
59
+
60
+ segments = relative.each_filename.to_a
61
+ return if segments.empty? || segments.any? { |segment| segment == '.' || segment == '..' }
62
+
63
+ candidate = root.join(*segments).expand_path
64
+ return unless candidate.to_s.start_with?("#{root}#{File::SEPARATOR}")
65
+
66
+ candidate if candidate.file?
67
+ end
68
+
69
+ def cache_control_for(relative_path)
70
+ if relative_path.start_with?('assets/')
71
+ 'public, max-age=31536000, immutable'
72
+ else
73
+ 'public, max-age=3600'
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,8 @@
1
+ Spree::Core::Engine.add_routes do
2
+ # Hosted React Dashboard (single-node topology). Serves the built SPA from
3
+ # `Spree::Dashboard.dist_path`; 404s when unconfigured. `format: false`
4
+ # keeps asset extensions (.js, .css, .svg) inside the splat instead of
5
+ # being parsed as a response format.
6
+ get '/dashboard', to: 'dashboard/app#show', as: :dashboard_app
7
+ get '/dashboard/*path', to: 'dashboard/app#show', format: false
8
+ end
@@ -0,0 +1,10 @@
1
+ require 'rails/engine'
2
+
3
+ module Spree
4
+ module Dashboard
5
+ class Engine < Rails::Engine
6
+ isolate_namespace Spree
7
+ engine_name 'spree_dashboard'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ require 'spree'
2
+
3
+ module Spree
4
+ module Dashboard
5
+ class << self
6
+ # Directory holding a built React Dashboard (`vite build` output),
7
+ # served at /dashboard. Set from an initializer, or via the
8
+ # SPREE_DASHBOARD_DIST_PATH env var (which the official Docker image
9
+ # and the Render Blueprint use). Unset, /dashboard responds 404.
10
+ attr_writer :dist_path
11
+
12
+ def dist_path
13
+ @dist_path.presence || ENV.fetch('SPREE_DASHBOARD_DIST_PATH', nil)
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ require 'spree/dashboard/engine'
@@ -0,0 +1 @@
1
+ require 'spree/dashboard'
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spree_dashboard
3
+ version: !ruby/object:Gem::Version
4
+ version: 5.6.0.rc1
5
+ platform: ruby
6
+ authors:
7
+ - Vendo Connect Inc.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-07-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: spree
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 5.6.0.rc1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 5.6.0.rc1
27
+ description: Serves a built Spree React Dashboard at /dashboard with SPA semantics
28
+ — the single-node topology where the dashboard and the Admin API share one origin.
29
+ Developer Preview; becomes the default admin delivery in Spree 6.
30
+ email: hello@spreecommerce.org
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - README.md
36
+ - Rakefile
37
+ - app/controllers/spree/dashboard/app_controller.rb
38
+ - config/routes.rb
39
+ - lib/spree/dashboard.rb
40
+ - lib/spree/dashboard/engine.rb
41
+ - lib/spree_dashboard.rb
42
+ homepage: https://spreecommerce.org
43
+ licenses:
44
+ - BSD-3-Clause
45
+ metadata:
46
+ bug_tracker_uri: https://github.com/spree/spree/issues
47
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0.rc1
48
+ documentation_uri: https://spreecommerce.org/docs/developer/dashboard/deployment
49
+ source_code_uri: https://github.com/spree/spree/tree/v5.6.0.rc1
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '3.2'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubygems_version: 3.5.22
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: Hosts the Spree React Dashboard from your Spree server
69
+ test_files: []