ruflet_cli 0.0.3 → 0.0.4
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/lib/ruflet/cli/build_command.rb +172 -1
- data/lib/ruflet/cli/extra_command.rb +180 -0
- data/lib/ruflet/cli/new_command.rb +36 -1
- data/lib/ruflet/cli/run_command.rb +377 -1
- data/lib/ruflet/cli/templates.rb +12 -4
- data/lib/ruflet/cli.rb +26 -0
- data/lib/ruflet/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 36d08da2d1377a858cd6703bd694b66d937604a21544af1de95d592aec4cb4df
|
|
4
|
+
data.tar.gz: 3feccf443f7ecae27bf835a90f6fbb81c04bdbc86bf1a71350bda6c97a0d555d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e8677cc79bdb4e546f88ea3140b95e0177a0f24f884fa679f1a5636098017186caa14859a189c755b42455bdd7bbf612a8ac78f608766cd19f36b1f756c5ad69
|
|
7
|
+
data.tar.gz: b07035a35c315f1b42cc5dda629d080a4956b1854dd96297420d6ad24986c3095101f86ff9889c13d0bc133e18967c2666fbadad3228f00b39eabd9442af88ac
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
3
6
|
module Ruflet
|
|
4
7
|
module CLI
|
|
5
8
|
module BuildCommand
|
|
@@ -23,7 +26,10 @@ module Ruflet
|
|
|
23
26
|
return 1
|
|
24
27
|
end
|
|
25
28
|
|
|
26
|
-
ok =
|
|
29
|
+
ok = prepare_flutter_client(client_dir)
|
|
30
|
+
return 1 unless ok
|
|
31
|
+
|
|
32
|
+
ok = system(*flutter_cmd, *args, chdir: client_dir)
|
|
27
33
|
ok ? 0 : 1
|
|
28
34
|
end
|
|
29
35
|
|
|
@@ -36,9 +42,174 @@ module Ruflet
|
|
|
36
42
|
local = File.expand_path("ruflet_client", Dir.pwd)
|
|
37
43
|
return local if Dir.exist?(local)
|
|
38
44
|
|
|
45
|
+
template = File.expand_path("templates/ruflet_flutter_template", Dir.pwd)
|
|
46
|
+
return template if Dir.exist?(template)
|
|
47
|
+
|
|
39
48
|
nil
|
|
40
49
|
end
|
|
41
50
|
|
|
51
|
+
def prepare_flutter_client(client_dir)
|
|
52
|
+
apply_build_config(client_dir)
|
|
53
|
+
unless system("flutter", "pub", "get", chdir: client_dir)
|
|
54
|
+
warn "flutter pub get failed"
|
|
55
|
+
return false
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
unless system("dart", "run", "flutter_native_splash:create", chdir: client_dir)
|
|
59
|
+
warn "flutter_native_splash failed"
|
|
60
|
+
return false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
unless system("dart", "run", "flutter_launcher_icons", chdir: client_dir)
|
|
64
|
+
warn "flutter_launcher_icons failed"
|
|
65
|
+
return false
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
true
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def apply_build_config(client_dir)
|
|
72
|
+
config_path = ENV["RUFLET_CONFIG"] || "ruflet.yaml"
|
|
73
|
+
unless File.file?(config_path)
|
|
74
|
+
alt = "ruflet.yml"
|
|
75
|
+
config_path = alt if File.file?(alt)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
config_present = File.file?(config_path)
|
|
79
|
+
config = config_present ? (YAML.load_file(config_path) || {}) : {}
|
|
80
|
+
build = config["build"] || {}
|
|
81
|
+
assets = config["assets"] || {}
|
|
82
|
+
config_dir = config_present ? File.dirname(File.expand_path(config_path)) : Dir.pwd
|
|
83
|
+
|
|
84
|
+
assets_root = build["assets_dir"] || assets["dir"] || config["assets_dir"] || "assets"
|
|
85
|
+
assets_root = File.expand_path(assets_root, config_dir)
|
|
86
|
+
|
|
87
|
+
unless config_present || Dir.exist?(assets_root) || ENV["RUFLET_SPLASH"] || ENV["RUFLET_ICON"]
|
|
88
|
+
return
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
resolve_asset = lambda do |path|
|
|
92
|
+
return nil if path.nil? || path.to_s.strip.empty?
|
|
93
|
+
full = File.expand_path(path.to_s, config_dir)
|
|
94
|
+
File.file?(full) ? full : nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
find_first = lambda do |dir, names|
|
|
98
|
+
names.each do |name|
|
|
99
|
+
candidate = File.join(dir, name)
|
|
100
|
+
return candidate if File.file?(candidate)
|
|
101
|
+
end
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
splash = resolve_asset.call(build["splash"] || assets["splash"] || ENV["RUFLET_SPLASH"])
|
|
106
|
+
splash_dark = resolve_asset.call(build["splash_dark"] || build["splash_dark_image"] || assets["splash_dark"])
|
|
107
|
+
icon = resolve_asset.call(build["icon"] || assets["icon"] || ENV["RUFLET_ICON"])
|
|
108
|
+
icon_android = resolve_asset.call(build["icon_android"] || assets["icon_android"])
|
|
109
|
+
icon_ios = resolve_asset.call(build["icon_ios"] || assets["icon_ios"])
|
|
110
|
+
icon_web = resolve_asset.call(build["icon_web"] || assets["icon_web"])
|
|
111
|
+
icon_windows = resolve_asset.call(build["icon_windows"] || assets["icon_windows"])
|
|
112
|
+
icon_macos = resolve_asset.call(build["icon_macos"] || assets["icon_macos"])
|
|
113
|
+
|
|
114
|
+
if Dir.exist?(assets_root)
|
|
115
|
+
splash ||= find_first.call(assets_root, ["splash.png", "splash.jpg", "splash.webp", "splash.bmp"])
|
|
116
|
+
splash_dark ||= find_first.call(assets_root, ["splash_dark.png", "splash_dark.jpg", "splash_dark.webp", "splash_dark.bmp"])
|
|
117
|
+
icon ||= find_first.call(assets_root, ["icon.png", "icon.jpg", "icon.webp", "icon.bmp"])
|
|
118
|
+
icon_android ||= find_first.call(assets_root, ["icon_android.png", "icon_android.jpg", "icon_android.webp"])
|
|
119
|
+
icon_ios ||= find_first.call(assets_root, ["icon_ios.png", "icon_ios.jpg", "icon_ios.webp"])
|
|
120
|
+
icon_web ||= find_first.call(assets_root, ["icon_web.png", "icon_web.jpg", "icon_web.webp"])
|
|
121
|
+
icon_windows ||= find_first.call(assets_root, ["icon_windows.ico", "icon_windows.png"])
|
|
122
|
+
icon_macos ||= find_first.call(assets_root, ["icon_macos.png", "icon_macos.jpg", "icon_macos.webp"])
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
splash_color = build["splash_color"]
|
|
126
|
+
splash_dark_color = build["splash_dark_color"] || build["splash_color_dark"]
|
|
127
|
+
icon_background = build["icon_background"]
|
|
128
|
+
theme_color = build["theme_color"]
|
|
129
|
+
|
|
130
|
+
assets_dir = File.join(client_dir, "assets")
|
|
131
|
+
FileUtils.mkdir_p(assets_dir)
|
|
132
|
+
|
|
133
|
+
copy_asset = lambda do |src, dest|
|
|
134
|
+
return unless src
|
|
135
|
+
FileUtils.cp(src, File.join(assets_dir, dest))
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
copy_asset.call(splash, "splash.png")
|
|
139
|
+
copy_asset.call(splash_dark, "splash_dark.png")
|
|
140
|
+
copy_asset.call(icon, "icon.png")
|
|
141
|
+
copy_asset.call(icon_android, "icon_android.png")
|
|
142
|
+
copy_asset.call(icon_ios, "icon_ios.png")
|
|
143
|
+
copy_asset.call(icon_web, "icon_web.png")
|
|
144
|
+
if icon_windows
|
|
145
|
+
ext = File.extname(icon_windows).downcase
|
|
146
|
+
copy_asset.call(icon_windows, ext == ".ico" ? "icon_windows.ico" : "icon_windows.png")
|
|
147
|
+
end
|
|
148
|
+
copy_asset.call(icon_macos, "icon_macos.png")
|
|
149
|
+
|
|
150
|
+
pubspec_path = File.join(client_dir, "pubspec.yaml")
|
|
151
|
+
return unless File.file?(pubspec_path)
|
|
152
|
+
|
|
153
|
+
if icon
|
|
154
|
+
update_pubspec_value(pubspec_path, "flutter_launcher_icons", "image_path", "\"assets/icon.png\"", multiple: true)
|
|
155
|
+
end
|
|
156
|
+
update_pubspec_value(pubspec_path, "flutter_launcher_icons", "image_path_android", "\"assets/icon_android.png\"", multiple: true) if icon_android
|
|
157
|
+
update_pubspec_value(pubspec_path, "flutter_launcher_icons", "image_path_ios", "\"assets/icon_ios.png\"", multiple: true) if icon_ios
|
|
158
|
+
update_pubspec_value(pubspec_path, "flutter_launcher_icons", "image_path_web", "\"assets/icon_web.png\"", multiple: true) if icon_web
|
|
159
|
+
if icon_windows
|
|
160
|
+
ext = File.extname(icon_windows).downcase
|
|
161
|
+
value = ext == ".ico" ? "\"assets/icon_windows.ico\"" : "\"assets/icon_windows.png\""
|
|
162
|
+
update_pubspec_value(pubspec_path, "flutter_launcher_icons", "image_path_windows", value, multiple: true)
|
|
163
|
+
end
|
|
164
|
+
update_pubspec_value(pubspec_path, "flutter_launcher_icons", "image_path_macos", "\"assets/icon_macos.png\"", multiple: true) if icon_macos
|
|
165
|
+
update_pubspec_value(pubspec_path, "flutter_launcher_icons", "background_color", "\"#{icon_background}\"") if icon_background
|
|
166
|
+
update_pubspec_value(pubspec_path, "flutter_launcher_icons", "theme_color", "\"#{theme_color}\"") if theme_color
|
|
167
|
+
|
|
168
|
+
update_pubspec_value(pubspec_path, "flutter_native_splash", "image", "\"assets/splash.png\"") if splash
|
|
169
|
+
update_pubspec_value(pubspec_path, "flutter_native_splash", "image_dark", "\"assets/splash_dark.png\"") if splash_dark
|
|
170
|
+
update_pubspec_value(pubspec_path, "flutter_native_splash", "color", "\"#{splash_color}\"") if splash_color
|
|
171
|
+
update_pubspec_value(pubspec_path, "flutter_native_splash", "color_dark", "\"#{splash_dark_color}\"") if splash_dark_color
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def update_pubspec_value(path, block, key, value, multiple: false)
|
|
175
|
+
lines = File.read(path).split("\n", -1)
|
|
176
|
+
out = []
|
|
177
|
+
in_block = false
|
|
178
|
+
replaced = false
|
|
179
|
+
block_indent = nil
|
|
180
|
+
lines.each do |line|
|
|
181
|
+
if line.start_with?("#{block}:")
|
|
182
|
+
in_block = true
|
|
183
|
+
block_indent = line[/^\s*/] + " "
|
|
184
|
+
out << line
|
|
185
|
+
next
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
if in_block
|
|
189
|
+
if line =~ /^\S/ && !line.start_with?("#{block}:")
|
|
190
|
+
unless replaced
|
|
191
|
+
out << "#{block_indent}#{key}: #{value}"
|
|
192
|
+
replaced = true
|
|
193
|
+
end
|
|
194
|
+
in_block = false
|
|
195
|
+
else
|
|
196
|
+
if line.strip.start_with?("#{key}:")
|
|
197
|
+
indent = line[/^\s*/]
|
|
198
|
+
out << "#{indent}#{key}: #{value}"
|
|
199
|
+
replaced = true
|
|
200
|
+
next
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
out << line
|
|
206
|
+
end
|
|
207
|
+
if in_block && !replaced
|
|
208
|
+
out << "#{block_indent}#{key}: #{value}"
|
|
209
|
+
end
|
|
210
|
+
File.write(path, out.join("\n"))
|
|
211
|
+
end
|
|
212
|
+
|
|
42
213
|
def flutter_build_command(platform)
|
|
43
214
|
case platform
|
|
44
215
|
when "apk", "android"
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
|
|
5
|
+
module Ruflet
|
|
6
|
+
module CLI
|
|
7
|
+
module ExtraCommand
|
|
8
|
+
def command_create(args)
|
|
9
|
+
command_new(args)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def command_doctor(args)
|
|
13
|
+
verbose = args.delete("--verbose") || args.delete("-v")
|
|
14
|
+
puts "Ruflet doctor"
|
|
15
|
+
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
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def command_devices(args)
|
|
26
|
+
ensure_flutter!("devices")
|
|
27
|
+
system("flutter", "devices", *args)
|
|
28
|
+
$?.exitstatus || 1
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def command_emulators(args)
|
|
32
|
+
ensure_flutter!("emulators")
|
|
33
|
+
action = nil
|
|
34
|
+
emulator_id = nil
|
|
35
|
+
verbose = false
|
|
36
|
+
parser = OptionParser.new do |o|
|
|
37
|
+
o.on("--create") { action = "create" }
|
|
38
|
+
o.on("--delete") { action = "delete" }
|
|
39
|
+
o.on("--start") { action = "start" }
|
|
40
|
+
o.on("--emulator ID") { |v| emulator_id = v }
|
|
41
|
+
o.on("-v", "--verbose") { verbose = true }
|
|
42
|
+
end
|
|
43
|
+
parser.parse!(args)
|
|
44
|
+
|
|
45
|
+
case action
|
|
46
|
+
when "start"
|
|
47
|
+
unless emulator_id
|
|
48
|
+
warn "Missing --emulator for start"
|
|
49
|
+
return 1
|
|
50
|
+
end
|
|
51
|
+
cmd = ["flutter", "emulators", "--launch", emulator_id]
|
|
52
|
+
cmd << "-v" if verbose
|
|
53
|
+
system(*cmd)
|
|
54
|
+
$?.exitstatus || 1
|
|
55
|
+
when "create", "delete"
|
|
56
|
+
warn "ruflet emulators --#{action} is not implemented yet. Use your platform tools."
|
|
57
|
+
1
|
|
58
|
+
else
|
|
59
|
+
cmd = ["flutter", "emulators"]
|
|
60
|
+
cmd << "-v" if verbose
|
|
61
|
+
system(*cmd)
|
|
62
|
+
$?.exitstatus || 1
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
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
|
+
def command_debug(args)
|
|
102
|
+
ensure_flutter!("debug")
|
|
103
|
+
options = {
|
|
104
|
+
platform: nil,
|
|
105
|
+
device_id: nil,
|
|
106
|
+
release: false,
|
|
107
|
+
verbose: false,
|
|
108
|
+
web_renderer: nil
|
|
109
|
+
}
|
|
110
|
+
parser = OptionParser.new do |o|
|
|
111
|
+
o.on("--platform NAME") { |v| options[:platform] = v }
|
|
112
|
+
o.on("--device-id ID") { |v| options[:device_id] = v }
|
|
113
|
+
o.on("--release") { options[:release] = true }
|
|
114
|
+
o.on("-v", "--verbose") { options[:verbose] = true }
|
|
115
|
+
o.on("--web-renderer NAME") { |v| options[:web_renderer] = v }
|
|
116
|
+
end
|
|
117
|
+
parser.parse!(args)
|
|
118
|
+
|
|
119
|
+
options[:platform] ||= args.shift
|
|
120
|
+
cmd = ["flutter", "run"]
|
|
121
|
+
cmd << "--release" if options[:release]
|
|
122
|
+
cmd << "-v" if options[:verbose]
|
|
123
|
+
cmd += ["--web-renderer", options[:web_renderer]] if options[:web_renderer]
|
|
124
|
+
|
|
125
|
+
if options[:device_id]
|
|
126
|
+
cmd += ["-d", options[:device_id]]
|
|
127
|
+
else
|
|
128
|
+
case options[:platform]
|
|
129
|
+
when "web"
|
|
130
|
+
cmd += ["-d", "chrome"]
|
|
131
|
+
when "macos", "windows", "linux"
|
|
132
|
+
cmd += ["-d", options[:platform]]
|
|
133
|
+
when "ios", "android"
|
|
134
|
+
# let flutter pick the default device
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
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)
|
|
146
|
+
$?.exitstatus || 1
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
151
|
+
def detect_client_dir
|
|
152
|
+
env_dir = ENV["RUFLET_CLIENT_DIR"]
|
|
153
|
+
return env_dir if env_dir && Dir.exist?(env_dir)
|
|
154
|
+
|
|
155
|
+
local = File.expand_path("ruflet_client", Dir.pwd)
|
|
156
|
+
return local if Dir.exist?(local)
|
|
157
|
+
|
|
158
|
+
template = File.expand_path("templates/ruflet_flutter_template", Dir.pwd)
|
|
159
|
+
return template if Dir.exist?(template)
|
|
160
|
+
|
|
161
|
+
nil
|
|
162
|
+
end
|
|
163
|
+
|
|
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
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -24,18 +24,53 @@ module Ruflet
|
|
|
24
24
|
File.write(File.join(root, "Gemfile"), Ruflet::CLI::GEMFILE_TEMPLATE)
|
|
25
25
|
File.write(File.join(root, ".bundle", "config"), Ruflet::CLI::BUNDLE_CONFIG_TEMPLATE)
|
|
26
26
|
File.write(File.join(root, "README.md"), format(Ruflet::CLI::README_TEMPLATE, app_name: File.basename(root)))
|
|
27
|
+
copy_ruflet_client_template(root)
|
|
27
28
|
|
|
28
29
|
project_name = File.basename(root)
|
|
29
30
|
puts "Ruflet app created: #{project_name}"
|
|
30
31
|
puts "Run:"
|
|
31
32
|
puts " cd #{project_name}"
|
|
32
33
|
puts " bundle install"
|
|
33
|
-
puts " bundle exec ruflet run main"
|
|
34
|
+
puts " bundle exec ruflet run main.rb"
|
|
35
|
+
puts
|
|
36
|
+
puts "Client template:"
|
|
37
|
+
puts " cd ruflet_client"
|
|
38
|
+
puts " flutter pub get"
|
|
39
|
+
puts " flutter run"
|
|
34
40
|
0
|
|
35
41
|
end
|
|
36
42
|
|
|
37
43
|
private
|
|
38
44
|
|
|
45
|
+
def copy_ruflet_client_template(root)
|
|
46
|
+
template_root = File.expand_path("../../../../../ruflet_client", __dir__)
|
|
47
|
+
return unless Dir.exist?(template_root)
|
|
48
|
+
|
|
49
|
+
target = File.join(root, "ruflet_client")
|
|
50
|
+
FileUtils.cp_r(template_root, target)
|
|
51
|
+
prune_client_template(target)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def prune_client_template(target)
|
|
55
|
+
paths = %w[
|
|
56
|
+
.dart_tool
|
|
57
|
+
.idea
|
|
58
|
+
build
|
|
59
|
+
ios/Pods
|
|
60
|
+
ios/.symlinks
|
|
61
|
+
ios/Podfile.lock
|
|
62
|
+
macos/Pods
|
|
63
|
+
macos/Podfile.lock
|
|
64
|
+
android/.gradle
|
|
65
|
+
android/.kotlin
|
|
66
|
+
android/local.properties
|
|
67
|
+
]
|
|
68
|
+
paths.each do |path|
|
|
69
|
+
full = File.join(target, path)
|
|
70
|
+
FileUtils.rm_rf(full) if File.exist?(full)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
39
74
|
def humanize_name(name)
|
|
40
75
|
name.to_s.gsub(/[_-]+/, " ").split.map(&:capitalize).join(" ")
|
|
41
76
|
end
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
require "optparse"
|
|
4
4
|
require "rbconfig"
|
|
5
5
|
require "socket"
|
|
6
|
+
require "timeout"
|
|
7
|
+
require "tmpdir"
|
|
8
|
+
require "fileutils"
|
|
9
|
+
require "json"
|
|
10
|
+
require "net/http"
|
|
11
|
+
require "uri"
|
|
6
12
|
|
|
7
13
|
module Ruflet
|
|
8
14
|
module CLI
|
|
@@ -30,8 +36,10 @@ module Ruflet
|
|
|
30
36
|
"RUFLET_SUPPRESS_SERVER_BANNER" => "1",
|
|
31
37
|
"RUFLET_PORT" => selected_port.to_s
|
|
32
38
|
}
|
|
39
|
+
assets_dir = File.join(File.dirname(script_path), "assets")
|
|
40
|
+
env["RUFLET_ASSETS_DIR"] = assets_dir if File.directory?(assets_dir)
|
|
33
41
|
|
|
34
|
-
|
|
42
|
+
print_run_banner(target: options[:target], port: selected_port)
|
|
35
43
|
print_mobile_qr_hint(port: selected_port) if options[:target] == "mobile"
|
|
36
44
|
|
|
37
45
|
cmd =
|
|
@@ -47,6 +55,7 @@ module Ruflet
|
|
|
47
55
|
end
|
|
48
56
|
|
|
49
57
|
child_pid = Process.spawn(env, *cmd, pgroup: true)
|
|
58
|
+
launched_client_pids = launch_target_client(options[:target], selected_port)
|
|
50
59
|
forward_signal = lambda do |signal|
|
|
51
60
|
begin
|
|
52
61
|
Process.kill(signal, -child_pid)
|
|
@@ -71,6 +80,18 @@ module Ruflet
|
|
|
71
80
|
nil
|
|
72
81
|
end
|
|
73
82
|
end
|
|
83
|
+
|
|
84
|
+
Array(defined?(launched_client_pids) ? launched_client_pids : nil).compact.each do |pid|
|
|
85
|
+
begin
|
|
86
|
+
Process.kill("TERM", -pid)
|
|
87
|
+
rescue Errno::ESRCH
|
|
88
|
+
begin
|
|
89
|
+
Process.kill("TERM", pid)
|
|
90
|
+
rescue Errno::ESRCH
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
74
95
|
end
|
|
75
96
|
|
|
76
97
|
private
|
|
@@ -85,6 +106,361 @@ module Ruflet
|
|
|
85
106
|
nil
|
|
86
107
|
end
|
|
87
108
|
|
|
109
|
+
def print_run_banner(target:, port:)
|
|
110
|
+
if port != 8550
|
|
111
|
+
puts "Requested port 8550 is busy; bound to #{port}"
|
|
112
|
+
end
|
|
113
|
+
if target == "desktop"
|
|
114
|
+
puts "Ruflet desktop URL: http://localhost:#{port}"
|
|
115
|
+
else
|
|
116
|
+
puts "Ruflet target: #{target}"
|
|
117
|
+
puts "Ruflet URL: http://localhost:#{port}"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def launch_target_client(target, port)
|
|
122
|
+
wait_for_server_boot(port)
|
|
123
|
+
|
|
124
|
+
case target
|
|
125
|
+
when "web"
|
|
126
|
+
launch_web_client(port)
|
|
127
|
+
when "desktop"
|
|
128
|
+
launch_desktop_client("http://localhost:#{port}")
|
|
129
|
+
else
|
|
130
|
+
[]
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def launch_web_client(port)
|
|
135
|
+
web_dir = detect_web_client_dir
|
|
136
|
+
unless web_dir
|
|
137
|
+
warn "Web client build not found and prebuilt download failed."
|
|
138
|
+
return []
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
web_port = find_available_port(port + 1)
|
|
142
|
+
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)
|
|
143
|
+
Process.detach(web_pid)
|
|
144
|
+
wait_for_server_boot(web_port)
|
|
145
|
+
browser_pid = open_in_browser_app_mode("http://localhost:#{web_port}")
|
|
146
|
+
open_in_browser("http://localhost:#{web_port}") if browser_pid.nil?
|
|
147
|
+
puts "Ruflet web client: http://localhost:#{web_port}"
|
|
148
|
+
puts "Ruflet backend ws: ws://localhost:#{port}/ws"
|
|
149
|
+
[web_pid, browser_pid].compact
|
|
150
|
+
rescue Errno::ENOENT
|
|
151
|
+
warn "python3 is required to host web client locally."
|
|
152
|
+
warn "Install Python 3 and rerun."
|
|
153
|
+
[]
|
|
154
|
+
rescue StandardError => e
|
|
155
|
+
warn "Failed to launch web client: #{e.class}: #{e.message}"
|
|
156
|
+
[]
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def wait_for_server_boot(port, timeout_seconds: 10)
|
|
160
|
+
Timeout.timeout(timeout_seconds) do
|
|
161
|
+
loop do
|
|
162
|
+
begin
|
|
163
|
+
sock = TCPSocket.new("127.0.0.1", port)
|
|
164
|
+
sock.write("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
|
|
165
|
+
sock.close
|
|
166
|
+
break
|
|
167
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
|
168
|
+
sleep 0.15
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
rescue Timeout::Error
|
|
173
|
+
warn "Server did not become reachable at http://localhost:#{port} yet."
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def open_in_browser(url)
|
|
177
|
+
cmd =
|
|
178
|
+
case RbConfig::CONFIG["host_os"]
|
|
179
|
+
when /darwin/i
|
|
180
|
+
["open", url]
|
|
181
|
+
when /mswin|mingw|cygwin/i
|
|
182
|
+
["cmd", "/c", "start", "", url]
|
|
183
|
+
else
|
|
184
|
+
["xdg-open", url]
|
|
185
|
+
end
|
|
186
|
+
if system(*cmd, out: File::NULL, err: File::NULL)
|
|
187
|
+
puts "Opened browser at #{url}"
|
|
188
|
+
else
|
|
189
|
+
warn "Could not auto-open browser. Open manually: #{url}"
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def open_in_browser_app_mode(url)
|
|
194
|
+
host_os = RbConfig::CONFIG["host_os"]
|
|
195
|
+
if host_os.match?(/darwin/i)
|
|
196
|
+
chrome = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
|
197
|
+
chromium = "/Applications/Chromium.app/Contents/MacOS/Chromium"
|
|
198
|
+
browser = [chrome, chromium].find { |p| File.file?(p) && File.executable?(p) }
|
|
199
|
+
return nil unless browser
|
|
200
|
+
|
|
201
|
+
profile_dir = Dir.mktmpdir("ruflet-webapp-")
|
|
202
|
+
pid = Process.spawn(
|
|
203
|
+
browser,
|
|
204
|
+
"--new-window",
|
|
205
|
+
"--no-first-run",
|
|
206
|
+
"--no-default-browser-check",
|
|
207
|
+
"--user-data-dir=#{profile_dir}",
|
|
208
|
+
"--app=#{url}",
|
|
209
|
+
pgroup: true,
|
|
210
|
+
out: File::NULL,
|
|
211
|
+
err: File::NULL
|
|
212
|
+
)
|
|
213
|
+
Process.detach(pid)
|
|
214
|
+
return pid
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
if host_os.match?(/linux/i)
|
|
218
|
+
browser = %w[google-chrome chromium chromium-browser].find { |cmd| system("which", cmd, out: File::NULL, err: File::NULL) }
|
|
219
|
+
return nil unless browser
|
|
220
|
+
|
|
221
|
+
profile_dir = Dir.mktmpdir("ruflet-webapp-")
|
|
222
|
+
pid = Process.spawn(
|
|
223
|
+
browser,
|
|
224
|
+
"--new-window",
|
|
225
|
+
"--no-first-run",
|
|
226
|
+
"--no-default-browser-check",
|
|
227
|
+
"--user-data-dir=#{profile_dir}",
|
|
228
|
+
"--app=#{url}",
|
|
229
|
+
pgroup: true,
|
|
230
|
+
out: File::NULL,
|
|
231
|
+
err: File::NULL
|
|
232
|
+
)
|
|
233
|
+
Process.detach(pid)
|
|
234
|
+
return pid
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
nil
|
|
238
|
+
rescue StandardError
|
|
239
|
+
nil
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def launch_desktop_client(url)
|
|
243
|
+
cmd = detect_desktop_client_command(url)
|
|
244
|
+
unless cmd
|
|
245
|
+
warn "Desktop client executable not found."
|
|
246
|
+
warn "Set RUFLET_CLIENT_DIR to your client path."
|
|
247
|
+
warn "Example: export RUFLET_CLIENT_DIR=/path/to/ruflet_client"
|
|
248
|
+
return
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
pid = Process.spawn(*cmd, out: File::NULL, err: File::NULL)
|
|
252
|
+
Process.detach(pid)
|
|
253
|
+
if !pid
|
|
254
|
+
warn "Failed to launch desktop client: #{cmd.first}"
|
|
255
|
+
warn "Start it manually with URL: #{url}"
|
|
256
|
+
end
|
|
257
|
+
[pid]
|
|
258
|
+
rescue StandardError => e
|
|
259
|
+
warn "Failed to launch desktop client: #{e.class}: #{e.message}"
|
|
260
|
+
warn "Start it manually with URL: #{url}"
|
|
261
|
+
[]
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def detect_desktop_client_command(url)
|
|
265
|
+
root = ENV["RUFLET_CLIENT_DIR"]
|
|
266
|
+
root = File.expand_path("ruflet_client", Dir.pwd) if root.to_s.strip.empty?
|
|
267
|
+
root = nil unless Dir.exist?(root)
|
|
268
|
+
root ||= ensure_prebuilt_client(desktop: true)
|
|
269
|
+
return nil unless root && Dir.exist?(root)
|
|
270
|
+
|
|
271
|
+
host_os = RbConfig::CONFIG["host_os"]
|
|
272
|
+
if host_os.match?(/darwin/i)
|
|
273
|
+
release_bin = File.join(root, "build", "macos", "Build", "Products", "Release", "ruflet_client.app", "Contents", "MacOS", "ruflet_client")
|
|
274
|
+
debug_bin = File.join(root, "build", "macos", "Build", "Products", "Debug", "ruflet_client.app", "Contents", "MacOS", "ruflet_client")
|
|
275
|
+
prebuilt_bin = File.join(root, "desktop", "ruflet_client.app", "Contents", "MacOS", "ruflet_client")
|
|
276
|
+
executable = [release_bin, debug_bin].find { |p| File.file?(p) && File.executable?(p) }
|
|
277
|
+
executable ||= prebuilt_bin if File.file?(prebuilt_bin) && File.executable?(prebuilt_bin)
|
|
278
|
+
return [executable, url] if executable
|
|
279
|
+
elsif host_os.match?(/mswin|mingw|cygwin/i)
|
|
280
|
+
exe = File.join(root, "build", "windows", "x64", "runner", "Release", "ruflet_client.exe")
|
|
281
|
+
prebuilt = File.join(root, "desktop", "ruflet_client.exe")
|
|
282
|
+
exe = prebuilt if !File.file?(exe) && File.file?(prebuilt)
|
|
283
|
+
return [exe, url] if File.file?(exe)
|
|
284
|
+
else
|
|
285
|
+
direct = File.join(root, "build", "linux", "x64", "release", "bundle", "ruflet_client")
|
|
286
|
+
prebuilt_direct = File.join(root, "desktop", "ruflet_client")
|
|
287
|
+
direct = prebuilt_direct if !File.file?(direct) && File.file?(prebuilt_direct)
|
|
288
|
+
return [direct, url] if File.file?(direct)
|
|
289
|
+
bundle_dir = File.join(root, "build", "linux", "x64", "release", "bundle")
|
|
290
|
+
if Dir.exist?(bundle_dir)
|
|
291
|
+
candidate = Dir.children(bundle_dir).map { |f| File.join(bundle_dir, f) }
|
|
292
|
+
.find { |path| File.file?(path) && File.executable?(path) }
|
|
293
|
+
return [candidate, url] if candidate
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
nil
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def detect_web_client_dir
|
|
301
|
+
root = ENV["RUFLET_CLIENT_DIR"]
|
|
302
|
+
root = File.expand_path("ruflet_client", Dir.pwd) if root.to_s.strip.empty?
|
|
303
|
+
root = nil unless Dir.exist?(root)
|
|
304
|
+
root ||= ensure_prebuilt_client(web: true)
|
|
305
|
+
return nil unless root && Dir.exist?(root)
|
|
306
|
+
|
|
307
|
+
built = File.join(root, "build", "web")
|
|
308
|
+
return built if Dir.exist?(built) && File.file?(File.join(built, "index.html"))
|
|
309
|
+
prebuilt = File.join(root, "web")
|
|
310
|
+
return prebuilt if Dir.exist?(prebuilt) && File.file?(File.join(prebuilt, "index.html"))
|
|
311
|
+
|
|
312
|
+
nil
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def ensure_prebuilt_client(web: false, desktop: false)
|
|
316
|
+
platform = host_platform_name
|
|
317
|
+
return nil if platform.nil?
|
|
318
|
+
|
|
319
|
+
cache_root = File.join(Dir.home, ".ruflet", "client", Ruflet::VERSION, platform)
|
|
320
|
+
FileUtils.mkdir_p(cache_root)
|
|
321
|
+
|
|
322
|
+
wanted_assets = []
|
|
323
|
+
wanted_assets << "ruflet_client-web.tar.gz" if web
|
|
324
|
+
if desktop
|
|
325
|
+
desktop_asset = desktop_asset_name_for(platform)
|
|
326
|
+
return nil if desktop_asset.nil?
|
|
327
|
+
wanted_assets << desktop_asset
|
|
328
|
+
end
|
|
329
|
+
return cache_root if wanted_assets.empty? || prebuilt_assets_present?(cache_root, web: web, desktop: desktop)
|
|
330
|
+
|
|
331
|
+
release = fetch_release_for_version
|
|
332
|
+
return nil unless release
|
|
333
|
+
|
|
334
|
+
assets = release.fetch("assets", [])
|
|
335
|
+
Dir.mktmpdir("ruflet-prebuilt-") do |tmpdir|
|
|
336
|
+
wanted_assets.each do |asset_name|
|
|
337
|
+
asset = assets.find { |a| a["name"] == asset_name }
|
|
338
|
+
unless asset
|
|
339
|
+
warn "Missing release asset: #{asset_name}"
|
|
340
|
+
return nil
|
|
341
|
+
end
|
|
342
|
+
archive_path = File.join(tmpdir, asset_name)
|
|
343
|
+
download_file(asset.fetch("browser_download_url"), archive_path)
|
|
344
|
+
subdir = asset_name.include?("-web.") ? "web" : "desktop"
|
|
345
|
+
target = File.join(cache_root, subdir)
|
|
346
|
+
FileUtils.mkdir_p(target)
|
|
347
|
+
unless extract_archive(archive_path, target)
|
|
348
|
+
warn "Failed to extract asset: #{asset_name}"
|
|
349
|
+
return nil
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
return cache_root if prebuilt_assets_present?(cache_root, web: web, desktop: desktop)
|
|
355
|
+
|
|
356
|
+
nil
|
|
357
|
+
rescue StandardError => e
|
|
358
|
+
warn "Prebuilt client bootstrap failed: #{e.class}: #{e.message}"
|
|
359
|
+
nil
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def prebuilt_assets_present?(root, web:, desktop:)
|
|
363
|
+
ok_web = !web || File.file?(File.join(root, "web", "index.html"))
|
|
364
|
+
ok_desktop = !desktop || prebuilt_desktop_present?(root)
|
|
365
|
+
ok_web && ok_desktop
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def prebuilt_desktop_present?(root)
|
|
369
|
+
platform = host_platform_name
|
|
370
|
+
return false if platform.nil?
|
|
371
|
+
|
|
372
|
+
case platform
|
|
373
|
+
when "macos"
|
|
374
|
+
File.file?(File.join(root, "desktop", "ruflet_client.app", "Contents", "MacOS", "ruflet_client"))
|
|
375
|
+
when "linux"
|
|
376
|
+
File.file?(File.join(root, "desktop", "ruflet_client"))
|
|
377
|
+
when "windows"
|
|
378
|
+
File.file?(File.join(root, "desktop", "ruflet_client.exe"))
|
|
379
|
+
else
|
|
380
|
+
false
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def host_platform_name
|
|
385
|
+
host_os = RbConfig::CONFIG["host_os"]
|
|
386
|
+
return "macos" if host_os.match?(/darwin/i)
|
|
387
|
+
return "linux" if host_os.match?(/linux/i)
|
|
388
|
+
return "windows" if host_os.match?(/mswin|mingw|cygwin/i)
|
|
389
|
+
|
|
390
|
+
nil
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def desktop_asset_name_for(platform)
|
|
394
|
+
case platform
|
|
395
|
+
when "macos" then "ruflet_client-macos-universal.zip"
|
|
396
|
+
when "linux" then "ruflet_client-linux-x64.tar.gz"
|
|
397
|
+
when "windows" then "ruflet_client-windows-x64.zip"
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def fetch_release_for_version
|
|
402
|
+
release_by_tag("v#{Ruflet::VERSION}") || release_latest
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def release_latest
|
|
406
|
+
github_get_json("https://api.github.com/repos/AdamMusa/Ruflet/releases/latest")
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def release_by_tag(tag)
|
|
410
|
+
github_get_json("https://api.github.com/repos/AdamMusa/Ruflet/releases/tags/#{tag}")
|
|
411
|
+
rescue StandardError
|
|
412
|
+
nil
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def github_get_json(url)
|
|
416
|
+
uri = URI(url)
|
|
417
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
|
418
|
+
req = Net::HTTP::Get.new(uri)
|
|
419
|
+
req["Accept"] = "application/vnd.github+json"
|
|
420
|
+
req["User-Agent"] = "ruflet-cli"
|
|
421
|
+
http.request(req)
|
|
422
|
+
end
|
|
423
|
+
return JSON.parse(response.body) if response.is_a?(Net::HTTPSuccess)
|
|
424
|
+
|
|
425
|
+
raise "GitHub API failed (#{response.code})"
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def download_file(url, destination, limit: 5)
|
|
429
|
+
raise "Too many redirects while downloading #{url}" if limit <= 0
|
|
430
|
+
|
|
431
|
+
uri = URI(url)
|
|
432
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
|
433
|
+
req = Net::HTTP::Get.new(uri)
|
|
434
|
+
req["User-Agent"] = "ruflet-cli"
|
|
435
|
+
http.request(req) do |res|
|
|
436
|
+
case res
|
|
437
|
+
when Net::HTTPSuccess
|
|
438
|
+
File.open(destination, "wb") { |f| res.read_body { |chunk| f.write(chunk) } }
|
|
439
|
+
return destination
|
|
440
|
+
when Net::HTTPRedirection
|
|
441
|
+
return download_file(res["location"], destination, limit: limit - 1)
|
|
442
|
+
else
|
|
443
|
+
raise "Download failed (#{res.code})"
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def extract_archive(archive, destination)
|
|
450
|
+
if archive.end_with?(".tar.gz")
|
|
451
|
+
return system("tar", "-xzf", archive, "-C", destination, out: File::NULL, err: File::NULL)
|
|
452
|
+
end
|
|
453
|
+
if archive.end_with?(".zip")
|
|
454
|
+
host_os = RbConfig::CONFIG["host_os"]
|
|
455
|
+
if host_os.match?(/darwin/i)
|
|
456
|
+
return system("ditto", "-x", "-k", archive, destination, out: File::NULL, err: File::NULL)
|
|
457
|
+
end
|
|
458
|
+
return system("unzip", "-oq", archive, "-d", destination, out: File::NULL, err: File::NULL)
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
false
|
|
462
|
+
end
|
|
463
|
+
|
|
88
464
|
def print_mobile_qr_hint(port: 8550)
|
|
89
465
|
host = best_lan_host
|
|
90
466
|
payload = "http://#{host}:#{port}"
|
data/lib/ruflet/cli/templates.rb
CHANGED
|
@@ -13,8 +13,6 @@ module Ruflet
|
|
|
13
13
|
|
|
14
14
|
def view(page)
|
|
15
15
|
page.title = "Counter Demo"
|
|
16
|
-
page.vertical_alignment = Ruflet::MainAxisAlignment::CENTER
|
|
17
|
-
page.horizontal_alignment = Ruflet::CrossAxisAlignment::CENTER
|
|
18
16
|
count_text = page.text(value: @count.to_s, size: 40)
|
|
19
17
|
|
|
20
18
|
page.add(
|
|
@@ -47,13 +45,13 @@ module Ruflet
|
|
|
47
45
|
end
|
|
48
46
|
|
|
49
47
|
MainApp.new.run
|
|
50
|
-
|
|
51
48
|
RUBY
|
|
52
49
|
|
|
53
50
|
GEMFILE_TEMPLATE = <<~GEMFILE
|
|
54
51
|
source "https://rubygems.org"
|
|
55
52
|
|
|
56
53
|
gem "ruflet", ">= 0.0.3"
|
|
54
|
+
gem "ruflet_protocol", ">= 0.0.3"
|
|
57
55
|
gem "ruflet_server", ">= 0.0.3"
|
|
58
56
|
GEMFILE
|
|
59
57
|
|
|
@@ -77,7 +75,17 @@ module Ruflet
|
|
|
77
75
|
## Run
|
|
78
76
|
|
|
79
77
|
```bash
|
|
80
|
-
bundle exec ruflet run main
|
|
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
|
|
81
89
|
```
|
|
82
90
|
|
|
83
91
|
## Build
|
data/lib/ruflet/cli.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative "cli/templates"
|
|
|
6
6
|
require_relative "cli/new_command"
|
|
7
7
|
require_relative "cli/run_command"
|
|
8
8
|
require_relative "cli/build_command"
|
|
9
|
+
require_relative "cli/extra_command"
|
|
9
10
|
|
|
10
11
|
module Ruflet
|
|
11
12
|
module CLI
|
|
@@ -13,17 +14,34 @@ module Ruflet
|
|
|
13
14
|
extend NewCommand
|
|
14
15
|
extend RunCommand
|
|
15
16
|
extend BuildCommand
|
|
17
|
+
extend ExtraCommand
|
|
16
18
|
|
|
17
19
|
def run(argv = ARGV)
|
|
18
20
|
command = (argv.shift || "help").downcase
|
|
19
21
|
|
|
20
22
|
case command
|
|
23
|
+
when "create"
|
|
24
|
+
command_create(argv)
|
|
21
25
|
when "new", "bootstrap", "init"
|
|
22
26
|
command_new(argv)
|
|
23
27
|
when "run"
|
|
24
28
|
command_run(argv)
|
|
29
|
+
when "debug"
|
|
30
|
+
command_debug(argv)
|
|
25
31
|
when "build"
|
|
26
32
|
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
|
+
when "devices"
|
|
40
|
+
command_devices(argv)
|
|
41
|
+
when "emulators"
|
|
42
|
+
command_emulators(argv)
|
|
43
|
+
when "doctor"
|
|
44
|
+
command_doctor(argv)
|
|
27
45
|
when "help", "-h", "--help"
|
|
28
46
|
print_help
|
|
29
47
|
0
|
|
@@ -39,9 +57,17 @@ module Ruflet
|
|
|
39
57
|
Ruflet CLI
|
|
40
58
|
|
|
41
59
|
Commands:
|
|
60
|
+
ruflet create <appname>
|
|
42
61
|
ruflet new <appname>
|
|
43
62
|
ruflet run [scriptname|path] [--web|--mobile|--desktop]
|
|
63
|
+
ruflet debug [scriptname|path]
|
|
44
64
|
ruflet build <apk|ios|aab|web|macos|windows|linux>
|
|
65
|
+
ruflet pack
|
|
66
|
+
ruflet publish
|
|
67
|
+
ruflet serve [--port N] [--root PATH]
|
|
68
|
+
ruflet devices
|
|
69
|
+
ruflet emulators
|
|
70
|
+
ruflet doctor
|
|
45
71
|
HELP
|
|
46
72
|
end
|
|
47
73
|
|
data/lib/ruflet/version.rb
CHANGED
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
|
+
version: 0.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- AdamMusa
|
|
@@ -35,6 +35,7 @@ files:
|
|
|
35
35
|
- bin/ruflet
|
|
36
36
|
- lib/ruflet/cli.rb
|
|
37
37
|
- lib/ruflet/cli/build_command.rb
|
|
38
|
+
- lib/ruflet/cli/extra_command.rb
|
|
38
39
|
- lib/ruflet/cli/new_command.rb
|
|
39
40
|
- lib/ruflet/cli/run_command.rb
|
|
40
41
|
- lib/ruflet/cli/templates.rb
|