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.
@@ -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
- DEFAULT_BACKEND_PORTS = {
21
- "web" => 8550,
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: nil }
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, "Backend port (defaults: web 8550, desktop 8560, mobile 8570)") { |v| options[:requested_port] = v }
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
- requested_port = options[:requested_port] || default_backend_port(options[:target])
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
- # Web: the Ruby backend serves the Flutter web client itself (same
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 "Port #{requested_port} is busy; using #{port} for #{target}."
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
- url = "http://localhost:#{port}/"
203
- browser_pid = open_in_browser_app_mode(url)
204
- open_in_browser(url) if browser_pid.nil?
205
- puts "Ruflet web app: #{url}"
206
- [browser_pid].compact
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 open web client: #{e.class}: #{e.message}"
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 = configured_client_root
320
-
321
- command = desktop_client_command_from_root(root, url)
322
- return command if command
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
- app_path = File.join(root, "desktop", "ruflet_client.app")
334
- bin = File.join(app_path, "Contents", "MacOS", "ruflet_client")
335
- return [bin, url] if File.file?(bin) && File.executable?(bin) && ensure_macos_file_picker_entitlement(app_path)
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, "desktop", "ruflet_client.exe")
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, "desktop", "ruflet_client")
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 = configured_client_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
- cached = prebuilt_assets_present?(cache_root, web: web, desktop: desktop, platform: platform)
383
- compatible_cache = cached && compatible_client_cache?(cache_root, platform: platform)
384
- if !force && (wanted_assets.empty? || compatible_cache)
385
- return cache_root
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 = fetch_release_for_version
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
- app_path = File.join(root, "desktop", "ruflet_client.app")
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
- client_release_tags.each do |tag|
484
- release = release_by_tag(tag)
485
- return release if release
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
- nil
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 client_release_tags
492
- channel = ENV["RUFLET_CLIENT_CHANNEL"].to_s.strip
493
- unless channel.empty?
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
- return [channel]
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
- ["v#{ruflet_version}", ruflet_version]
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 compatible_client_cache?(root, platform:)
503
- manifest = read_client_manifest(root)
521
+ def client_release_current?(manifest, release, wanted_assets)
504
522
  return false unless manifest
505
523
 
506
- manifest["schema"] == 1 &&
507
- manifest["ruflet_version"] == ruflet_version &&
508
- manifest["platform"] == platform &&
509
- client_release_tags.include?(manifest["release_tag"])
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" => 1,
683
+ "schema" => 2,
692
684
  "ruflet_version" => ruflet_version,
693
685
  "platform" => platform,
694
- "release_tag" => release && release["tag_name"],
695
- "released_at" => release && release["published_at"],
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" => assets
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
- return port if port_available?(port)
721
-
722
- port += 1
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
- nil
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(target, requested_port: nil)
738
+ def resolve_backend_port(_target, requested_port: 8550)
733
739
  base = requested_port.to_i
734
- base = default_backend_port(target) if base <= 0
735
- selected = find_available_port(base)
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, Errno::EACCES, Errno::EPERM
753
+ rescue Errno::EADDRINUSE
750
754
  false
751
755
  ensure
752
756
  probe&.close
@@ -5,9 +5,16 @@ module Ruflet
5
5
  MAIN_TEMPLATE = <<~RUBY
6
6
  require "ruflet"
7
7
  Ruflet.run do |page|
8
- page.title = "%<app_title>s"
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 if %w[version -v --version help -h --help].include?(command)
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ruflet
4
- VERSION = "0.0.17" unless const_defined?(:VERSION)
4
+ VERSION = "0.0.19" unless const_defined?(:VERSION)
5
5
  end
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.17
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