ruflet 0.0.22 → 0.0.23

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: a925aef31bb20fe4f6f48f2d6f68e1f0bc1599fd494a517c2412c524c535e6c8
4
- data.tar.gz: ddd86413840811bb99b66dbb5f9e325a338e981bcc1a126b4b73e46ec06a63fb
3
+ metadata.gz: bb526fe95e57e72ddf182e86be54239b05a6f782a0f574780aa6fabca3a69ef6
4
+ data.tar.gz: fd8a42692977eb964011e2d087e838345e9d836dca2f6d11f7560197b9cae562
5
5
  SHA512:
6
- metadata.gz: 8fb021bc622f6ea04a0a6f444bae379546cc790b9349b0dea81c5d8d25926275cc9102ec9c05d08fb993f4b2ed749e7b4e502dfa35cad1551d5df74e81bc3859
7
- data.tar.gz: 4124df68e4b93d77af36a7132bc1a59a88764c02cb1e875e3c84bd76e763f805e4381be292b1727ea9f1cd53103c9f73c1a86da01d9817fe45e2363460b4f2dd
6
+ metadata.gz: d2b0ef3741d51fe6cdba91d41db6fd5dba388974dedcd00751981ee89a399bfad1f0d07e9b8d7c32e512f80fd652ee784fcda80d71105f70fad323e63e7876c0
7
+ data.tar.gz: cfe6ec3fe141f6edc414e866d204bc7831cbc1aa8fed27d82cae43a84b20df6e78e56819bd7341818025a3f1ed4dc63322b46014cccb8007acffae090e0ec14e
data/README.md CHANGED
@@ -25,21 +25,16 @@ dependencies. Run Ruflet commands directly inside the generated project.
25
25
 
26
26
  ```bash
27
27
  ruflet new <appname>
28
- ruflet run [scriptname|path] [--web|--desktop] [--experimental|--exp] [--port PORT]
28
+ ruflet run [scriptname|path] [--web|--desktop] [--port PORT]
29
29
  ruflet debug [scriptname|path]
30
30
  ruflet devices
31
31
  ruflet emulators
32
32
  ruflet doctor [--fix]
33
33
  ruflet update [web|desktop|all] [--check] [--force]
34
- ruflet build <apk|android|ios|ipa|aab|web|macos|windows|linux> [--lite|--full|--self] [--experimental|--exp]
34
+ ruflet build <apk|android|ios|ipa|aab|web|macos|windows|linux> [--lite|--full|--self]
35
35
  ruflet install [--device DEVICE_ID]
36
36
  ```
37
37
 
38
- `--experimental` is available only for iOS and macOS builds. It selects the
39
- native Apple renderer while the normal Ruflet build pipeline continues to
40
- resolve the declared services, extensions, permissions, assets, and runtime
41
- mode. Without it, Apple builds use only the Flutter renderer.
42
-
43
38
  `--lite` creates the compact self-contained build: project Ruby is precompiled
44
39
  when the matching mruby compiler is available, and the bundled VM starts in
45
40
  parallel with Flutter. `--self` remains an alias for this profile.
@@ -53,15 +48,11 @@ a target-specific CRuby runtime. Set `RUFLET_FULL_RUNTIME_PATH`, or
53
48
  Ruflet refuses to substitute the lite mruby engine for a requested full build.
54
49
 
55
50
  Before Flutter resolves or bundles packages, Ruflet removes extension plugins
56
- that are not selected by the current services/extensions configuration. With
57
- the experimental Apple renderer, built-in declarations are registered as Swift
58
- extensions and their duplicate Dart/Flutter plugins are excluded entirely.
59
-
60
- On macOS, `ruflet run --experimental` (or `ruflet run --exp`) downloads the
61
- experimental Ruflet Explorer iOS Simulator prebuild the first time, reuses its
62
- versioned cache afterward, and launches it on the already-booted simulator
63
- with the current backend URL. Add `--desktop` to download and launch the native
64
- macOS prebuild instead: `ruflet run --desktop --exp`.
51
+ that are not selected by the current services/extensions configuration.
52
+
53
+ `ruflet run --desktop` downloads the Ruflet Explorer prebuild for the host the
54
+ first time, reuses its versioned cache afterward, and launches it with the
55
+ current backend URL.
65
56
 
66
57
  Commands that create, diagnose, or build a Flutter client compare the cached
67
58
  template revision with `AdamMusa/ruflet-template` on GitHub. When `main`
@@ -35,30 +35,6 @@ module Ruflet
35
35
  "video" => { package: "ruflet_video", alias: "ruflet_video" },
36
36
  "webview" => { package: "ruflet_webview", alias: "ruflet_webview" }
37
37
  }.freeze
