ruflet_cli 0.0.4 → 0.0.5

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: 36d08da2d1377a858cd6703bd694b66d937604a21544af1de95d592aec4cb4df
4
- data.tar.gz: 3feccf443f7ecae27bf835a90f6fbb81c04bdbc86bf1a71350bda6c97a0d555d
3
+ metadata.gz: 83772c8986411d4680455374f1f350c015183318e3c090426ea53edfb4ed2a89
4
+ data.tar.gz: 45f42f969bc487714f24c0833ca8fdbf2236e789622deb604a231d1a254ea483
5
5
  SHA512:
6
- metadata.gz: e8677cc79bdb4e546f88ea3140b95e0177a0f24f884fa679f1a5636098017186caa14859a189c755b42455bdd7bbf612a8ac78f608766cd19f36b1f756c5ad69
7
- data.tar.gz: b07035a35c315f1b42cc5dda629d080a4956b1854dd96297420d6ad24986c3095101f86ff9889c13d0bc133e18967c2666fbadad3228f00b39eabd9442af88ac
6
+ metadata.gz: 412a6524bff1cdf335256193390aca08c71b299f517da00af1da6efada84c7fc0ebd0d399be9e92235d6713ebcfbd8c8f9695695b68e3465242ed998deecf1bb
7
+ data.tar.gz: 0f77d2ee780f5d443e58d356ac02382ae0e5a53144ac64d9df5bf8d4e9bec2fcf51646f8d92fdc69e527259a7100e772fafcd26861c2d63ba829606531b03824
@@ -6,6 +6,8 @@ require "yaml"
6
6
  module Ruflet
7
7
  module CLI
8
8
  module BuildCommand
9
+ include FlutterSdk
10
+
9
11
  def command_build(args)
10
12
  platform = (args.shift || "").downcase
11
13
  if platform.empty?
@@ -26,10 +28,11 @@ module Ruflet
26
28
  return 1
27
29
  end
28
30
 
29
- ok = prepare_flutter_client(client_dir)
31
+ tools = ensure_flutter!("build", client_dir: client_dir)
32
+ ok = prepare_flutter_client(client_dir, tools: tools)
30
33
  return 1 unless ok
31
34
 
32
- ok = system(*flutter_cmd, *args, chdir: client_dir)
35
+ ok = system(tools[:env], tools[:flutter], *flutter_cmd, *args, chdir: client_dir)
33
36
  ok ? 0 : 1
34
37
  end
35
38
 
@@ -48,19 +51,19 @@ module Ruflet
48
51
  nil
49
52
  end
50
53
 
51
- def prepare_flutter_client(client_dir)
54
+ def prepare_flutter_client(client_dir, tools:)
52
55
  apply_build_config(client_dir)
53
- unless system("flutter", "pub", "get", chdir: client_dir)
56
+ unless system(tools[:env], tools[:flutter], "pub", "get", chdir: client_dir)
54
57
  warn "flutter pub get failed"
55
58
  return false
56
59
  end
57
60
 
58
- unless system("dart", "run", "flutter_native_splash:create", chdir: client_dir)
61
+ unless system(tools[:env], tools[:dart], "run", "flutter_native_splash:create", chdir: client_dir)
59
62
  warn "flutter_native_splash failed"
60
63
  return false
61
64
  end
62
65
 
63
- unless system("dart", "run", "flutter_launcher_icons", chdir: client_dir)
66
+ unless system(tools[:env], tools[:dart], "run", "flutter_launcher_icons", chdir: client_dir)
64
67
  warn "flutter_launcher_icons failed"
65
68
  return false
66
69
  end
@@ -213,19 +216,19 @@ module Ruflet
213
216
  def flutter_build_command(platform)
214
217
  case platform
215
218
  when "apk", "android"
216
- ["flutter", "build", "apk"]
219
+ ["build", "apk"]
217
220
  when "aab", "appbundle"
218
- ["flutter", "build", "appbundle"]
221
+ ["build", "appbundle"]
219
222
  when "ios"
220
- ["flutter", "build", "ios", "--no-codesign"]
223
+ ["build", "ios", "--no-codesign"]
221
224
  when "web"
222
- ["flutter", "build", "web"]
225
+ ["build", "web"]
223
226
  when "macos"
224
- ["flutter", "build", "macos"]
227
+ ["build", "macos"]
225
228
  when "windows"
226
- ["flutter", "build", "windows"]
229
+ ["build", "windows"]
227
230
  when "linux"
