ruflet 0.0.8 → 0.0.9

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.
Files changed (216) hide show
  1. checksums.yaml +4 -4
  2. data/bin/ruflet +6 -0
  3. data/lib/ruflet/cli/build_command.rb +372 -0
  4. data/lib/ruflet/cli/extra_command.rb +146 -0
  5. data/lib/ruflet/cli/flutter_sdk.rb +359 -0
  6. data/lib/ruflet/cli/new_command.rb +221 -0
  7. data/lib/ruflet/cli/run_command.rb +699 -0
  8. data/lib/ruflet/cli/templates.rb +68 -0
  9. data/lib/ruflet/cli/update_command.rb +111 -0
  10. data/lib/ruflet/cli.rb +85 -0
  11. data/lib/ruflet/version.rb +1 -1
  12. data/lib/ruflet_cli.rb +3 -0
  13. metadata +59 -209
  14. data/lib/ruflet.rb +0 -40
  15. data/lib/ruflet_protocol/ruflet/protocol.rb +0 -62
  16. data/lib/ruflet_protocol.rb +0 -4
  17. data/lib/ruflet_ui/ruflet/app.rb +0 -27
  18. data/lib/ruflet_ui/ruflet/colors.rb +0 -234
  19. data/lib/ruflet_ui/ruflet/control.rb +0 -281
  20. data/lib/ruflet_ui/ruflet/dsl.rb +0 -291
  21. data/lib/ruflet_ui/ruflet/event.rb +0 -42
  22. data/lib/ruflet_ui/ruflet/events/gesture_events.rb +0 -552
  23. data/lib/ruflet_ui/ruflet/icon_data.rb +0 -62
  24. data/lib/ruflet_ui/ruflet/icons/cupertino/cupertino_icons.rb +0 -54
  25. data/lib/ruflet_ui/ruflet/icons/cupertino_icon_lookup.rb +0 -112
  26. data/lib/ruflet_ui/ruflet/icons/material_icon_lookup.rb +0 -112
  27. data/lib/ruflet_ui/ruflet/icons/material_icons.rb +0 -55
  28. data/lib/ruflet_ui/ruflet/page.rb +0 -1222
  29. data/lib/ruflet_ui/ruflet/types/geometry.rb +0 -65
  30. data/lib/ruflet_ui/ruflet/types/text_style.rb +0 -191
  31. data/lib/ruflet_ui/ruflet/ui/control_factory.rb +0 -53
  32. data/lib/ruflet_ui/ruflet/ui/control_methods.rb +0 -17
  33. data/lib/ruflet_ui/ruflet/ui/control_registry.rb +0 -33
  34. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoactionsheet_control.rb +0 -57
  35. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoactionsheetaction_control.rb +0 -58
  36. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoactivityindicator_control.rb +0 -57
  37. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoalertdialog_control.rb +0 -39
  38. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoappbar_control.rb +0 -44
  39. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinobottomsheet_control.rb +0 -38
  40. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinobutton_control.rb +0 -73
  41. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinocheckbox_control.rb +0 -70
  42. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinocontextmenu_control.rb +0 -34
  43. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinocontextmenuaction_control.rb +0 -36
  44. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinodatepicker_control.rb +0 -67
  45. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinodialogaction_control.rb +0 -35
  46. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinofilledbutton_control.rb +0 -73
  47. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinolisttile_control.rb +0 -67
  48. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinonavigationbar_control.rb +0 -61
  49. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinopicker_control.rb +0 -66
  50. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoradio_control.rb +0 -66
  51. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinosegmentedbutton_control.rb +0 -63
  52. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoslider_control.rb +0 -64
  53. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoslidingsegmentedbutton_control.rb +0 -60
  54. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinoswitch_control.rb +0 -73
  55. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinotextfield_control.rb +0 -159
  56. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinotimerpicker_control.rb +0 -61
  57. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/cupertinotintedbutton_control.rb +0 -73
  58. data/lib/ruflet_ui/ruflet/ui/controls/cupertinos/ruflet_controls.rb +0 -89
  59. data/lib/ruflet_ui/ruflet/ui/controls/materials/alertdialog_control.rb +0 -58
  60. data/lib/ruflet_ui/ruflet/ui/controls/materials/appbar_control.rb +0 -53
  61. data/lib/ruflet_ui/ruflet/ui/controls/materials/audio_control.rb +0 -36
  62. data/lib/ruflet_ui/ruflet/ui/controls/materials/autocomplete_control.rb +0 -58
  63. data/lib/ruflet_ui/ruflet/ui/controls/materials/badge_control.rb +0 -31
  64. data/lib/ruflet_ui/ruflet/ui/controls/materials/banner_control.rb +0 -47
  65. data/lib/ruflet_ui/ruflet/ui/controls/materials/bottomappbar_control.rb +0 -62
  66. data/lib/ruflet_ui/ruflet/ui/controls/materials/bottomsheet_control.rb +0 -48
  67. data/lib/ruflet_ui/ruflet/ui/controls/materials/button_control.rb +0 -69
  68. data/lib/ruflet_ui/ruflet/ui/controls/materials/card_control.rb +0 -63
  69. data/lib/ruflet_ui/ruflet/ui/controls/materials/chart_controls.rb +0 -321
  70. data/lib/ruflet_ui/ruflet/ui/controls/materials/checkbox_control.rb +0 -76
  71. data/lib/ruflet_ui/ruflet/ui/controls/materials/chip_control.rb +0 -88
  72. data/lib/ruflet_ui/ruflet/ui/controls/materials/circleavatar_control.rb +0 -62
  73. data/lib/ruflet_ui/ruflet/ui/controls/materials/container_control.rb +0 -81
  74. data/lib/ruflet_ui/ruflet/ui/controls/materials/contextmenu_control.rb +0 -63
  75. data/lib/ruflet_ui/ruflet/ui/controls/materials/datacell_control.rb +0 -38
  76. data/lib/ruflet_ui/ruflet/ui/controls/materials/datacolumn_control.rb +0 -34
  77. data/lib/ruflet_ui/ruflet/ui/controls/materials/datarow_control.rb +0 -35
  78. data/lib/ruflet_ui/ruflet/ui/controls/materials/datatable_control.rb +0 -78
  79. data/lib/ruflet_ui/ruflet/ui/controls/materials/datepicker_control.rb +0 -55
  80. data/lib/ruflet_ui/ruflet/ui/controls/materials/daterangepicker_control.rb +0 -57
  81. data/lib/ruflet_ui/ruflet/ui/controls/materials/divider_control.rb +0 -36
  82. data/lib/ruflet_ui/ruflet/ui/controls/materials/dropdown_control.rb +0 -99
  83. data/lib/ruflet_ui/ruflet/ui/controls/materials/dropdownm2_control.rb +0 -119
  84. data/lib/ruflet_ui/ruflet/ui/controls/materials/dropdownoption_control.rb +0 -35
  85. data/lib/ruflet_ui/ruflet/ui/controls/materials/expansionpanel_control.rb +0 -61
  86. data/lib/ruflet_ui/ruflet/ui/controls/materials/expansionpanellist_control.rb +0 -60
  87. data/lib/ruflet_ui/ruflet/ui/controls/materials/expansiontile_control.rb +0 -82
  88. data/lib/ruflet_ui/ruflet/ui/controls/materials/filledbutton_control.rb +0 -69
  89. data/lib/ruflet_ui/ruflet/ui/controls/materials/fillediconbutton_control.rb +0 -81
  90. data/lib/ruflet_ui/ruflet/ui/controls/materials/filledtonalbutton_control.rb +0 -69
  91. data/lib/ruflet_ui/ruflet/ui/controls/materials/filledtonaliconbutton_control.rb +0 -81
  92. data/lib/ruflet_ui/ruflet/ui/controls/materials/floatingactionbutton_control.rb +0 -73
  93. data/lib/ruflet_ui/ruflet/ui/controls/materials/iconbutton_control.rb +0 -81
  94. data/lib/ruflet_ui/ruflet/ui/controls/materials/listtile_control.rb +0 -87
  95. data/lib/ruflet_ui/ruflet/ui/controls/materials/menubar_control.rb +0 -33
  96. data/lib/ruflet_ui/ruflet/ui/controls/materials/menuitembutton_control.rb +0 -67
  97. data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationbar_control.rb +0 -67
  98. data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationbardestination_control.rb +0 -35
  99. data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationdrawer_control.rb +0 -41
  100. data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationdrawerdestination_control.rb +0 -34
  101. data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationrail_control.rb +0 -70
  102. data/lib/ruflet_ui/ruflet/ui/controls/materials/navigationraildestination_control.rb +0 -36
  103. data/lib/ruflet_ui/ruflet/ui/controls/materials/option_control.rb +0 -35
  104. data/lib/ruflet_ui/ruflet/ui/controls/materials/outlinedbutton_control.rb +0 -66
  105. data/lib/ruflet_ui/ruflet/ui/controls/materials/outlinediconbutton_control.rb +0 -81
  106. data/lib/ruflet_ui/ruflet/ui/controls/materials/popupmenubutton_control.rb +0 -74
  107. data/lib/ruflet_ui/ruflet/ui/controls/materials/popupmenuitem_control.rb +0 -38
  108. data/lib/ruflet_ui/ruflet/ui/controls/materials/progressbar_control.rb +0 -64
  109. data/lib/ruflet_ui/ruflet/ui/controls/materials/progressring_control.rb +0 -65
  110. data/lib/ruflet_ui/ruflet/ui/controls/materials/radio_control.rb +0 -70
  111. data/lib/ruflet_ui/ruflet/ui/controls/materials/radiogroup_control.rb +0 -33
  112. data/lib/ruflet_ui/ruflet/ui/controls/materials/rangeslider_control.rb +0 -67
  113. data/lib/ruflet_ui/ruflet/ui/controls/materials/reorderablelistview_control.rb +0 -80
  114. data/lib/ruflet_ui/ruflet/ui/controls/materials/ruflet_controls.rb +0 -252
  115. data/lib/ruflet_ui/ruflet/ui/controls/materials/searchbar_control.rb +0 -94
  116. data/lib/ruflet_ui/ruflet/ui/controls/materials/segment_control.rb +0 -33
  117. data/lib/ruflet_ui/ruflet/ui/controls/materials/segmentedbutton_control.rb +0 -63
  118. data/lib/ruflet_ui/ruflet/ui/controls/materials/selectionarea_control.rb +0 -32
  119. data/lib/ruflet_ui/ruflet/ui/controls/materials/slider_control.rb +0 -76
  120. data/lib/ruflet_ui/ruflet/ui/controls/materials/snackbar_control.rb +0 -51
  121. data/lib/ruflet_ui/ruflet/ui/controls/materials/submenubutton_control.rb +0 -66
  122. data/lib/ruflet_ui/ruflet/ui/controls/materials/switch_control.rb +0 -77
  123. data/lib/ruflet_ui/ruflet/ui/controls/materials/tab_control.rb +0 -35
  124. data/lib/ruflet_ui/ruflet/ui/controls/materials/tabbar_control.rb +0 -77
  125. data/lib/ruflet_ui/ruflet/ui/controls/materials/tabbarview_control.rb +0 -57
  126. data/lib/ruflet_ui/ruflet/ui/controls/materials/tabs_control.rb +0 -59
  127. data/lib/ruflet_ui/ruflet/ui/controls/materials/textbutton_control.rb +0 -66
  128. data/lib/ruflet_ui/ruflet/ui/controls/materials/textfield_control.rb +0 -148
  129. data/lib/ruflet_ui/ruflet/ui/controls/materials/timepicker_control.rb +0 -50
  130. data/lib/ruflet_ui/ruflet/ui/controls/materials/verticaldivider_control.rb +0 -36
  131. data/lib/ruflet_ui/ruflet/ui/controls/materials/video_control.rb +0 -39
  132. data/lib/ruflet_ui/ruflet/ui/controls/materials/webview_control.rb +0 -35
  133. data/lib/ruflet_ui/ruflet/ui/controls/ruflet_controls.rb +0 -447
  134. data/lib/ruflet_ui/ruflet/ui/controls/shared/animatedswitcher_control.rb +0 -59
  135. data/lib/ruflet_ui/ruflet/ui/controls/shared/arc_control.rb +0 -29
  136. data/lib/ruflet_ui/ruflet/ui/controls/shared/autofillgroup_control.rb +0 -32
  137. data/lib/ruflet_ui/ruflet/ui/controls/shared/basepage_control.rb +0 -44
  138. data/lib/ruflet_ui/ruflet/ui/controls/shared/browsercontextmenu_control.rb +0 -21
  139. data/lib/ruflet_ui/ruflet/ui/controls/shared/canvas_control.rb +0 -57
  140. data/lib/ruflet_ui/ruflet/ui/controls/shared/circle_control.rb +0 -25
  141. data/lib/ruflet_ui/ruflet/ui/controls/shared/color_control.rb +0 -23
  142. data/lib/ruflet_ui/ruflet/ui/controls/shared/column_control.rb +0 -67
  143. data/lib/ruflet_ui/ruflet/ui/controls/shared/dialogs_control.rb +0 -22
  144. data/lib/ruflet_ui/ruflet/ui/controls/shared/dismissible_control.rb +0 -66
  145. data/lib/ruflet_ui/ruflet/ui/controls/shared/draggable_control.rb +0 -39
  146. data/lib/ruflet_ui/ruflet/ui/controls/shared/dragtarget_control.rb +0 -36
  147. data/lib/ruflet_ui/ruflet/ui/controls/shared/fill_control.rb +0 -22
  148. data/lib/ruflet_ui/ruflet/ui/controls/shared/fletapp_control.rb +0 -62
  149. data/lib/ruflet_ui/ruflet/ui/controls/shared/gesturedetector_control.rb +0 -129
  150. data/lib/ruflet_ui/ruflet/ui/controls/shared/gridview_control.rb +0 -71
  151. data/lib/ruflet_ui/ruflet/ui/controls/shared/hero_control.rb +0 -56
  152. data/lib/ruflet_ui/ruflet/ui/controls/shared/icon_control.rb +0 -64
  153. data/lib/ruflet_ui/ruflet/ui/controls/shared/image_control.rb +0 -27
  154. data/lib/ruflet_ui/ruflet/ui/controls/shared/interactiveviewer_control.rb +0 -69
  155. data/lib/ruflet_ui/ruflet/ui/controls/shared/keyboardlistener_control.rb +0 -36
  156. data/lib/ruflet_ui/ruflet/ui/controls/shared/line_control.rb +0 -26
  157. data/lib/ruflet_ui/ruflet/ui/controls/shared/listview_control.rb +0 -71
  158. data/lib/ruflet_ui/ruflet/ui/controls/shared/markdown_control.rb +0 -70
  159. data/lib/ruflet_ui/ruflet/ui/controls/shared/mergesemantics_control.rb +0 -31
  160. data/lib/ruflet_ui/ruflet/ui/controls/shared/oval_control.rb +0 -26
  161. data/lib/ruflet_ui/ruflet/ui/controls/shared/overlay_control.rb +0 -22
  162. data/lib/ruflet_ui/ruflet/ui/controls/shared/page_control.rb +0 -74
  163. data/lib/ruflet_ui/ruflet/ui/controls/shared/pagelet_control.rb +0 -64
  164. data/lib/ruflet_ui/ruflet/ui/controls/shared/pageview_control.rb +0 -64
  165. data/lib/ruflet_ui/ruflet/ui/controls/shared/path_control.rb +0 -23
  166. data/lib/ruflet_ui/ruflet/ui/controls/shared/placeholder_control.rb +0 -58
  167. data/lib/ruflet_ui/ruflet/ui/controls/shared/points_control.rb +0 -24
  168. data/lib/ruflet_ui/ruflet/ui/controls/shared/rect_control.rb +0 -27
  169. data/lib/ruflet_ui/ruflet/ui/controls/shared/reorderabledraghandle_control.rb +0 -56
  170. data/lib/ruflet_ui/ruflet/ui/controls/shared/responsiverow_control.rb +0 -61
  171. data/lib/ruflet_ui/ruflet/ui/controls/shared/row_control.rb +0 -67
  172. data/lib/ruflet_ui/ruflet/ui/controls/shared/ruflet_controls.rb +0 -140
  173. data/lib/ruflet_ui/ruflet/ui/controls/shared/safearea_control.rb +0 -61
  174. data/lib/ruflet_ui/ruflet/ui/controls/shared/semantics_control.rb +0 -79
  175. data/lib/ruflet_ui/ruflet/ui/controls/shared/serviceregistry_control.rb +0 -21
  176. data/lib/ruflet_ui/ruflet/ui/controls/shared/shadermask_control.rb +0 -57
  177. data/lib/ruflet_ui/ruflet/ui/controls/shared/shadow_control.rb +0 -25
  178. data/lib/ruflet_ui/ruflet/ui/controls/shared/shimmer_control.rb +0 -60
  179. data/lib/ruflet_ui/ruflet/ui/controls/shared/stack_control.rb +0 -58
  180. data/lib/ruflet_ui/ruflet/ui/controls/shared/text_control.rb +0 -32
  181. data/lib/ruflet_ui/ruflet/ui/controls/shared/textspan_control.rb +0 -39
  182. data/lib/ruflet_ui/ruflet/ui/controls/shared/transparentpointer_control.rb +0 -54
  183. data/lib/ruflet_ui/ruflet/ui/controls/shared/view_control.rb +0 -77
  184. data/lib/ruflet_ui/ruflet/ui/controls/shared/window_control.rb +0 -56
  185. data/lib/ruflet_ui/ruflet/ui/controls/shared/windowdragarea_control.rb +0 -58
  186. data/lib/ruflet_ui/ruflet/ui/cupertino_control_factory.rb +0 -13
  187. data/lib/ruflet_ui/ruflet/ui/cupertino_control_methods.rb +0 -26
  188. data/lib/ruflet_ui/ruflet/ui/cupertino_control_registry.rb +0 -49
  189. data/lib/ruflet_ui/ruflet/ui/material_control_factory.rb +0 -13
  190. data/lib/ruflet_ui/ruflet/ui/material_control_methods.rb +0 -170
  191. data/lib/ruflet_ui/ruflet/ui/material_control_registry.rb +0 -168
  192. data/lib/ruflet_ui/ruflet/ui/services/ruflet/accelerometer_control.rb +0 -26
  193. data/lib/ruflet_ui/ruflet/ui/services/ruflet/barometer_control.rb +0 -26
  194. data/lib/ruflet_ui/ruflet/ui/services/ruflet/battery_control.rb +0 -22
  195. data/lib/ruflet_ui/ruflet/ui/services/ruflet/camera_control.rb +0 -58
  196. data/lib/ruflet_ui/ruflet/ui/services/ruflet/clipboard_control.rb +0 -21
  197. data/lib/ruflet_ui/ruflet/ui/services/ruflet/connectivity_control.rb +0 -22
  198. data/lib/ruflet_ui/ruflet/ui/services/ruflet/filepicker_control.rb +0 -23
  199. data/lib/ruflet_ui/ruflet/ui/services/ruflet/flashlight_control.rb +0 -22
  200. data/lib/ruflet_ui/ruflet/ui/services/ruflet/gyroscope_control.rb +0 -26
  201. data/lib/ruflet_ui/ruflet/ui/services/ruflet/hapticfeedback_control.rb +0 -21
  202. data/lib/ruflet_ui/ruflet/ui/services/ruflet/magnetometer_control.rb +0 -26
  203. data/lib/ruflet_ui/ruflet/ui/services/ruflet/screenbrightness_control.rb +0 -23
  204. data/lib/ruflet_ui/ruflet/ui/services/ruflet/screenshot_control.rb +0 -31
  205. data/lib/ruflet_ui/ruflet/ui/services/ruflet/semanticsservice_control.rb +0 -21
  206. data/lib/ruflet_ui/ruflet/ui/services/ruflet/shakedetector_control.rb +0 -26
  207. data/lib/ruflet_ui/ruflet/ui/services/ruflet/share_control.rb +0 -21
  208. data/lib/ruflet_ui/ruflet/ui/services/ruflet/sharedpreferences_control.rb +0 -21
  209. data/lib/ruflet_ui/ruflet/ui/services/ruflet/storagepaths_control.rb +0 -21
  210. data/lib/ruflet_ui/ruflet/ui/services/ruflet/urllauncher_control.rb +0 -21
  211. data/lib/ruflet_ui/ruflet/ui/services/ruflet/useraccelerometer_control.rb +0 -26
  212. data/lib/ruflet_ui/ruflet/ui/services/ruflet/wakelock_control.rb +0 -21
  213. data/lib/ruflet_ui/ruflet/ui/services/ruflet_services.rb +0 -66
  214. data/lib/ruflet_ui/ruflet/ui/shared_control_forwarders.rb +0 -129
  215. data/lib/ruflet_ui/ruflet/ui/widget_builder.rb +0 -64
  216. data/lib/ruflet_ui.rb +0 -124
