ruflet_rails 0.0.11 → 0.0.13
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 +56 -55
- data/lib/generators/ruflet/install/install_generator.rb +46 -15
- data/lib/ruflet/rails/configuration.rb +6 -24
- data/lib/ruflet/rails/generator_hooks.rb +25 -0
- data/lib/ruflet/rails/install_support.rb +40 -67
- data/lib/ruflet/rails/native_app.rb +4 -3
- data/lib/ruflet/rails/protocol/runner.rb +0 -2
- data/lib/ruflet/rails/protocol/web_app.rb +83 -34
- data/lib/ruflet/rails/protocol.rb +0 -4
- data/lib/ruflet/rails/railtie.rb +28 -12
- data/lib/ruflet/rails/resource_component.rb +4 -5
- data/lib/ruflet/rails/web_installer.rb +137 -0
- data/lib/ruflet/rails/webview_app.rb +1 -1
- data/lib/ruflet/rails.rb +16 -201
- data/lib/ruflet/version.rb +1 -1
- data/lib/ruflet_rails.rb +1 -1
- metadata +11 -14
- data/lib/ruflet/rails/protocol/middleware.rb +0 -90
- data/lib/ruflet/rails/protocol/static_index_guard.rb +0 -6
- data/lib/ruflet/rails/protocol/web_app_endpoint.rb +0 -44
- data/lib/ruflet/rails/protocol/web_socket_connection.rb +0 -11
- data/lib/ruflet/rails/protocol/wire_codec.rb +0 -11
- data/lib/ruflet/rails/view.rb +0 -57
|
@@ -8,10 +8,10 @@ module Ruflet
|
|
|
8
8
|
# the same mount point, so it mounts like any Rack app:
|
|
9
9
|
#
|
|
10
10
|
# # config/routes.rb
|
|
11
|
-
# mount Ruflet::Rails.web_app, at: "/myfrontend"
|
|
11
|
+
# mount Ruflet::Rails.web_app(app_file: Rails.root.join("app/views/ruflet/main.rb")), at: "/myfrontend"
|
|
12
12
|
#
|
|
13
13
|
# # or with an explicit entrypoint and build directory:
|
|
14
|
-
# mount Ruflet::Rails.web_app(build_dir: Rails.root.join("
|
|
14
|
+
# mount Ruflet::Rails.web_app(build_dir: Rails.root.join("frontend")) { |page|
|
|
15
15
|
# CounterView.render(page)
|
|
16
16
|
# }, at: "/myfrontend"
|
|
17
17
|
#
|
|
@@ -73,15 +73,16 @@ module Ruflet
|
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
def entrypoint
|
|
76
|
-
@entrypoint_option || @app_block ||
|
|
76
|
+
@entrypoint_option || @app_block ||
|
|
77
|
+
raise(ArgumentError, "web_app requires one of view:, app_file:, or a block")
|
|
77
78
|
end
|
|
78
79
|
|
|
79
80
|
# The web build must NOT live under public/, or Rails' static
|
|
80
81
|
# middleware would serve it directly (e.g. /app/index.html) and bypass
|
|
81
82
|
# the mount — making it reachable at a path no route declares. The
|
|
82
|
-
# default is Rails.root/
|
|
83
|
-
#
|
|
84
|
-
#
|
|
83
|
+
# default is Rails.root/frontend, exactly where `rake ruflet:web`
|
|
84
|
+
# installs the prebuilt web client, which Rails never serves statically.
|
|
85
|
+
# An explicit build_dir: under public/ is rejected.
|
|
85
86
|
def build_dir
|
|
86
87
|
dir = resolve_build_dir
|
|
87
88
|
reject_public_build_dir!(dir)
|
|
@@ -92,10 +93,7 @@ module Ruflet
|
|
|
92
93
|
explicit = @explicit_build_dir.to_s
|
|
93
94
|
return explicit unless explicit.empty?
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
return configured unless configured.empty?
|
|
97
|
-
|
|
98
|
-
return ::Rails.root.join("build", "web").to_s if defined?(::Rails.root) && ::Rails.root
|
|
96
|
+
return ::Rails.root.join("frontend").to_s if defined?(::Rails.root) && ::Rails.root
|
|
99
97
|
|
|
100
98
|
""
|
|
101
99
|
end
|
|
@@ -110,19 +108,20 @@ module Ruflet
|
|
|
110
108
|
|
|
111
109
|
raise ArgumentError,
|
|
112
110
|
"Ruflet web build dir (#{dir}) is under public/, which Rails serves " \
|
|
113
|
-
"statically and would expose the app outside its mount.
|
|
114
|
-
"non-public directory such as #{::Rails.root.join('
|
|
111
|
+
"statically and would expose the app outside its mount. Use a " \
|
|
112
|
+
"non-public directory such as #{::Rails.root.join('frontend')}."
|
|
115
113
|
end
|
|
116
114
|
|
|
117
115
|
def serve_index(env)
|
|
118
116
|
index_path = File.join(build_dir, "index.html")
|
|
119
117
|
unless File.file?(index_path)
|
|
120
118
|
return [404, { "content-type" => "text/plain" },
|
|
121
|
-
["Ruflet web
|
|
119
|
+
["Ruflet web client not found at #{index_path}. Run `rake ruflet:web`."]]
|
|
122
120
|
end
|
|
123
121
|
|
|
124
122
|
html = rewrite_base_href(File.read(index_path), mount_base(env))
|
|
125
|
-
html =
|
|
123
|
+
html = inject_mount_websocket(html)
|
|
124
|
+
html = inject_service_worker_cleanup(html)
|
|
126
125
|
[200,
|
|
127
126
|
{ "content-type" => "text/html; charset=utf-8",
|
|
128
127
|
"cache-control" => "no-cache",
|
|
@@ -130,28 +129,47 @@ module Ruflet
|
|
|
130
129
|
[html]]
|
|
131
130
|
end
|
|
132
131
|
|
|
133
|
-
|
|
134
|
-
# ws(s)://<page authority>/<window.flet.webSocketEndpoint || "ws">.
|
|
135
|
-
# Without help, every build connects to the origin's /ws and escapes
|
|
136
|
-
# the mount. Deriving the endpoint from document.baseURI (which
|
|
137
|
-
# follows the rewritten <base href>) pins any build to <mount>/ws —
|
|
138
|
-
# answered by this same Rack app.
|
|
139
|
-
MOUNT_URL_BOOTSTRAP = <<~HTML
|
|
140
|
-
<script>
|
|
141
|
-
(function () {
|
|
142
|
-
window.flet = window.flet || {};
|
|
143
|
-
if (!window.flet.webSocketEndpoint) {
|
|
144
|
-
var basePath = new URL(document.baseURI).pathname;
|
|
145
|
-
window.flet.webSocketEndpoint = (basePath + "ws").replace(/^\\/+/, "");
|
|
146
|
-
}
|
|
147
|
-
})();
|
|
148
|
-
</script>
|
|
149
|
-
HTML
|
|
150
|
-
|
|
151
|
-
def inject_mount_url_param(html)
|
|
132
|
+
def inject_mount_websocket(html)
|
|
152
133
|
return html if html.include?("window.flet.webSocketEndpoint")
|
|
153
134
|
|
|
154
|
-
html.sub(%r{(<base\s+href="[^"]*"\s*/?>)}) { "#{::Regexp.last_match(1)}\n#{
|
|
135
|
+
html.sub(%r{(<base\s+href="[^"]*"\s*/?>)}) { "#{::Regexp.last_match(1)}\n#{mount_websocket_bootstrap}" }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Mounted Rails apps are server-driven and must always load the client
|
|
139
|
+
# shipped by the Rails app. Remove service workers previously installed
|
|
140
|
+
# for this mount so they cannot keep serving a stale connection client.
|
|
141
|
+
def inject_service_worker_cleanup(html)
|
|
142
|
+
return html if html.include?("ruflet-rails-service-worker-cleanup")
|
|
143
|
+
|
|
144
|
+
script = <<~HTML
|
|
145
|
+
<script id="ruflet-rails-service-worker-cleanup">
|
|
146
|
+
if ("serviceWorker" in navigator) {
|
|
147
|
+
navigator.serviceWorker.getRegistrations().then(function (registrations) {
|
|
148
|
+
registrations.forEach(function (registration) {
|
|
149
|
+
if (registration.scope.indexOf(document.baseURI) === 0) registration.unregister();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
</script>
|
|
154
|
+
HTML
|
|
155
|
+
html.include?("</head>") ? html.sub("</head>", "#{script}</head>") : "#{script}#{html}"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Pins Flet-style clients to <mount>/ws (derived from the rewritten
|
|
159
|
+
# <base href>) instead of the origin's /ws. The Ruflet client itself
|
|
160
|
+
# uses the mounted page URL, so Rails needs no separate backend URL.
|
|
161
|
+
def mount_websocket_bootstrap
|
|
162
|
+
<<~HTML
|
|
163
|
+
<script>
|
|
164
|
+
(function () {
|
|
165
|
+
window.flet = window.flet || {};
|
|
166
|
+
if (!window.flet.webSocketEndpoint) {
|
|
167
|
+
var basePath = new URL(document.baseURI).pathname;
|
|
168
|
+
window.flet.webSocketEndpoint = (basePath + "ws").replace(/^\\/+/, "");
|
|
169
|
+
}
|
|
170
|
+
})();
|
|
171
|
+
</script>
|
|
172
|
+
HTML
|
|
155
173
|
end
|
|
156
174
|
|
|
157
175
|
# <base href> drives the Flutter client's asset and WebSocket URLs;
|
|
@@ -172,6 +190,8 @@ module Ruflet
|
|
|
172
190
|
end
|
|
173
191
|
|
|
174
192
|
def serve_static(path)
|
|
193
|
+
return retire_service_worker if path == "/flutter_service_worker.js"
|
|
194
|
+
|
|
175
195
|
root = File.expand_path(build_dir)
|
|
176
196
|
full = File.expand_path(File.join(root, path))
|
|
177
197
|
unless full.start_with?(root + File::SEPARATOR) && File.file?(full)
|
|
@@ -179,8 +199,37 @@ module Ruflet
|
|
|
179
199
|
end
|
|
180
200
|
|
|
181
201
|
body = File.binread(full)
|
|
202
|
+
body = disable_service_worker(body) if path == "/flutter_bootstrap.js"
|
|
182
203
|
[200,
|
|
183
204
|
{ "content-type" => content_type_for(full),
|
|
205
|
+
"cache-control" => "no-cache",
|
|
206
|
+
"content-length" => body.bytesize.to_s },
|
|
207
|
+
[body]]
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def disable_service_worker(body)
|
|
211
|
+
body.sub(
|
|
212
|
+
/serviceWorkerSettings:\s*\{\s*serviceWorkerVersion:\s*[^}]+\}\s*,?/m,
|
|
213
|
+
""
|
|
214
|
+
)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def retire_service_worker
|
|
218
|
+
body = <<~JS
|
|
219
|
+
self.addEventListener("install", function () { self.skipWaiting(); });
|
|
220
|
+
self.addEventListener("activate", function (event) {
|
|
221
|
+
event.waitUntil((async function () {
|
|
222
|
+
await self.registration.unregister();
|
|
223
|
+
for (const key of await caches.keys()) await caches.delete(key);
|
|
224
|
+
for (const client of await self.clients.matchAll({ type: "window" })) {
|
|
225
|
+
client.navigate(client.url);
|
|
226
|
+
}
|
|
227
|
+
})());
|
|
228
|
+
});
|
|
229
|
+
JS
|
|
230
|
+
[200,
|
|
231
|
+
{ "content-type" => "application/javascript",
|
|
232
|
+
"cache-control" => "no-store",
|
|
184
233
|
"content-length" => body.bytesize.to_s },
|
|
185
234
|
[body]]
|
|
186
235
|
end
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "protocol/context"
|
|
4
|
-
require_relative "protocol/middleware"
|
|
5
|
-
require_relative "protocol/wire_codec"
|
|
6
|
-
require_relative "protocol/web_socket_connection"
|
|
7
4
|
require_relative "protocol/local_server"
|
|
8
5
|
require_relative "protocol/websocket_detection"
|
|
9
6
|
require_relative "protocol/endpoint"
|
|
10
7
|
require_relative "protocol/mobile_loader"
|
|
11
8
|
require_relative "protocol/runner"
|
|
12
|
-
require_relative "protocol/web_app_endpoint"
|
|
13
9
|
require_relative "protocol/web_app"
|
data/lib/ruflet/rails/railtie.rb
CHANGED
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
module Ruflet
|
|
4
4
|
module Rails
|
|
5
5
|
class Railtie < ::Rails::Railtie
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
app.middleware.insert_before(ActionDispatch::Static, Ruflet::Rails::Protocol::Middleware)
|
|
6
|
+
generators do
|
|
7
|
+
require "ruflet/rails/generator_hooks"
|
|
8
|
+
Ruflet::Rails::GeneratorHooks.install!
|
|
10
9
|
end
|
|
11
10
|
|
|
12
11
|
# Make ruflet_frame and friends available in every .erb template.
|
|
@@ -16,6 +15,18 @@ module Ruflet
|
|
|
16
15
|
end
|
|
17
16
|
end
|
|
18
17
|
|
|
18
|
+
# Ruflet components live under app/views so Rails will not discover them
|
|
19
|
+
# by default. Add that directory as a Zeitwerk root and collapse its
|
|
20
|
+
# organizational subdirectories, matching generated top-level constants
|
|
21
|
+
# such as components/products/product_component.rb -> ProductComponent.
|
|
22
|
+
initializer "ruflet_rails.components", before: :bootstrap_hook do |app|
|
|
23
|
+
components = app.root.join("app/views/ruflet/components")
|
|
24
|
+
next unless components.directory?
|
|
25
|
+
|
|
26
|
+
app.autoloaders.main.push_dir(components)
|
|
27
|
+
app.autoloaders.main.collapse(components.join("**"))
|
|
28
|
+
end
|
|
29
|
+
|
|
19
30
|
initializer "ruflet_rails.desktop_launcher", after: :load_config_initializers do |_app|
|
|
20
31
|
next unless defined?(::Rails.root)
|
|
21
32
|
|
|
@@ -27,22 +38,21 @@ module Ruflet
|
|
|
27
38
|
|
|
28
39
|
rake_tasks do
|
|
29
40
|
namespace :ruflet do
|
|
30
|
-
desc "Build Ruflet client for this Rails app. Usage: rake ruflet:build[platform]"
|
|
41
|
+
desc "Build Ruflet native client for this Rails app. Usage: rake ruflet:build[platform]"
|
|
31
42
|
task :build, [:platform] do |_task, args|
|
|
32
43
|
platform = args[:platform].to_s.strip.downcase
|
|
33
44
|
|
|
45
|
+
if platform == "web"
|
|
46
|
+
warn "ruflet_rails does not build the web client. Install the prebuilt web client with: rake ruflet:web"
|
|
47
|
+
next
|
|
48
|
+
end
|
|
49
|
+
|
|
34
50
|
cfg = Ruflet::Rails.config
|
|
35
51
|
ruflet_url = cfg.backend_url.to_s.strip
|
|
36
52
|
build_args = Ruflet::Rails::InstallSupport.build_args_for_platform(platform, ruflet_url: ruflet_url)
|
|
37
53
|
|
|
38
|
-
# Derive --base-href from config.web_build_dir so the built index.html
|
|
39
|
-
# uses the correct path for assets and Uri.base in the Dart client.
|
|
40
|
-
if platform == "web" && (dir = cfg.web_build_dir)
|
|
41
|
-
build_args += ["--base-href", "/#{File.basename(dir.to_s)}/"]
|
|
42
|
-
end
|
|
43
|
-
|
|
44
54
|
if build_args.empty?
|
|
45
|
-
warn "Usage: rake ruflet:build[apk|android|ios|aab|
|
|
55
|
+
warn "Usage: rake ruflet:build[apk|android|ios|aab|desktop|macos|windows|linux]"
|
|
46
56
|
next
|
|
47
57
|
end
|
|
48
58
|
|
|
@@ -72,6 +82,12 @@ module Ruflet
|
|
|
72
82
|
raise SystemExit, exit_code unless exit_code.to_i.zero?
|
|
73
83
|
end
|
|
74
84
|
|
|
85
|
+
desc "Install the prebuilt Ruflet web client into frontend/. Usage: rake ruflet:web"
|
|
86
|
+
task :web do
|
|
87
|
+
ok = Ruflet::Rails::WebInstaller.install!(root: ::Rails.root)
|
|
88
|
+
raise SystemExit, 1 unless ok
|
|
89
|
+
end
|
|
90
|
+
|
|
75
91
|
desc "Download/update prebuilt Ruflet clients from GitHub releases. Usage: rake ruflet:update[target]"
|
|
76
92
|
task :update, [:target] do |_task, args|
|
|
77
93
|
target = args[:target].to_s.strip
|
|
@@ -12,7 +12,7 @@ module Ruflet
|
|
|
12
12
|
# The generated subclass owns the explicit CRUD UI (render, show, the
|
|
13
13
|
# create/edit form) AND the database calls (record.update, record.destroy!,
|
|
14
14
|
# model_class.new) — so a developer can read and change anything. This base
|
|
15
|
-
# provides
|
|
15
|
+
# provides reusable helpers: model resolution, record loading,
|
|
16
16
|
# field inference (resource_fields/display_fields/display_value), navigation
|
|
17
17
|
# (render_index/render_show/refresh), dialog management, snackbars, and the
|
|
18
18
|
# date/time picker value helpers.
|
|
@@ -88,9 +88,8 @@ module Ruflet
|
|
|
88
88
|
model_class.respond_to?(:model_name) ? model_class.model_name.human.titleize : self.class.singular_title
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
# Fields rendered on the detail (show) screen. The
|
|
92
|
-
#
|
|
93
|
-
# the model's own attribute names.
|
|
91
|
+
# Fields rendered on the detail (show) screen. The default uses the
|
|
92
|
+
# model's own attribute names; subclasses can override it.
|
|
94
93
|
def resource_fields
|
|
95
94
|
default_resource_fields
|
|
96
95
|
end
|
|
@@ -105,7 +104,7 @@ module Ruflet
|
|
|
105
104
|
record.public_send(field).to_s
|
|
106
105
|
end
|
|
107
106
|
|
|
108
|
-
# --- Record loading &
|
|
107
|
+
# --- Record loading & navigation ---------------------------------------
|
|
109
108
|
|
|
110
109
|
def records
|
|
111
110
|
scope = model_class.respond_to?(:limit) ? model_class.limit(50) : model_class.all
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "tmpdir"
|
|
6
|
+
require "open3"
|
|
7
|
+
|
|
8
|
+
module Ruflet
|
|
9
|
+
module Rails
|
|
10
|
+
# Installs the prebuilt Ruflet web client into a Rails app's frontend/
|
|
11
|
+
# directory. The web client is the `ruflet_client-web.tar.gz` release asset
|
|
12
|
+
# (the same prebuilt Flutter web build shipped on GitHub releases) — so the
|
|
13
|
+
# Rails app serves a web frontend without ever running a Flutter build.
|
|
14
|
+
#
|
|
15
|
+
# rake ruflet:web
|
|
16
|
+
#
|
|
17
|
+
# The extracted build is what Ruflet::Rails.web_app serves (its default
|
|
18
|
+
# build dir is <Rails.root>/frontend).
|
|
19
|
+
module WebInstaller
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
REPO = "AdamMusa/Ruflet"
|
|
23
|
+
ASSET_NAME = "ruflet_client-web.tar.gz"
|
|
24
|
+
TARGET_DIR = "frontend"
|
|
25
|
+
|
|
26
|
+
# Downloads and extracts the web client into <root>/<dir>. Returns true on
|
|
27
|
+
# success. The asset name and release tag are overridable via ENV so a
|
|
28
|
+
# project can pin a specific build.
|
|
29
|
+
def install!(root:, dir: TARGET_DIR, asset_name: env_asset_name, tag: env_tag, force: true)
|
|
30
|
+
target = File.join(root.to_s, dir)
|
|
31
|
+
|
|
32
|
+
url = release_asset_url(asset_name, tag: tag)
|
|
33
|
+
unless url
|
|
34
|
+
warn "Ruflet web client asset #{asset_name.inspect} not found in the #{REPO} release#{tag ? " #{tag}" : " (latest)"}."
|
|
35
|
+
return false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
Dir.mktmpdir("ruflet-web-") do |tmp|
|
|
39
|
+
archive = File.join(tmp, asset_name)
|
|
40
|
+
return false unless download(url, archive)
|
|
41
|
+
|
|
42
|
+
staging = File.join(tmp, "frontend")
|
|
43
|
+
FileUtils.mkdir_p(staging)
|
|
44
|
+
unless safe_archive?(archive) && extract(archive, staging)
|
|
45
|
+
warn "Failed to safely extract #{asset_name}."
|
|
46
|
+
return false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
unless File.file?(File.join(staging, "index.html"))
|
|
50
|
+
warn "Extracted web client but no index.html was found."
|
|
51
|
+
return false
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
return false if Dir.exist?(target) && !force
|
|
55
|
+
|
|
56
|
+
replace_target(staging, target)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
puts "Installed Ruflet web client into #{target} (#{asset_name})."
|
|
60
|
+
true
|
|
61
|
+
rescue SystemCallError => e
|
|
62
|
+
warn "Failed to install Ruflet web client into #{target}: #{e.message}"
|
|
63
|
+
false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def release_asset_url(asset_name, tag: nil)
|
|
67
|
+
api = tag ? "releases/tags/#{tag}" : "releases/latest"
|
|
68
|
+
release = github_json("https://api.github.com/repos/#{REPO}/#{api}")
|
|
69
|
+
return nil unless release
|
|
70
|
+
|
|
71
|
+
asset = Array(release["assets"]).find { |a| a["name"] == asset_name }
|
|
72
|
+
unless asset
|
|
73
|
+
names = Array(release["assets"]).map { |a| a["name"] }
|
|
74
|
+
warn "Available release assets: #{names.join(', ')}" unless names.empty?
|
|
75
|
+
end
|
|
76
|
+
asset && asset["browser_download_url"]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def github_json(url)
|
|
80
|
+
out, status = Open3.capture2(
|
|
81
|
+
"curl", "-sSL", "--fail",
|
|
82
|
+
"-H", "Accept: application/vnd.github+json",
|
|
83
|
+
"-H", "User-Agent: ruflet_rails",
|
|
84
|
+
url
|
|
85
|
+
)
|
|
86
|
+
return nil unless status.success?
|
|
87
|
+
|
|
88
|
+
JSON.parse(out)
|
|
89
|
+
rescue JSON::ParserError
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def download(url, path)
|
|
94
|
+
ok = system("curl", "-sSL", "--fail", "-o", path, url)
|
|
95
|
+
warn "Download failed: #{url}" unless ok
|
|
96
|
+
ok
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def extract(archive, target)
|
|
100
|
+
system("tar", "-xzf", archive, "-C", target)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def safe_archive?(archive)
|
|
104
|
+
out, status = Open3.capture2("tar", "-tzf", archive)
|
|
105
|
+
return false unless status.success?
|
|
106
|
+
|
|
107
|
+
out.lines.all? do |line|
|
|
108
|
+
path = line.strip
|
|
109
|
+
!path.start_with?("/") && path.split("/").none?("..")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def replace_target(staging, target)
|
|
114
|
+
FileUtils.mkdir_p(File.dirname(target))
|
|
115
|
+
backup = "#{target}.ruflet-backup"
|
|
116
|
+
FileUtils.rm_rf(backup)
|
|
117
|
+
FileUtils.mv(target, backup) if File.exist?(target)
|
|
118
|
+
FileUtils.mv(staging, target)
|
|
119
|
+
FileUtils.rm_rf(backup)
|
|
120
|
+
rescue StandardError
|
|
121
|
+
FileUtils.rm_rf(target)
|
|
122
|
+
FileUtils.mv(backup, target) if File.exist?(backup)
|
|
123
|
+
raise
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def env_asset_name
|
|
127
|
+
value = ENV["RUFLET_RAILS_WEB_ARTIFACT"].to_s.strip
|
|
128
|
+
value.empty? ? ASSET_NAME : value
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def env_tag
|
|
132
|
+
value = ENV["RUFLET_RAILS_WEB_TAG"].to_s.strip
|
|
133
|
+
value.empty? ? nil : value
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -33,7 +33,7 @@ module Ruflet
|
|
|
33
33
|
def webview_app(url:, appbar: nil, navigation_bar: nil, bottom_appbar: nil,
|
|
34
34
|
route: "/", prevent_links: nil, on_navigate: nil,
|
|
35
35
|
on_page_started: nil, on_page_ended: nil, **webview_props)
|
|
36
|
-
webview_args = { url: url, expand: true }
|
|
36
|
+
webview_args = { url: url, method: "get", expand: true }
|
|
37
37
|
webview_args[:prevent_links] = prevent_links unless prevent_links.nil?
|
|
38
38
|
webview_args[:on_url_change] = ->(event) { on_navigate.call(event.data) } if on_navigate
|
|
39
39
|
webview_args[:on_page_started] = on_page_started if on_page_started
|