ruflet 0.0.17 → 0.0.19
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 +16 -1
- data/lib/ruflet/cli/build_command.rb +165 -707
- data/lib/ruflet/cli/extra_command.rb +14 -23
- data/lib/ruflet/cli/flutter_sdk.rb +34 -95
- data/lib/ruflet/cli/new_command.rb +89 -49
- data/lib/ruflet/cli/run_command.rb +186 -182
- data/lib/ruflet/cli/templates.rb +8 -9
- data/lib/ruflet/cli.rb +2 -15
- data/lib/ruflet/version.rb +1 -1
- metadata +1 -3
- data/lib/ruflet/cli/android_sdk.rb +0 -306
- data/lib/ruflet/cli/environment_setup.rb +0 -241
|
@@ -12,23 +12,19 @@ require "uri"
|
|
|
12
12
|
require "thread"
|
|
13
13
|
require "io/console"
|
|
14
14
|
require "time"
|
|
15
|
-
require "yaml"
|
|
16
15
|
|
|
17
16
|
module Ruflet
|
|
18
17
|
module CLI
|
|
19
18
|
module RunCommand
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"desktop" => 8560,
|
|
23
|
-
"mobile" => 8570
|
|
24
|
-
}.freeze
|
|
19
|
+
CLIENT_CHANNEL_MANIFEST = "ruflet_client-manifest.json"
|
|
20
|
+
DEFAULT_CLIENT_UPDATE_INTERVAL = 6 * 60 * 60
|
|
25
21
|
|
|
26
22
|
def command_run(args)
|
|
27
|
-
options = { target: "mobile", requested_port:
|
|
23
|
+
options = { target: "mobile", requested_port: 8550 }
|
|
28
24
|
parser = OptionParser.new do |o|
|
|
29
25
|
o.on("--web") { options[:target] = "web" }
|
|
30
26
|
o.on("--desktop") { options[:target] = "desktop" }
|
|
31
|
-
o.on("--port PORT", Integer
|
|
27
|
+
o.on("--port PORT", Integer) { |v| options[:requested_port] = v }
|
|
32
28
|
end
|
|
33
29
|
parser.parse!(args)
|
|
34
30
|
|
|
@@ -40,33 +36,18 @@ module Ruflet
|
|
|
40
36
|
return 1
|
|
41
37
|
end
|
|
42
38
|
|
|
43
|
-
|
|
44
|
-
selected_port = resolve_backend_port(options[:target], requested_port: requested_port)
|
|
39
|
+
selected_port = resolve_backend_port(options[:target], requested_port: options[:requested_port])
|
|
45
40
|
return 1 unless selected_port
|
|
46
41
|
env = {
|
|
47
42
|
"RUFLET_TARGET" => options[:target],
|
|
48
43
|
"RUFLET_SUPPRESS_SERVER_BANNER" => "1",
|
|
49
|
-
"RUFLET_PORT" => selected_port.to_s
|
|
50
|
-
"RUFLET_STRICT_PORT" => "1"
|
|
44
|
+
"RUFLET_PORT" => selected_port.to_s
|
|
51
45
|
}
|
|
52
46
|
apply_local_ruflet_dev_overrides(env)
|
|
53
47
|
assets_dir = File.join(File.dirname(script_path), "assets")
|
|
54
48
|
env["RUFLET_ASSETS_DIR"] = assets_dir if File.directory?(assets_dir)
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
# origin/port as /ws), so no separate static server or proxy is needed.
|
|
58
|
-
if options[:target] == "web"
|
|
59
|
-
web_dir = detect_web_client_dir
|
|
60
|
-
if web_dir
|
|
61
|
-
env["RUFLET_WEB_CLIENT_DIR"] = web_dir
|
|
62
|
-
else
|
|
63
|
-
warn "Ruflet web client unavailable for version #{ruflet_version}."
|
|
64
|
-
warn "Install the matching GitHub release client before running --web."
|
|
65
|
-
return 1
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
print_run_banner(target: options[:target], requested_port: requested_port, port: selected_port)
|
|
50
|
+
print_run_banner(target: options[:target], requested_port: options[:requested_port], port: selected_port)
|
|
70
51
|
print_mobile_qr_hint(port: selected_port) if options[:target] == "mobile"
|
|
71
52
|
|
|
72
53
|
gemfile_path = find_nearest_gemfile(Dir.pwd)
|
|
@@ -171,14 +152,16 @@ module Ruflet
|
|
|
171
152
|
|
|
172
153
|
def print_run_banner(target:, requested_port:, port:)
|
|
173
154
|
if port != requested_port.to_i
|
|
174
|
-
puts "
|
|
155
|
+
puts "Requested port #{requested_port} is busy; bound to #{port}"
|
|
175
156
|
end
|
|
176
157
|
if target == "desktop"
|
|
177
158
|
puts "Ruflet desktop URL: http://localhost:#{port}"
|
|
178
159
|
elsif target == "mobile"
|
|
179
160
|
puts "Ruflet target: #{target}"
|
|
161
|
+
else
|
|
162
|
+
puts "Ruflet target: #{target}"
|
|
163
|
+
puts "Ruflet URL: http://localhost:#{port}"
|
|
180
164
|
end
|
|
181
|
-
# web: launch_web_client prints the single app URL after the server boots
|
|
182
165
|
end
|
|
183
166
|
|
|
184
167
|
def launch_target_client(target, port)
|
|
@@ -194,19 +177,30 @@ module Ruflet
|
|
|
194
177
|
end
|
|
195
178
|
end
|
|
196
179
|
|
|
197
|
-
# The Ruby backend (Ruflet::Server) serves the Flutter web client on its
|
|
198
|
-
# own port, so the client loads and opens its websocket on that same
|
|
199
|
-
# origin. The prebuilt web client derives its backend from Uri.base, so
|
|
200
|
-
# the public URL stays clean and dynamic port selection works naturally.
|
|
201
180
|
def launch_web_client(port)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
181
|
+
web_dir = detect_web_client_dir
|
|
182
|
+
unless web_dir
|
|
183
|
+
warn "Web client build not found and prebuilt download failed."
|
|
184
|
+
return []
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
web_port = find_available_port(port + 1)
|
|
188
|
+
web_pid = Process.spawn("python3", "-m", "http.server", web_port.to_s, "--bind", "127.0.0.1", chdir: web_dir, out: File::NULL, err: File::NULL)
|
|
189
|
+
Process.detach(web_pid)
|
|
190
|
+
wait_for_server_boot(web_port)
|
|
191
|
+
backend_url = "http://localhost:#{port}"
|
|
192
|
+
web_url = "http://localhost:#{web_port}/?#{URI.encode_www_form(url: backend_url)}"
|
|
193
|
+
browser_pid = open_in_browser_app_mode(web_url)
|
|
194
|
+
open_in_browser(web_url) if browser_pid.nil?
|
|
195
|
+
puts "Ruflet web client: #{web_url}"
|
|
196
|
+
puts "Ruflet backend ws: ws://localhost:#{port}/ws"
|
|
197
|
+
[web_pid, browser_pid].compact
|
|
198
|
+
rescue Errno::ENOENT
|
|
199
|
+
warn "python3 is required to host web client locally."
|
|
200
|
+
warn "Install Python 3 and rerun."
|
|
201
|
+
[]
|
|
207
202
|
rescue StandardError => e
|
|
208
|
-
warn "Failed to
|
|
209
|
-
warn "Open it manually: http://localhost:#{port}/"
|
|
203
|
+
warn "Failed to launch web client: #{e.class}: #{e.message}"
|
|
210
204
|
[]
|
|
211
205
|
end
|
|
212
206
|
|
|
@@ -316,36 +310,45 @@ module Ruflet
|
|
|
316
310
|
end
|
|
317
311
|
|
|
318
312
|
def detect_desktop_client_command(url)
|
|
319
|
-
root =
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
cached_root = ensure_prebuilt_client(desktop: true)
|
|
325
|
-
desktop_client_command_from_root(cached_root, url)
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
def desktop_client_command_from_root(root, url)
|
|
313
|
+
root = ENV["RUFLET_CLIENT_DIR"]
|
|
314
|
+
root = File.expand_path("ruflet_client", Dir.pwd) if root.to_s.strip.empty?
|
|
315
|
+
root = nil unless Dir.exist?(root)
|
|
316
|
+
root ||= ensure_prebuilt_client(desktop: true)
|
|
329
317
|
return nil unless root && Dir.exist?(root)
|
|
330
318
|
|
|
331
319
|
host_os = RbConfig::CONFIG["host_os"]
|
|
332
320
|
if host_os.match?(/darwin/i)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
321
|
+
release_bin = File.join(root, "build", "macos", "Build", "Products", "Release", "ruflet_client.app", "Contents", "MacOS", "ruflet_client")
|
|
322
|
+
debug_bin = File.join(root, "build", "macos", "Build", "Products", "Debug", "ruflet_client.app", "Contents", "MacOS", "ruflet_client")
|
|
323
|
+
prebuilt_bin = File.join(root, "desktop", "ruflet_client.app", "Contents", "MacOS", "ruflet_client")
|
|
324
|
+
executable = [release_bin, debug_bin].find { |p| File.file?(p) && File.executable?(p) }
|
|
325
|
+
executable ||= prebuilt_bin if File.file?(prebuilt_bin) && File.executable?(prebuilt_bin)
|
|
326
|
+
return [executable, url] if executable
|
|
336
327
|
elsif host_os.match?(/mswin|mingw|cygwin/i)
|
|
337
|
-
exe = File.join(root, "
|
|
328
|
+
exe = File.join(root, "build", "windows", "x64", "runner", "Release", "ruflet_client.exe")
|
|
329
|
+
prebuilt = File.join(root, "desktop", "ruflet_client.exe")
|
|
330
|
+
exe = prebuilt if !File.file?(exe) && File.file?(prebuilt)
|
|
338
331
|
return [exe, url] if File.file?(exe)
|
|
339
332
|
else
|
|
340
|
-
direct = File.join(root, "
|
|
333
|
+
direct = File.join(root, "build", "linux", "x64", "release", "bundle", "ruflet_client")
|
|
334
|
+
prebuilt_direct = File.join(root, "desktop", "ruflet_client")
|
|
335
|
+
direct = prebuilt_direct if !File.file?(direct) && File.file?(prebuilt_direct)
|
|
341
336
|
return [direct, url] if File.file?(direct)
|
|
337
|
+
bundle_dir = File.join(root, "build", "linux", "x64", "release", "bundle")
|
|
338
|
+
if Dir.exist?(bundle_dir)
|
|
339
|
+
candidate = Dir.children(bundle_dir).map { |f| File.join(bundle_dir, f) }
|
|
340
|
+
.find { |path| File.file?(path) && File.executable?(path) }
|
|
341
|
+
return [candidate, url] if candidate
|
|
342
|
+
end
|
|
342
343
|
end
|
|
343
344
|
|
|
344
345
|
nil
|
|
345
346
|
end
|
|
346
347
|
|
|
347
348
|
def detect_web_client_dir
|
|
348
|
-
root =
|
|
349
|
+
root = ENV["RUFLET_CLIENT_DIR"]
|
|
350
|
+
root = File.expand_path("ruflet_client", Dir.pwd) if root.to_s.strip.empty?
|
|
351
|
+
root = nil unless Dir.exist?(root)
|
|
349
352
|
root ||= ensure_prebuilt_client(web: true)
|
|
350
353
|
return nil unless root && Dir.exist?(root)
|
|
351
354
|
|
|
@@ -357,14 +360,6 @@ module Ruflet
|
|
|
357
360
|
nil
|
|
358
361
|
end
|
|
359
362
|
|
|
360
|
-
def configured_client_root
|
|
361
|
-
root = ENV["RUFLET_CLIENT_DIR"].to_s.strip
|
|
362
|
-
return nil if root.empty?
|
|
363
|
-
|
|
364
|
-
expanded = File.expand_path(root)
|
|
365
|
-
Dir.exist?(expanded) ? expanded : nil
|
|
366
|
-
end
|
|
367
|
-
|
|
368
363
|
def ensure_prebuilt_client(web: false, desktop: false, platform: nil, force: false)
|
|
369
364
|
platform ||= host_platform_name
|
|
370
365
|
return nil if platform.nil?
|
|
@@ -379,19 +374,29 @@ module Ruflet
|
|
|
379
374
|
return nil if desktop_asset.nil?
|
|
380
375
|
wanted_assets << { kind: :desktop, name: desktop_asset, platform: platform }
|
|
381
376
|
end
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if !force &&
|
|
385
|
-
|
|
377
|
+
cache_ready = wanted_assets.empty? || prebuilt_assets_present?(cache_root, web: web, desktop: desktop, platform: platform)
|
|
378
|
+
release = nil
|
|
379
|
+
if !force && cache_ready
|
|
380
|
+
ensure_client_manifest(cache_root, platform: platform)
|
|
381
|
+
manifest = read_client_manifest(cache_root)
|
|
382
|
+
return cache_root unless client_update_due?(manifest)
|
|
383
|
+
|
|
384
|
+
release = fetch_release_for_version(wanted_assets: wanted_assets)
|
|
385
|
+
if release.nil? || client_release_current?(manifest, release, wanted_assets)
|
|
386
|
+
mark_client_update_checked(cache_root)
|
|
387
|
+
return cache_root
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
force = true
|
|
386
391
|
end
|
|
387
|
-
force = true if cached && !compatible_cache
|
|
388
392
|
|
|
389
|
-
release
|
|
393
|
+
release ||= fetch_release_for_version(wanted_assets: wanted_assets)
|
|
390
394
|
return nil unless release
|
|
391
395
|
|
|
392
396
|
assets = release.fetch("assets", [])
|
|
393
397
|
asset_names = assets.map { |a| a["name"].to_s }
|
|
394
398
|
installed_assets = []
|
|
399
|
+
release_revision = client_release_revision(release)
|
|
395
400
|
Dir.mktmpdir("ruflet-prebuilt-") do |tmpdir|
|
|
396
401
|
wanted_assets.each do |wanted|
|
|
397
402
|
asset_name = wanted.fetch(:name)
|
|
@@ -418,7 +423,8 @@ module Ruflet
|
|
|
418
423
|
"kind" => wanted[:kind].to_s,
|
|
419
424
|
"platform" => wanted[:platform] || platform,
|
|
420
425
|
"asset_name" => resolved_name,
|
|
421
|
-
"download_url" => asset.fetch("browser_download_url")
|
|
426
|
+
"download_url" => asset.fetch("browser_download_url"),
|
|
427
|
+
"release_revision" => release_revision
|
|
422
428
|
}
|
|
423
429
|
end
|
|
424
430
|
end
|
|
@@ -446,9 +452,7 @@ module Ruflet
|
|
|
446
452
|
|
|
447
453
|
case platform
|
|
448
454
|
when "macos"
|
|
449
|
-
|
|
450
|
-
bin = File.join(app_path, "Contents", "MacOS", "ruflet_client")
|
|
451
|
-
File.file?(bin) && ensure_macos_file_picker_entitlement(app_path)
|
|
455
|
+
File.file?(File.join(root, "desktop", "ruflet_client.app", "Contents", "MacOS", "ruflet_client"))
|
|
452
456
|
when "linux"
|
|
453
457
|
File.file?(File.join(root, "desktop", "ruflet_client"))
|
|
454
458
|
when "windows"
|
|
@@ -479,34 +483,71 @@ module Ruflet
|
|
|
479
483
|
File.join(Dir.home, ".ruflet", "client", ruflet_version, platform.to_s)
|
|
480
484
|
end
|
|
481
485
|
|
|
482
|
-
def fetch_release_for_version
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
+
def fetch_release_for_version(wanted_assets: [])
|
|
487
|
+
releases = []
|
|
488
|
+
channel = client_release_channel
|
|
489
|
+
if channel != "stable"
|
|
490
|
+
rolling = release_by_tag(channel)
|
|
491
|
+
releases << rolling if rolling && rolling_release_complete?(rolling)
|
|
486
492
|
end
|
|
493
|
+
releases << release_by_tag("v#{ruflet_version}")
|
|
494
|
+
releases << release_by_tag(ruflet_version)
|
|
495
|
+
releases << release_latest
|
|
496
|
+
releases.compact.find { |release| release_has_wanted_assets?(release, wanted_assets) }
|
|
497
|
+
end
|
|
487
498
|
|
|
488
|
-
|
|
499
|
+
def client_release_channel
|
|
500
|
+
value = ENV.fetch("RUFLET_CLIENT_CHANNEL", "prebuild-main").to_s.strip
|
|
501
|
+
value.empty? ? "prebuild-main" : value
|
|
489
502
|
end
|
|
490
503
|
|
|
491
|
-
def
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
raise ArgumentError, "Invalid RUFLET_CLIENT_CHANNEL" unless channel.match?(/\A[A-Za-z0-9._-]+\z/)
|
|
504
|
+
def rolling_release_complete?(release)
|
|
505
|
+
release.fetch("assets", []).any? { |asset| asset["name"] == CLIENT_CHANNEL_MANIFEST }
|
|
506
|
+
end
|
|
495
507
|
|
|
496
|
-
|
|
508
|
+
def release_has_wanted_assets?(release, wanted_assets)
|
|
509
|
+
assets = release.fetch("assets", [])
|
|
510
|
+
wanted_assets.all? do |wanted|
|
|
511
|
+
assets.any? { |asset| asset["name"] == wanted[:name] } || fallback_release_asset(assets, wanted)
|
|
497
512
|
end
|
|
513
|
+
end
|
|
498
514
|
|
|
499
|
-
|
|
515
|
+
def client_release_revision(release)
|
|
516
|
+
marker = release.fetch("assets", []).find { |asset| asset["name"] == CLIENT_CHANNEL_MANIFEST }
|
|
517
|
+
source = marker || release
|
|
518
|
+
[source["id"], source["updated_at"] || source["published_at"], source["size"]].compact.join(":")
|
|
500
519
|
end
|
|
501
520
|
|
|
502
|
-
def
|
|
503
|
-
manifest = read_client_manifest(root)
|
|
521
|
+
def client_release_current?(manifest, release, wanted_assets)
|
|
504
522
|
return false unless manifest
|
|
505
523
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
524
|
+
revision = client_release_revision(release)
|
|
525
|
+
targets = Array(manifest["targets"])
|
|
526
|
+
wanted_assets.all? do |wanted|
|
|
527
|
+
targets.any? do |target|
|
|
528
|
+
target["kind"] == wanted[:kind].to_s &&
|
|
529
|
+
(wanted[:platform].nil? || target["platform"] == wanted[:platform]) &&
|
|
530
|
+
target["release_revision"] == revision
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
def client_update_due?(manifest)
|
|
536
|
+
return false if ENV["RUFLET_CLIENT_AUTO_UPDATE"].to_s.match?(/\A(?:0|false|no|off)\z/i)
|
|
537
|
+
return true unless manifest
|
|
538
|
+
|
|
539
|
+
checked_at = manifest["checked_at"] || manifest["installed_at"]
|
|
540
|
+
return true if checked_at.to_s.empty?
|
|
541
|
+
|
|
542
|
+
Time.now.utc - Time.iso8601(checked_at) >= client_update_interval
|
|
543
|
+
rescue ArgumentError
|
|
544
|
+
true
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def client_update_interval
|
|
548
|
+
Integer(ENV.fetch("RUFLET_CLIENT_UPDATE_INTERVAL", DEFAULT_CLIENT_UPDATE_INTERVAL.to_s), 10).clamp(0, 7 * 24 * 60 * 60)
|
|
549
|
+
rescue ArgumentError
|
|
550
|
+
DEFAULT_CLIENT_UPDATE_INTERVAL
|
|
510
551
|
end
|
|
511
552
|
|
|
512
553
|
def ruflet_version
|
|
@@ -516,59 +557,16 @@ module Ruflet
|
|
|
516
557
|
Ruflet::VERSION
|
|
517
558
|
end
|
|
518
559
|
|
|
560
|
+
def release_latest
|
|
561
|
+
github_get_json("https://api.github.com/repos/AdamMusa/Ruflet/releases/latest")
|
|
562
|
+
end
|
|
563
|
+
|
|
519
564
|
def release_by_tag(tag)
|
|
520
565
|
github_get_json("https://api.github.com/repos/AdamMusa/Ruflet/releases/tags/#{tag}")
|
|
521
566
|
rescue StandardError
|
|
522
567
|
nil
|
|
523
568
|
end
|
|
524
569
|
|
|
525
|
-
def ensure_macos_file_picker_entitlement(app_path)
|
|
526
|
-
return true if macos_app_has_file_picker_entitlement?(app_path)
|
|
527
|
-
|
|
528
|
-
Dir.mktmpdir("ruflet-entitlements-") do |dir|
|
|
529
|
-
entitlements_path = File.join(dir, "ruflet_file_picker.entitlements")
|
|
530
|
-
File.write(entitlements_path, macos_file_picker_entitlements_plist)
|
|
531
|
-
system(
|
|
532
|
-
"/usr/bin/codesign",
|
|
533
|
-
"--force",
|
|
534
|
-
"--deep",
|
|
535
|
-
"--sign",
|
|
536
|
-
"-",
|
|
537
|
-
"--entitlements",
|
|
538
|
-
entitlements_path,
|
|
539
|
-
app_path,
|
|
540
|
-
out: File::NULL,
|
|
541
|
-
err: File::NULL
|
|
542
|
-
)
|
|
543
|
-
end
|
|
544
|
-
|
|
545
|
-
macos_app_has_file_picker_entitlement?(app_path)
|
|
546
|
-
end
|
|
547
|
-
|
|
548
|
-
def macos_app_has_file_picker_entitlement?(app_path)
|
|
549
|
-
output = IO.popen(["/usr/bin/codesign", "-d", "--entitlements", ":-", app_path], err: [:child, :out], &:read)
|
|
550
|
-
$?.success? && output.include?("com.apple.security.files.user-selected.read-write")
|
|
551
|
-
rescue StandardError
|
|
552
|
-
false
|
|
553
|
-
end
|
|
554
|
-
|
|
555
|
-
def macos_file_picker_entitlements_plist
|
|
556
|
-
<<~PLIST
|
|
557
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
558
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
559
|
-
<plist version="1.0">
|
|
560
|
-
<dict>
|
|
561
|
-
\t<key>com.apple.security.app-sandbox</key>
|
|
562
|
-
\t<true/>
|
|
563
|
-
\t<key>com.apple.security.files.user-selected.read-write</key>
|
|
564
|
-
\t<true/>
|
|
565
|
-
\t<key>com.apple.security.network.client</key>
|
|
566
|
-
\t<true/>
|
|
567
|
-
</dict>
|
|
568
|
-
</plist>
|
|
569
|
-
PLIST
|
|
570
|
-
end
|
|
571
|
-
|
|
572
570
|
def fallback_release_asset(assets, wanted)
|
|
573
571
|
kind = wanted[:kind]
|
|
574
572
|
platform = wanted[:platform]
|
|
@@ -607,8 +605,6 @@ module Ruflet
|
|
|
607
605
|
return JSON.parse(response.body) if response.is_a?(Net::HTTPSuccess)
|
|
608
606
|
|
|
609
607
|
raise "GitHub API failed (#{response.code})"
|
|
610
|
-
rescue OpenSSL::SSL::SSLError
|
|
611
|
-
JSON.parse(curl_get(url, headers: ["Accept: application/vnd.github+json", "User-Agent: ruflet-cli"]))
|
|
612
608
|
end
|
|
613
609
|
|
|
614
610
|
def download_file(url, destination, limit: 5)
|
|
@@ -630,31 +626,6 @@ module Ruflet
|
|
|
630
626
|
end
|
|
631
627
|
end
|
|
632
628
|
end
|
|
633
|
-
rescue OpenSSL::SSL::SSLError
|
|
634
|
-
curl_download(url, destination)
|
|
635
|
-
end
|
|
636
|
-
|
|
637
|
-
def curl_get(url, headers: [])
|
|
638
|
-
args = ["curl", "-fsSL"]
|
|
639
|
-
headers.each do |header|
|
|
640
|
-
args += ["-H", header]
|
|
641
|
-
end
|
|
642
|
-
args << url
|
|
643
|
-
output = IO.popen(args, err: File::NULL, &:read)
|
|
644
|
-
return output if $?.success?
|
|
645
|
-
|
|
646
|
-
raise "curl failed while requesting #{url}"
|
|
647
|
-
rescue Errno::ENOENT
|
|
648
|
-
raise "Ruby SSL verification failed and curl is not available"
|
|
649
|
-
end
|
|
650
|
-
|
|
651
|
-
def curl_download(url, destination)
|
|
652
|
-
ok = system("curl", "-fL", "--retry", "2", "--connect-timeout", "20", "-o", destination, url, out: File::NULL, err: File::NULL)
|
|
653
|
-
return destination if ok
|
|
654
|
-
|
|
655
|
-
raise "curl failed while downloading #{url}"
|
|
656
|
-
rescue Errno::ENOENT
|
|
657
|
-
raise "Ruby SSL verification failed and curl is not available"
|
|
658
629
|
end
|
|
659
630
|
|
|
660
631
|
def extract_archive(archive, destination)
|
|
@@ -685,20 +656,51 @@ module Ruflet
|
|
|
685
656
|
nil
|
|
686
657
|
end
|
|
687
658
|
|
|
659
|
+
def ensure_client_manifest(root, platform:)
|
|
660
|
+
return if read_client_manifest(root)
|
|
661
|
+
|
|
662
|
+
assets = []
|
|
663
|
+
assets << { "kind" => "web", "platform" => platform, "asset_name" => nil } if File.file?(File.join(root, "web", "index.html"))
|
|
664
|
+
if prebuilt_desktop_present?(root, platform: platform)
|
|
665
|
+
assets << { "kind" => "desktop", "platform" => platform, "asset_name" => nil }
|
|
666
|
+
end
|
|
667
|
+
return if assets.empty?
|
|
668
|
+
|
|
669
|
+
write_client_manifest(root, platform: platform, release: nil, assets: assets)
|
|
670
|
+
end
|
|
671
|
+
|
|
688
672
|
def write_client_manifest(root, platform:, release:, assets:)
|
|
689
673
|
FileUtils.mkdir_p(root)
|
|
674
|
+
existing = read_client_manifest(root) || {}
|
|
675
|
+
installed_targets = Array(existing["targets"])
|
|
676
|
+
assets.each do |asset|
|
|
677
|
+
installed_targets.reject! do |target|
|
|
678
|
+
target["kind"] == asset["kind"] && target["platform"] == asset["platform"]
|
|
679
|
+
end
|
|
680
|
+
installed_targets << asset
|
|
681
|
+
end
|
|
690
682
|
payload = {
|
|
691
|
-
"schema" =>
|
|
683
|
+
"schema" => 2,
|
|
692
684
|
"ruflet_version" => ruflet_version,
|
|
693
685
|
"platform" => platform,
|
|
694
|
-
"release_tag" => release && release["tag_name"],
|
|
695
|
-
"
|
|
686
|
+
"release_tag" => release && release["tag_name"] || existing["release_tag"],
|
|
687
|
+
"release_revision" => release && client_release_revision(release) || existing["release_revision"],
|
|
688
|
+
"released_at" => release && release["published_at"] || existing["released_at"],
|
|
689
|
+
"checked_at" => Time.now.utc.iso8601,
|
|
696
690
|
"installed_at" => Time.now.utc.iso8601,
|
|
697
|
-
"targets" =>
|
|
691
|
+
"targets" => installed_targets
|
|
698
692
|
}
|
|
699
693
|
File.write(client_manifest_path(root), JSON.pretty_generate(payload))
|
|
700
694
|
end
|
|
701
695
|
|
|
696
|
+
def mark_client_update_checked(root)
|
|
697
|
+
manifest = read_client_manifest(root)
|
|
698
|
+
return unless manifest
|
|
699
|
+
|
|
700
|
+
manifest["checked_at"] = Time.now.utc.iso8601
|
|
701
|
+
File.write(client_manifest_path(root), JSON.pretty_generate(manifest))
|
|
702
|
+
end
|
|
703
|
+
|
|
702
704
|
def print_mobile_qr_hint(port: 8550)
|
|
703
705
|
host = best_lan_host
|
|
704
706
|
payload = "http://#{host}:#{port}"
|
|
@@ -717,24 +719,26 @@ module Ruflet
|
|
|
717
719
|
port = start_port.to_i
|
|
718
720
|
|
|
719
721
|
max_attempts.times do
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
722
|
+
begin
|
|
723
|
+
begin
|
|
724
|
+
probe = TCPServer.new("0.0.0.0", port)
|
|
725
|
+
rescue Errno::EACCES, Errno::EPERM
|
|
726
|
+
probe = TCPServer.new("127.0.0.1", port)
|
|
727
|
+
end
|
|
728
|
+
probe.close
|
|
729
|
+
return port
|
|
730
|
+
rescue Errno::EADDRINUSE, Errno::EACCES, Errno::EPERM
|
|
731
|
+
port += 1
|
|
732
|
+
end
|
|
723
733
|
end
|
|
724
734
|
|
|
725
|
-
|
|
726
|
-
end
|
|
727
|
-
|
|
728
|
-
def default_backend_port(target)
|
|
729
|
-
DEFAULT_BACKEND_PORTS.fetch(target.to_s, DEFAULT_BACKEND_PORTS.fetch("mobile"))
|
|
735
|
+
start_port
|
|
730
736
|
end
|
|
731
737
|
|
|
732
|
-
def resolve_backend_port(
|
|
738
|
+
def resolve_backend_port(_target, requested_port: 8550)
|
|
733
739
|
base = requested_port.to_i
|
|
734
|
-
base =
|
|
735
|
-
|
|
736
|
-
warn "No available Ruflet port found starting at #{base}." unless selected
|
|
737
|
-
selected
|
|
740
|
+
base = 8550 if base <= 0
|
|
741
|
+
find_available_port(base)
|
|
738
742
|
end
|
|
739
743
|
|
|
740
744
|
def port_available?(port)
|
|
@@ -746,7 +750,7 @@ module Ruflet
|
|
|
746
750
|
probe = TCPServer.new("127.0.0.1", port)
|
|
747
751
|
end
|
|
748
752
|
true
|
|
749
|
-
rescue Errno::EADDRINUSE
|
|
753
|
+
rescue Errno::EADDRINUSE
|
|
750
754
|
false
|
|
751
755
|
ensure
|
|
752
756
|
probe&.close
|
data/lib/ruflet/cli/templates.rb
CHANGED
|
@@ -5,9 +5,16 @@ module Ruflet
|
|
|
5
5
|
MAIN_TEMPLATE = <<~RUBY
|
|
6
6
|
require "ruflet"
|
|
7
7
|
Ruflet.run do |page|
|
|
8
|
-
page.title = "
|
|
8
|
+
page.title = "Counter Demo"
|
|
9
9
|
count = 0
|
|
10
10
|
count_text = text(count.to_s, style: {size: 40})
|
|
11
|
+
page.floating_action_button = fab(
|
|
12
|
+
icon: "add",
|
|
13
|
+
on_click: ->(_e) do
|
|
14
|
+
count += 1
|
|
15
|
+
page.update(count_text, value: count.to_s)
|
|
16
|
+
end
|
|
17
|
+
)
|
|
11
18
|
page.add(
|
|
12
19
|
container(
|
|
13
20
|
expand: true,
|
|
@@ -20,13 +27,6 @@ module Ruflet
|
|
|
20
27
|
count_text
|
|
21
28
|
]
|
|
22
29
|
)
|
|
23
|
-
),
|
|
24
|
-
floating_action_button: fab(
|
|
25
|
-
icon: "add",
|
|
26
|
-
on_click: ->(_e) do
|
|
27
|
-
count += 1
|
|
28
|
-
page.update(count_text, value: count.to_s)
|
|
29
|
-
end
|
|
30
30
|
)
|
|
31
31
|
)
|
|
32
32
|
end
|
|
@@ -36,7 +36,6 @@ module Ruflet
|
|
|
36
36
|
GEMFILE_TEMPLATE = <<~GEMFILE
|
|
37
37
|
source "https://rubygems.org"
|
|
38
38
|
|
|
39
|
-
gem "ruflet", ">= #{Ruflet::VERSION}"
|
|
40
39
|
gem "ruflet_core", ">= #{Ruflet::VERSION}"
|
|
41
40
|
gem "ruflet_server", ">= #{Ruflet::VERSION}"
|
|
42
41
|
GEMFILE
|
data/lib/ruflet/cli.rb
CHANGED
|
@@ -2,23 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
require "optparse"
|
|
4
4
|
|
|
5
|
-
# Project files (pbxproj, plists, embedded runtime sources) are UTF-8; never
|
|
6
|
-
# depend on the caller's locale for reading them.
|
|
7
|
-
Encoding.default_external = Encoding::UTF_8 if Encoding.default_external == Encoding::US_ASCII
|
|
8
|
-
|
|
9
|
-
# Provisioning (downloads, package installs, SDK extraction) can run for
|
|
10
|
-
# minutes. When stdout is not a TTY (CI logs, pipes, editor consoles) Ruby
|
|
11
|
-
# buffers it, so progress notes would only appear once the whole run finishes.
|
|
12
|
-
# Unbuffer both streams so the pipeline reports progress as it happens.
|
|
13
|
-
$stdout.sync = true
|
|
14
|
-
$stderr.sync = true
|
|
15
|
-
|
|
16
5
|
require_relative "version"
|
|
17
6
|
require_relative "cli/templates"
|
|
18
7
|
require_relative "cli/new_command"
|
|
19
8
|
require_relative "cli/flutter_sdk"
|
|
20
|
-
require_relative "cli/environment_setup"
|
|
21
|
-
require_relative "cli/android_sdk"
|
|
22
9
|
require_relative "cli/run_command"
|
|
23
10
|
require_relative "cli/update_command"
|
|
24
11
|
require_relative "cli/build_command"
|
|
@@ -86,7 +73,7 @@ module Ruflet
|
|
|
86
73
|
ruflet install [--device DEVICE_ID] [--verbose]
|
|
87
74
|
ruflet devices
|
|
88
75
|
ruflet emulators
|
|
89
|
-
ruflet doctor
|
|
76
|
+
ruflet doctor [--fix] [--verbose]
|
|
90
77
|
HELP
|
|
91
78
|
end
|
|
92
79
|
|
|
@@ -102,7 +89,7 @@ module Ruflet
|
|
|
102
89
|
private
|
|
103
90
|
|
|
104
91
|
def ensure_first_run_assets(command)
|
|
105
|
-
return
|
|
92
|
+
return unless %w[create new bootstrap init update debug build install doctor].include?(command)
|
|
106
93
|
return unless respond_to?(:download_ruflet_assets, true)
|
|
107
94
|
|
|
108
95
|
send(:download_ruflet_assets)
|
data/lib/ruflet/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruflet
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.19
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- AdamMusa
|
|
@@ -37,9 +37,7 @@ files:
|
|
|
37
37
|
- assets/splash.png
|
|
38
38
|
- bin/ruflet
|
|
39
39
|
- lib/ruflet/cli.rb
|
|
40
|
-
- lib/ruflet/cli/android_sdk.rb
|
|
41
40
|
- lib/ruflet/cli/build_command.rb
|
|
42
|
-
- lib/ruflet/cli/environment_setup.rb
|
|
43
41
|
- lib/ruflet/cli/extra_command.rb
|
|
44
42
|
- lib/ruflet/cli/flutter_sdk.rb
|
|
45
43
|
- lib/ruflet/cli/new_command.rb
|