228
- ["flutter", "build", "linux"]
231
+ ["build", "linux"]
229
232
  else
230
233
  nil
231
234
  end
@@ -5,6 +5,8 @@ require "optparse"
5
5
  module Ruflet
6
6
  module CLI
7
7
  module ExtraCommand
8
+ include FlutterSdk
9
+
8
10
  def command_create(args)
9
11
  command_new(args)
10
12
  end
@@ -13,23 +15,20 @@ module Ruflet
13
15
  verbose = args.delete("--verbose") || args.delete("-v")
14
16
  puts "Ruflet doctor"
15
17
  puts " Ruby: #{RUBY_VERSION}"
16
- flutter = system("which flutter > /dev/null 2>&1")
17
- puts " Flutter: #{flutter ? 'found' : 'missing'}"
18
- if flutter
19
- system("flutter", "doctor", *(verbose ? ["-v"] : []))
20
- return $?.exitstatus || 0
21
- end
22
- 1
18
+ tools = ensure_flutter!("doctor")
19
+ puts " Flutter: #{tools[:flutter]}"
20
+ system(tools[:env], tools[:flutter], "doctor", *(verbose ? ["-v"] : []))
21
+ $?.exitstatus || 1
23
22
  end
24
23
 
25
24
  def command_devices(args)
26
- ensure_flutter!("devices")
27
- system("flutter", "devices", *args)
25
+ tools = ensure_flutter!("devices")
26
+ system(tools[:env], tools[:flutter], "devices", *args)
28
27
  $?.exitstatus || 1
29
28
  end
30
29
 
31
30
  def command_emulators(args)
32
- ensure_flutter!("emulators")
31
+ tools = ensure_flutter!("emulators")
33
32
  action = nil
34
33
  emulator_id = nil
35
34
  verbose = false
@@ -48,58 +47,22 @@ module Ruflet
48
47
  warn "Missing --emulator for start"
49
48
  return 1
50
49
  end
51
- cmd = ["flutter", "emulators", "--launch", emulator_id]
50
+ cmd = [tools[:flutter], "emulators", "--launch", emulator_id]
52
51
  cmd << "-v" if verbose
53
- system(*cmd)
52
+ system(tools[:env], *cmd)
54
53
  $?.exitstatus || 1
55
54
  when "create", "delete"
56
55
  warn "ruflet emulators --#{action} is not implemented yet. Use your platform tools."
57
56
  1
58
57
  else
59
- cmd = ["flutter", "emulators"]
58
+ cmd = [tools[:flutter], "emulators"]
60
59
  cmd << "-v" if verbose
61
- system(*cmd)
60
+ system(tools[:env], *cmd)
62
61
  $?.exitstatus || 1
63
62
  end
64
63
  end
65
64
 
66
- def command_serve(args)
67
- options = { port: 8550, root: Dir.pwd }
68
- parser = OptionParser.new do |o|
69
- o.on("-p", "--port PORT", Integer, "Port (default: 8550)") { |v| options[:port] = v }
70
- o.on("-r", "--root PATH", "Root directory (default: current dir)") { |v| options[:root] = v }
71
- end
72
- parser.parse!(args)
73
-
74
- require "webrick"
75
- root = File.expand_path(options[:root])
76
- server = WEBrick::HTTPServer.new(
77
- Port: options[:port],
78
- DocumentRoot: root,
79
- AccessLog: [],
80
- Logger: WEBrick::Log.new($stderr, WEBrick::Log::WARN)
81
- )
82
- trap("INT") { server.shutdown }
83
- puts "Serving #{root} on http://127.0.0.1:#{options[:port]}"
84
- server.start
85
- 0
86
- end
87
-
88
- def command_pack(args)
89
- platform = default_desktop_platform
90
- unless platform
91
- warn "pack is only supported on desktop hosts (macOS, Windows, Linux)"
92
- return 1
93
- end
94
- command_build([platform] + args)
95
- end
96
-
97
- def command_publish(args)
98
- command_build(["web"] + args)
99
- end
100
-
101
65
  def command_debug(args)
