solara 0.1.0

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 (175) hide show
  1. checksums.yaml +7 -0
  2. data/bin/solara +18 -0
  3. data/solara/lib/.DS_Store +0 -0
  4. data/solara/lib/core/.DS_Store +0 -0
  5. data/solara/lib/core/aliases/alias_generator.rb +128 -0
  6. data/solara/lib/core/aliases/alias_generator_manager.rb +28 -0
  7. data/solara/lib/core/aliases/solara_terminal_setup.rb +103 -0
  8. data/solara/lib/core/brands/brand_onboarder.rb +46 -0
  9. data/solara/lib/core/brands/brand_switcher.rb +204 -0
  10. data/solara/lib/core/brands/brands_manager.rb +154 -0
  11. data/solara/lib/core/dashboard/.DS_Store +0 -0
  12. data/solara/lib/core/dashboard/brand/.DS_Store +0 -0
  13. data/solara/lib/core/dashboard/brand/BrandDetail.js +11 -0
  14. data/solara/lib/core/dashboard/brand/BrandDetailController.js +361 -0
  15. data/solara/lib/core/dashboard/brand/BrandDetailModel.js +155 -0
  16. data/solara/lib/core/dashboard/brand/BrandDetailView.js +245 -0
  17. data/solara/lib/core/dashboard/brand/brand.html +477 -0
  18. data/solara/lib/core/dashboard/brand/source/BrandLocalSource.js +123 -0
  19. data/solara/lib/core/dashboard/brand/source/BrandRemoteSource.js +260 -0
  20. data/solara/lib/core/dashboard/brands/Brands.js +10 -0
  21. data/solara/lib/core/dashboard/brands/BrandsController.js +155 -0
  22. data/solara/lib/core/dashboard/brands/BrandsModel.js +136 -0
  23. data/solara/lib/core/dashboard/brands/BrandsView.js +136 -0
  24. data/solara/lib/core/dashboard/brands/brands.html +345 -0
  25. data/solara/lib/core/dashboard/component/AddFieldSheet.js +212 -0
  26. data/solara/lib/core/dashboard/component/AliasesBottomSheet.js +128 -0
  27. data/solara/lib/core/dashboard/component/BrandOptionsBottomSheet.js +130 -0
  28. data/solara/lib/core/dashboard/component/ConfirmationDialog.js +103 -0
  29. data/solara/lib/core/dashboard/component/MessageBottomSheet.js +80 -0
  30. data/solara/lib/core/dashboard/component/OnboardBrandBottomSheet.js +214 -0
  31. data/solara/lib/core/dashboard/dashboard_manager.rb +19 -0
  32. data/solara/lib/core/dashboard/dashboard_server.rb +132 -0
  33. data/solara/lib/core/dashboard/handler/base_handler.rb +25 -0
  34. data/solara/lib/core/dashboard/handler/brand_alisases_handler.rb +33 -0
  35. data/solara/lib/core/dashboard/handler/brand_configurations_handler.rb +18 -0
  36. data/solara/lib/core/dashboard/handler/brand_configurations_manager.rb +73 -0
  37. data/solara/lib/core/dashboard/handler/brand_icon_handler.rb +20 -0
  38. data/solara/lib/core/dashboard/handler/brand_section_handler.rb +20 -0
  39. data/solara/lib/core/dashboard/handler/brands_handler.rb +14 -0
  40. data/solara/lib/core/dashboard/handler/current_brand_handler.rb +18 -0
  41. data/solara/lib/core/dashboard/handler/doctor_handler.rb +39 -0
  42. data/solara/lib/core/dashboard/handler/edit_section_handler.rb +55 -0
  43. data/solara/lib/core/dashboard/handler/offboard_brand_handler.rb +34 -0
  44. data/solara/lib/core/dashboard/handler/onboard_brand_handler.rb +53 -0
  45. data/solara/lib/core/dashboard/handler/redirect_handler.rb +12 -0
  46. data/solara/lib/core/dashboard/handler/switch_handler.rb +25 -0
  47. data/solara/lib/core/dashboard/index.html +36 -0
  48. data/solara/lib/core/dashboard/local.html +41 -0
  49. data/solara/lib/core/dashboard/res/favicon/android-chrome-192x192.png +0 -0
  50. data/solara/lib/core/dashboard/res/favicon/android-chrome-512x512.png +0 -0
  51. data/solara/lib/core/dashboard/res/favicon/apple-touch-icon.png +0 -0
  52. data/solara/lib/core/dashboard/res/favicon/favicon-16x16.png +0 -0
  53. data/solara/lib/core/dashboard/res/favicon/favicon-32x32.png +0 -0
  54. data/solara/lib/core/dashboard/res/favicon/favicon.ico +0 -0
  55. data/solara/lib/core/dashboard/res/favicon/site.webmanifest +1 -0
  56. data/solara/lib/core/dashboard/solara.png +0 -0
  57. data/solara/lib/core/doctor/brand_doctor.rb +94 -0
  58. data/solara/lib/core/doctor/doctor_manager.rb +35 -0
  59. data/solara/lib/core/doctor/project_doctor.rb +8 -0
  60. data/solara/lib/core/doctor/schema/brand_configurations.json +60 -0
  61. data/solara/lib/core/doctor/schema/platform/android/android_config.json +23 -0
  62. data/solara/lib/core/doctor/schema/platform/android/android_signing.json +23 -0
  63. data/solara/lib/core/doctor/schema/platform/ios/ios_config.json +27 -0
  64. data/solara/lib/core/doctor/schema/platform/ios/ios_signing.json +27 -0
  65. data/solara/lib/core/doctor/schema/platform/shared/theme.json +48 -0
  66. data/solara/lib/core/doctor/validator/brand_settings_validator.rb +55 -0
  67. data/solara/lib/core/doctor/validator/brand_settings_validator_manager.rb +82 -0
  68. data/solara/lib/core/doctor/validator/directory_structure_validator.rb +38 -0
  69. data/solara/lib/core/doctor/validator/file_structure_validator.rb +37 -0
  70. data/solara/lib/core/doctor/validator/json_file_validator.rb +21 -0
  71. data/solara/lib/core/doctor/validator/json_schema_validator.rb +32 -0
  72. data/solara/lib/core/doctor/validator/project_filesystem_validator.rb +70 -0
  73. data/solara/lib/core/doctor/validator/template/android_template_validation_config.yml +51 -0
  74. data/solara/lib/core/doctor/validator/template/flutter_template_validation_config.yml +53 -0
  75. data/solara/lib/core/doctor/validator/template/ios_template_validation_config.yml +51 -0
  76. data/solara/lib/core/doctor/validator/template/template_validator.rb +108 -0
  77. data/solara/lib/core/doctor/validator/validation_strategy.rb +7 -0
  78. data/solara/lib/core/scripts/brand_config_generator.rb +245 -0
  79. data/solara/lib/core/scripts/brand_config_manager.rb +90 -0
  80. data/solara/lib/core/scripts/brand_exporter.rb +38 -0
  81. data/solara/lib/core/scripts/brand_importer.rb +84 -0
  82. data/solara/lib/core/scripts/brand_offboarder.rb +19 -0
  83. data/solara/lib/core/scripts/brand_resources_manager.rb +77 -0
  84. data/solara/lib/core/scripts/directory_creator.rb +22 -0
  85. data/solara/lib/core/scripts/file_manager.rb +90 -0
  86. data/solara/lib/core/scripts/file_path.rb +327 -0
  87. data/solara/lib/core/scripts/folder_copier.rb +41 -0
  88. data/solara/lib/core/scripts/gitignore_manager.rb +54 -0
  89. data/solara/lib/core/scripts/interactive_file_system_validator.rb +110 -0
  90. data/solara/lib/core/scripts/platform/android/android_manifest_switcher.rb +24 -0
  91. data/solara/lib/core/scripts/platform/android/android_strings_switcher.rb +39 -0
  92. data/solara/lib/core/scripts/platform/android/gradle_switcher.rb +233 -0
  93. data/solara/lib/core/scripts/platform/android/properties_generator.rb +31 -0
  94. data/solara/lib/core/scripts/platform/ios/ios_file_path_manager.rb +109 -0
  95. data/solara/lib/core/scripts/platform/ios/ios_plist_manager.rb +42 -0
  96. data/solara/lib/core/scripts/platform/ios/xcconfig_generator.rb +44 -0
  97. data/solara/lib/core/scripts/platform/ios/xcode_asset_manager.rb +56 -0
  98. data/solara/lib/core/scripts/platform/ios/xcode_project_manager.rb +82 -0
  99. data/solara/lib/core/scripts/platform/ios/xcode_project_switcher.rb +130 -0
  100. data/solara/lib/core/scripts/project_settings_manager.rb +39 -0
  101. data/solara/lib/core/scripts/solara_logger.rb +103 -0
  102. data/solara/lib/core/scripts/solara_settings_manager.rb +73 -0
  103. data/solara/lib/core/scripts/solara_status_manager.rb +55 -0
  104. data/solara/lib/core/scripts/solara_version_manager.rb +42 -0
  105. data/solara/lib/core/scripts/strings_xml_manager.rb +22 -0
  106. data/solara/lib/core/scripts/terminal_input_manager.rb +22 -0
  107. data/solara/lib/core/scripts/theme_generator.rb +250 -0
  108. data/solara/lib/core/scripts/yaml_manager.rb +72 -0
  109. data/solara/lib/core/solara_configurator.rb +15 -0
  110. data/solara/lib/core/template/brands/android/android_config.json +8 -0
  111. data/solara/lib/core/template/brands/android/android_signing.json +6 -0
  112. data/solara/lib/core/template/brands/android/res/.DS_Store +0 -0
  113. data/solara/lib/core/template/brands/android/res/mipmap-hdpi/ic_launcher.png +0 -0
  114. data/solara/lib/core/template/brands/android/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  115. data/solara/lib/core/template/brands/android/res/mipmap-mdpi/ic_launcher.png +0 -0
  116. data/solara/lib/core/template/brands/android/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  117. data/solara/lib/core/template/brands/android/res/mipmap-xhdpi/ic_launcher.png +0 -0
  118. data/solara/lib/core/template/brands/android/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  119. data/solara/lib/core/template/brands/android/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  120. data/solara/lib/core/template/brands/android/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  121. data/solara/lib/core/template/brands/android/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  122. data/solara/lib/core/template/brands/android/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  123. data/solara/lib/core/template/brands/brands.json +4 -0
  124. data/solara/lib/core/template/brands/ios/assets/.DS_Store +0 -0
  125. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/100.png +0 -0
  126. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/102.png +0 -0
  127. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/1024.png +0 -0
  128. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/114.png +0 -0
  129. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/120.png +0 -0
  130. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/128.png +0 -0
  131. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/144.png +0 -0
  132. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/152.png +0 -0
  133. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/16.png +0 -0
  134. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/167.png +0 -0
  135. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/172.png +0 -0
  136. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/180.png +0 -0
  137. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/196.png +0 -0
  138. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/20.png +0 -0
  139. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/216.png +0 -0
  140. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/256.png +0 -0
  141. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/29.png +0 -0
  142. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/32.png +0 -0
  143. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/40.png +0 -0
  144. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/48.png +0 -0
  145. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/50.png +0 -0
  146. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/512.png +0 -0
  147. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/55.png +0 -0
  148. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/57.png +0 -0
  149. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/58.png +0 -0
  150. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/60.png +0 -0
  151. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/64.png +0 -0
  152. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/66.png +0 -0
  153. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/72.png +0 -0
  154. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/76.png +0 -0
  155. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/80.png +0 -0
  156. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/87.png +0 -0
  157. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/88.png +0 -0
  158. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/92.png +0 -0
  159. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/Contents.json +1 -0
  160. data/solara/lib/core/template/brands/ios/ios_config.json +7 -0
  161. data/solara/lib/core/template/brands/ios/ios_signing.json +7 -0
  162. data/solara/lib/core/template/brands/shared/.DS_Store +0 -0
  163. data/solara/lib/core/template/brands/shared/brand_config.json +2 -0
  164. data/solara/lib/core/template/brands/shared/theme.json +46 -0
  165. data/solara/lib/core/template/config/android_template_config.json +57 -0
  166. data/solara/lib/core/template/config/flutter_template_config.json +62 -0
  167. data/solara/lib/core/template/config/ios_template_config.json +57 -0
  168. data/solara/lib/core/template/project_template_generator.rb +63 -0
  169. data/solara/lib/platform_detector.rb +84 -0
  170. data/solara/lib/solara/cli.rb +5 -0
  171. data/solara/lib/solara/version.rb +3 -0
  172. data/solara/lib/solara.rb +238 -0
  173. data/solara/lib/solara_initializer.rb +44 -0
  174. data/solara/lib/solara_manager.rb +73 -0
  175. metadata +346 -0
