ruflet_rails 0.0.9 → 0.0.11

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: 314ea2aafb0153d3d6f5d4b0b37d3f24761b9cf1d54e1b0af4f82cfd54034c3b
4
- data.tar.gz: 593cbbfbf58d8cdd12fa0c01e2d5033b6f66a26f28298a0682a241a4bc407645
3
+ metadata.gz: ebf6ba0607ab65bf23420e9d03d39283aac94ad1ffd92bcd6e96c21fa7d79c9a
4
+ data.tar.gz: 74bba5b6bbf7976457a49556f5ee3c81c42f7afe30ede8979e5cf6f3f4e77354
5
5
  SHA512:
6
- metadata.gz: 8757f1cbd09bee0c205f638714777e2a96626a9836af535d6ff9a9b4734e0968b0943ee24109016058ff3801635c56801db2dbd5fd7a348c00cdddbadd0b12c3
7
- data.tar.gz: f5c74694f0073a7e69e1608767ccbbf6920795bead5c43549fe99d5ecf349d9c9eb53615fc026f73ea7fde5384c516e4cb51df758550911ed2a17279160a0a93
6
+ metadata.gz: d1027674f71e87d89e24e2ca90bb9906fe60a67b7f23d93b30e90e053e7a710439fe8d22774976f9c83cdbf83ca82b28aa6610eeec6178d413430fa105ced16c
7
+ data.tar.gz: e55b5f9c8a3549fb37833f5cc88d9950cf35428a2bd688884205b6c83a1ff7d68c627a25469a5893d0b81dd445d5c697fe66b80dcac91c57ae129748da9f1e6b
@@ -6,10 +6,8 @@ require "ruflet/rails/install_support"
6
6
  module Ruflet
7
7
  module Generators
8
8
  class InstallGenerator < ::Rails::Generators::Base
9
- class_option :frontend, type: :boolean, default: false, desc: "Install the Rails-hosted Ruflet web client"
10
- class_option :web, type: :boolean, default: false, desc: "Install the Rails-hosted Ruflet web client"
11
9
  class_option :desktop, type: :boolean, default: false, desc: "Download the server-driven desktop Ruflet client"
12
- class_option :client, type: :string, default: nil, desc: "Download prebuilt client from GitHub releases: web, desktop, all, or none"
10
+ class_option :client, type: :string, default: nil, desc: "Download prebuilt client from GitHub releases: desktop or none"
13
11
 
14
12
  desc "Install Ruflet into a Rails app."
15
13
 
@@ -27,14 +25,11 @@ module Ruflet
27
25
  create_file target, Ruflet::Rails::InstallSupport.default_ruflet_yaml(app_name: app_name)
28
26
  end
29
27
 
30
- def add_routes
31
- target = File.join(destination_root, "config/routes.rb")
32
- return unless File.file?(target)
33
-
34
- route = Ruflet::Rails::InstallSupport.route_snippet(entrypoint: entrypoint_path)
35
- return if File.read(target).include?(route)
28
+ def create_ruflet_initializer
29
+ target = File.join(destination_root, Ruflet::Rails::InstallSupport.initializer_path)
30
+ return if File.exist?(target)
36
31
 
37
- insert_into_file target, " #{route}\n", after: /Rails\.application\.routes\.draw do\s*\n/
32
+ create_file target, Ruflet::Rails::InstallSupport.initializer_template(entrypoint: entrypoint_path)
38
33
  end
39
34
 
40
35
  def add_desktop_flag_to_binstubs
@@ -46,17 +41,8 @@ module Ruflet
46
41
 
47
42
  def download_prebuilt_client
48
43
  client = requested_client
49
- @web_client_published = false
50
44
  return if client == "none"
51
45
 
52
- if %w[web all].include?(client)
53
- @web_client_published = Ruflet::Rails::InstallSupport.publish_web_build(destination_root)
54
- if @web_client_published
55
- say "Ruflet web build copied from build/web to public/#{Ruflet::Rails::InstallSupport.default_web_public_path}"
56
- return if client == "web"
57
- end
58
- end
59
-
60
46
  require "ruflet/cli"
61
47
  exit_code = Dir.chdir(destination_root) do
62
48
  Ruflet::CLI.command_update([client])
@@ -64,17 +50,6 @@ module Ruflet
64
50
  unless exit_code.to_i.zero?
65
51
  @client_download_failed = true
66
52
  say_status(:warn, "Ruflet client download failed; install files were generated and build/update steps are printed below", :yellow)