@@ -0,0 +1,359 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+ require "net/http"
6
+ require "open3"
7
+ require "rbconfig"
8
+ require "tmpdir"
9
+ require "uri"
10
+ require "yaml"
11
+
12
+ module Ruflet
13
+ module CLI
14
+ module FlutterSdk
15
+ RELEASES_BASE = "https://storage.googleapis.com/flutter_infra_release/releases".freeze
16
+ DEFAULT_FLUTTER_CHANNEL = "stable".freeze
17
+
18
+ def ensure_flutter!(command_name, client_dir: nil, auto_install: true)
19
+ tools = flutter_tools(client_dir: client_dir, auto_install: auto_install)
20
+ return tools if tools
21
+
22
+ warn "Flutter is required for `ruflet #{command_name}` and FVM bootstrap failed."
23
+ warn "Set RUFLET_FLUTTER_VERSION or add .fvmrc to the project."
24
+ exit 1
25
+ end
26
+
27
+ def flutter_version_summary(tools)
28
+ flutter_bin = tools[:flutter]
29
+ env = tools[:env] || {}
30
+
31
+ machine_output, status = Open3.capture2e(env, flutter_bin, "--version", "--machine")
32
+ if status.success?
33
+ parsed = JSON.parse(machine_output) rescue nil
34
+ version = parsed && parsed["frameworkVersion"].to_s.strip
35
+ channel = parsed && parsed["channel"].to_s.strip
36
+ return [version, channel].reject(&:empty?).join(" ") unless version.to_s.empty?
37
+ end
38
+
39
+ text_output, text_status = Open3.capture2e(env, flutter_bin, "--version")
40
+ return text_output.lines.first.to_s.strip if text_status.success?
41
+
42
+ File.basename(flutter_bin.to_s)
43
+ rescue StandardError
44
+ File.basename(flutter_bin.to_s)
45
+ end
46
+
47
+ private
48
+
49
+ def flutter_tools(client_dir: nil, auto_install: true)
50
+ # Always use FVM so Flutter/Dart match pinned SDK.
51
+ fvm_tools = flutter_tools_via_fvm(client_dir: client_dir, auto_install: auto_install)
52
+ return fvm_tools if fvm_tools
53
+
54
+ nil
55
+ end
56
+
57
+ def flutter_tools_via_fvm(client_dir: nil, auto_install: true)
58
+ version = desired_flutter_version(client_dir: client_dir)
59
+ return nil if version.to_s.strip.empty?
60
+
61
+ project_dir = fvm_project_dir(client_dir: client_dir)
62
+ flutter = existing_fvm_flutter_bin(project_dir)
63
+ return tools_from_flutter_bin(flutter) if flutter
64
+
65
+ return nil unless auto_install
66
+
67
+ fvm = ensure_fvm_available(client_dir: client_dir)
68
+ return nil unless fvm
69
+ FileUtils.mkdir_p(project_dir)
70
+ fvmrc_path = File.join(project_dir, ".fvmrc")
71
+ unless File.file?(fvmrc_path)
72
+ File.write(fvmrc_path, "{\"flutter\":\"#{version}\"}\n")
73
+ end
74
+
75
+ system(fvm_env, fvm, "install", version.to_s, chdir: project_dir, out: File::NULL, err: File::NULL)
76
+ system(fvm_env, fvm, "use", "--force", version.to_s, chdir: project_dir, out: File::NULL, err: File::NULL)
77
+
78
+ flutter = File.join(project_dir, ".fvm", "flutter_sdk", "bin", windows_host? ? "flutter.bat" : "flutter")
79
+ return nil unless File.executable?(flutter)
80
+
81
+ tools_from_flutter_bin(flutter)
82
+ rescue StandardError => e
83
+ warn "FVM bootstrap failed: #{e.class}: #{e.message}"
84
+ nil
85
+ end
86
+
87
+ def ensure_fvm_available(client_dir: nil)
88
+ fvm = which_command("fvm")
89
+ return fvm if fvm
90
+
91
+ dart = which_command("dart")
92
+ unless dart
93
+ sdk_root = ensure_flutter_sdk_downloaded(client_dir: client_dir)
94
+ dart = sdk_root ? File.join(sdk_root, "bin", windows_host? ? "dart.bat" : "dart") : nil
95
+ end
96
+ return nil unless dart && File.executable?(dart)
97
+
98
+ system(dart, "pub", "global", "activate", "fvm", out: File::NULL, err: File::NULL)
99
+ which_command("fvm")
100
+ end
101
+
102
+ def fvm_env
103
+ pub_bin = File.join(Dir.home, ".pub-cache", "bin")
104
+ { "PATH" => "#{pub_bin}#{File::PATH_SEPARATOR}#{ENV.fetch('PATH', '')}" }
105
+ end
106
+
107
+ def tools_from_flutter_bin(flutter_bin)
108
+ return nil unless File.executable?(flutter_bin)
109
+
110
+ bin_dir = File.dirname(flutter_bin)
111
+ dart = File.join(bin_dir, windows_host? ? "dart.bat" : "dart")
112
+ {
113
+ flutter: flutter_bin,
114
+ dart: (File.executable?(dart) ? dart : "dart"),
115
+ env: { "PATH" => "#{bin_dir}#{File::PATH_SEPARATOR}#{ENV.fetch('PATH', '')}" }
116
+ }
117
+ end
118
+
119
+ def ensure_flutter_sdk_downloaded(client_dir: nil)
120
+ release_info = resolve_flutter_release(client_dir: client_dir)
121
+ return nil unless release_info
122
+
123
+ release = release_info[:release]
124
+ host = release_info[:host]
125
+ archive = release.fetch("archive")
126
+ install_root = File.join(Dir.home, ".ruflet", "flutter", release.fetch("version"), host)
127
+ sdk_root = File.join(install_root, "flutter")
128
+ flutter_bin = File.join(sdk_root, "bin", windows_host? ? "flutter.bat" : "flutter")
129
+ return sdk_root if File.executable?(flutter_bin)
130
+
131
+ FileUtils.mkdir_p(install_root)
132
+ Dir.mktmpdir("ruflet-flutter-sdk-") do |tmpdir|
133
+ archive_path = File.join(tmpdir, File.basename(archive))
134
+ download_file("#{RELEASES_BASE}/#{archive}", archive_path)
135
+ extract_archive(archive_path, install_root)
136
+ end
137
+
138
+ return sdk_root if File.executable?(flutter_bin)
139
+
140
+ # Some archives may unpack into a different folder name.
141
+ guessed = Dir.glob(File.join(install_root, "**", windows_host? ? "flutter.bat" : "flutter"))
142
+ .map { |p| File.expand_path("../..", p) }
143
+ .find { |root| File.executable?(File.join(root, "bin", windows_host? ? "flutter.bat" : "flutter")) }
144
+ return guessed if guessed
145
+
146
+ nil
147
+ rescue StandardError => e
148
+ warn "Flutter auto-install failed: #{e.class}: #{e.message}"
149
+ nil
150
+ end
151
+
152
+ def resolve_flutter_release(client_dir: nil)
153
+ host = flutter_host
154
+ return nil unless host
155
+
156
+ manifest = fetch_releases_manifest(host)
157
+ return nil unless manifest
158
+
159
+ desired = desired_flutter_spec(client_dir: client_dir)
160
+ release = pick_release(
161
+ manifest,
162
+ version: desired[:version],
163
+ revision: desired[:revision],
164
+ channel: desired[:channel]
165
+ )
166
+ return nil unless release
167
+
168
+ { release: release, host: host }
169
+ end
170
+
171
+ def desired_flutter_version(client_dir: nil)
172
+ desired_flutter_spec(client_dir: client_dir)[:version] || DEFAULT_FLUTTER_CHANNEL
173
+ end
174
+
175
+ def desired_flutter_spec(client_dir: nil)
176
+ env = ENV["RUFLET_FLUTTER_VERSION"].to_s.strip
177
+ return { version: env, source: :env } unless env.empty?
178
+
179
+ fvm = parse_fvmrc(find_fvmrc(client_dir))
180
+ return { version: fvm, source: :fvmrc } if fvm
181
+
182
+ metadata = parse_flutter_metadata(find_flutter_metadata(client_dir))
183
+ return metadata.merge(source: :metadata) if metadata
184
+
185
+ { channel: DEFAULT_FLUTTER_CHANNEL, version: DEFAULT_FLUTTER_CHANNEL, source: :default }
186
+ end
187
+
188
+ def fvm_project_dir(client_dir: nil)
189
+ return client_dir if client_dir
190
+
191
+ cwd_fvmrc = find_fvmrc(nil)
192
+ return File.dirname(cwd_fvmrc) if cwd_fvmrc
193
+
194
+ File.join(Dir.home, ".ruflet", "fvm_project")
195
+ end
196
+
197
+ def existing_fvm_flutter_bin(project_dir)
198
+ flutter = File.join(project_dir, ".fvm", "flutter_sdk", "bin", windows_host? ? "flutter.bat" : "flutter")
199
+ return flutter if File.executable?(flutter)
200
+
201
+ nil
202
+ end
203
+
204
+ def find_fvmrc(client_dir)
205
+ candidates = []
206
+ candidates << File.join(client_dir, ".fvmrc") if client_dir
207
+ candidates << File.join(Dir.pwd, ".fvmrc")
208
+ candidates.find { |p| File.file?(p) }
209
+ end
210
+
211
+ def find_flutter_metadata(client_dir)
212
+ candidates = []
213
+ candidates << File.join(client_dir, ".metadata") if client_dir
214
+ repo_client = File.expand_path("../../../../../ruflet_client/.metadata", __dir__)
215
+ template_client = File.expand_path("../../../../../templates/ruflet_flutter_template/.metadata", __dir__)
216
+ candidates << repo_client
217
+ candidates << template_client
218
+ candidates.find { |path| File.file?(path) }
219
+ end
220
+
221
+ def parse_fvmrc(path)
222
+ return nil unless path && File.file?(path)
223
+
224
+ raw = File.read(path).strip
225
+ return nil if raw.empty?
226
+
227
+ if raw.start_with?("{")
228
+ json = JSON.parse(raw) rescue {}
229
+ val = json["flutter"] || json["flutterSdkVersion"] || json["flutter_version"]
230
+ return val.to_s.strip unless val.to_s.strip.empty?
231
+ end
232
+
233
+ raw
234
+ end
235
+
236
+ def parse_flutter_metadata(path)
237
+ return nil unless path && File.file?(path)
238
+
239
+ parsed = YAML.safe_load(File.read(path), aliases: true) || {}
240
+ version_info = parsed["version"]
241
+ return nil unless version_info.is_a?(Hash)
242
+
243
+ revision = version_info["revision"].to_s.strip
244
+ channel = version_info["channel"].to_s.strip
245
+ return nil if revision.empty?
246
+
247
+ {
248
+ revision: revision,
249
+ channel: channel.empty? ? DEFAULT_FLUTTER_CHANNEL : channel
250
+ }
251
+ rescue StandardError
252
+ nil
253
+ end
254
+
255
+ def fetch_releases_manifest(host)
256
+ url = "#{RELEASES_BASE}/releases_#{host}.json"
257
+ uri = URI(url)
258
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
259
+ req = Net::HTTP::Get.new(uri)
260
+ req["User-Agent"] = "ruflet-cli"
261
+ http.request(req)
262
+ end
263
+ return JSON.parse(response.body) if response.is_a?(Net::HTTPSuccess)
264
+
265
+ nil
266
+ end
267
+
268
+ def pick_release(manifest, version: nil, revision: nil, channel: nil)
269
+ releases = manifest.fetch("releases", [])
270
+ if revision
271
+ pinned = releases.find { |r| r["hash"] == revision }
272
+ return pinned if pinned
273
+ warn "Requested Flutter revision #{revision} not found for host #{flutter_host}; falling back."
274
+ end
275
+
276
+ if version
277
+ pinned = releases.find { |r| r["channel"] == "stable" && r["version"] == version }
278
+ return pinned if pinned
279
+ warn "Requested Flutter #{version} not found in stable releases; falling back to latest stable."
280
+ end
281
+
282
+ current = manifest.fetch("current_release", {})[(channel || "stable")]
283
+ if current
284
+ by_hash = releases.find { |r| r["hash"] == current }
285
+ return by_hash if by_hash
286
+ end
287
+
288
+ releases.reverse.find { |r| r["channel"] == (channel || "stable") } ||
289
+ releases.reverse.find { |r| r["channel"] == "stable" }
290
+ end
291
+
292
+ def flutter_host
293
+ os = RbConfig::CONFIG["host_os"]
294
+ if os.match?(/darwin/i)
295
+ return machine_arch.include?("arm") ? "macos_arm64" : "macos"
296
+ end
297
+ return "linux" if os.match?(/linux/i)
298
+ return "windows" if os.match?(/mswin|mingw|cygwin/i)
299
+
300
+ nil
301
+ end
302
+
303
+ def machine_arch
304
+ RbConfig::CONFIG["host_cpu"].to_s.downcase
305
+ end
306
+
307
+ def windows_host?
308
+ RbConfig::CONFIG["host_os"].match?(/mswin|mingw|cygwin/i)
309
+ end
310
+
311
+ def which_command(name)
312
+ exts = windows_host? ? ENV.fetch("PATHEXT", ".EXE;.BAT;.CMD").split(";") : [""]
313
+ ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).each do |dir|
314
+ exts.each do |ext|
315
+ candidate = File.join(dir, "#{name}#{ext}")
316
+ return candidate if File.file?(candidate) && File.executable?(candidate)
317
+ end
318
+ end
319
+ nil
320
+ end
321
+
322
+ def download_file(url, destination, limit: 5)
323
+ raise "Too many redirects while downloading #{url}" if limit <= 0
324
+
325
+ uri = URI(url)
326
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
327
+ req = Net::HTTP::Get.new(uri)
328
+ req["User-Agent"] = "ruflet-cli"
329
+ http.request(req) do |res|
330
+ case res
331
+ when Net::HTTPSuccess
332
+ File.open(destination, "wb") { |f| res.read_body { |chunk| f.write(chunk) } }
333
+ return destination
334
+ when Net::HTTPRedirection
335
+ return download_file(res["location"], destination, limit: limit - 1)
336
+ else
337
+ raise "Download failed (#{res.code})"
338
+ end
339
+ end
340
+ end
341
+ end
342
+
343
+ def extract_archive(archive, destination)
344
+ if archive.end_with?(".zip")
345
+ if windows_host?
346
+ return system("powershell", "-NoProfile", "-Command", "Expand-Archive -Path '#{archive}' -DestinationPath '#{destination}' -Force")
347
+ end
348
+ return system("unzip", "-oq", archive, "-d", destination)
349
+ end
350
+
351
+ if archive.end_with?(".tar.xz") || archive.end_with?(".tar.gz") || archive.end_with?(".tgz")
352
+ return system("tar", "-xf", archive, "-C", destination)
353
+ end
354
+
355
+ false
356
+ end
357
+ end
358
+ end
359
+ end
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "yaml"
5
+
6
+ module Ruflet
7
+ module CLI
8
+ module NewCommand
9
+ CLIENT_EXTENSION_MAP = {
10
+ "ads" => { package: "flet_ads", alias: "ruflet_ads" },
11
+ "audio" => { package: "flet_audio", alias: "ruflet_audio" },
12
+ "audio_recorder" => { package: "flet_audio_recorder", alias: "ruflet_audio_recorder" },
13
+ "camera" => { package: "flet_camera", alias: "ruflet_camera" },
14
+ "charts" => { package: "flet_charts", alias: "ruflet_charts" },
15
+ "code_editor" => { package: "flet_code_editor", alias: "ruflet_code_editor" },
16
+ "color_pickers" => { package: "flet_color_pickers", alias: "ruflet_color_picker" },
17
+ "datatable2" => { package: "flet_datatable2", alias: "ruflet_datatable2" },
18
+ "flashlight" => { package: "flet_flashlight", alias: "ruflet_flashlight" },
19
+ "geolocator" => { package: "flet_geolocator", alias: "ruflet_geolocator" },
20
+ "lottie" => { package: "flet_lottie", alias: "ruflet_lottie" },
21
+ "map" => { package: "flet_map", alias: "ruflet_map" },
22
+ "permission_handler" => { package: "flet_permission_handler", alias: "ruflet_permission_handler" },
23
+ "secure_storage" => { package: "flet_secure_storage", alias: "ruflet_secure_storage" },
24
+ "video" => { package: "flet_video", alias: "ruflet_video" },
25
+ "webview" => { package: "flet_webview", alias: "ruflet_webview" }
26
+ }.freeze
27
+
28
+ def command_new(args)
29
+ app_name = args.shift
30
+ if app_name.nil? || app_name.strip.empty?
31
+ warn "Usage: ruflet new <appname>"
32
+ return 1
33
+ end
34
+
35
+ root = File.expand_path(app_name, Dir.pwd)
36
+ if Dir.exist?(root)
37
+ warn "Directory already exists: #{root}"
38
+ return 1
39
+ end
40
+
41
+ FileUtils.mkdir_p(root)
42
+ File.write(File.join(root, "main.rb"), format(Ruflet::CLI::MAIN_TEMPLATE, app_title: humanize_name(File.basename(root))))
43
+ File.write(File.join(root, "Gemfile"), Ruflet::CLI::GEMFILE_TEMPLATE)
44
+ File.write(File.join(root, "README.md"), format(Ruflet::CLI::README_TEMPLATE, app_name: File.basename(root)))
45
+ write_default_ruflet_config(root, File.basename(root))
46
+ copy_ruflet_client_template(root)
47
+ configure_ruflet_client(root)
48
+
49
+ project_name = File.basename(root)
50
+ puts "Run:"
51
+ puts " cd #{project_name}"
52
+ puts " bundle install"
53
+ puts " bundle exec ruflet run main.rb"
54
+ puts
55
+ puts "Client template:"
56
+ puts " cd ruflet_client"
57
+ puts " flutter pub get"
58
+ puts " flutter run"
59
+ 0
60
+ end
61
+
62
+ private
63
+
64
+ def copy_ruflet_client_template(root)
65
+ template_root = File.expand_path("../../../../../ruflet_client", __dir__)
66
+ return unless Dir.exist?(template_root)
67
+
68
+ target = File.join(root, "ruflet_client")
69
+ FileUtils.cp_r(template_root, target)
70
+ prune_client_template(target)
71
+ end
72
+
73
+ def prune_client_template(target)
74
+ paths = %w[
75
+ .dart_tool
76
+ .idea
77
+ build
78
+ ios/Pods
79
+ ios/.symlinks
80
+ ios/Podfile.lock
81
+ macos/Pods
82
+ macos/Podfile.lock
83
+ android/.gradle
84
+ android/.kotlin
85
+ android/local.properties
86
+ ]
87
+ paths.each do |path|
88
+ full = File.join(target, path)
89
+ FileUtils.rm_rf(full) if File.exist?(full)
90
+ end
91
+ end
92
+
93
+ def write_default_ruflet_config(root, app_name)
94
+ File.write(File.join(root, "ruflet.yaml"), <<~YAML)
95
+ app:
96
+ name: #{app_name}
97
+ # Optional production client endpoint used by `ruflet build`.
98
+ # Example: https://api.example.com
99
+ ruflet_client_url: ""
100
+
101
+ # Source of truth for Flutter client extensions/plugins.
102
+ # Examples: camera, video, audio, flashlight, webview, map
103
+ services: []
104
+
105
+ # Build assets configuration consumed by `ruflet build`.
106
+ # Paths are relative to this file unless absolute.
107
+ assets:
108
+ dir: assets
109
+ splash_screen: assets/splash.png
110
+ icon_launcher: assets/icon.png
111
+
112
+ build:
113
+ splash_color: "#FFFFFF"
114
+ splash_dark_color: "#0B0B0B"
115
+ icon_background: "#FFFFFF"
116
+ theme_color: "#FFFFFF"
117
+ YAML
118
+ end
119
+
120
+ def configure_ruflet_client(root)
121
+ config_path = File.join(root, "ruflet.yaml")
122
+ return unless File.file?(config_path)
123
+
124
+ config = YAML.safe_load(File.read(config_path), aliases: true) || {}
125
+ extension_keys = extract_extension_keys(config)
126
+ extension_packages = extension_keys.filter_map { |key| CLIENT_EXTENSION_MAP[key]&.fetch(:package) }.uniq
127
+ extension_aliases = extension_keys.filter_map { |key| CLIENT_EXTENSION_MAP[key]&.fetch(:alias) }.uniq
128
+
129
+ client_dir = File.join(root, "ruflet_client")
130
+ apply_client_manifest!(client_dir, extension_packages, extension_aliases)
131
+ rescue StandardError => e
132
+ warn "Failed to configure ruflet_client from ruflet.yaml: #{e.class}: #{e.message}"
133
+ end
134
+
135
+ def extract_extension_keys(config)
136
+ from_services = Array(config["services"])
137
+
138
+ from_services
139
+ .map { |v| normalize_extension_key(v) }
140
+ .compact
141
+ .uniq
142
+ end
143
+
144
+ def normalize_extension_key(value)
145
+ key = value.to_s.strip.downcase
146
+ return nil if key.empty?
147
+
148
+ key.tr!("-", "_")
149
+ key.gsub!(/\A(flet_)+/, "")
150
+ key.gsub!(/\Aservice_/, "")
151
+ key.gsub!(/\Acontrol_/, "")
152
+ key = "file_picker" if key == "filepicker"
153
+ key
154
+ end
155
+
156
+ def apply_client_manifest!(client_dir, extension_packages, extension_aliases)
157
+ return unless Dir.exist?(client_dir)
158
+
159
+ pubspec_path = File.join(client_dir, "pubspec.yaml")
160
+ main_path = File.join(client_dir, "lib", "main.dart")
161
+ prune_client_pubspec(pubspec_path, extension_packages) if File.file?(pubspec_path)
162
+ prune_client_main(main_path, extension_aliases) if File.file?(main_path)
163
+ end
164
+
165
+ def prune_client_pubspec(path, selected_packages)
166
+ data = YAML.safe_load(File.read(path), aliases: true) || {}
167
+ deps = (data["dependencies"] || {}).dup
168
+
169
+ deps.keys.each do |name|
170
+ next unless name.start_with?("flet_")
171
+ next if name == "flet"
172
+ next if selected_packages.include?(name)
173
+
174
+ deps.delete(name)
175
+ end
176
+
177
+ data["dependencies"] = deps
178
+ File.write(path, YAML.dump(data))
179
+ end
180
+
181
+ def prune_client_main(path, selected_aliases)
182
+ lines = File.readlines(path)
183
+ alias_to_package = {}
184
+
185
+ lines.each do |line|
186
+ match = line.match(%r{\Aimport 'package:(flet_[^/]+)/\1\.dart' as ([a-zA-Z0-9_]+);})
187
+ next unless match
188
+
189
+ alias_to_package[match[2]] = match[1]
190
+ end
191
+
192
+ kept = lines.select do |line|
193
+ import_match = line.match(%r{\Aimport 'package:(flet_[^/]+)/\1\.dart' as ([a-zA-Z0-9_]+);})
194
+ if import_match
195
+ package_name = import_match[1]
196
+ next true if package_name == "flet"
197
+ next true if selected_aliases.include?(import_match[2])
198
+ next false
199
+ end
200
+
201
+ extension_match = line.match(/\A\s*([a-zA-Z0-9_]+)\.Extension\(\),\s*\z/)
202
+ if extension_match
203
+ extension_alias = extension_match[1]
204
+ package_name = alias_to_package[extension_alias]
205
+ next true if package_name.nil? # non-Flet extension lines
206
+ next true if selected_aliases.include?(extension_alias)
207
+ next false
208
+ end
209
+
210
+ true
211
+ end
212
+
213
+ File.write(path, kept.join)
214
+ end
215
+
216
+ def humanize_name(name)
217
+ name.to_s.gsub(/[_-]+/, " ").split.map(&:capitalize).join(" ")
218
+ end
219
+ end
220
+ end
221
+ end