102
- ensure_flutter!("debug")
103
66
  options = {
104
67
  platform: nil,
105
68
  device_id: nil,
@@ -117,7 +80,17 @@ module Ruflet
117
80
  parser.parse!(args)
118
81
 
119
82
  options[:platform] ||= args.shift
120
- cmd = ["flutter", "run"]
83
+ client_dir = detect_client_dir
84
+ unless client_dir
85
+ warn "Could not find Flutter client directory."
86
+ warn "Set RUFLET_CLIENT_DIR or place client at ./ruflet_client"
87
+ warn "`ruflet debug` requires Flutter client source code."
88
+ warn "For prebuilt clients, use: `ruflet run --web` or `ruflet run --desktop`."
89
+ return 1
90
+ end
91
+
92
+ tools = ensure_flutter!("debug", client_dir: client_dir)
93
+ cmd = [tools[:flutter], "run"]
121
94
  cmd << "--release" if options[:release]
122
95
  cmd << "-v" if options[:verbose]
123
96
  cmd += ["--web-renderer", options[:web_renderer]] if options[:web_renderer]
@@ -135,14 +108,7 @@ module Ruflet
135
108
  end
136
109
  end
137
110
 
138
- client_dir = detect_client_dir
139
- unless client_dir
140
- warn "Could not find Flutter client directory."
141
- warn "Set RUFLET_CLIENT_DIR or place client at ./ruflet_client"
142
- return 1
143
- end
144
-
145
- system(*cmd, chdir: client_dir)
111
+ system(tools[:env], *cmd, chdir: client_dir)
146
112
  $?.exitstatus || 1
147
113
  end
148
114
 
@@ -161,20 +127,6 @@ module Ruflet
161
127
  nil
162
128
  end
163
129
 
164
- def default_desktop_platform
165
- host = RbConfig::CONFIG["host_os"]
166
- return "macos" if host =~ /darwin/i
167
- return "windows" if host =~ /mswin|mingw|cygwin/i
168
- return "linux" if host =~ /linux/i
169
- nil
170
- end
171
-
172
- def ensure_flutter!(command_name)
173
- return if system("which flutter > /dev/null 2>&1")
174
-
175
- warn "Flutter is required for `ruflet #{command_name}`. Install Flutter and ensure it is on PATH."
176
- exit 1
177
- end
178
130
  end
179
131
  end
180
132
  end
@@ -0,0 +1,275 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+ require "net/http"
6
+ require "rbconfig"
7
+ require "tmpdir"
8
+ require "uri"
9
+
10
+ module Ruflet
11
+ module CLI
12
+ module FlutterSdk
13
+ RELEASES_BASE = "https://storage.googleapis.com/flutter_infra_release/releases".freeze
14
+ DEFAULT_FLUTTER_CHANNEL = "stable".freeze
15
+
16
+ def ensure_flutter!(command_name, client_dir: nil)
17
+ tools = flutter_tools(client_dir: client_dir)
18
+ return tools if tools
19
+
20
+ warn "Flutter is required for `ruflet #{command_name}` and FVM bootstrap failed."
21
+ warn "Set RUFLET_FLUTTER_VERSION or add .fvmrc to the project."
22
+ exit 1
23
+ end
24
+
25
+ private
26
+
27
+ def flutter_tools(client_dir: nil)
28
+ # Always use FVM so Flutter/Dart match pinned SDK.
29
+ fvm_tools = flutter_tools_via_fvm(client_dir: client_dir)
30
+ return fvm_tools if fvm_tools
31
+
32
+ nil
33
+ end
34
+
35
+ def flutter_tools_via_fvm(client_dir: nil)
36
+ version = desired_flutter_version(client_dir: client_dir)
37
+ return nil if version.to_s.strip.empty?
38
+
39
+ project_dir = fvm_project_dir(client_dir: client_dir)
40
+ fvm = ensure_fvm_available(client_dir: client_dir)
41
+ return nil unless fvm
42
+
43
+ FileUtils.mkdir_p(project_dir)
44
+ fvmrc_path = File.join(project_dir, ".fvmrc")
45
+ unless File.file?(fvmrc_path)
46
+ File.write(fvmrc_path, "{\"flutter\":\"#{version}\"}\n")
47
+ end
48
+
49
+ system(fvm_env, fvm, "install", version.to_s, chdir: project_dir, out: File::NULL, err: File::NULL)
50
+ system(fvm_env, fvm, "use", "--force", version.to_s, chdir: project_dir, out: File::NULL, err: File::NULL)
51
+
52
+ flutter = File.join(project_dir, ".fvm", "flutter_sdk", "bin", windows_host? ? "flutter.bat" : "flutter")
53
+ return nil unless File.executable?(flutter)
54
+
55
+ tools_from_flutter_bin(flutter)
56
+ rescue StandardError => e
57
+ warn "FVM bootstrap failed: #{e.class}: #{e.message}"
58
+ nil
59
+ end
60
+
61
+ def ensure_fvm_available(client_dir: nil)
62
+ fvm = which_command("fvm")
63
+ return fvm if fvm
64
+
65
+ dart = which_command("dart")
66
+ unless dart
67
+ sdk_root = ensure_flutter_sdk_downloaded(client_dir: client_dir)
68
+ dart = sdk_root ? File.join(sdk_root, "bin", windows_host? ? "dart.bat" : "dart") : nil
69
+ end
70
+ return nil unless dart && File.executable?(dart)
71
+
72
+ system(dart, "pub", "global", "activate", "fvm", out: File::NULL, err: File::NULL)
73
+ which_command("fvm")
74
+ end
75
+
76
+ def fvm_env
77
+ pub_bin = File.join(Dir.home, ".pub-cache", "bin")
78
+ { "PATH" => "#{pub_bin}#{File::PATH_SEPARATOR}#{ENV.fetch('PATH', '')}" }
79
+ end
80
+
81
+ def tools_from_flutter_bin(flutter_bin)
82
+ return nil unless File.executable?(flutter_bin)
83
+
84
+ bin_dir = File.dirname(flutter_bin)
85
+ dart = File.join(bin_dir, windows_host? ? "dart.bat" : "dart")
86
+ {
87
+ flutter: flutter_bin,
88
+ dart: (File.executable?(dart) ? dart : "dart"),
89
+ env: { "PATH" => "#{bin_dir}#{File::PATH_SEPARATOR}#{ENV.fetch('PATH', '')}" }
90
+ }
91
+ end
92
+
93
+ def ensure_flutter_sdk_downloaded(client_dir: nil)
94
+ release_info = resolve_flutter_release(client_dir: client_dir)
95
+ return nil unless release_info
96
+
97
+ release = release_info[:release]
98
+ host = release_info[:host]
99
+ archive = release.fetch("archive")
100
+ install_root = File.join(Dir.home, ".ruflet", "flutter", release.fetch("version"), host)
101
+ sdk_root = File.join(install_root, "flutter")
102
+ flutter_bin = File.join(sdk_root, "bin", windows_host? ? "flutter.bat" : "flutter")
103
+ return sdk_root if File.executable?(flutter_bin)
104
+
105
+ FileUtils.mkdir_p(install_root)
106
+ Dir.mktmpdir("ruflet-flutter-sdk-") do |tmpdir|
107
+ archive_path = File.join(tmpdir, File.basename(archive))
108
+ download_file("#{RELEASES_BASE}/#{archive}", archive_path)
109
+ extract_archive(archive_path, install_root)
110
+ end
111
+
112
+ return sdk_root if File.executable?(flutter_bin)
113
+
114
+ # Some archives may unpack into a different folder name.
115
+ guessed = Dir.glob(File.join(install_root, "**", windows_host? ? "flutter.bat" : "flutter"))
116
+ .map { |p| File.expand_path("../..", p) }
117
+ .find { |root| File.executable?(File.join(root, "bin", windows_host? ? "flutter.bat" : "flutter")) }
118
+ return guessed if guessed
119
+
120
+ nil
121
+ rescue StandardError => e
122
+ warn "Flutter auto-install failed: #{e.class}: #{e.message}"
123
+ nil
124
+ end
125
+
126
+ def resolve_flutter_release(client_dir: nil)
127
+ host = flutter_host
128
+ return nil unless host
129
+
130
+ manifest = fetch_releases_manifest(host)
131
+ return nil unless manifest
132
+
133
+ version = desired_flutter_version(client_dir: client_dir)
134
+ release = pick_release(manifest, version: version)
135
+ return nil unless release
136
+
137
+ { release: release, host: host }
138
+ end
139
+
140
+ def desired_flutter_version(client_dir: nil)
141
+ env = ENV["RUFLET_FLUTTER_VERSION"].to_s.strip
142
+ return env unless env.empty?
143
+
144
+ fvm = parse_fvmrc(find_fvmrc(client_dir))
145
+ return fvm if fvm
146
+
147
+ DEFAULT_FLUTTER_CHANNEL
148
+ end
149
+
150
+ def fvm_project_dir(client_dir: nil)
151
+ return client_dir if client_dir
152
+
153
+ File.join(Dir.home, ".ruflet", "fvm_project")
154
+ end
155
+
156
+ def find_fvmrc(client_dir)
157
+ candidates = []
158
+ candidates << File.join(client_dir, ".fvmrc") if client_dir
159
+ candidates << File.join(Dir.pwd, ".fvmrc")
160
+ candidates.find { |p| File.file?(p) }
161
+ end
162
+
163
+ def parse_fvmrc(path)
164
+ return nil unless path && File.file?(path)
165
+
166
+ raw = File.read(path).strip
167
+ return nil if raw.empty?
168
+
169
+ if raw.start_with?("{")
170
+ json = JSON.parse(raw) rescue {}
171
+ val = json["flutter"] || json["flutterSdkVersion"] || json["flutter_version"]
172
+ return val.to_s.strip unless val.to_s.strip.empty?
173
+ end
174
+
175
+ raw
176
+ end
177
+
178
+ def fetch_releases_manifest(host)
179
+ url = "#{RELEASES_BASE}/releases_#{host}.json"
180
+ uri = URI(url)
181
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
182
+ req = Net::HTTP::Get.new(uri)
183
+ req["User-Agent"] = "ruflet-cli"
184
+ http.request(req)
185
+ end
186
+ return JSON.parse(response.body) if response.is_a?(Net::HTTPSuccess)
187
+
188
+ nil
189
+ end
190
+
191
+ def pick_release(manifest, version: nil)
192
+ releases = manifest.fetch("releases", [])
193
+ if version
194
+ pinned = releases.find { |r| r["channel"] == "stable" && r["version"] == version }
195
+ return pinned if pinned
196
+ warn "Requested Flutter #{version} not found in stable releases; falling back to latest stable."
197
+ end
198
+
199
+ current = manifest.fetch("current_release", {})["stable"]
200
+ if current
201
+ by_hash = releases.find { |r| r["hash"] == current }
202
+ return by_hash if by_hash
203
+ end
204
+
205
+ releases.reverse.find { |r| r["channel"] == "stable" }
206
+ end
207
+
208
+ def flutter_host
209
+ os = RbConfig::CONFIG["host_os"]
210
+ if os.match?(/darwin/i)
211
+ return machine_arch.include?("arm") ? "macos_arm64" : "macos"
212
+ end
213
+ return "linux" if os.match?(/linux/i)
214
+ return "windows" if os.match?(/mswin|mingw|cygwin/i)
215
+
216
+ nil
217
+ end
218
+
219
+ def machine_arch
220
+ RbConfig::CONFIG["host_cpu"].to_s.downcase
221
+ end
222
+
223
+ def windows_host?
224
+ RbConfig::CONFIG["host_os"].match?(/mswin|mingw|cygwin/i)
225
+ end
226
+
227
+ def which_command(name)
228
+ exts = windows_host? ? ENV.fetch("PATHEXT", ".EXE;.BAT;.CMD").split(";") : [""]
229
+ ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).each do |dir|
230
+ exts.each do |ext|
231
+ candidate = File.join(dir, "#{name}#{ext}")
232
+ return candidate if File.file?(candidate) && File.executable?(candidate)
233
+ end
234
+ end
235
+ nil
236
+ end
237
+
238
+ def download_file(url, destination, limit: 5)
239
+ raise "Too many redirects while downloading #{url}" if limit <= 0
240
+
241
+ uri = URI(url)
242
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
243
+ req = Net::HTTP::Get.new(uri)
244
+ req["User-Agent"] = "ruflet-cli"
245
+ http.request(req) do |res|
246
+ case res
247
+ when Net::HTTPSuccess
248
+ File.open(destination, "wb") { |f| res.read_body { |chunk| f.write(chunk) } }
249
+ return destination
250
+ when Net::HTTPRedirection
251
+ return download_file(res["location"], destination, limit: limit - 1)
252
+ else
253
+ raise "Download failed (#{res.code})"
254
+ end
255
+ end
256
+ end
257
+ end
258
+
259
+ def extract_archive(archive, destination)
260
+ if archive.end_with?(".zip")
261
+ if windows_host?
262
+ return system("powershell", "-NoProfile", "-Command", "Expand-Archive -Path '#{archive}' -DestinationPath '#{destination}' -Force")
263
+ end
264
+ return system("unzip", "-oq", archive, "-d", destination)
265
+ end
266
+
267
+ if archive.end_with?(".tar.xz") || archive.end_with?(".tar.gz") || archive.end_with?(".tgz")
268
+ return system("tar", "-xf", archive, "-C", destination)
269
+ end
270
+
271
+ false
272
+ end
273
+ end
274
+ end
275
+ end
@@ -19,10 +19,8 @@ module Ruflet
19
19
  end