67
- return
68
- end
69
-
70
- return unless %w[web all].include?(client)
71
-
72
- published = Ruflet::Rails::InstallSupport.publish_prebuilt_web_client(destination_root)
73
- @web_client_published = published
74
- if published
75
- say "Ruflet web client published at /#{Ruflet::Rails::InstallSupport.default_web_public_path}/"
76
- else
77
- say_status(:warn, "Ruflet web client downloaded, but no prebuilt web index.html was found to publish", :yellow)
78
53
  end
79
54
  rescue StandardError => e
80
55
  @client_download_failed = true
@@ -85,8 +60,7 @@ module Ruflet
85
60
  Ruflet::Rails::InstallSupport.install_next_steps(
86
61
  target: install_target,
87
62
  entrypoint: entrypoint_path,
88
- client: requested_client,
89
- web_published: !!@web_client_published
63
+ client: requested_client
90
64
  ).each { |line| say line }
91
65
  end
92
66
 
@@ -103,22 +77,18 @@ module Ruflet
103
77
  def requested_client
104
78
  explicit = options[:client].to_s.strip.downcase
105
79
  unless explicit.empty?
106
- raise Thor::Error, "--client must be web, desktop, all, or none" unless %w[web desktop all none].include?(explicit)
80
+ raise Thor::Error, "--client must be desktop or none" unless %w[desktop none].include?(explicit)
107
81
 
108
82
  return explicit
109
83
  end
110
84
 
111
- wants_web = options[:frontend] || options[:web]
112
- wants_desktop = options[:desktop]
113
- return "all" if wants_web && wants_desktop
114
- return "web" if wants_web
115
- return "desktop" if wants_desktop
85
+ return "desktop" if options[:desktop]
116
86
 
117
87
  "none"
118
88
  end
119
89
 
120
90
  def desktop_requested?
121
- %w[desktop all].include?(requested_client)
91
+ requested_client == "desktop"
122
92
  end
123
93
 
124
94
  def install_target
@@ -10,17 +10,7 @@ module Ruflet
10
10
  argument :model_name, type: :string
11
11
  argument :attributes, type: :array, default: [], banner: "field:type field:type"
12
12
 
13
- desc "Generate a Rails-first Ruflet resource view for an existing model."
14
-
15
- def create_ruflet_resource_view
16
- create_file(
17
- File.join(destination_root, scaffold_view_path),
18
- Ruflet::Rails::InstallSupport.scaffold_view_template(
19
- model_name: model_name,
20
- attributes: scaffold_attributes
21
- )
22
- )
23
- end
13
+ desc "Generate a Rails-first Ruflet resource component for an existing model."
24
14
 
25
15
  def create_ruflet_resource_component
