ruflet 0.0.17 → 0.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +16 -1
- data/lib/ruflet/cli/build_command.rb +165 -707
- data/lib/ruflet/cli/extra_command.rb +14 -23
- data/lib/ruflet/cli/flutter_sdk.rb +34 -95
- data/lib/ruflet/cli/new_command.rb +89 -49
- data/lib/ruflet/cli/run_command.rb +186 -182
- data/lib/ruflet/cli/templates.rb +8 -9
- data/lib/ruflet/cli.rb +2 -15
- data/lib/ruflet/version.rb +1 -1
- metadata +1 -3
- data/lib/ruflet/cli/android_sdk.rb +0 -306
- data/lib/ruflet/cli/environment_setup.rb +0 -241
|
@@ -6,8 +6,6 @@ module Ruflet
|
|
|
6
6
|
module CLI
|
|
7
7
|
module ExtraCommand
|
|
8
8
|
include FlutterSdk
|
|
9
|
-
include EnvironmentSetup
|
|
10
|
-
include AndroidSdk
|
|
11
9
|
include NewCommand
|
|
12
10
|
|
|
13
11
|
def command_create(args)
|
|
@@ -18,26 +16,23 @@ module Ruflet
|
|
|
18
16
|
verbose = args.delete("--verbose") || args.delete("-v")
|
|
19
17
|
fix = args.delete("--fix")
|
|
20
18
|
client_dir = detect_client_dir
|
|
21
|
-
template_root =
|
|
19
|
+
template_root =
|
|
20
|
+
if fix
|
|
21
|
+
ensure_cached_ruflet_client_template!(force: true, verbose: !!verbose)
|
|
22
|
+
else
|
|
23
|
+
resolve_ruflet_client_template_root
|
|
24
|
+
end
|
|
22
25
|
puts "Ruflet doctor"
|
|
23
26
|
puts " Ruby: #{RUBY_VERSION}"
|
|
24
27
|
puts " Flutter host target: #{flutter_host || 'unsupported'}"
|
|
25
|
-
|
|
26
|
-
# System prerequisites first: Flutter cannot be installed (or even
|
|
27
|
-
# extracted) without them.
|
|
28
|
-
environment_issues = environment_setup!(fix: !!fix, verbose: !!verbose)
|
|
29
|
-
return 1 unless flutter_host
|
|
30
|
-
|
|
31
28
|
if template_root
|
|
32
29
|
puts " Template: #{template_root}"
|
|
30
|
+
revision = cached_template_revision if respond_to?(:cached_template_revision, true)
|
|
31
|
+
puts " Template revision: #{revision}" if revision
|
|
33
32
|
elsif fix
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
warn "Failed to fetch the Ruflet Flutter template from GitHub."
|
|
38
|
-
return 1
|
|
39
|
-
end
|
|
40
|
-
puts " Template: #{template_root}"
|
|
33
|
+
warn " Template: missing"
|
|
34
|
+
warn "Failed to fetch the Ruflet Flutter template from GitHub."
|
|
35
|
+
return 1
|
|
41
36
|
else
|
|
42
37
|
warn " Template: missing"
|
|
43
38
|
warn "Run `ruflet doctor --fix` to fetch the Flutter template from GitHub."
|
|
@@ -54,13 +49,9 @@ module Ruflet
|
|
|
54
49
|
end
|
|
55
50
|
end
|
|
56
51
|
puts " Flutter: #{flutter_version_summary(tools)}"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
status
|
|
60
|
-
if environment_issues.any?
|
|
61
|
-
warn "Unresolved environment issues: #{environment_issues.join('; ')}"
|
|
62
|
-
status = 1 if status.zero?
|
|
63
|
-
end
|
|
52
|
+
ok = system(tools[:env], tools[:flutter], "doctor", *(verbose ? ["-v"] : []))
|
|
53
|
+
status = $?.exitstatus if $?
|
|
54
|
+
status ||= ok ? 0 : 1
|
|
64
55
|
status
|
|
65
56
|
end
|
|
66
57
|
|
|
@@ -47,9 +47,18 @@ module Ruflet
|
|
|
47
47
|
private
|
|
48
48
|
|
|
49
49
|
def flutter_tools(client_dir: nil, auto_install: true)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
desired = desired_flutter_spec(client_dir: client_dir)
|
|
51
|
+
|
|
52
|
+
# Environment and .fvmrc versions are intentional pins. Flutter's
|
|
53
|
+
# generated .metadata revision only records which SDK created the
|
|
54
|
+
# project; it must not force every build to download that old SDK.
|
|
55
|
+
if %i[env fvmrc].include?(desired[:source])
|
|
56
|
+
fvm_tools = flutter_tools_via_fvm(client_dir: client_dir, auto_install: auto_install)
|
|
57
|
+
return fvm_tools if fvm_tools
|
|
58
|
+
else
|
|
59
|
+
system_tools = flutter_tools_via_system
|
|
60
|
+
return system_tools if system_tools
|
|
61
|
+
end
|
|
53
62
|
|
|
54
63
|
managed_tools = flutter_tools_via_managed_sdk(client_dir: client_dir, auto_install: auto_install)
|
|
55
64
|
return managed_tools if managed_tools
|
|
@@ -57,6 +66,13 @@ module Ruflet
|
|
|
57
66
|
nil
|
|
58
67
|
end
|
|
59
68
|
|
|
69
|
+
def flutter_tools_via_system
|
|
70
|
+
flutter = which_command("flutter")
|
|
71
|
+
return nil unless flutter
|
|
72
|
+
|
|
73
|
+
tools_from_flutter_bin(flutter)
|
|
74
|
+
end
|
|
75
|
+
|
|
60
76
|
def flutter_tools_via_managed_sdk(client_dir: nil, auto_install: true)
|
|
61
77
|
return nil unless auto_install
|
|
62
78
|
|
|
@@ -84,12 +100,8 @@ module Ruflet
|
|
|
84
100
|
File.write(fvmrc_path, "{\"flutter\":\"#{version}\"}\n")
|
|
85
101
|
end
|
|
86
102
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
puts " $ fvm install #{version}"
|
|
90
|
-
system(fvm_env, fvm, "install", version.to_s, chdir: project_dir, out: $stdout, err: $stderr)
|
|
91
|
-
puts " $ fvm use --force #{version}"
|
|
92
|
-
system(fvm_env, fvm, "use", "--force", version.to_s, chdir: project_dir, out: $stdout, err: $stderr)
|
|
103
|
+
system(fvm_env, fvm, "install", version.to_s, chdir: project_dir, out: File::NULL, err: File::NULL)
|
|
104
|
+
system(fvm_env, fvm, "use", "--force", version.to_s, chdir: project_dir, out: File::NULL, err: File::NULL)
|
|
93
105
|
|
|
94
106
|
flutter = flutter_bin_path(project_dir)
|
|
95
107
|
return nil unless File.executable?(flutter)
|
|
@@ -111,8 +123,7 @@ module Ruflet
|
|
|
111
123
|
end
|
|
112
124
|
return nil unless dart && File.executable?(dart)
|
|
113
125
|
|
|
114
|
-
|
|
115
|
-
system(dart, "pub", "global", "activate", "fvm", out: $stdout, err: $stderr)
|
|
126
|
+
system(dart, "pub", "global", "activate", "fvm", out: File::NULL, err: File::NULL)
|
|
116
127
|
installed_fvm = File.join(pub_cache_bin_dir, fvm_executable_name)
|
|
117
128
|
return installed_fvm if File.executable?(installed_fvm)
|
|
118
129
|
|
|
@@ -153,13 +164,10 @@ module Ruflet
|
|
|
153
164
|
return sdk_root if File.executable?(flutter_bin)
|
|
154
165
|
|
|
155
166
|
FileUtils.mkdir_p(install_root)
|
|
156
|
-
puts " Installing Flutter #{release.fetch('version')} (#{host}) into #{install_root}"
|
|
157
167
|
Dir.mktmpdir("ruflet-flutter-sdk-") do |tmpdir|
|
|
158
168
|
archive_path = File.join(tmpdir, File.basename(archive))
|
|
159
|
-
download_file("#{RELEASES_BASE}/#{archive}", archive_path
|
|
160
|
-
puts " Extracting #{File.basename(archive)} (this can take a minute)"
|
|
169
|
+
download_file("#{RELEASES_BASE}/#{archive}", archive_path)
|
|
161
170
|
extract_archive(archive_path, install_root)
|
|
162
|
-
puts " Extracted Flutter SDK"
|
|
163
171
|
end
|
|
164
172
|
|
|
165
173
|
return sdk_root if File.executable?(flutter_bin)
|
|
@@ -207,7 +215,12 @@ module Ruflet
|
|
|
207
215
|
return { version: fvm, source: :fvmrc } if fvm
|
|
208
216
|
|
|
209
217
|
metadata = parse_flutter_metadata(find_flutter_metadata(client_dir))
|
|
210
|
-
|
|
218
|
+
if metadata
|
|
219
|
+
return {
|
|
220
|
+
channel: metadata[:channel] || DEFAULT_FLUTTER_CHANNEL,
|
|
221
|
+
source: :metadata
|
|
222
|
+
}
|
|
223
|
+
end
|
|
211
224
|
|
|
212
225
|
{ channel: DEFAULT_FLUTTER_CHANNEL, version: DEFAULT_FLUTTER_CHANNEL, source: :default }
|
|
213
226
|
end
|
|
@@ -297,8 +310,6 @@ module Ruflet
|
|
|
297
310
|
return JSON.parse(response.body) if response.is_a?(Net::HTTPSuccess)
|
|
298
311
|
|
|
299
312
|
nil
|
|
300
|
-
rescue OpenSSL::SSL::SSLError
|
|
301
|
-
JSON.parse(curl_get(url, headers: ["User-Agent: ruflet-cli"]))
|
|
302
313
|
end
|
|
303
314
|
|
|
304
315
|
def pick_release(manifest, version: nil, revision: nil, channel: nil)
|
|
@@ -330,10 +341,7 @@ module Ruflet
|
|
|
330
341
|
if os.match?(/darwin/i)
|
|
331
342
|
return machine_arch.include?("arm") ? "macos_arm64" : "macos"
|
|
332
343
|
end
|
|
333
|
-
if os.match?(/linux/i)
|
|
334
|
-
# Flutter publishes Linux archives for x64 only.
|
|
335
|
-
return machine_arch.match?(/arm|aarch64/) ? nil : "linux"
|
|
336
|
-
end
|
|
344
|
+
return "linux" if os.match?(/linux/i)
|
|
337
345
|
return "windows" if os.match?(/mswin|mingw|cygwin/i)
|
|
338
346
|
|
|
339
347
|
nil
|
|
@@ -374,7 +382,7 @@ module Ruflet
|
|
|
374
382
|
nil
|
|
375
383
|
end
|
|
376
384
|
|
|
377
|
-
def download_file(url, destination, limit: 5
|
|
385
|
+
def download_file(url, destination, limit: 5)
|
|
378
386
|
raise "Too many redirects while downloading #{url}" if limit <= 0
|
|
379
387
|
|
|
380
388
|
uri = URI(url)
|
|
@@ -384,73 +392,15 @@ module Ruflet
|
|
|
384
392
|
http.request(req) do |res|
|
|
385
393
|
case res
|
|
386
394
|
when Net::HTTPSuccess
|
|
387
|
-
|
|
388
|
-
total = res["content-length"].to_i
|
|
389
|
-
downloaded = 0
|
|
390
|
-
marker = -1
|
|
391
|
-
File.open(destination, "wb") do |f|
|
|
392
|
-
res.read_body do |chunk|
|
|
393
|
-
f.write(chunk)
|
|
394
|
-
downloaded += chunk.bytesize
|
|
395
|
-
marker = report_download_progress(name, downloaded, total, marker)
|
|
396
|
-
end
|
|
397
|
-
end
|
|
398
|
-
finish_download_progress(name, downloaded, total)
|
|
395
|
+
File.open(destination, "wb") { |f| res.read_body { |chunk| f.write(chunk) } }
|
|
399
396
|
return destination
|
|
400
397
|
when Net::HTTPRedirection
|
|
401
|
-
return download_file(res["location"], destination, limit: limit - 1
|
|
398
|
+
return download_file(res["location"], destination, limit: limit - 1)
|
|
402
399
|
else
|
|
403
400
|
raise "Download failed (#{res.code})"
|
|
404
401
|
end
|
|
405
402
|
end
|
|
406
403
|
end
|
|
407
|
-
rescue OpenSSL::SSL::SSLError
|
|
408
|
-
curl_download(url, destination)
|
|
409
|
-
end
|
|
410
|
-
|
|
411
|
-
# Net::HTTP gives no progress for a multi-hundred-MB SDK download, which
|
|
412
|
-
# looks like a hang. Report percentage on a TTY (rewritten in place) and
|
|
413
|
-
# at coarse steps when piped, so logs stay readable either way.
|
|
414
|
-
def report_download_progress(name, downloaded, total, marker)
|
|
415
|
-
if total.positive?
|
|
416
|
-
percent = downloaded * 100 / total
|
|
417
|
-
return marker if percent <= marker
|
|
418
|
-
|
|
419
|
-
line = " Downloading #{name}: #{percent}% (#{human_size(downloaded)} / #{human_size(total)})"
|
|
420
|
-
if $stdout.tty?
|
|
421
|
-
$stdout.print("\r#{line}")
|
|
422
|
-
elsif (percent % 10).zero?
|
|
423
|
-
puts line
|
|
424
|
-
end
|
|
425
|
-
percent
|
|
426
|
-
else
|
|
427
|
-
step = downloaded / (5 * 1024 * 1024)
|
|
428
|
-
return marker if step <= marker
|
|
429
|
-
|
|
430
|
-
line = " Downloading #{name}: #{human_size(downloaded)}"
|
|
431
|
-
$stdout.tty? ? $stdout.print("\r#{line}") : puts(line)
|
|
432
|
-
step
|
|
433
|
-
end
|
|
434
|
-
end
|
|
435
|
-
|
|
436
|
-
def finish_download_progress(name, downloaded, total)
|
|
437
|
-
summary = total.positive? ? "#{human_size(downloaded)} / #{human_size(total)}" : human_size(downloaded)
|
|
438
|
-
if $stdout.tty?
|
|
439
|
-
$stdout.print("\r Downloaded #{name}: #{summary}\n")
|
|
440
|
-
else
|
|
441
|
-
puts " Downloaded #{name}: #{summary}"
|
|
442
|
-
end
|
|
443
|
-
end
|
|
444
|
-
|
|
445
|
-
def human_size(bytes)
|
|
446
|
-
units = %w[B KB MB GB TB]
|
|
447
|
-
size = bytes.to_f
|
|
448
|
-
unit = units.shift
|
|
449
|
-
while size >= 1024 && units.any?
|
|
450
|
-
size /= 1024
|
|
451
|
-
unit = units.shift
|
|
452
|
-
end
|
|
453
|
-
format("%.1f %s", size, unit)
|
|
454
404
|
end
|
|
455
405
|
|
|
456
406
|
def extract_archive(archive, destination)
|
|
@@ -458,25 +408,14 @@ module Ruflet
|
|
|
458
408
|
if windows_host?
|
|
459
409
|
return system("powershell", "-NoProfile", "-Command", "Expand-Archive -Path '#{archive}' -DestinationPath '#{destination}' -Force")
|
|
460
410
|
end
|
|
461
|
-
|
|
462
|
-
require_extract_tool!("unzip")
|
|
463
411
|
return system("unzip", "-oq", archive, "-d", destination)
|
|
464
412
|
end
|
|
465
413
|
|
|
466
414
|
if archive.end_with?(".tar.xz") || archive.end_with?(".tar.gz") || archive.end_with?(".tgz")
|
|
467
|
-
require_extract_tool!("tar")
|
|
468
|
-
require_extract_tool!("xz") if archive.end_with?(".tar.xz") && !windows_host?
|
|
469
415
|
return system("tar", "-xf", archive, "-C", destination)
|
|
470
416
|
end
|
|
471
417
|
|
|
472
|
-
|
|
473
|
-
end
|
|
474
|
-
|
|
475
|
-
def require_extract_tool!(name)
|
|
476
|
-
return if which_command(name)
|
|
477
|
-
|
|
478
|
-
raise "`#{name}` is required to extract the Flutter SDK but was not found. " \
|
|
479
|
-
"Run `ruflet doctor --fix` to install the missing system tools."
|
|
418
|
+
false
|
|
480
419
|
end
|
|
481
420
|
end
|
|
482
421
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
|
+
require "open3"
|
|
4
5
|
require "tmpdir"
|
|
5
6
|
require "yaml"
|
|
6
7
|
|
|
@@ -8,10 +9,10 @@ module Ruflet
|
|
|
8
9
|
module CLI
|
|
9
10
|
module NewCommand
|
|
10
11
|
TEMPLATE_REPO_URL = ENV.fetch("RUFLET_TEMPLATE_REPO_URL", "https://github.com/AdamMusa/ruflet-template.git")
|
|
12
|
+
TEMPLATE_REPO_REF = ENV.fetch("RUFLET_TEMPLATE_REPO_REF", "main")
|
|
11
13
|
RUNTIME_REPO_URL = ENV.fetch("RUFLET_RUNTIME_REPO_URL", "https://github.com/AdamMusa/ruflet.git")
|
|
12
14
|
|
|
13
15
|
CLIENT_EXTENSION_MAP = {
|
|
14
|
-
"ads" => { package: "flet_ads", alias: "ruflet_ads" },
|
|
15
16
|
"audio" => { package: "flet_audio", alias: "ruflet_audio" },
|
|
16
17
|
"audio_recorder" => { package: "flet_audio_recorder", alias: "ruflet_audio_recorder" },
|
|
17
18
|
"camera" => { package: "flet_camera", alias: "ruflet_camera" },
|
|
@@ -24,8 +25,8 @@ module Ruflet
|
|
|
24
25
|
"lottie" => { package: "flet_lottie", alias: "ruflet_lottie" },
|
|
25
26
|
"map" => { package: "flet_map", alias: "ruflet_map" },
|
|
26
27
|
"permission_handler" => { package: "flet_permission_handler", alias: "ruflet_permission_handler" },
|
|
28
|
+
"rive" => { package: "flet_rive", alias: "ruflet_rive" },
|
|
27
29
|
"secure_storage" => { package: "flet_secure_storage", alias: "ruflet_secure_storage" },
|
|
28
|
-
"spinkit" => { package: "flet_spinkit", alias: "ruflet_spinkit" },
|
|
29
30
|
"video" => { package: "flet_video", alias: "ruflet_video" },
|
|
30
31
|
"webview" => { package: "flet_webview", alias: "ruflet_webview" }
|
|
31
32
|
}.freeze
|
|
@@ -69,8 +70,10 @@ module Ruflet
|
|
|
69
70
|
|
|
70
71
|
target = hidden_flutter_client_dir(root)
|
|
71
72
|
FileUtils.mkdir_p(File.dirname(target))
|
|
73
|
+
FileUtils.rm_rf(target)
|
|
72
74
|
FileUtils.cp_r(template_root, target)
|
|
73
75
|
prune_client_template(target)
|
|
76
|
+
write_client_template_revision(target, installed_template_revision(template_root))
|
|
74
77
|
end
|
|
75
78
|
|
|
76
79
|
def hidden_flutter_client_dir(root = Dir.pwd)
|
|
@@ -85,20 +88,7 @@ module Ruflet
|
|
|
85
88
|
end
|
|
86
89
|
|
|
87
90
|
cached_template = cached_ruflet_client_template_root
|
|
88
|
-
if Dir.exist?(cached_template)
|
|
89
|
-
return cached_template if cached_template_current?(cached_template)
|
|
90
|
-
|
|
91
|
-
# The cache was written by a different ruflet version; stale
|
|
92
|
-
# framework Dart breaks newer asset layouts. Refresh, but never
|
|
93
|
-
# block a build on the network: fall back to the stale copy loudly.
|
|
94
|
-
refreshed = download_ruflet_template(force: true)
|
|
95
|
-
return refreshed if refreshed && Dir.exist?(refreshed)
|
|
96
|
-
|
|
97
|
-
warn "ruflet: could not refresh the Flutter client template cache; " \
|
|
98
|
-
"using a stale copy from another ruflet version at #{cached_template}. " \
|
|
99
|
-
"Delete it or run with network access to update."
|
|
100
|
-
return cached_template
|
|
101
|
-
end
|
|
91
|
+
return cached_template if Dir.exist?(cached_template)
|
|
102
92
|
|
|
103
93
|
[
|
|
104
94
|
File.expand_path("../../../ruflet_client", __dir__),
|
|
@@ -112,8 +102,6 @@ module Ruflet
|
|
|
112
102
|
|
|
113
103
|
def ensure_cached_ruflet_client_template!(force: false, verbose: false)
|
|
114
104
|
cached_template = cached_ruflet_client_template_root
|
|
115
|
-
return cached_template if !force && Dir.exist?(cached_template)
|
|
116
|
-
|
|
117
105
|
download_ruflet_template(force: force, verbose: verbose)
|
|
118
106
|
Dir.exist?(cached_template) ? cached_template : nil
|
|
119
107
|
end
|
|
@@ -126,19 +114,8 @@ module Ruflet
|
|
|
126
114
|
File.join(template_cache_root, "ruflet_flutter_template")
|
|
127
115
|
end
|
|
128
116
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def cached_template_current?(target)
|
|
132
|
-
stamp = File.join(target, TEMPLATE_CACHE_STAMP)
|
|
133
|
-
File.file?(stamp) && File.read(stamp).strip == Ruflet::VERSION
|
|
134
|
-
rescue StandardError
|
|
135
|
-
false
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def write_template_cache_stamp(target)
|
|
139
|
-
File.write(File.join(target, TEMPLATE_CACHE_STAMP), Ruflet::VERSION)
|
|
140
|
-
rescue StandardError
|
|
141
|
-
nil
|
|
117
|
+
def cached_ruflet_client_template_revision_path
|
|
118
|
+
File.join(template_cache_root, "ruflet_flutter_template.revision")
|
|
142
119
|
end
|
|
143
120
|
|
|
144
121
|
def cached_ruby_runtime_root
|
|
@@ -154,41 +131,104 @@ module Ruflet
|
|
|
154
131
|
end
|
|
155
132
|
|
|
156
133
|
def download_ruflet_assets(force: false, verbose: false)
|
|
157
|
-
|
|
158
|
-
return true if !force && Dir.exist?(template_target)
|
|
159
|
-
|
|
160
|
-
Dir.exist?(template_target) || download_ruflet_template(force: force, verbose: verbose)
|
|
134
|
+
!ensure_cached_ruflet_client_template!(force: force, verbose: verbose).nil?
|
|
161
135
|
end
|
|
162
136
|
|
|
163
137
|
def download_ruflet_template(force: false, verbose: false)
|
|
164
138
|
target = cached_ruflet_client_template_root
|
|
165
|
-
|
|
139
|
+
remote_revision = remote_template_revision(verbose: verbose)
|
|
140
|
+
current_revision = cached_template_revision
|
|
141
|
+
cache_available = valid_ruflet_template?(target)
|
|
142
|
+
if !force && cache_available && remote_revision && current_revision == remote_revision
|
|
143
|
+
return target
|
|
144
|
+
end
|
|
145
|
+
if !force && cache_available && remote_revision.nil?
|
|
146
|
+
build_log(verbose, "Unable to check the Ruflet template revision; using cached #{current_revision || 'template'}") if respond_to?(:build_log, true)
|
|
147
|
+
return target
|
|
148
|
+
end
|
|
166
149
|
|
|
167
150
|
FileUtils.mkdir_p(template_cache_root)
|
|
168
151
|
|
|
169
152
|
Dir.mktmpdir("ruflet-assets") do |tmp|
|
|
170
153
|
repo_dir = File.join(tmp, "Ruflet")
|
|
171
|
-
clone_cmd = ["git", "clone", "--depth", "1", "--filter=blob:none", "--sparse", TEMPLATE_REPO_URL, repo_dir]
|
|
172
|
-
return
|
|
173
|
-
return
|
|
154
|
+
clone_cmd = ["git", "clone", "--depth", "1", "--branch", TEMPLATE_REPO_REF, "--filter=blob:none", "--sparse", TEMPLATE_REPO_URL, repo_dir]
|
|
155
|
+
return target_if_present(target) unless run_template_command(clone_cmd, verbose: verbose)
|
|
156
|
+
return target_if_present(target) unless run_template_command(["git", "-C", repo_dir, "sparse-checkout", "set", "templates/ruflet_flutter_template"], verbose: verbose)
|
|
174
157
|
|
|
175
158
|
source = File.join(repo_dir, "templates", "ruflet_flutter_template")
|
|
176
|
-
return
|
|
159
|
+
return target_if_present(target) unless valid_ruflet_template?(source)
|
|
177
160
|
|
|
161
|
+
fetched_revision = git_revision(repo_dir) || remote_revision
|
|
162
|
+
staged_target = File.join(tmp, "ruflet_flutter_template")
|
|
163
|
+
FileUtils.cp_r(source, staged_target)
|
|
178
164
|
FileUtils.rm_rf(target)
|
|
179
|
-
FileUtils.
|
|
165
|
+
FileUtils.mv(staged_target, target)
|
|
166
|
+
File.write(cached_ruflet_client_template_revision_path, "#{fetched_revision}\n") if fetched_revision
|
|
180
167
|
end
|
|
181
168
|
|
|
182
|
-
write_template_cache_stamp(target)
|
|
183
169
|
target
|
|
184
170
|
rescue StandardError => e
|
|
185
171
|
warn "Failed to fetch Ruflet template: #{e.class}: #{e.message}"
|
|
186
|
-
|
|
172
|
+
target_if_present(target)
|
|
187
173
|
end
|
|
188
174
|
|
|
189
175
|
def run_template_command(cmd, verbose: false)
|
|
190
176
|
output = verbose ? $stdout : File::NULL
|
|
191
|
-
|
|
177
|
+
env = { "GIT_TERMINAL_PROMPT" => "0" }
|
|
178
|
+
system(env, *cmd, out: output, err: verbose ? $stderr : File::NULL)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def remote_template_revision(verbose: false)
|
|
182
|
+
return @ruflet_template_remote_revision if defined?(@ruflet_template_remote_revision)
|
|
183
|
+
|
|
184
|
+
env = { "GIT_TERMINAL_PROMPT" => "0" }
|
|
185
|
+
stdout, stderr, status = Open3.capture3(env, "git", "ls-remote", TEMPLATE_REPO_URL, "refs/heads/#{TEMPLATE_REPO_REF}")
|
|
186
|
+
warn stderr unless status.success? || !verbose || stderr.empty?
|
|
187
|
+
@ruflet_template_remote_revision = status.success? ? stdout.split.first : nil
|
|
188
|
+
rescue StandardError => e
|
|
189
|
+
warn "Failed to check Ruflet template revision: #{e.message}" if verbose
|
|
190
|
+
@ruflet_template_remote_revision = nil
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def cached_template_revision
|
|
194
|
+
return nil unless File.file?(cached_ruflet_client_template_revision_path)
|
|
195
|
+
|
|
196
|
+
File.read(cached_ruflet_client_template_revision_path).strip.then { |value| value.empty? ? nil : value }
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def installed_template_revision(template_root)
|
|
200
|
+
return cached_template_revision if File.expand_path(template_root) == File.expand_path(cached_ruflet_client_template_root)
|
|
201
|
+
|
|
202
|
+
git_revision(File.expand_path("../..", template_root))
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def write_client_template_revision(target, revision)
|
|
206
|
+
return unless revision
|
|
207
|
+
|
|
208
|
+
File.write(File.join(target, ".ruflet-template-revision"), "#{revision}\n")
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def client_template_current?(target, template_root)
|
|
212
|
+
expected = installed_template_revision(template_root)
|
|
213
|
+
return false unless expected
|
|
214
|
+
|
|
215
|
+
marker = File.join(target, ".ruflet-template-revision")
|
|
216
|
+
File.file?(marker) && File.read(marker).strip == expected
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def git_revision(repo_dir)
|
|
220
|
+
stdout, _stderr, status = Open3.capture3("git", "-C", repo_dir, "rev-parse", "HEAD")
|
|
221
|
+
status.success? ? stdout.strip : nil
|
|
222
|
+
rescue StandardError
|
|
223
|
+
nil
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def valid_ruflet_template?(path)
|
|
227
|
+
Dir.exist?(path) && File.file?(File.join(path, "pubspec.yaml")) && File.file?(File.join(path, "lib", "main.dart"))
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def target_if_present(target)
|
|
231
|
+
valid_ruflet_template?(target) ? target : nil
|
|
192
232
|
end
|
|
193
233
|
|
|
194
234
|
def prune_client_template(target)
|
|
@@ -225,10 +265,8 @@ module Ruflet
|
|
|
225
265
|
# Example: https://api.example.com
|
|
226
266
|
backend_url: ""
|
|
227
267
|
|
|
228
|
-
# Flutter
|
|
229
|
-
#
|
|
230
|
-
# geolocator, and permission_handler are activated by services.yaml
|
|
231
|
-
# and ignored here.
|
|
268
|
+
# Optional Flutter client extensions. Only listed extensions are bundled.
|
|
269
|
+
# Examples: audio, charts, code_editor, map, rive, video, webview
|
|
232
270
|
extensions: []
|
|
233
271
|
|
|
234
272
|
# Build assets configuration consumed by `ruflet build`.
|
|
@@ -246,7 +284,9 @@ module Ruflet
|
|
|
246
284
|
YAML
|
|
247
285
|
|
|
248
286
|
File.write(File.join(root, "services.yaml"), <<~YAML)
|
|
249
|
-
#
|
|
287
|
+
# Native capabilities that require platform permissions. Ruflet activates
|
|
288
|
+
# the matching client extensions and writes Android/iOS permissions.
|
|
289
|
+
# Supported services: camera, microphone, location, motion
|
|
250
290
|
services: []
|
|
251
291
|
YAML
|
|
252
292
|
end
|