38
- # Native Swift products matching the Ruflet extensions selected by the
39
- # ordinary Ruflet build configuration. The same resolved extension keys
40
- # drive Dart imports, Swift imports/registration, and Xcode linkage.
41
- NATIVE_APPLE_EXTENSION_MAP = {
42
- "ads" => { module: "RufletAds", initializer: "RufletAds.Extension()" },
43
- "audio" => { module: "RufletAudio", initializer: "RufletAudioExtension()" },
44
- "audio_recorder" => { module: "RufletAudioRecorder", initializer: "RufletAudioRecorderExtension()" },
45
- "camera" => { module: "RufletCamera", initializer: "RufletCameraExtension()" },
46
- "charts" => { module: "RufletCharts", initializer: "RufletChartsExtension()" },
47
- "code_editor" => { module: "RufletCodeEditor", initializer: "RufletCodeEditorExtension()" },
48
- "color_pickers" => { module: "RufletColorPickers", initializer: "RufletColorPickersExtension()" },
49
- "datatable2" => { module: "RufletDataTable2", initializer: "RufletDataTable2Extension()" },
50
- "flashlight" => { module: "RufletFlashlight", initializer: "RufletFlashlightExtension()" },
51
- "geolocator" => { module: "RufletGeolocator", initializer: "RufletGeolocatorExtension()" },
52
- "lottie" => { module: "RufletLottie", initializer: "RufletLottieExtension()" },
53
- "map" => { module: "RufletMap", initializer: "RufletMap.Extension()" },
54
- "permission_handler" => { module: "RufletPermissionHandler", initializer: "RufletPermissionHandlerExtension()" },
55
- "qrcode_scanner" => { module: "RufletQRScanner", initializer: "RufletQRScannerExtension()" },
56
- "rive" => { module: "RufletRive", initializer: "RufletRiveExtension()" },
57
- "secure_storage" => { module: "RufletSecureStorage", initializer: "RufletSecureStorageExtension()" },
58
- "spinkit" => { module: "RufletSpinKit", initializer: "RufletSpinKitExtension()" },
59
- "video" => { module: "RufletVideo", initializer: "RufletVideoExtension()" },
60
- "webview" => { module: "RufletWebView", initializer: "RufletWebViewExtension()" }
61
- }.freeze
62
38
  PROTECTED_SERVICE_EXTENSIONS = {
63
39
  "camera" => %w[camera permission_handler],
64
40
  "microphone" => %w[audio_recorder permission_handler],
@@ -152,18 +128,10 @@ module Ruflet
152
128
  runtime_profile = full ? :full : :lite
153
129
  @ruflet_runtime_profile = self_contained ? runtime_profile : :server
154
130
  @ruflet_runtime_profile_explicit = !!(lite || full)
155
- experimental = args.delete("--experimental")
156
- experimental_alias = args.delete("--exp")
157
- experimental ||= experimental_alias
158
131
  verbose = args.delete("--verbose") || args.delete("-v")
159
132
  platform = (args.shift || "").downcase
160
133
  if platform.empty?
161
- warn "Usage: ruflet build <apk|android|aab|ios|ipa|web|macos|windows|linux> [--lite|--full|--self] [--experimental|--exp] [--verbose]"
162
- return 1
163
- end
164
-
165
- if experimental && !%w[ios ipa macos].include?(platform)
166
- warn "build config error: --experimental is supported only for ios and macos"
134
+ warn "Usage: ruflet build <apk|android|aab|ios|ipa|web|macos|windows|linux> [--lite|--full|--self] [--verbose]"
167
135
  return 1
168
136
  end
169
137
 
@@ -178,7 +146,6 @@ module Ruflet
178
146
  requested_platform = platform
179
147
  platform = "ios" if platform == "ipa"
180
148
  @ruflet_build_platform = platform
181
- @ruflet_experimental_native_renderer = !!experimental
182
149
 
183
150
  # The embedded Ruby VM is a native plugin with no browser
184
151
  # implementation, so a self-contained web build produces an app that
@@ -198,9 +165,8 @@ module Ruflet
198
165
  return 1
199
166
  end
200
167
 
201
- renderer = experimental ? ", experimental native renderer" : ""
202
168
  mode = self_contained ? "self-contained #{runtime_profile}" : "server-driven"
203
- build_note("Preparing #{platform} build (#{mode}#{renderer})")
169
+ build_note("Preparing #{platform} build (#{mode})")
204
170
  config = load_ruflet_config
205
171
  if runtime_profile == :full && self_contained
206
172
  return 1 unless configure_full_runtime_distribution(config, platform, verbose: !!verbose)
@@ -230,6 +196,9 @@ module Ruflet
230
196
  if @ruflet_runtime_profile_explicit
231
197
  build_args += ["--dart-define", "RUFLET_RUNTIME_PROFILE=#{runtime_profile}"]
232
198
  end
199
+ # The platform layer starts the VM before Flutter exists, so the app
200
+ # bundle -- not a Dart define -- has to carry the same decision.
201
+ configure_platform_autostart(client_dir, platform, verbose: !!verbose)
233
202
  elsif backend_url
234
203
  build_args += ["--dart-define", "RUFLET_BACKEND_URL=#{backend_url}"]
235
204
  elsif RUNTIME_RESOLVED_BACKEND_PLATFORMS.include?(platform)
@@ -243,9 +212,6 @@ module Ruflet
243
212
  warn "Set app.backend_url or backend_url in ruflet.yaml"
244
213
  return 1
245
214
  end
246
- if experimental
247
- build_args += ["--dart-define", "RUFLET_EXPERIMENTAL_NATIVE_RENDERER=true"]
248
- end
249
215
  build_args << "-v" if verbose
250
216
  stage_ios_simulator_ruby_runtime(client_dir, build_args, verbose: !!verbose) if self_contained
251
217
 
@@ -326,10 +292,6 @@ module Ruflet
326
292
 
327
293
  private
328
294
 
329
- def experimental_native_renderer?
330
- @ruflet_experimental_native_renderer == true
331
- end
332
-
333
295
  def extract_option_value!(args, *flags)
334
296
  flags.each do |flag|
335
297
  index = args.index(flag)
@@ -586,15 +548,10 @@ module Ruflet
586
548
  refreshed = refresh_managed_client_template_files(
587
549
  client_dir, platform: platform, verbose: verbose)
588
550
  return false if refreshed == false
589
- unless configure_apple_renderer_integration(
590
- client_dir, platform: platform, enabled: experimental_native_renderer?,
591
- verbose: verbose)
551
+ unless remove_native_apple_renderer_integration(
552
+ client_dir, platform: platform, verbose: verbose)
592
553
  return false
593
554
  end
594
- if experimental_native_renderer?
595
- sync_application_apple_extensions(
596
- client_dir, platform: platform, verbose: verbose)
597
- end
598
555
  metadata = sync_client_metadata(client_dir, config, verbose: verbose)
599
556
  return false unless validate_mobile_app_identity(metadata, platform: platform)
600
557
 
@@ -605,7 +562,7 @@ module Ruflet
605
562
  return false if configured == false
606
563
  configure_native_apple_runtime(
607
564
  client_dir, platform: platform, self_contained: self_contained,
608
- config: config, experimental: experimental_native_renderer?, verbose: verbose)
565
+ config: config, verbose: verbose)
609
566
  configure_android_runtime(
610
567
  client_dir, platform: platform, self_contained: self_contained,
611
568
  verbose: verbose)
@@ -614,11 +571,6 @@ module Ruflet
614
571
  if @ruflet_extension_selection_applied && !validate_flutter_extension_selection(client_dir)
615
572
  return false
616
573
  end
617
- if experimental_native_renderer?
618
- apply_native_apple_extension_config(
619
- client_dir, platform: platform, extension_keys: extension_keys,
620
- verbose: verbose)
621
- end
622
574
  asset_flags = apply_build_config(client_dir, config)
623
575
  if asset_flags[:error]
624
576
  warn asset_flags[:error]
@@ -1731,15 +1683,12 @@ module Ruflet
1731
1683
 
1732
1684
  def apply_service_extension_config(client_dir, config = {}, self_contained: @ruflet_self_contained_build)
1733
1685
  extension_keys = configured_extension_keys(config)
1734
- # The experimental Apple host renders these extensions in Swift. Keep
1735
- # the resolved keys for native registration, but do not also ship the
1736
- # equivalent Dart plugins and their native Flutter frameworks.
1737
- flutter_extension_keys = experimental_native_renderer? ? [] : extension_keys
1686
+ flutter_extension_keys = extension_keys
1738
1687
  extension_packages = flutter_extension_keys.filter_map { |key| CLIENT_EXTENSION_MAP[key]&.fetch(:package) }.uniq
1739
1688
  extension_aliases = flutter_extension_keys.filter_map { |key| CLIENT_EXTENSION_MAP[key]&.fetch(:alias) }.uniq
1740
1689
 
1741
1690
  configured_external = external_extension_entries(config)