26
16
  create_file(
@@ -33,15 +23,19 @@ module Ruflet
33
23
  end
34
24
 
35
25
  def print_scaffold_status
36
- say "Ruflet scaffold generated at #{scaffold_view_path}"
37
- say "Ruflet UI component generated at #{scaffold_component_path}"
38
- say "The generated view owns the resource logic; the generated component owns the UI."
26
+ say "Ruflet resource component generated at #{scaffold_component_path}"
27
+ say "Mount it in config/routes.rb:"
28
+ say " mount Ruflet::Rails.web_app(view: #{scaffold_component_class.inspect}), at: \"/#{scaffold_route_segment}\""
39
29
  end
40
30
 
41
31
  private
42
32
 
43
- def scaffold_view_path
44
- Ruflet::Rails::InstallSupport.scaffold_view_path(model_name)
33
+ def scaffold_component_class
34
+ "#{model_name.to_s.camelize}Component"
35
+ end
36
+
37
+ def scaffold_route_segment
38
+ model_name.to_s.underscore.pluralize
45
39
  end
46
40
 
47
41
  def scaffold_component_path
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ module Rails
5
+ # Resolve Rails assets to absolute URLs the Flutter client can load over
6
+ # HTTP, so server-driven UI can show app images:
7
+ #
8
+ # image(src: Ruflet::Rails.asset_url("logo.png"))
9
+ # image(src: Ruflet::Rails.image_url("brand/header.png"), fit: "cover")
10
+ #
11
+ # The *path* comes from the Rails asset pipeline — digested in production
12
+ # (/assets/logo-<digest>.png), plain otherwise — so it survives
13
+ # fingerprinting and CDNs. The *host* is resolved, in order, from:
14
+ #
15
+ # 1. an explicit `host:` argument
16
+ # 2. Ruflet::Rails.config.backend_url
17
+ # 3. the host the client connected on (the live WebSocket request)
18
+ #
19
+ # The client is a separate device (simulator, phone, browser), so a bare
20
+ # "/assets/..." path would not resolve — the URL must be absolute. If Rails
21
+ # already has an asset_host/CDN configured, the pipeline returns an absolute
22
+ # URL and it is used unchanged. A value that is already a full URL passes
23
+ # through untouched.
24
+ module_function
25
+
26
+ # The base URL the Flutter client uses to reach this Rails app — the single
27
+ # source of truth for asset URLs, the build-time RUFLET_URL define and the
28
+ # desktop launcher. A Rails Ruflet app always needs one, so this always
29
+ # resolves to a usable value:
30
+ #
31
+ # 1. an explicit host: argument
32
+ # 2. Ruflet::Rails.config.backend_url (set it in config/initializers/ruflet.rb)
33
+ # 3. the host the client connected on (the live WebSocket request)
34
+ #
35
+ # Returns "" only when none of those are available (e.g. a build with no
36
+ # configured backend_url) — set config.backend_url to cover that case.
37
+ def backend_url(host: nil)
38
+ candidate = host || config.backend_url
39
+ candidate = request_base_url if candidate.to_s.strip.empty?
40
+ candidate.to_s.strip.sub(%r{/+\z}, "")
41
+ end
42
+
43
+ def asset_url(source, host: nil)
44
+ raw = source.to_s
45
+ return raw if absolute_url?(raw)
46
+
47
+ path = asset_pipeline_path(raw)
48
+ return path if absolute_url?(path)
49
+
50
+ base = backend_url(host: host)
51
+ base.empty? ? path : "#{base}#{path}"
52
+ end
53
+
54
+ # Readability alias for image sources — identical resolution.
55
+ def image_url(source, host: nil)
56
+ asset_url(source, host: host)
57
+ end
58
+
59
+ def asset_pipeline_path(source)
60
+ ::ActionController::Base.helpers.asset_path(source)
61
+ rescue StandardError
62
+ source.start_with?("/") ? source : "/#{source}"
63
+ end
64
+ private_class_method :asset_pipeline_path
65
+
66
+ # Derive scheme://host from the live WebSocket request env so the URL points
67
+ # back at the exact host the client reached — the one address guaranteed to
68
+ # be reachable from that device.
69
+ def request_base_url
70
+ env = Protocol::Context.current_env
71
+ return nil unless env
72
+
73
+ host = env["HTTP_X_FORWARDED_HOST"] || env["HTTP_HOST"]
74
+ return nil if host.to_s.strip.empty?
75
+
76
+ scheme = (env["HTTP_X_FORWARDED_PROTO"] || env["rack.url_scheme"] || "http").to_s.split(",").first.to_s.strip
77
+ scheme = "http" if scheme.empty?
78
+ "#{scheme}://#{host}"
79
+ end
80
+ private_class_method :request_base_url
81
+
82
+ def absolute_url?(value)
83
+ !(value.to_s =~ %r{\A[a-z][a-z0-9+.-]*://}i).nil?
84
+ end
85
+ private_class_method :absolute_url?
86
+ end
87
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ module Rails
5
+ # Central configuration for ruflet_rails.
6
+ #
7
+ # Mirrors the full ruflet.yaml schema so a Rails app needs only
8
+ # config/initializers/ruflet.rb — no ruflet.yaml on disk.
9
+ #
10
+ # Set in config/initializers/ruflet.rb:
11
+ #
12
+ # Ruflet::Rails.configure do |config|
13
+ # # Runtime / server
14
+ # config.app_file = Rails.root.join("app/views/ruflet/main.rb")
15
+ # config.ws_path = "/ws"
16
+ # config.backend_url = Rails.env.production? ? "https://example.com" : "http://localhost:3000"
17
+ #
18
+ # # Flutter web build output directory
19
+ # config.web_build_dir = Rails.root.join("public/app")
20
+ #
21
+ # # App metadata (ruflet.yaml → app:)
22
+ # config.app_name = "My App"
23
+ #
24
+ # # Services (ruflet.yaml → services:)
25
+ # config.services = []
26
+ #
27
+ # # Assets (ruflet.yaml → assets:)
28
+ # config.splash_screen = Rails.root.join("app/assets/images/splash.png")
29
+ # config.splash_dark = Rails.root.join("app/assets/images/splash_dark.png")
30
+ # config.icon_launcher = Rails.root.join("app/assets/images/icon.png")
31
+ # config.icon_android = Rails.root.join("app/assets/images/icon_android.png")
32
+ # config.icon_ios = Rails.root.join("app/assets/images/icon_ios.png")
33
+ # config.icon_web = Rails.root.join("app/assets/images/icon_web.png")
34
+ # config.icon_windows = Rails.root.join("app/assets/images/icon_windows.png")
35
+ # config.icon_macos = Rails.root.join("app/assets/images/icon_macos.png")
36
+ #
37
+ # # Build options (ruflet.yaml → build:)
38
+ # config.splash_color = "#FFFFFF"
39
+ # config.splash_dark_color = "#000000"
40
+ # config.icon_background = "#FFFFFF"
41
+ # config.theme_color = "#FFFFFF"
42
+ # end
43
+ class Configuration
44
+ # --- Runtime / server ---
45
+
46
+ # Absolute path to the Ruflet app entry-point.
47
+ attr_accessor :app_file
48
+
49
+ # URL path the WebSocket endpoint listens on. Defaults to "/ws".
50
+ attr_accessor :ws_path
51
+
52
+ # Backend base URL. Used as --dart-define=RUFLET_URL at build time
53
+ # and by the desktop launcher. Replaces ruflet.yaml → app.backend_url.
54
+ attr_accessor :backend_url
55
+
56
+ # Absolute directory containing the Flutter web build (index.html + assets).
57
+ attr_accessor :web_build_dir
58
+
59
+ # --- App metadata (ruflet.yaml → app:) ---
60
+
61
+ attr_accessor :app_name
62
+
63
+ # --- Services (ruflet.yaml → services:) ---
64
+
65
+ attr_accessor :services
66
+
67
+ # --- Assets (ruflet.yaml → assets:) ---
68
+
69
+ attr_accessor :splash_screen
70
+ attr_accessor :splash_dark
71
+ attr_accessor :icon_launcher
72
+ attr_accessor :icon_android
73
+ attr_accessor :icon_ios
74
+ attr_accessor :icon_web
75
+ attr_accessor :icon_windows
76
+ attr_accessor :icon_macos
77
+
78
+ # --- Build options (ruflet.yaml → build:) ---
79
+
80
+ attr_accessor :splash_color
81
+ attr_accessor :splash_dark_color
82
+ attr_accessor :icon_background
83
+ attr_accessor :theme_color
84
+
85
+ def initialize
86
+ @ws_path = "/ws"
87
+ @app_file = nil
88
+ @backend_url = nil
89
+ @web_build_dir = nil
90
+ @app_name = nil
91
+ @services = []
92
+ end
93
+
94
+ # Serialises config to the ruflet.yaml hash structure so the CLI can
95
+ # consume it without a yaml file on disk (written to a temp file by
96
+ # the Railtie's build task).
97
+ def to_ruflet_yaml_hash
98
+ hash = {}
99
+
100
+ app = {}
101
+ app["name"] = @app_name if @app_name
102
+ app["backend_url"] = @backend_url if @backend_url
103
+ hash["app"] = app unless app.empty?
104
+
105
+ hash["services"] = Array(@services)
106
+
107
+ assets = {}
108
+ assets["splash_screen"] = @splash_screen.to_s if @splash_screen
109
+ assets["splash_dark"] = @splash_dark.to_s if @splash_dark
110
+ assets["icon_launcher"] = @icon_launcher.to_s if @icon_launcher
111
+ assets["icon_android"] = @icon_android.to_s if @icon_android
112
+ assets["icon_ios"] = @icon_ios.to_s if @icon_ios
113
+ assets["icon_web"] = @icon_web.to_s if @icon_web
114
+ assets["icon_windows"] = @icon_windows.to_s if @icon_windows
115
+ assets["icon_macos"] = @icon_macos.to_s if @icon_macos
116
+ hash["assets"] = assets unless assets.empty?
117
+
118
+ build = {}
119
+ build["splash_color"] = @splash_color if @splash_color
120
+ build["splash_dark_color"] = @splash_dark_color if @splash_dark_color
121
+ build["icon_background"] = @icon_background if @icon_background
122
+ build["theme_color"] = @theme_color if @theme_color
123
+ hash["build"] = build unless build.empty?
124
+
125
+ hash
126
+ end
127
+ end
128
+ end
129
+ end