@@ -0,0 +1,62 @@
1
+ {
2
+ "files": [
3
+ {
4
+ "source": "brands.json",
5
+ "target": "../brands.json",
6
+ "condition": "true"
7
+ },
8
+ {
9
+ "source": "android/assets/",
10
+ "target": "android/assets/",
11
+ "condition": "true"
12
+ },
13
+ {
14
+ "source": "android/res/",
15
+ "target": "android/res/",
16
+ "condition": "true"
17
+ },
18
+ {
19
+ "source": "android/android_config.json",
20
+ "target": "android/android_config.json",
21
+ "condition": "true"
22
+ },
23
+ {
24
+ "source": "android/android_signing.json",
25
+ "target": "android/android_signing.json",
26
+ "condition": "true"
27
+ },
28
+ {
29
+ "source": "ios/assets/",
30
+ "target": "ios/assets/",
31
+ "condition": "true"
32
+ },
33
+ {
34
+ "source": "ios/ios_config.json",
35
+ "target": "ios/ios_config.json",
36
+ "condition": "true"
37
+ },
38
+ {
39
+ "source": "ios/ios_signing.json",
40
+ "target": "ios/ios_signing.json",
41
+ "condition": "true"
42
+ },
43
+ {
44
+ "source": "shared/assets/",
45
+ "target": "shared/assets/",
46
+ "condition": "true"
47
+ },
48
+ {
49
+ "source": "shared/theme.json",
50
+ "target": "shared/theme.json",
51
+ "condition": "true"
52
+ },
53
+ {
54
+ "source": "shared/brand_config.json",
55
+ "target": "shared/brand_config.json",
56
+ "condition": "true"
57
+ }
58
+ ],
59
+ "variables": {
60
+ "platform": "flutter"
61
+ }
62
+ }
@@ -0,0 +1,57 @@
1
+ {
2
+ "files": [
3
+ {
4
+ "source": "brands.json",
5
+ "target": "../brands.json",
6
+ "condition": "true"
7
+ },
8
+ {
9
+ "source": "android/assets/",
10
+ "target": "android/assets/",
11
+ "condition": "true"
12
+ },
13
+ {
14
+ "source": "android/res/",
15
+ "target": "android/res/",
16
+ "condition": "true"
17
+ },
18
+ {
19
+ "source": "android/android_config.json",
20
+ "target": "android/android_config.json",
21
+ "condition": "true"
22
+ },
23
+ {
24
+ "source": "android/android_signing.json",
25
+ "target": "android/android_signing.json",
26
+ "condition": "true"
27
+ },
28
+ {
29
+ "source": "ios/assets/",
30
+ "target": "ios/assets/",
31
+ "condition": "true"
32
+ },
33
+ {
34
+ "source": "ios/ios_config.json",
35
+ "target": "ios/ios_config.json",
36
+ "condition": "true"
37
+ },
38
+ {
39
+ "source": "ios/ios_signing.json",
40
+ "target": "ios/ios_signing.json",
41
+ "condition": "true"
42
+ },
43
+ {
44
+ "source": "shared/theme.json",
45
+ "target": "shared/theme.json",
46
+ "condition": "true"
47
+ },
48
+ {
49
+ "source": "shared/brand_config.json",
50
+ "target": "shared/brand_config.json",
51
+ "condition": "true"
52
+ }
53
+ ],
54
+ "variables": {
55
+ "platform": "ios"
56
+ }
57
+ }
@@ -0,0 +1,63 @@
1
+ require 'fileutils'
2
+ require 'json'
3
+
4
+ class ProjectTemplateGenerator
5
+ def initialize(template_dir, target_dir, config_file)
6
+ @template_dir = template_dir
7
+ @target_dir = target_dir
8
+ @config_file = config_file
9
+ @config = read_config
10
+ end
11
+
12
+ def create_project
13
+ @config["files"].each do |file|
14
+ if evaluate_condition(file["condition"], @config["variables"])
15
+ source_path = File.join(@template_dir, file["source"])
16
+ target_path = File.join(@target_dir, file["target"])
17
+
18
+ copy_content = file.fetch("copy_content", true)
19
+ copy_item(source_path, target_path, copy_content)
20
+ replace_variables(target_path, @config["variables"]) if copy_content
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def read_config
28
+ JSON.parse(File.read(@config_file))
29
+ end
30
+
31
+ def evaluate_condition(condition, variables)
32
+ true
33
+ end
34
+
35
+ def copy_item(source, target, copy_content)
36
+ if File.directory?(source)
37
+ FileUtils.mkdir_p(target)
38
+ Dir.foreach(source) do |item|
39
+ next if item == '.' || item == '..'
40
+ copy_item(File.join(source, item), File.join(target, item), copy_content)
41
+ end
42
+ else
43
+ FileUtils.mkdir_p(File.dirname(target))
44
+ if File.exist?(source) && copy_content
45
+ FileUtils.cp(source, target)
46
+ else
47
+ FileUtils.touch(target)
48
+ end
49
+ end
50
+ end
51
+
52
+ def replace_variables(file_path, variables)
53
+ return unless File.exist?(file_path) && File.file?(file_path)
54
+
55
+ content = File.read(file_path)
56
+
57
+ variables.each do |key, value|
58
+ content.gsub!(/\{\{#{key}\}\}/, value.to_s)
59
+ end
60
+
61
+ File.write(file_path, content)
62
+ end
63
+ end
@@ -0,0 +1,84 @@
1
+ require 'find'
2
+
3
+ class PlatformDetector
4
+ def initialize
5
+ @project_path = SolaraSettingsManager.instance.project_root
6
+ end
7
+
8
+ def platform
9
+ Solara.logger.start_step("Detecting current platform")
10
+ detected_platform = detect
11
+
12
+ if detected_platform != Platform::Unknown
13
+ Solara.logger.info("👀 Detected platform: #{detected_platform}")
14
+ else
15
+ Solara.logger.failure("Unable to detect platfrom")
16
+ return enter_correct_platform
17
+ end
18
+
19
+ print "Is this #{detected_platform.upcase} project? (y/n): ".yellow
20
+
21
+ loop do
22
+ response = STDIN.gets.chomp.downcase
23
+ if response == 'y' || response.empty?
24
+ Solara.logger.end_step("Detecting current platform")
25
+ return detected_platform
26
+ elsif response == 'n'
27
+ return enter_correct_platform
28
+ else
29
+ Solara.logger.failure("Invalid input. Please enter 'y' or 'n'.")
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def enter_correct_platform
37
+ loop do
38
+ print "Enter the platform (#{Platform.all.join(', ')}): ".green
39
+ user_input = STDIN.gets.chomp.downcase
40
+ if Platform.all.map(&:downcase).include?(user_input)
41
+ Solara.logger.end_step("Detecting current platform")
42
+ return user_input
43
+ else
44
+ Solara.logger.failure("Invalid platform. Please enter one of: #{Platform.all.join(', ')}")
45
+ end
46
+ end
47
+ end
48
+
49
+ def detect
50
+ if flutter?
51
+ Platform::Flutter
52
+ elsif ios?
53
+ Platform::IOS
54
+ elsif android?
55
+ Platform::Android
56
+ else
57
+ Platform::Unknown
58
+ end
59
+ end
60
+
61
+ def flutter?
62
+ flutter_config_file = File.join(@project_path, 'pubspec.yaml')
63
+ flutter_main_file = File.join(@project_path, 'lib', 'main.dart')
64
+ File.exist?(flutter_config_file) && File.exist?(flutter_main_file)
65
+ end
66
+
67
+ def ios?
68
+ ios_project_files = []
69
+ Find.find(@project_path) do |path|
70
+ if File.directory?(path) && (path.end_with?('.xcodeproj') || path.end_with?('.xcworkspace'))
71
+ ios_project_files << path
72
+ Find.prune # Stop searching further in this directory
73
+ end
74
+ end
75
+ !ios_project_files.empty?
76
+ end
77
+
78
+ def android?
79
+ app_build_gradle = File.join(@project_path, 'app', 'build.gradle')
80
+ app_build_gradle_kts = File.join(@project_path, 'app', 'build.gradle.kts')
81
+ gradle_properties = File.join(@project_path, 'gradle.properties')
82
+ (File.exist?(app_build_gradle) || File.exist?(app_build_gradle_kts)) && File.exist?(gradle_properties)
83
+ end
84
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'solara'
4
+
5
+ Solara::CLI.start(ARGV)
@@ -0,0 +1,3 @@
1
+ module Solara
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,238 @@
1
+ require 'thor'
2
+ require 'fileutils'
3
+ Dir['*.rb'].each { |file| require_relative file }
4
+ Dir.glob("#{__dir__}/core/scripts/*.rb").each { |file| require file }
5
+ require 'solara_manager'
6
+ require 'rubygems'
7
+
8
+ module Solara
9
+ ROOT = Pathname.new(File.expand_path('..', __FILE__))
10
+ PROJECT_ROOT = Dir.pwd
11
+
12
+ class << self
13
+ attr_reader :logger
14
+ attr_reader :verbose
15
+
16
+ def verbose=(value)
17
+ @verbose = value
18
+ @logger.verbose = value
19
+ end
20
+ end
21
+
22
+ @logger = SolaraLogger.new
23
+ @verbose = false
24
+
25
+ class Setup
26
+ def setup
27
+ SolaraSettingsManager.instance.root = ROOT
28
+ SolaraSettingsManager.instance.project_root = PROJECT_ROOT
29
+
30
+ Solara.logger.debug("Solara installation at path: #{PROJECT_ROOT}")
31
+ end
32
+ end
33
+
34
+ class CLI < Thor
35
+ class_option :verbose, type: :boolean, :aliases => "-v", default: false, desc: 'Run verbosely'
36
+
37
+ def initialize(*args)
38
+ super
39
+ setup
40
+ end
41
+
42
+ def self.exit_on_failure?
43
+ true
44
+ end
45
+
46
+ desc "init -p YOUR_PLATFORM -k YOUR_BRAND_KEY -n YOUR_BRAND_NAME",
47
+ "Initialize Solara. Options: #{Platform.all.join(', ')}"
48
+ method_option :brand_key, :type => :string, :aliases => "-k"
49
+ method_option :brand_name, :type => :string, :aliases => "-n"
50
+ method_option :platform, :type => :string, :aliases => "-p"
51
+
52
+ def init
53
+ brand_key = options['brand_key']
54
+ brand_name = options['brand_name']
55
+ input_platform = options['platform']
56
+
57
+ brand_key = validate_brand_key(brand_key, ignore_brand_check: true)
58
+ brand_name = validate_brand_name(brand_name)
59
+
60
+ platform = if !input_platform.nil?
61
+ input_platform
62
+ else
63
+ PlatformDetector.new.platform
64
+ end
65
+
66
+ platform = platform.to_s.downcase
67
+
68
+ SolaraSettingsManager.instance.platform = platform
69
+ SolaraManager.new.init(platform, brand_key, brand_name)
70
+ end
71
+
72
+ desc "status", "Check the current status of Solara. The results may include information about the brand's current standing, the list of brands, and additional details."
73
+
74
+ def status
75
+ check_project_health
76
+ SolaraManager.new.status
77
+ end
78
+
79
+ desc "import -configurations CONFIGURATIONS_JSON_FILE --brand_key YOUR_BRAND_KEY", "Import the brand's configurations. If the brand is existing, it will be updated with these configurations, otherwise, a new brand will be onboarded."
80
+
81
+ method_option :configurations, :type => :array, :aliases => "-c"
82
+ def import
83
+ check_project_health
84
+
85
+ configurations = options['configurations']
86
+
87
+ SolaraManager.new.import(configurations)
88
+ end
89
+
90
+
91
+ desc "export --brand_key YOUR_BRAND_KEY --directory DIRECTORY", "Export the brand's configurations. The brand details will be saved to a JSON file at the specified location."
92
+ method_option :brand_keys, :type => :array, :aliases => "-k"
93
+ method_option :directory, :type => :string, :aliases => "-d"
94
+ def export
95
+ check_project_health
96
+
97
+ brand_keys = options['brand_keys']
98
+ directory = options['directory']
99
+
100
+
101
+ SolaraManager.new.export(brand_keys, directory)
102
+ end
103
+
104
+ desc "onboard -k YOUR_BRAND_KEY -n YOUR_BRAND_NAME", "Onboard a new brand"
105
+ method_option :brand_key, :type => :string, :aliases => "-k"
106
+ method_option :brand_name, :type => :string, :aliases => "-n"
107
+ method_option :clone, :type => :string, :aliases => "-c"
108
+
109
+ def onboard
110
+ check_project_health
111
+
112
+ brand_key = options['brand_key']
113
+ brand_name = options['brand_name']
114
+ clone_brand_key = options['clone']
115
+
116
+ brand_key = validate_brand_key(brand_key, ignore_brand_check: true)
117
+ brand_name = validate_brand_name(brand_name)
118
+
119
+ unless clone_brand_key.nil? || clone_brand_key.empty?
120
+ clone_brand_key = validate_brand_key(clone_brand_key, message: "Clone brand key is not existing, please enter correct key: ")
121
+ end
122
+
123
+ SolaraManager.new.onboard(brand_key, brand_name, clone_brand_key: clone_brand_key)
124
+ end
125
+
126
+ desc "offboard -k YOUR_BRAND_KEY", "Offboard a brand by deleting it from brands."
127
+ method_option :brand_key, :type => :string, :aliases => "-k"
128
+
129
+ def offboard
130
+ check_project_health
131
+ brand_key = options['brand_key']
132
+ brand_key = validate_brand_key(brand_key)
133
+ SolaraManager.new.offboard(brand_key)
134
+ end
135
+
136
+ desc "`switch -k YOUR_BRAND_KEY", "Switch to a brand."
137
+ method_option :brand_key, :type => :string, :aliases => "-k"
138
+
139
+ def switch
140
+ check_project_health
141
+ brand_key = options['brand_key']
142
+ brand_key = validate_brand_key(brand_key)
143
+ begin
144
+ SolaraManager.new.switch(brand_key)
145
+ rescue StandardError => e
146
+ Solara.logger.fatal("Switching to #{brand_key} failed.")
147
+ exit 1
148
+ end
149
+
150
+ end
151
+
152
+ desc "dashboard -k YOUR_OPTIONAL_BRAND_KEY", "Open the dashboard for a brand if brank_key is provided."
153
+ method_option :brand_key, :type => :string, :aliases => "-k"
154
+ method_option :port, :type => :numeric, :aliases => "-p"
155
+
156
+ def dashboard
157
+ check_project_health
158
+ brand_key = options['brand_key']
159
+ brand_key = validate_brand_key(brand_key, ignore_if_nil: true)
160
+ SolaraManager.new.dashboard(brand_key, options['port'] || 8000)
161
+ end
162
+
163
+ desc "doctor -k YOUR_BRAND_KEY", "Visit Doctor for a brand if brankd_key provided."
164
+ method_option :brand_key, :type => :string, :aliases => "-k"
165
+
166
+ def doctor
167
+ check_project_health
168
+ brand_key = options['brand_key']
169
+ brand_key = validate_brand_key(brand_key, ignore_if_nil: true)
170
+ SolaraManager.new.doctor(brand_key)
171
+ end
172
+
173
+ private
174
+
175
+ # Ensure the platform is set when Solara is initialized but the platform is not set
176
+ # This happens in scenarios like cloning the project and running Solara, In this case the
177
+ # platfrom is not set yet.
178
+ def ensure_platform
179
+ brands = FilePath.brands
180
+ platform = SolaraSettingsManager.instance.platform || ''
181
+ if File.exist?(brands) && platform.empty?
182
+ SolaraSettingsManager.instance.platform = PlatformDetector.new.platform
183
+ end
184
+ end
185
+
186
+ def check_project_health
187
+ DoctorManager.new.visit_project!
188
+ end
189
+
190
+ def validate_brand_key(brand_key, ignore_if_nil: false, ignore_brand_check: false, message: nil)
191
+ if ignore_if_nil && brand_key.nil?
192
+ return brand_key
193
+ end
194
+
195
+ if brand_key.nil? || brand_key.empty? || (!ignore_brand_check && BrandsManager.instance.find(brand_key).nil?)
196
+ message = message || (brand_key.nil? || brand_key.empty? ? "Please enter brand key: " : "Please enter existing brand key: ")
197
+ brand_key = TerminalInputManager.new.get_validated(message) do |input|
198
+ # Validate that it starts with a letter and contains no spaces
199
+ unless input.match?(/^[a-zA-Z][\S]*$/)
200
+ Solara.logger.failure("Invalid brand key. It must start with at least a letter and contain no spaces.")
201
+ next false # Use `next` to continue the loop
202
+ end
203
+
204
+ # Check if the brand exists in the list
205
+ if !ignore_brand_check && BrandsManager.instance.find(input).nil?
206
+ Solara.logger.failure("Brand key does not exist.")
207
+ next false # Use `next` to continue the loop
208
+ end
209
+
210
+ true # Valid input and brand exists
211
+ end
212
+ end
213
+
214
+ # If brand_key is valid and exists, return it
215
+ brand_key
216
+ end
217
+
218
+ def validate_brand_name(brand_name)
219
+ if brand_name.nil? || brand_name.empty?
220
+ message = "Please enter brand name: "
221
+ return TerminalInputManager.new.get(message)
222
+ end
223
+ brand_name
224
+ end
225
+
226
+
227
+ def setup
228
+ Solara.logger.verbose = options[:verbose] || false
229
+
230
+ ensure_platform
231
+
232
+ # Solara version is mandatory because the structure and logic may change in the future and need to migrate.
233
+ SolaraVersionManager.instance.version = Gem.loaded_specs['solara'].version.to_s
234
+ end
235
+
236
+ end
237
+
238
+ end
@@ -0,0 +1,44 @@
1
+ Dir.glob("#{__dir__}/core/scripts/*.rb").each { |file| require file }
2
+ Dir.glob("#{__dir__}/core/scripts/*.rb").each { |file| require file }
3
+ Dir.glob("#{__dir__}/core/*.rb").each { |file| require file }
4
+ Dir.glob("#{__dir__}/core/doctor/*.rb").each { |file| require file }
5
+ Dir.glob("#{__dir__}/core/aliases/*.rb").each { |file| require file }
6
+ Dir.glob("#{__dir__}/core/dashboard/*.rb").each { |file| require file }
7
+ Dir.glob("#{__dir__}/core/brands/*.rb").each { |file| require file }
8
+
9
+ require 'json'
10
+ require 'xcodeproj'
11
+
12
+ class SolaraInitializer
13
+
14
+ def initialize(brand_key, brand_name)
15
+ @brand_key = brand_key
16
+ @brand_name = brand_name
17
+ @project_root = SolaraSettingsManager.instance.project_root
18
+ end
19
+
20
+ def init
21
+ Solara.logger.header("Initializing Solara")
22
+ confirm_init_if_necessary
23
+ message = "Initialized #{SolaraSettingsManager.instance.platform} successfully."
24
+ SolaraManager.new.onboard(@brand_key, @brand_name, init: true, success_message: message)
25
+ end
26
+
27
+ def confirm_init_if_necessary
28
+ brand_path = File.join(FilePath.solara, 'brands')
29
+ # Check if Solara path exists
30
+ if Dir.exist?(brand_path)
31
+ Solara.logger.warn("Solara already initialized! Be aware that reinitializing will delete all current brands!")
32
+ Solara.logger.warn("Don't say I didn't warn you!")
33
+ Solara.logger.warn("Do you want to proceed? (y/n)")
34
+ confirmation = STDIN.gets.chomp.downcase
35
+
36
+ unless confirmation == 'y'
37
+ Solara.logger.info("Solara initialization cancelled.")
38
+ exit 1
39
+ end
40
+ FileUtils.rm_rf(brand_path)
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,73 @@
1
+ Dir.glob("#{__dir__}/*.rb").each { |file| require file }
2
+ Dir.glob("#{__dir__}/core/scripts/*.rb").each { |file| require file }
3
+ Dir.glob("#{__dir__}/core/scripts/*.rb").each { |file| require file }
4
+ Dir.glob("#{__dir__}/core/*.rb").each { |file| require file }
5
+ Dir.glob("#{__dir__}/core/doctor/*.rb").each { |file| require file }
6
+ Dir.glob("#{__dir__}/core/aliases/*.rb").each { |file| require file }
7
+ Dir.glob("#{__dir__}/core/dashboard/*.rb").each { |file| require file }
8
+ Dir.glob("#{__dir__}/core/brands/*.rb").each { |file| require file }
9
+
10
+ require 'json'
11
+ require 'xcodeproj'
12
+
13
+ class SolaraManager
14
+
15
+ def initialize
16
+ end
17
+
18
+ def init(platform, brand_key, brand_name)
19
+ SolaraInitializer.new(brand_key, brand_name).init
20
+ end
21
+
22
+ def import(configurations)
23
+ BrandImporter.new.start(configurations)
24
+ end
25
+
26
+ def export(brand_keys, path)
27
+ BrandExporter.new.start(brand_keys, path)
28
+ end
29
+
30
+ def status
31
+ SolaraStatusManager.new.start
32
+ end
33
+
34
+ def onboard(brand_key, brand_name, init: false, clone_brand_key: nil, open_dashboard: true, success_message: nil)
35
+ Solara.logger.header("Onboarding #{brand_key}")
36
+
37
+ if !init && BrandsManager.instance.exists(brand_key)
38
+ Solara.logger.fatal("Brand with key (#{brand_key}) already added to brands!")
39
+ return
40
+ end
41
+
42
+ BrandOnboarder.new(brand_key, brand_name, clone_brand_key: clone_brand_key).onboard
43
+
44
+ switch(brand_key, ignore_health_check: true)
45
+
46
+ clone_message = clone_brand_key.nil? || clone_brand_key.empty? ? '.' : ", cloned from #{clone_brand_key}."
47
+ message = success_message || "Onboarded #{brand_key} successfully#{clone_message}"
48
+ Solara.logger.success(message)
49
+
50
+ if open_dashboard
51
+ Solara.logger.success("Openning the dashboard for #{brand_key} to complete its details.")
52
+ dashboard(brand_key)
53
+ end
54
+ end
55
+
56
+ def offboard(brand_key, confirm: true)
57
+ BrandOffboarder.new.offboard(brand_key, confirm: confirm)
58
+ end
59
+
60
+ def switch(brand_key, ignore_health_check: false)
61
+ BrandSwitcher.new(brand_key, ignore_health_check: ignore_health_check).start
62
+ end
63
+
64
+ def dashboard(brand_key, port = 8000)
65
+ DashboardManager.new.start(brand_key, port)
66
+ end
67
+
68
+ def doctor(brand_key = nil, print_logs: true)
69
+ keys = brand_key.nil? || brand_key.empty? ? [] : [brand_key]
70
+ DoctorManager.new.visit_brands(keys, print_logs: print_logs)
71
+ end
72
+
73
+ end