1742
- external = experimental_native_renderer? ? [] : configured_external
1691
+ external = configured_external
1743
1692
  previous_external_names = managed_external_extension_names(client_dir)
1744
1693
  discovered_external_names = discover_registered_external_extension_names(client_dir)
1745
1694
  removable_external_names = (
@@ -1773,9 +1722,6 @@ module Ruflet
1773
1722
  @ruflet_selected_external_extension_packages = external.map { |entry| entry[:name] }
1774
1723
  @ruflet_managed_external_extension_packages = removable_external_names
1775
1724
  @ruflet_extension_selection_applied = true
1776
- if experimental_native_renderer? && (!extension_keys.empty? || !configured_external.empty?)
1777
- build_note("Native Apple extensions selected; excluded equivalent Flutter plugins from the app bundle")
1778
- end
1779
1725
  extension_keys
1780
1726
  end
1781
1727
 
@@ -2249,7 +2195,10 @@ module Ruflet
2249
2195
  # A self-contained build needs the embedded VM. Prefer a local checkout so
2250
2196
  # the runtime under development is the one packaged, and otherwise resolve
2251
2197
  # the published package.
2252
- PUBLISHED_RUBY_RUNTIME_CONSTRAINT = "^0.0.9"
2198
+ # 0.0.13 is the first release where the platform layer starts the VM and
2199
+ # exposes serverUrl(); the client entrypoint calls it, so an older runtime
2200
+ # would not build.
2201
+ PUBLISHED_RUBY_RUNTIME_CONSTRAINT = "^0.0.14"
2253
2202
 
2254
2203
  def ruby_runtime_dependency(current_dependency = nil)
2255
2204
  if embedded_runtime_profile == :full && @ruflet_full_runtime_path
@@ -2341,16 +2290,10 @@ module Ruflet
2341
2290
  return false unless validate_template_ruflet_source_integrity(
2342
2291
  template_root, verbose: verbose)
2343
2292
 
2344
- if experimental_native_renderer? && %w[ios ipa macos].include?(platform.to_s)
2345
- return false unless validate_native_apple_renderer_distribution(
2346
- template_root, verbose: verbose)
2347
- end
2348
-
2349
2293
  managed_files = [
2350
2294
  "lib/main.dart",
2351
2295
  "lib/main.self.dart",
2352
2296
  "lib/main.server.dart",
2353
- "lib/native_renderer.dart",
2354
2297
  "lib/connection_probe.dart",
2355
2298
  "lib/connection_probe_io.dart",
2356
2299
  "lib/connection_probe_stub.dart",
@@ -2371,7 +2314,6 @@ module Ruflet
2371
2314
  managed_files.concat(
2372
2315
  [
2373
2316
  "ios/Runner/AppDelegate.swift",
2374
- "ios/Runner/RufletEngineChoice.swift",
2375
2317
  "ios/Runner.xcodeproj/project.pbxproj"
2376
2318
  ]
2377
2319
  )
@@ -2379,7 +2321,6 @@ module Ruflet
2379
2321
  managed_files.concat(
2380
2322
  [
2381
2323
  "macos/Runner/MainFlutterWindow.swift",
2382
- "macos/Runner/RufletEngineChoice.swift",
2383
2324
  "macos/Runner.xcodeproj/project.pbxproj"
2384
2325
  ]
2385
2326
  )
@@ -2395,11 +2336,6 @@ module Ruflet
2395
2336
  build_log(verbose, "refreshed template file #{relative_path}")
2396
2337
  end
2397
2338
 
2398
- if experimental_native_renderer? && %w[ios ipa macos].include?(platform.to_s)
2399
- sync_managed_template_tree(
2400
- template_root, client_dir, "apple_packages/ruflet_apple", verbose: verbose)
2401
- end
2402
-
2403
2339
  # Older managed clients carried a macOS-only FilePicker override. Ruflet's
2404
2340
  # core service owns FilePicker now, so this duplicate must not survive a
2405
2341
  # template refresh.
@@ -2475,44 +2411,17 @@ module Ruflet
2475
2411
  false
2476
2412
  end
2477
2413
 
2478
- # The standalone client template owns the editable Apple renderer. Build
2479
- # clients receive an exact managed copy from that package; there is no
2480
- # second renderer in this repository and no fallback implementation.
2481
- def validate_native_apple_renderer_distribution(template_root, verbose: false)
2482
- package_root = File.join(template_root, "apple_packages", "ruflet_apple")
2483
- required = [
2484
- "Package.swift",
2485
- "Sources/RufletEngine/RufletApp.swift",
2486
- "Sources/RufletEngine/RufletAppView.swift"
2487
- ]
2488
- missing = required.reject { |relative| File.file?(File.join(package_root, relative)) }
2489
- return true if missing.empty?
2490
-
2491
- warn "build config error: the authoritative Apple renderer is incomplete"
2492
- missing.each { |relative| warn " missing: apple_packages/ruflet_apple/#{relative}" }
2493
- false
2494
- end
2495
-
2496
- def configure_apple_renderer_integration(client_dir, platform:, enabled:, verbose: false)
2414
+ # Ruflet had a second, Swift-rendered Apple host. It is gone: every
2415
+ # platform renders through the Ruflet Flutter engine. A client generated
2416
+ # while it existed still carries the Swift package, the engine-choice
2417
+ # shim and the Xcode references, so a rebuild strips them.
2418
+ def remove_native_apple_renderer_integration(client_dir, platform:, verbose: false)
2497
2419
  apple_platform = case platform.to_s
2498
2420
  when "ios", "ipa" then "ios"
2499
2421
  when "macos" then "macos"
2500
2422
  end
2501
2423
  return true unless apple_platform
2502
2424
 
2503
- if enabled
2504
- package = File.join(client_dir, "apple_packages", "ruflet_apple", "Package.swift")
2505
- choice = File.join(client_dir, apple_platform, "Runner", "RufletEngineChoice.swift")
2506
- unless File.file?(package) && File.file?(choice)
2507
- warn "build config error: the Ruflet client template does not contain the native Apple renderer"
2508
- warn "Refresh the Ruflet template, then run the build again."
2509
- return false
2510
- end
2511
- configure_ios_scene_delegate(client_dir, native: true) if apple_platform == "ios"
2512
- build_log(verbose, "enabled experimental native Apple renderer")
2513
- return true
2514
- end
2515
-
2516
2425
  unless restore_flutter_apple_host(client_dir, apple_platform, verbose: verbose)
2517
2426
  return false
2518
2427
  end
@@ -2520,12 +2429,12 @@ module Ruflet
2520
2429
  FileUtils.rm_f(File.join(client_dir, apple_platform, "Runner", "RufletEngineChoice.swift"))
2521
2430
  FileUtils.rm_rf(File.join(client_dir, "apple_packages"))
2522
2431
  FileUtils.rm_rf(File.join(client_dir, "apple_extensions"))
2523
- configure_ios_scene_delegate(client_dir, native: false) if apple_platform == "ios"
2432
+ configure_ios_scene_delegate(client_dir) if apple_platform == "ios"
2524
2433
  apple_info_plist_paths(client_dir).each do |path|
2525
2434
  remove_plist_value(path, "RufletExperimentalNativeRenderer")
2526
2435
  remove_plist_value(path, "GADApplicationIdentifier")
2527
2436
  end
2528
- build_log(verbose, "using Flutter renderer; removed native Apple package integration")
2437
+ build_log(verbose, "removed native Apple renderer integration")
2529
2438
  true
2530
2439
  end
2531
2440
 
@@ -2564,15 +2473,19 @@ module Ruflet
2564
2473
  write_text_file(path, content)
2565
2474
  end
2566
2475
 
2567
- def configure_ios_scene_delegate(client_dir, native:)
2476
+ # Only the Flutter host remains, so the scene delegate is Flutter's own.
2477
+ # A client built against the Swift renderer names RufletSceneDelegate,
2478
+ # which lived in the deleted package -- launching it would trap on a
2479
+ # missing class. FlutterSceneDelegate comes from the Flutter framework,
2480
+ # so it carries no $(PRODUCT_MODULE_NAME) prefix.
2481
+ def configure_ios_scene_delegate(client_dir)
2568
2482
  path = File.join(client_dir, "ios", "Runner", "Info.plist")
2569
2483
  return unless File.file?(path)
2570
2484
 
2571
2485
  content = read_text_file(path)
2572
- delegate = native ? "RufletSceneDelegate" : "SceneDelegate"
2573
2486
  content.gsub!(
2574
- %r{(<key>UISceneDelegateClassName</key>\s*<string>)\$\(PRODUCT_MODULE_NAME\)\.(?:RufletSceneDelegate|FlutterSceneDelegate|SceneDelegate)(</string>)}m,
2575
- "\\1$(PRODUCT_MODULE_NAME).#{delegate}\\2"
2487
+ %r{(<key>UISceneDelegateClassName</key>\s*<string>)(?:\$\(PRODUCT_MODULE_NAME\)\.)?(?:RufletSceneDelegate|FlutterSceneDelegate|SceneDelegate)(</string>)}m,
2488
+ "\\1FlutterSceneDelegate\\2"
2576
2489
  )
2577
2490
  write_text_file(path, content)
2578
2491
  end
@@ -2637,136 +2550,8 @@ module Ruflet
2637
2550
  write_text_file(path, content)
2638
2551
  end
2639
2552
 
2640
- def apply_native_apple_extension_config(
2641
- client_dir, platform:, extension_keys:, verbose: false
2642
- )
2643
- apple_platform = %w[ios ipa].include?(platform.to_s) ? "ios" : platform.to_s
2644
- return unless %w[ios macos].include?(apple_platform)
2645
-
2646
- selected = Array(extension_keys).filter_map do |key|
2647
- spec = NATIVE_APPLE_EXTENSION_MAP[key]
2648
- [key, spec] if spec
2649
- end
2650
- selected_modules = selected.map { |_key, spec| spec.fetch(:module) }
2651
- unselected_modules = NATIVE_APPLE_EXTENSION_MAP.values
2652
- .map { |spec| spec.fetch(:module) }
2653
- .uniq - selected_modules
2654
-
2655
- choice_path = File.join(client_dir, apple_platform, "Runner", "RufletEngineChoice.swift")
2656
- if File.file?(choice_path)
2657
- imports = selected_modules.map { |name| "import #{name}" }.join("\n")
2658
- registrations = selected.map do |_key, spec|
2659
- " result.append(#{spec.fetch(:initializer)})"
2660
- end.join("\n")
2661
- source = <<~SWIFT
2662
- import Foundation
2663
- import RufletApple
2664
- import RufletAppExtensions
2665
- #{imports}
2666
-
2667
- /// Generated by `ruflet build --experimental` from the same
2668
- /// services/extensions configuration used by the Flutter client.
2669
- enum RufletEngineChoice {
2670
- static var usesNativeRenderer: Bool {
2671
- let value = Bundle.main.object(
2672
- forInfoDictionaryKey: "RufletExperimentalNativeRenderer")
2673
- if let flag = value as? Bool { return flag }
2674
- if let flag = value as? NSNumber { return flag.boolValue }
2675
- return false
2676
- }
2677
-
2678
- static func pageURL(from raw: String) -> URL? {
2679
- RufletPageAddress.parse(raw)
2680
- }
2681
-
2682
- @MainActor static var extensions: [any RufletExtension] {
2683
- var result = RufletAppExtensionRegistry.extensions
2684
- #{registrations}
2685
- return result
2686
- }
2687
- }
2688
- SWIFT
2689
- write_text_file(choice_path, source)
2690
- end
2691
-
2692
- pbxproj = File.join(client_dir, apple_platform, "Runner.xcodeproj", "project.pbxproj")
2693
- if File.file?(pbxproj)
2694
- content = read_text_file(pbxproj)
2695
- content = content.lines.reject do |line|
2696
- unselected_modules.any? do |name|
2697
- line.match?(/\/\* #{Regexp.escape(name)}(?: in Frameworks)? \*\//)
2698
- end
2699
- end.join
2700
- write_text_file(pbxproj, content)
2701
- end
2702
-
2703
- unless selected_modules.include?("RufletAds")
2704
- remove_plist_value(
2705
- File.join(client_dir, "ios", "Runner", "Info.plist"),
2706
- "GADApplicationIdentifier") if apple_platform == "ios"
2707
- end
2708
- build_log(
2709
- verbose,
2710
- "native Apple extensions=#{selected.empty? ? 'core only' : selected.map(&:first).join(',')}"
2711
- )
2712
- end
2713
-
2714
- def sync_managed_template_tree(template_root, client_dir, relative_path, verbose: false)
2715
- source = File.join(template_root, relative_path)
2716
- return unless Dir.exist?(source)
2717
-
2718
- destination = File.join(client_dir, relative_path)
2719
- sync_apple_source_tree(source, destination)
2720
- build_log(verbose, "refreshed template tree #{relative_path}")
2721
- end
2722
-
2723
- # Application-defined RufletExtension implementations live outside the
2724
- # generated Flutter client. New projects own <project>/apple_extensions;
2725
- # existing projects receive the empty template registry until they opt
2726
- # in. Both server-driven and self-contained builds use the same package.
2727
- def sync_application_apple_extensions(client_dir, platform:, verbose: false)
2728
- return unless %w[ios ipa macos].include?(platform.to_s)
2729
-
2730
- template_root = if Ruflet::CLI.respond_to?(:resolve_ruflet_client_template_root, true)
2731
- Ruflet::CLI.send(:resolve_ruflet_client_template_root)
2732
- end
2733
- project_source = File.join(Dir.pwd, "apple_extensions")
2734
- template_source = template_root && File.join(template_root, "apple_extensions")
2735
- source = if File.file?(File.join(project_source, "Package.swift"))
2736
- project_source
2737
- elsif template_source && File.file?(File.join(template_source, "Package.swift"))
2738
- template_source
2739
- end
2740
- return unless source
2741
-
2742
- destination = File.join(client_dir, "apple_extensions")
2743
- sync_apple_source_tree(source, destination)
2744
- owner = source == project_source ? "project" : "template default"
2745
- build_log(verbose, "refreshed #{owner} Apple extensions")
2746
- end
2747
-
2748
- def sync_apple_source_tree(source, destination)
2749
- FileUtils.rm_rf(destination)
2750
- FileUtils.mkdir_p(destination)
2751
- # SwiftPM and Dart create mutable caches directly inside the package.
2752
- # Copying the whole directory and deleting those caches afterwards is
2753
- # too late: framework resource symlinks inside `.build` can collide or
2754
- # escape while FileUtils is still traversing them. Treat only the
2755
- # package's source tree as managed template input from the outset.
2756
- generated_entries = %w[
2757
- .swiftpm .dart_tool .claude DerivedData xcuserdata
2758
- ]
2759
- Dir.children(source).each do |entry|
2760
- next if generated_entries.include?(entry) || entry == ".build" || entry.start_with?(".build-")
2761
-
2762
- FileUtils.cp_r(
2763
- File.join(source, entry), File.join(destination, entry),
2764
- preserve: true)
2765
- end
2766
- end
2767
-
2768
2553
  def configure_native_apple_runtime(
2769
- client_dir, platform:, self_contained:, config: {}, experimental: true, verbose: false
2554
+ client_dir, platform:, self_contained:, config: {}, verbose: false
2770
2555
  )
2771
2556
  plist_paths = case platform.to_s
2772
2557
  when "ios", "ipa"
@@ -2787,9 +2572,7 @@ module Ruflet
2787
2572
  upsert_plist_string(
2788
2573
  path, "RufletRuntimeProfile",
2789
2574
  self_contained ? embedded_runtime_profile.to_s : "")
2790
- upsert_plist_boolean(
2791
- path, "RufletExperimentalNativeRenderer",
2792
- experimental)
2575
+ remove_plist_value(path, "RufletExperimentalNativeRenderer")
2793
2576
  apple_platform = platform.to_s == "ipa" ? "ios" : platform.to_s
2794
2577
  platform_config = platform_build_config(config, apple_platform)
2795
2578
  if platform_config["local_network"] == true
@@ -3317,6 +3100,62 @@ module Ruflet
3317
3100
  name
3318
3101
  end
3319
3102
 
3103
+ # Tells the platform layer to start the embedded runtime, and which
3104
+ # packaged project to run.
3105
+ #
3106
+ # This is platform configuration rather than a dart-define because the VM
3107
+ # starts before Dart does: on Apple platforms from the plugin's +load, on
3108
+ # Android from an androidx.startup provider. Neither can read a value that
3109
+ # only exists inside the Dart isolate.
3110
+ #
3111
+ # Desktop needs nothing here -- it has no manifest to carry a flag, and
3112
+ # treats the presence of a packaged project as the opt-in, which is the
3113
+ # same distinction this flag draws on the other platforms.
3114
+ def configure_platform_autostart(client_dir, platform, verbose: false)
3115
+ project = self_contained_project_name
3116
+ case platform
3117
+ when "ios", "macos"
3118
+ plist = File.join(client_dir, platform, "Runner", "Info.plist")
3119
+ return unless File.file?(plist)
3120
+
3121
+ set_plist_value(plist, "RufletRuntimeAutostart", "bool", "true")
3122
+ set_plist_value(plist, "RufletEmbeddedProject", "string", project)
3123
+ build_log(verbose, "enabled runtime autostart in #{platform}/Runner/Info.plist")
3124
+ when "apk", "appbundle", "android"
3125
+ manifest = File.join(
3126
+ client_dir, "android", "app", "src", "main", "AndroidManifest.xml"
3127
+ )
3128
+ return unless File.file?(manifest)
3129
+
3130
+ set_android_meta_data(manifest, "ruflet.runtime.autostart", "true")
3131
+ set_android_meta_data(manifest, "ruflet.runtime.project", project)
3132
+ build_log(verbose, "enabled runtime autostart in AndroidManifest.xml")
3133
+ end
3134
+ end
3135
+
3136
+ def set_plist_value(plist, key, type, value)
3137
+ # Delete first: PlistBuddy's Add fails on an existing key, and a rebuild
3138
+ # after renaming the project would otherwise keep the stale name.
3139
+ system("/usr/libexec/PlistBuddy", "-c", "Delete :#{key}", plist,
3140
+ out: File::NULL, err: File::NULL)
3141
+ system("/usr/libexec/PlistBuddy", "-c", "Add :#{key} #{type} #{value}",
3142
+ plist, out: File::NULL, err: File::NULL)
3143
+ end
3144
+
3145
+ def set_android_meta_data(manifest, name, value)
3146
+ contents = File.read(manifest)
3147
+ entry = %(<meta-data android:name="#{name}" android:value="#{value}" />)
3148
+ existing = /<meta-data\s+android:name="#{Regexp.escape(name)}"[^>]*\/>/
3149
+
3150
+ contents =
3151
+ if contents.match?(existing)
3152
+ contents.sub(existing, entry)
3153
+ else
3154
+ contents.sub(%r{(\n(\s*)</application>)}) { "\n#{$2} #{entry}#{$1}" }
3155
+ end
3156
+ File.write(manifest, contents)
3157
+ end
3158
+
3320
3159
  def skip_project_asset_directory?(relative)
3321
3160
  excluded_directories = %w[
3322
3161
  .git
@@ -3518,13 +3357,13 @@ module Ruflet
3518
3357
  queue << local
3519
3358
  end
3520
3359
  end
3521
- base = "file://#{URI::DEFAULT_PARSER.escape(File.expand_path(config_path))}"
3360
+ base = file_uri_for(File.expand_path(config_path))
3522
3361
  expected.each do |name, root|
3523
3362
  entry = resolved.find { |package| package["name"] == name }
3524
3363
  raise "missing local package #{name}" unless entry
3525
3364
 
3526
3365
  uri = URI.join(base, entry.fetch("rootUri"))
3527
- actual = URI::DEFAULT_PARSER.unescape(uri.path)
3366
+ actual = path_from_file_uri(uri)
3528
3367
  unless uri.scheme == "file" && File.realpath(actual) == File.realpath(root)
3529
3368
  raise "#{name} must resolve to the bundled Ruflet source at #{root}, got #{uri}"
3530
3369
  end
@@ -3536,6 +3375,21 @@ module Ruflet
3536
3375
  false
3537
3376
  end
3538
3377
 
3378
+ # A POSIX path already starts with "/", so "file://" + path gives the
3379
+ # three slashes a file URI needs. A Windows path starts with the drive
3380
+ # ("D:/..."), and without the extra slash "D:" is parsed as the URI's
3381
+ # host -- which is why this read every bundled package as missing there.
3382
+ def file_uri_for(path)
3383
+ escaped = URI::DEFAULT_PARSER.escape(path.tr("\\", "/"))
3384
+ escaped.start_with?("/") ? "file://#{escaped}" : "file:///#{escaped}"
3385
+ end
3386
+
3387
+ # And back: URI#path keeps that leading slash, so a Windows path returns
3388
+ # as "/D:/..." which no file API accepts.
3389
+ def path_from_file_uri(uri)
3390
+ URI::DEFAULT_PARSER.unescape(uri.path).sub(%r{\A/(?=[A-Za-z]:)}, "")
3391
+ end
3392
+
3539
3393
  def template_client_pubspec_dependencies
3540
3394
  template_root =
3541
3395
  if Ruflet::CLI.respond_to?(:resolve_ruflet_client_template_root, true)
@@ -53,7 +53,6 @@ module Ruflet
53
53
  File.write(File.join(root, "README.md"), format(Ruflet::CLI::README_TEMPLATE, app_name: File.basename(root)))
54
54
  write_default_ruflet_config(root, File.basename(root))
55
55
  copy_default_project_assets(root)
56
- copy_default_apple_extensions(root)
57
56
  project_name = File.basename(root)
58
57
  puts "Run:"
59
58
  puts " cd #{project_name}"
@@ -368,21 +367,6 @@ module Ruflet
368
367
  end
369
368
  end
370
369
 
371
- def copy_default_apple_extensions(root)
372
- template_root = resolve_ruflet_client_template_root || ensure_cached_ruflet_client_template!
373
- source = template_root && File.join(template_root, "apple_extensions")
374
- return unless source && File.file?(File.join(source, "Package.swift"))
375
-
376
- destination = File.join(root, "apple_extensions")
377
- FileUtils.mkdir_p(destination)
378
- generated_entries = %w[.build .swiftpm .dart_tool .claude]
379
- Dir.children(source).each do |entry|
380
- next if generated_entries.include?(entry)
381
-
382
- FileUtils.cp_r(File.join(source, entry), File.join(destination, entry), preserve: true)
383
- end
384
- end
385
-
386
370
  def default_project_assets_root
387
371
  [
388
372
  File.expand_path("../../../assets", __dir__),
@@ -24,8 +24,6 @@ module Ruflet
24
24
  LEGACY_ASSET_PREFIX = "ruflet_client"
25
25
  CLIENT_CHANNEL_MANIFEST = "#{ASSET_PREFIX}-manifest.json"
26
26
  LEGACY_CLIENT_CHANNEL_MANIFEST = "#{LEGACY_ASSET_PREFIX}-manifest.json"
27
- EXPERIMENTAL_IOS_SIMULATOR_ASSET = "#{ASSET_PREFIX}-ios-experimental-simulator.zip"
28
- EXPERIMENTAL_MACOS_ASSET = "#{ASSET_PREFIX}-macos-experimental-universal.zip"
29
27
  DEFAULT_CLIENT_UPDATE_INTERVAL = 6 * 60 * 60
30
28
 
31
29
  def command_run(args)
@@ -39,9 +37,6 @@ module Ruflet
39
37
  return 1
40
38
  end
41
39
 
42
- experimental_client = prepare_experimental_run_client(options)
43
- return 1 if options[:experimental] && !experimental_client
44
-
45
40
  selected_port = resolve_backend_port(options[:target], requested_port: options[:requested_port])
46
41
  return 1 unless selected_port
47
42
  env = {
@@ -75,10 +70,7 @@ module Ruflet
75
70
 
76
71
  run_state = { child_pid: Process.spawn(env, *cmd, pgroup: true), restart: false }
77
72
  reload_input_thread = options[:reload] ? start_reload_input_thread(run_state) : nil
78
- launched_client_pids = launch_target_client(
79
- options[:target], selected_port,
80
- experimental_client: experimental_client
81
- )
73
+ launched_client_pids = launch_target_client(options[:target], selected_port)
82
74
  forward_signal = lambda do |signal|
83
75
  begin
84
76
  Process.kill(signal, -run_state[:child_pid])
@@ -134,71 +126,17 @@ module Ruflet
134
126
  private
135
127
 
136
128
  def parse_run_options(args)
137
- options = { target: "mobile", requested_port: 8550, reload: true, experimental: false }
129
+ options = { target: "mobile", requested_port: 8550, reload: true }
138
130
  parser = OptionParser.new do |o|
139
131
  o.on("--web") { options[:target] = "web" }
140
132
  o.on("--desktop") { options[:target] = "desktop" }
141
133
  o.on("--port PORT", Integer) { |v| options[:requested_port] = v }
142
134
  o.on("--no-reload") { options[:reload] = false }
143
- o.on("--experimental", "--exp") { options[:experimental] = true }
144
135
  end
145
136
  parser.parse!(args)
146
137
  options
147
138
  end
148
139
 
149
- def prepare_experimental_run_client(options)
150
- return nil unless options[:experimental]
151
-
152
- if options[:target] == "web"
153
- warn "run config error: --experimental/--exp is supported only for iOS and macOS"
154
- return nil
155
- end
156
- unless host_platform_name == "macos"
157
- warn "run config error: the experimental Apple client requires macOS"
158
- return nil
159
- end
160
-
161
- if options[:target] == "desktop"
162
- root = ensure_prebuilt_client(desktop_experimental: true, platform: "macos")
163
- unless root
164
- warn "Experimental macOS client is unavailable."
165
- warn "The release must contain #{EXPERIMENTAL_MACOS_ASSET}."
166
- return nil
167
- end
168
-
169
- return { kind: :desktop, root: root }
170
- end
171
-
172
- root = ensure_prebuilt_client(ios_experimental: true, platform: "macos")
173
- unless root
174
- warn "Experimental iOS Simulator client is unavailable."
175
- warn "The release must contain #{EXPERIMENTAL_IOS_SIMULATOR_ASSET}."
176
- return nil
177
- end
178
-
179
- app_bundle = experimental_ios_app_bundle(root)
180
- simulator = booted_ios_simulator
181
- unless simulator
182
- warn "No booted iOS Simulator was found."
183
- warn "Start the simulator you want to use, then run the command again."
184
- return nil
185
- end
186
-
187
- bundle_identifier = ios_bundle_identifier(app_bundle)
188
- if bundle_identifier.to_s.empty?
189
- warn "Experimental client has no CFBundleIdentifier: #{app_bundle}"
190
- return nil
191
- end
192
-
193
- {
194
- kind: :ios_simulator,
195
- app_bundle: app_bundle,
196
- bundle_identifier: bundle_identifier,
197
- simulator_udid: simulator.fetch("udid"),
198
- simulator_name: simulator.fetch("name", simulator.fetch("udid"))
199
- }
200
- end
201
-
202
140
  def build_runtime_command(script_path, gemfile_path:, env:, reload: false)
203
141
  entry_script = script_path
204
142
  if reload
@@ -361,20 +299,9 @@ module Ruflet
361
299
  end
362
300
  end
363
301
 
364
- def launch_target_client(target, port, experimental_client: nil)
302
+ def launch_target_client(target, port)
365
303
  wait_for_server_boot(port)
366
304
 
367
- if experimental_client
368
- if experimental_client[:kind] == :desktop
369
- return launch_desktop_client(
370
- "http://localhost:#{port}",
371
- root: experimental_client.fetch(:root), experimental: true)
372
- end
373
-
374
- return launch_experimental_mobile_client(
375
- "http://#{best_lan_host}:#{port}", client: experimental_client)
376
- end
377
-
378
305
  case target
379
306
  when "web"
380
307
  launch_web_client(port)
@@ -385,33 +312,6 @@ module Ruflet
385
312
  end
386
313
  end
387
314
 
388
- def launch_experimental_mobile_client(url, client:)
389
- udid = client.fetch(:simulator_udid)
390
- app_bundle = client.fetch(:app_bundle)
391
- bundle_identifier = client.fetch(:bundle_identifier)
392
-
393
- unless system("xcrun", "simctl", "install", udid, app_bundle)
394
- warn "Failed to install the experimental client on #{client[:simulator_name] || udid}."
395
- return []
396
- end
397
-
398
- launched = system(
399
- { "SIMCTL_CHILD_RUFLET_URL" => url },
400
- "xcrun", "simctl", "launch", "--terminate-running-process", udid, bundle_identifier
401
- )
402
- unless launched
403
- warn "Failed to launch the experimental client on #{client[:simulator_name] || udid}."
404
- return []
405
- end
406
-
407
- puts "Ruflet experimental client: #{client[:simulator_name] || udid}"
408
- puts "Ruflet experimental backend: #{url}"
409
- []
410
- rescue StandardError => e
411
- warn "Failed to launch experimental client: #{e.class}: #{e.message}"
412
- []
413
- end
414
-
415
315
  # The backend serves the web client on its own port, so the client reads
416
316
  # the origin it was loaded from and opens its websocket there. No URL is
417
317
  # passed in the query string: nothing in the client parses one.
@@ -511,8 +411,8 @@ module Ruflet
511
411
  nil
512
412
  end
513
413
 
514
- def launch_desktop_client(url, root: nil, experimental: false)
515
- cmd = detect_desktop_client_command(url, root: root, experimental: experimental)
414
+ def launch_desktop_client(url, root: nil)
415
+ cmd = detect_desktop_client_command(url, root: root)
516
416
  unless cmd
517
417
  warn "Desktop client executable not found."
518
418
  warn "Set RUFLET_CLIENT_DIR to your client path."
@@ -568,7 +468,7 @@ module Ruflet
568
468
  .find { |path| executable_file?(path) }
569
469
  end
570
470
 
571
- def detect_desktop_client_command(url, root: nil, experimental: false)
471
+ def detect_desktop_client_command(url, root: nil)
572
472
  unless root
573
473
  root = ENV["RUFLET_CLIENT_DIR"]
574
474
  root = File.expand_path("ruflet_client", Dir.pwd) if root.to_s.strip.empty?
@@ -581,7 +481,7 @@ module Ruflet
581
481
  client_build_roots(root).each do |base|
582
482
  if host_os.match?(/darwin/i)
583
483
  search = %w[Release Debug].map { |config| File.join(base, "build", "macos", "Build", "Products", config) }
584
- search << File.join(base, experimental ? "desktop-experimental" : "desktop")
484
+ search << File.join(base, "desktop")
585
485
  search.each do |dir|
586
486
  next unless Dir.exist?(dir)
587
487
 
@@ -636,10 +536,7 @@ module Ruflet
636
536
  nil
637
537
  end
638
538
 
639
- def ensure_prebuilt_client(
640
- web: false, desktop: false, desktop_experimental: false,
641
- ios_experimental: false, platform: nil, force: false
642
- )
539
+ def ensure_prebuilt_client(web: false, desktop: false, platform: nil, force: false)
643
540
  platform ||= host_platform_name
644
541
  return nil if platform.nil?
645
542
 
@@ -653,31 +550,8 @@ module Ruflet
653
550
  return nil if desktop_asset.nil?
654
551
  wanted_assets << { kind: :desktop, name: desktop_asset, platform: platform }
655
552
  end
656
- if desktop_experimental
657
- return nil unless platform == "macos"
658
-
659
- wanted_assets << {
660
- kind: :desktop_experimental,
661
- name: EXPERIMENTAL_MACOS_ASSET,
662
- platform: platform
663
- }
664
- end
665
- if ios_experimental
666
- return nil unless platform == "macos"
667
-
668
- wanted_assets << {
669
- kind: :ios_experimental,
670
- name: EXPERIMENTAL_IOS_SIMULATOR_ASSET,
671
- platform: platform
672
- }
673
- end
674
553
  cache_ready = wanted_assets.empty? || prebuilt_assets_present?(
675
- cache_root,
676
- web: web,
677
- desktop: desktop,
678
- desktop_experimental: desktop_experimental,
679
- ios_experimental: ios_experimental,
680
- platform: platform
554
+ cache_root, web: web, desktop: desktop, platform: platform
681
555
  )
682
556
  release = nil
683
557
  if !force && cache_ready
@@ -718,8 +592,6 @@ module Ruflet
718
592
  subdir = case wanted[:kind]
719
593
  when :web then "web"
720
594
  when :desktop then "desktop"
721
- when :desktop_experimental then "desktop-experimental"
722
- when :ios_experimental then "ios-experimental"
723
595
  end
724
596
  target = File.join(cache_root, subdir)
725
597
  FileUtils.rm_rf(target) if force && Dir.exist?(target)
@@ -739,12 +611,7 @@ module Ruflet
739
611
  end
740
612
 
741
613
  if prebuilt_assets_present?(
742
- cache_root,
743
- web: web,
744
- desktop: desktop,
745
- desktop_experimental: desktop_experimental,
746
- ios_experimental: ios_experimental,
747
- platform: platform
614
+ cache_root, web: web, desktop: desktop, platform: platform
748
615
  )
749
616
  write_client_manifest(cache_root, platform: platform, release: release, assets: installed_assets)
750
617
  return cache_root
@@ -761,61 +628,10 @@ module Ruflet
761
628
  # will actually accept later, or a half-extracted cache reports success
762
629
  # here and then fails to launch -- and, because a present cache short
763
630
  # circuits the download, never repairs itself without --force.
764
- def prebuilt_assets_present?(
765
- root, web:, desktop:, desktop_experimental: false,
766
- ios_experimental: false, platform: nil
767
- )
631
+ def prebuilt_assets_present?(root, web:, desktop:, platform: nil)
768
632
  ok_web = !web || built_web_client_dir?(File.join(root, "web"))
769
633
  ok_desktop = !desktop || prebuilt_desktop_present?(root, platform: platform)
770
- ok_desktop_experimental = !desktop_experimental ||
771
- prebuilt_experimental_desktop_present?(root, platform: platform)
772
- ok_ios_experimental = !ios_experimental || !experimental_ios_app_bundle(root).nil?
773
- ok_web && ok_desktop && ok_desktop_experimental && ok_ios_experimental
774
- end
775
-
776
- def experimental_ios_app_bundle(root)
777
- ios_root = File.join(root, "ios-experimental")
778
- return nil unless Dir.exist?(ios_root)
779
-
780
- Dir.glob(File.join(ios_root, "*.app")).sort.find do |app_bundle|
781
- File.file?(File.join(app_bundle, "Info.plist")) && ios_app_executable(app_bundle)
782
- end
783
- end
784
-
785
- def ios_app_executable(app_bundle)
786
- return nil unless Dir.exist?(app_bundle)
787
-
788
- Dir.children(app_bundle)
789
- .map { |entry| File.join(app_bundle, entry) }
790
- .find { |path| executable_file?(path) }
791
- end
792
-
793
- def booted_ios_simulator
794
- output, status = Open3.capture2e("xcrun", "simctl", "list", "devices", "booted", "--json")
795
- return nil unless status.success?
796
-
797
- devices = JSON.parse(output).fetch("devices", {})
798
- devices.each do |runtime, candidates|
799
- next unless runtime.to_s.include?("SimRuntime.iOS")
800
-
801
- match = Array(candidates).find do |device|
802
- device["state"] == "Booted" && device.fetch("isAvailable", true)
803
- end
804
- return match if match
805
- end
806
- nil
807
- rescue StandardError
808
- nil
809
- end
810
-
811
- def ios_bundle_identifier(app_bundle)
812
- plist = File.join(app_bundle, "Info.plist")
813
- output, status = Open3.capture2e(
814
- "/usr/bin/plutil", "-extract", "CFBundleIdentifier", "raw", "-o", "-", plist
815
- )
816
- status.success? ? output.strip : nil
817
- rescue StandardError
818
- nil
634
+ ok_web && ok_desktop
819
635
  end
820
636
 
821
637
  # The prebuilt client is whatever `ruflet build` produced from Ruflet
@@ -841,17 +657,6 @@ module Ruflet
841
657
  end
842
658
  end
843
659
 
844
- def prebuilt_experimental_desktop_present?(root, platform: nil)
845
- return false unless (platform || host_platform_name) == "macos"
846
-
847
- desktop = File.join(root, "desktop-experimental")
848
- return false unless Dir.exist?(desktop)
849
-
850
- Dir.glob(File.join(desktop, "*.app")).any? do |app_bundle|
851
- macos_app_executable(app_bundle)
852
- end
853
- end
854
-
855
660
  def host_platform_name
856
661
  host_os = RbConfig::CONFIG["host_os"]
857
662
  return "macos" if host_os.match?(/darwin/i)
@@ -887,9 +692,7 @@ module Ruflet
887
692
  end
888
693
 
889
694
  def client_release_channel(wanted_assets: [])
890
- default_channel = wanted_assets.any? do |wanted|
891
- %i[desktop_experimental ios_experimental].include?(wanted[:kind])
892
- end ? "prebuild-experimental" : "prebuild-main"
695
+ default_channel = "prebuild-main"
893
696
  value = ENV.fetch("RUFLET_CLIENT_CHANNEL", default_channel).to_s.strip
894
697
  value.empty? ? default_channel : value
895
698
  end
@@ -982,16 +785,11 @@ module Ruflet
982
785
  return n.include?("web") && (n.end_with?(".tar.gz") || n.end_with?(".zip"))
983
786
  end
984
787
 
985
- if kind == :ios_experimental
986
- return n.include?("ios") && n.include?("experimental") && n.include?("simulator") && n.end_with?(".zip")
987
- end
988
-
989
- if kind == :desktop_experimental
990
- return n.include?("macos") && n.include?("experimental") && n.end_with?(".zip")
991
- end
992
-
993
788
  case platform
994
789
  when "macos"
790
+ # Releases cut before the experimental channel was retired still carry
791
+ # ruflet_explorer-macos-experimental-universal.zip. Installing that as
792
+ # the desktop client would hand back the wrong app, so skip it.
995
793
  n.include?("macos") && !n.include?("experimental") && n.end_with?(".zip")
996
794
  when "linux"
997
795
  n.include?("linux") && (n.end_with?(".tar.gz") || n.end_with?(".tgz"))
@@ -1072,12 +870,6 @@ module Ruflet
1072
870
  if prebuilt_desktop_present?(root, platform: platform)
1073
871
  assets << { "kind" => "desktop", "platform" => platform, "asset_name" => nil }
1074
872
  end
1075
- if prebuilt_experimental_desktop_present?(root, platform: platform)
1076
- assets << { "kind" => "desktop_experimental", "platform" => platform, "asset_name" => nil }
1077
- end
1078
- if experimental_ios_app_bundle(root)
1079
- assets << { "kind" => "ios_experimental", "platform" => platform, "asset_name" => nil }
1080
- end
1081
873
  return if assets.empty?
1082
874
 
1083
875
  write_client_manifest(root, platform: platform, release: nil, assets: assets)
data/lib/ruflet/cli.rb CHANGED
@@ -66,10 +66,10 @@ module Ruflet
66
66
  ruflet --version
67
67
  ruflet create <appname>
68
68
  ruflet new <appname>
69
- ruflet run [scriptname|path] [--web|--desktop] [--experimental|--exp] [--port PORT] [--no-reload]
69
+ ruflet run [scriptname|path] [--web|--desktop] [--port PORT] [--no-reload]
70
70
  ruflet update [web|desktop|all] [--check] [--force] [--platform PLATFORM]
71
71
  ruflet debug [scriptname|path]
72
- ruflet build <apk|android|ios|ipa|aab|web|macos|windows|linux> [--lite|--full|--self] [--experimental|--exp] [--verbose]
72
+ ruflet build <apk|android|ios|ipa|aab|web|macos|windows|linux> [--lite|--full|--self] [--verbose]
73
73
  ruflet install [--device DEVICE_ID] [--verbose]
74
74
  ruflet devices
75
75
  ruflet emulators
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ruflet
4
- VERSION = "0.0.22" unless const_defined?(:VERSION)
4
+ VERSION = "0.0.23" 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.22
4
+ version: 0.0.23
5
5
  platform: ruby
6
6
  authors:
7
7
  - AdamMusa