20
20
 
21
21
  FileUtils.mkdir_p(root)
22
- FileUtils.mkdir_p(File.join(root, ".bundle"))
23
22
  File.write(File.join(root, "main.rb"), format(Ruflet::CLI::MAIN_TEMPLATE, app_title: humanize_name(File.basename(root))))
24
23
  File.write(File.join(root, "Gemfile"), Ruflet::CLI::GEMFILE_TEMPLATE)
25
- File.write(File.join(root, ".bundle", "config"), Ruflet::CLI::BUNDLE_CONFIG_TEMPLATE)
26
24
  File.write(File.join(root, "README.md"), format(Ruflet::CLI::README_TEMPLATE, app_name: File.basename(root)))
27
25
  copy_ruflet_client_template(root)
28
26
 
@@ -30,7 +30,8 @@ module Ruflet
30
30
  return 1
31
31
  end
32
32
 
33
- selected_port = find_available_port(8550)
33
+ selected_port = resolve_backend_port(options[:target])
34
+ return 1 unless selected_port
34
35
  env = {
35
36
  "RUFLET_TARGET" => options[:target],
36
37
  "RUFLET_SUPPRESS_SERVER_BANNER" => "1",
@@ -42,10 +43,10 @@ module Ruflet
42
43
  print_run_banner(target: options[:target], port: selected_port)
43
44
  print_mobile_qr_hint(port: selected_port) if options[:target] == "mobile"
44
45
 
46
+ gemfile_path = find_nearest_gemfile(Dir.pwd)
45
47
  cmd =
46
- if File.file?(File.expand_path("Gemfile", Dir.pwd))
47
- env["BUNDLE_PATH"] = "vendor/bundle"
48
- env["BUNDLE_DISABLE_SHARED_GEMS"] = "true"
48
+ if gemfile_path
49
+ env["BUNDLE_GEMFILE"] = gemfile_path
49
50
  bundle_ready = system(env, "bundle", "check", out: File::NULL, err: File::NULL)
50
51
  return 1 unless bundle_ready || system(env, "bundle", "install")
51
52
 
@@ -106,8 +107,21 @@ module Ruflet
106
107
  nil
107
108
  end
108
109
 
110
+ def find_nearest_gemfile(start_dir)
111
+ current = File.expand_path(start_dir)
112
+ loop do
113
+ candidate = File.join(current, "Gemfile")
114
+ return candidate if File.file?(candidate)
115
+
116
+ parent = File.expand_path("..", current)
117
+ return nil if parent == current
118
+
119
+ current = parent
120
+ end
121
+ end
122
+
109
123
  def print_run_banner(target:, port:)
110
- if port != 8550
124
+ if target == "mobile" && port != 8550
111
125
  puts "Requested port 8550 is busy; bound to #{port}"
112
126
  end
113
127
  if target == "desktop"
@@ -316,15 +330,15 @@ module Ruflet
316
330
  platform = host_platform_name
317
331
  return nil if platform.nil?
318
332
 
319
- cache_root = File.join(Dir.home, ".ruflet", "client", Ruflet::VERSION, platform)
333
+ cache_root = File.join(Dir.home, ".ruflet", "client", ruflet_version, platform)
320
334
  FileUtils.mkdir_p(cache_root)
321
335
 
322
336
  wanted_assets = []
323
- wanted_assets << "ruflet_client-web.tar.gz" if web
337
+ wanted_assets << { kind: :web, name: "ruflet_client-web.tar.gz" } if web
324
338
  if desktop
325
339
  desktop_asset = desktop_asset_name_for(platform)
326
340
  return nil if desktop_asset.nil?
327
- wanted_assets << desktop_asset
341
+ wanted_assets << { kind: :desktop, name: desktop_asset, platform: platform }
328
342
  end
329
343
  return cache_root if wanted_assets.empty? || prebuilt_assets_present?(cache_root, web: web, desktop: desktop)
330
344
 
@@ -332,20 +346,26 @@ module Ruflet
332
346
  return nil unless release
333
347
 
334
348
  assets = release.fetch("assets", [])
349
+ asset_names = assets.map { |a| a["name"].to_s }
335
350
  Dir.mktmpdir("ruflet-prebuilt-") do |tmpdir|
336
- wanted_assets.each do |asset_name|
351
+ wanted_assets.each do |wanted|
352
+ asset_name = wanted.fetch(:name)
337
353
  asset = assets.find { |a| a["name"] == asset_name }
354
+ asset ||= fallback_release_asset(assets, wanted)
338
355
  unless asset
339
356
  warn "Missing release asset: #{asset_name}"
357
+ warn "Available assets: #{asset_names.join(', ')}" unless asset_names.empty?
340
358
  return nil
341
359
  end
342
- archive_path = File.join(tmpdir, asset_name)
360
+ resolved_name = asset.fetch("name")
361
+ puts "Downloading prebuilt client asset: #{resolved_name}"
362
+ archive_path = File.join(tmpdir, resolved_name)
343
363
  download_file(asset.fetch("browser_download_url"), archive_path)
344
- subdir = asset_name.include?("-web.") ? "web" : "desktop"
364
+ subdir = wanted[:kind] == :web ? "web" : "desktop"
345
365
  target = File.join(cache_root, subdir)
346
366
  FileUtils.mkdir_p(target)
347
367
  unless extract_archive(archive_path, target)
348
- warn "Failed to extract asset: #{asset_name}"
368
+ warn "Failed to extract asset: #{resolved_name}"
349
369
  return nil
350
370
  end
351
371
  end
@@ -399,7 +419,18 @@ module Ruflet
399
419
  end
400
420
 
401
421
  def fetch_release_for_version
402
- release_by_tag("v#{Ruflet::VERSION}") || release_latest
422
+ release_by_tag("v#{ruflet_version}") ||
423
+ release_by_tag(ruflet_version) ||
424
+ release_by_tag("prebuild") ||
425
+ release_by_tag("prebuild-main") ||
426
+ release_latest
427
+ end
428
+
429
+ def ruflet_version
430
+ return Ruflet::VERSION if Ruflet.const_defined?(:VERSION)
431
+
432
+ require_relative "../version"
433
+ Ruflet::VERSION
403
434
  end
404
435
 
405
436
  def release_latest
@@ -412,6 +443,33 @@ module Ruflet
412
443
  nil
413
444
  end
414
445
 
446
+ def fallback_release_asset(assets, wanted)
447
+ kind = wanted[:kind]
448
+ platform = wanted[:platform]
449
+ candidates = assets.select { |asset| release_asset_matches?(asset.fetch("name", ""), kind, platform) }
450
+ candidates.first
451
+ end
452
+
453
+ def release_asset_matches?(name, kind, platform)
454
+ n = name.to_s.downcase
455
+ return false unless n.include?("ruflet_client")
456
+
457
+ if kind == :web
458
+ return n.include?("web") && (n.end_with?(".tar.gz") || n.end_with?(".zip"))
459
+ end
460
+
461
+ case platform
462
+ when "macos"
463
+ n.include?("macos") && n.end_with?(".zip")
464
+ when "linux"
465
+ n.include?("linux") && (n.end_with?(".tar.gz") || n.end_with?(".tgz"))
466
+ when "windows"
467
+ n.include?("windows") && n.end_with?(".zip")
468
+ else
469
+ false
470
+ end
471
+ end
472
+
415
473
  def github_get_json(url)
416
474
  uri = URI(url)
417
475
  response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
@@ -497,6 +555,32 @@ module Ruflet
497
555
  start_port
498
556
  end
499
557
 
558
+ def resolve_backend_port(target)
559
+ return find_available_port(8550) if target == "mobile"
560
+
561
+ return 8550 if port_available?(8550)
562
+
563
+ warn "Port 8550 is required for `ruflet run --#{target}`."
564
+ warn "Stop the process using 8550 and run again."
565
+ nil
566
+ end
567
+
568
+ def port_available?(port)
569
+ probe = nil
570
+ begin
571
+ begin
572
+ probe = TCPServer.new("0.0.0.0", port)
573
+ rescue Errno::EACCES, Errno::EPERM
574
+ probe = TCPServer.new("127.0.0.1", port)
575
+ end
576
+ true
577
+ rescue Errno::EADDRINUSE
578
+ false
579
+ ensure
580
+ probe&.close
581
+ end
582
+ end
583
+
500
584
  def best_lan_host
501
585
  ips = Socket.ip_address_list
502
586
  addr = ips.find { |ip| ip.ipv4_private? && !ip.ipv4_loopback? }
@@ -13,28 +13,30 @@ module Ruflet
13
13
 
14
14
  def view(page)
15
15
  page.title = "Counter Demo"
16
- count_text = page.text(value: @count.to_s, size: 40)
16
+ page.vertical_alignment = Ruflet::MainAxisAlignment::CENTER
17
+ page.horizontal_alignment = Ruflet::CrossAxisAlignment::CENTER
18
+ count_text = text(value: @count.to_s, size: 40)
17
19
 
18
20
  page.add(
19
- page.container(
21
+ container(
20
22
  expand: true,
21
23
  padding: 24,
22
- content: page.column(
24
+ content: column(
23
25
  expand: true,
24
26
  alignment: Ruflet::MainAxisAlignment::CENTER,
25
27
  horizontal_alignment: Ruflet::CrossAxisAlignment::CENTER,
26
28
  spacing: 12,
27
29
  controls: [
28
- page.text(value: "You have pushed the button this many times:"),
30
+ text(value: "You have pushed the button this many times:"),
29
31
  count_text
30
32
  ]
31
33
  )
32
34
  ),
33
- appbar: page.app_bar(
34
- title: page.text(value: "Counter Demo")
35
+ appbar: app_bar(
36
+ title: text(value: "Counter Demo")
35
37
  ),
36
- floating_action_button: page.fab(
37
- page.icon(icon: Ruflet::MaterialIcons::ADD),
38
+ floating_action_button: fab(
39
+ icon(icon: Ruflet::MaterialIcons::ADD),
38
40
  on_click: ->(_e) {
39
41
  @count += 1
40
42
  page.update(count_text, value: @count.to_s)
@@ -45,22 +47,16 @@ module Ruflet
45
47
  end
46
48
 
47
49
  MainApp.new.run
50
+
48
51
  RUBY
49
52
 
50
53
  GEMFILE_TEMPLATE = <<~GEMFILE
51
54
  source "https://rubygems.org"
52
55
 
53
56
  gem "ruflet", ">= 0.0.3"
54
- gem "ruflet_protocol", ">= 0.0.3"
55
57
  gem "ruflet_server", ">= 0.0.3"
56
58
  GEMFILE
57
59
 
58
- BUNDLE_CONFIG_TEMPLATE = <<~YAML
59
- ---
60
- BUNDLE_PATH: "vendor/bundle"
61
- BUNDLE_DISABLE_SHARED_GEMS: "true"
62
- YAML
63
-
64
60
  README_TEMPLATE = <<~MD
65
61
  # %<app_name>s
66
62
 
@@ -75,17 +71,7 @@ module Ruflet
75
71
  ## Run
76
72
 
77
73
  ```bash
78
- bundle exec ruflet run main.rb
79
- ```
80
-
81
- ## Client Template
82
-
83
- `ruflet_client` template is generated inside this app.
84
-
85
- ```bash
86
- cd ruflet_client
87
- flutter pub get
88
- flutter run
74
+ bundle exec ruflet run main
89
75
  ```
90
76
 
91
77
  ## Build
data/lib/ruflet/cli.rb CHANGED
@@ -5,6 +5,7 @@ require "optparse"
5
5
  require_relative "cli/templates"
6
6
  require_relative "cli/new_command"
7
7
  require_relative "cli/run_command"
8
+ require_relative "cli/flutter_sdk"
8
9
  require_relative "cli/build_command"
9
10
  require_relative "cli/extra_command"
10
11
 
@@ -30,12 +31,6 @@ module Ruflet
30
31
  command_debug(argv)
31
32
  when "build"
32
33
  command_build(argv)
33
- when "pack"
34
- command_pack(argv)
35
- when "publish"
36
- command_publish(argv)
37
- when "serve"
38
- command_serve(argv)
39
34
  when "devices"
40
35
  command_devices(argv)
41
36
  when "emulators"
@@ -62,9 +57,6 @@ module Ruflet
62
57
  ruflet run [scriptname|path] [--web|--mobile|--desktop]
63
58
  ruflet debug [scriptname|path]
64
59
  ruflet build <apk|ios|aab|web|macos|windows|linux>
65
- ruflet pack
66
- ruflet publish
67
- ruflet serve [--port N] [--root PATH]
68
60
  ruflet devices
69
61
  ruflet emulators
70
62
  ruflet doctor
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ruflet
4
- VERSION = "0.0.4" unless const_defined?(:VERSION)
4
+ VERSION = "0.0.5" unless const_defined?(:VERSION)
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruflet_cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - AdamMusa
@@ -36,6 +36,7 @@ files:
36
36
  - lib/ruflet/cli.rb
37
37
  - lib/ruflet/cli/build_command.rb
38
38
  - lib/ruflet/cli/extra_command.rb
39
+ - lib/ruflet/cli/flutter_sdk.rb
39
40
  - lib/ruflet/cli/new_command.rb
40
41
  - lib/ruflet/cli/run_command.rb
41
42
  - lib/ruflet/cli/templates.rb