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,82 @@
1
+ class XcodeProjectManager
2
+ def initialize
3
+ end
4
+
5
+ def add_files_to_group(project, group, directory_path)
6
+ Dir.foreach(directory_path) do |entry|
7
+ next if entry == '.' || entry == '..' || entry == '.DS_Store'
8
+
9
+ file_path = File.join(directory_path, entry)
10
+ if File.directory?(file_path)
11
+ Solara.logger.debug("Processing directory: #{file_path}")
12
+ subgroup = group.groups.find { |g| g.name == entry } || group.new_group(entry)
13
+ add_files_to_group(project, subgroup, file_path)
14
+ else
15
+ add_single_file_to_group(project, group, file_path)
16
+ end
17
+ end
18
+ end
19
+
20
+ def add_single_file_to_group(project, group, file_path)
21
+ Solara.logger.debug("Adding new file: #{file_path}")
22
+ file_name = File.basename(file_path, File.extname(file_path))
23
+
24
+ existing_file = group.files.find { |f| File.basename(f.path, File.extname(f.path))&.downcase == file_name.downcase }
25
+ existing_group = group.groups.find { |g| g.name&.downcase == file_name.downcase }
26
+ existing_reference = existing_file || existing_group
27
+
28
+ if existing_reference
29
+ if existing_reference.is_a?(Xcodeproj::Project::Object::PBXFileReference)
30
+ Solara.logger.debug("Ignoring file: #{file_path} (Existing file reference: #{existing_reference.path})")
31
+ else
32
+ Solara.logger.debug("Ignoring file: #{file_path} (Existing group with same name: #{existing_reference.name})")
33
+ end
34
+ return nil
35
+ else
36
+ file_reference = group.new_file(file_path)
37
+ Solara.logger.debug("Adding new file: #{file_path} (File reference: #{file_reference.uuid})")
38
+
39
+ # Add the file to the project's main target if it's a source file
40
+ if %w[.swift .m .mm .c .cpp].include?(File.extname(file_path).downcase)
41
+ target = project.targets.first
42
+ target.add_file_references([file_reference])
43
+ Solara.logger.debug(" Added to target: #{target.name}")
44
+ end
45
+
46
+ file_reference
47
+ end
48
+ end
49
+ def self.set_base_xcconfigs(project, xcconfigs)
50
+ # Iterate through all build configurations in the target
51
+ project.targets.first.build_configurations.each do |config|
52
+ # Find the matching XCConfig from the array
53
+ matching_xcconfig = xcconfigs.find { |xc| config.name.downcase.include?(xc[:type].downcase) }
54
+
55
+ # If no matching XCConfig found, use debug as base
56
+ matching_xcconfig ||= xcconfigs.find { |xc| xc[:type].downcase == 'debug' }
57
+
58
+ if matching_xcconfig
59
+ set_xcconfig(project, config, matching_xcconfig)
60
+ else
61
+ Solara.logger.warn("Warning: No matching XCConfig found for #{config.name} configuration, and no Debug XCConfig available")
62
+ end
63
+ end
64
+
65
+ Solara.logger.debug("Base XCConfig files have been set for all build configurations.")
66
+ end
67
+
68
+ def self.set_xcconfig(project, config, xcconfig)
69
+ # Find the existing file reference or create a new one if it doesn't exist
70
+ file_reference = project.reference_for_path(xcconfig[:path])
71
+ if file_reference
72
+ config.base_configuration_reference = file_reference
73
+ Solara.logger.debug("Set #{xcconfig[:type]} XCConfig for #{config.name} configuration")
74
+ else
75
+ # If the file reference doesn't exist, create a new one
76
+ new_file_reference = project.new_file(xcconfig[:path])
77
+ config.base_configuration_reference = new_file_reference
78
+ Solara.logger.debug("Created and set new #{xcconfig[:type]} XCConfig for #{config.name} configuration")
79
+ end
80
+ end
81
+
82
+ end
@@ -0,0 +1,130 @@
1
+ $LOAD_PATH.unshift(File.expand_path(__dir__))
2
+
3
+ class XcodeProjectSwitcher
4
+ def initialize(project_path, brand_key)
5
+ @project_path = project_path
6
+ @brand_key = brand_key
7
+ @project = Xcodeproj::Project.open(@project_path)
8
+ @target = @project.targets.first
9
+ @platform = SolaraSettingsManager.instance.platform
10
+ end
11
+
12
+ def switch
13
+ update_xcode_target_settings
14
+ update_info_plist
15
+ add_artifacts_group
16
+ update_debug_and_release_xcconfig
17
+ @project.save
18
+ Solara.logger.debug("Updated #{@project_path} to use the values of #{IOSFilePathManager.instance.brand_xcconfig}")
19
+ end
20
+
21
+ private
22
+
23
+ def update_xcode_target_settings
24
+ @target.build_configurations.each do |config|
25
+ config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = '$(PRODUCT_BUNDLE_IDENTIFIER)'
26
+ config.build_settings['PRODUCT_NAME'] = '$(PRODUCT_NAME)'
27
+ config.build_settings['MARKETING_VERSION'] = '$(MARKETING_VERSION)'
28
+
29
+ # We need to apply code signing only if the user has provided its config
30
+ path = FilePath.brand_signing(@brand_key, Platform::IOS)
31
+ signing = JSON.parse(File.read(path))
32
+ unless signing['PROVISIONING_PROFILE_SPECIFIER'].empty?
33
+ config.build_settings['CODE_SIGN_IDENTITY'] = '$(CODE_SIGN_IDENTITY)'
34
+ config.build_settings['DEVELOPMENT_TEAM'] = '$(DEVELOPMENT_TEAM)'
35
+ config.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = '$(PROVISIONING_PROFILE_SPECIFIER)'
36
+ config.build_settings['CODE_SIGN_STYLE'] = '$(CODE_SIGN_STYLE)'
37
+ config.build_settings['CODE_SIGN_ENTITLEMENTS'] = '$(CODE_SIGN_ENTITLEMENTS)'
38
+ end
39
+
40
+ # We need to apply APL_MRCH_ID only if the user has provided its config
41
+ path = FilePath.ios_config(@brand_key)
42
+ ios_config = JSON.parse(File.read(path))
43
+ merchant_id = ios_config['APL_MRCH_ID']
44
+ unless merchant_id.nil? || merchant_id.empty?
45
+ config.build_settings['APL_MRCH_ID'] = '$(APL_MRCH_ID)'
46
+ end
47
+ end
48
+ end
49
+
50
+ def update_info_plist
51
+ info_plist_path = IOSFilePathManager.instance.info_plist
52
+ manager = IOSPlistManager.new(@project, info_plist_path)
53
+ manager.create_and_add_info_plist
54
+
55
+ xcconfig_values = {
56
+ 'PRODUCT_NAME' => "$(PRODUCT_NAME)",
57
+ 'PRODUCT_BUNDLE_IDENTIFIER' => "$(PRODUCT_BUNDLE_IDENTIFIER)",
58
+ 'MARKETING_VERSION' => "$(MARKETING_VERSION)",
59
+ 'BUNDLE_VERSION' => "$(BUNDLE_VERSION)"
60
+ }
61
+
62
+ info_plist = Xcodeproj::Plist.read_from_path(info_plist_path)
63
+ info_plist['CFBundleDisplayName'] = xcconfig_values['PRODUCT_NAME']
64
+ info_plist['CFBundleIdentifier'] = xcconfig_values['PRODUCT_BUNDLE_IDENTIFIER']
65
+ info_plist['CFBundleShortVersionString'] = xcconfig_values['MARKETING_VERSION']
66
+ info_plist['CFBundleVersion'] = xcconfig_values['BUNDLE_VERSION']
67
+
68
+ Xcodeproj::Plist.write_to_path(info_plist, info_plist_path)
69
+ end
70
+
71
+ def update_debug_and_release_xcconfig
72
+ debug_xcconfig_path = IOSFilePathManager.instance.app_xcconfig('Debug.xcconfig')
73
+ release_xcconfig_path = IOSFilePathManager.instance.app_xcconfig('Release.xcconfig')
74
+
75
+ create_xcconfigs_and_add_to_project(debug_xcconfig_path, release_xcconfig_path)
76
+ set_base_xcconfigs(debug_xcconfig_path, release_xcconfig_path)
77
+ end
78
+
79
+ def create_xcconfigs_and_add_to_project(debug_xcconfig_path, release_xcconfig_path)
80
+ FileManager.create_files_if_not_exist([debug_xcconfig_path, release_xcconfig_path])
81
+ base_xcconfig_name = 'Artifacts/Brand.xcconfig'
82
+
83
+ case @platform
84
+ when Platform::Flutter
85
+ # Nothing
86
+ when Platform::IOS
87
+ group_name = 'XCConfig'
88
+ config_group = @project.groups.find { |group| group.name == group_name }
89
+ config_group = @project.new_group(group_name) if config_group.nil?
90
+ add_files_to_group(config_group, IOSFilePathManager.instance.app_xcconfig_directory)
91
+ else
92
+ raise ArgumentError, "Invalid platform: #{@platform}"
93
+ end
94
+
95
+ File.open(debug_xcconfig_path, "w") { |file| file.write("#include \"#{base_xcconfig_name}\"\n") }
96
+ File.open(release_xcconfig_path, "w") { |file| file.write("#include \"#{base_xcconfig_name}\"\n") }
97
+ end
98
+
99
+ def set_base_xcconfigs(debug_xcconfig_path, release_xcconfig_path)
100
+ xcconfigs = [
101
+ { type: 'Debug', path: debug_xcconfig_path },
102
+ { type: 'Release', path: release_xcconfig_path },
103
+ ]
104
+ XcodeProjectManager.set_base_xcconfigs(
105
+ @project,
106
+ xcconfigs
107
+ )
108
+ end
109
+
110
+ def add_artifacts_group
111
+ case @platform
112
+ when Platform::Flutter
113
+ flutter_group = @project.groups.find { |group| group.name == 'Flutter' }
114
+ artifacts_group = flutter_group.groups.find { |group| group.name == 'Artifacts' }
115
+ artifacts_group = flutter_group.new_group("Artifacts") if artifacts_group.nil?
116
+ add_files_to_group(artifacts_group, IOSFilePathManager.instance.artifacts)
117
+ when Platform::IOS
118
+ artifacts_group = @project.groups.find { |group| group.name == 'Artifacts' }
119
+ artifacts_group = @project.new_group("Artifacts") if artifacts_group.nil?
120
+ add_files_to_group(artifacts_group, IOSFilePathManager.instance.artifacts)
121
+ else
122
+ raise ArgumentError, "Invalid platform: #{@platform}"
123
+ end
124
+ end
125
+
126
+ def add_files_to_group(group, directory_path)
127
+ XcodeProjectManager.new.add_files_to_group(@project, group, directory_path)
128
+ end
129
+
130
+ end
@@ -0,0 +1,39 @@
1
+ require 'singleton'
2
+
3
+ require 'singleton'
4
+ require 'json'
5
+
6
+ require 'singleton'
7
+ require 'json'
8
+
9
+ class ProjectSettingsManager
10
+ include Singleton
11
+
12
+ attr_accessor :root, :project_root
13
+
14
+ def value(key, platform)
15
+ settings.dig(platform.to_s, key)
16
+ end
17
+
18
+ def add(key, value, platform)
19
+ json = settings
20
+ json[platform.to_s] ||= {}
21
+ json[platform.to_s][key] = value
22
+ save(json)
23
+ end
24
+
25
+ private
26
+
27
+ def settings
28
+ path = FilePath.project_settings
29
+ FileManager.create_file_if_not_exist(path)
30
+ JSON.parse(File.read(path))
31
+ rescue JSON::ParserError => e
32
+ {}
33
+ end
34
+
35
+ def save(json)
36
+ path = FilePath.project_settings
37
+ File.write(path, JSON.pretty_generate(json))
38
+ end
39
+ end
@@ -0,0 +1,103 @@
1
+ $LOAD_PATH.unshift(File.expand_path(__dir__))
2
+
3
+ require 'singleton'
4
+
5
+ require 'logger'
6
+ require 'colorize'
7
+
8
+ class SolaraLogger
9
+ LEVELS = %i[debug info warn error fatal]
10
+ EMOJIS = {
11
+ debug: "",
12
+ info: "",
13
+ warn: "⚠️",
14
+ error: "❌ ",
15
+ fatal: "💀"
16
+ }
17
+
18
+ def initialize
19
+ @logger = Logger.new(STDOUT)
20
+ # Set the default to INFO until we override it later.
21
+ @logger.level = Logger::INFO
22
+ @verbose = false
23
+ @step_count = 0
24
+ set_default_format
25
+ end
26
+
27
+ def verbose=(value)
28
+ @verbose = value
29
+ @logger.level = @verbose ? Logger::DEBUG : Logger::INFO
30
+ end
31
+
32
+ LEVELS.each do |level|
33
+ define_method(level) do |message|
34
+ emoji = EMOJIS[level]
35
+ @logger.send(level, colorize("#{emoji.empty? ? '' : emoji + "\s"}#{message}", level))
36
+ end
37
+ end
38
+
39
+ def start_step(message)
40
+ return unless @verbose
41
+ @step_count += 1
42
+ @logger.debug("Step #{@step_count}: #{message}".green)
43
+ line
44
+ end
45
+
46
+ def title(message)
47
+ @logger.info(message.green)
48
+ line
49
+ end
50
+
51
+ def end_step(message = "Step completed")
52
+ return unless @verbose
53
+ @logger.debug("FINISHED Step #{@step_count}: #{message}".green)
54
+ line
55
+ info("") # Empty line for better readability
56
+ end
57
+
58
+ def line(char: '-', length: 50)
59
+ message = char * length
60
+ @logger.info(message.green)
61
+ end
62
+
63
+ def header(message)
64
+ return unless @verbose
65
+
66
+ line(char: '=')
67
+ @logger.info(message.upcase.green)
68
+ line(char: '=')
69
+ info("") # Empty line for better readability
70
+ end
71
+
72
+ def success(message)
73
+ @logger.info("🎉 #{message.green}")
74
+ end
75
+
76
+ def failure(message)
77
+ error(message)
78
+ end
79
+
80
+ def passed(message)
81
+ @logger.debug("✅ #{message.green}")
82
+ end
83
+
84
+
85
+ private
86
+
87
+ def set_default_format
88
+ @logger.formatter = proc do |severity, datetime, progname, msg|
89
+ "#{msg}\n"
90
+ end
91
+ end
92
+
93
+ def colorize(message, level)
94
+ case level
95
+ when :debug then message
96
+ when :info then message
97
+ when :warn then message.yellow
98
+ when :error then message.red
99
+ when :fatal then message.red.bold
100
+ else message
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,73 @@
1
+ require 'singleton'
2
+
3
+ require 'singleton'
4
+ require 'json'
5
+
6
+ class SolaraSettingsManager
7
+ include Singleton
8
+
9
+ attr_accessor :root, :project_root
10
+
11
+ def platform
12
+ settings['platform']
13
+ end
14
+
15
+ def platform=(new_platform)
16
+ json = settings
17
+ json['platform'] = new_platform
18
+ save(json)
19
+ end
20
+
21
+ def value(key)
22
+ settings[key]
23
+ end
24
+
25
+ def add(key, value)
26
+ json = settings
27
+ json[key] = value
28
+ save(json)
29
+ end
30
+
31
+ private
32
+
33
+ def settings
34
+ path = FilePath.solara_settings
35
+ FileManager.create_file_if_not_exist(path)
36
+ JSON.parse(File.read(path))
37
+ rescue JSON::ParserError => e
38
+ {}
39
+ end
40
+
41
+ def save(json)
42
+ File.write(FilePath.solara_settings, JSON.pretty_generate(json))
43
+ end
44
+ end
45
+
46
+ module Platform
47
+ Flutter = 'flutter'
48
+ Android = 'android'
49
+ IOS = 'ios'
50
+ Unknown = 'unknown'
51
+
52
+ def self.all
53
+ [Flutter, Android, IOS]
54
+ end
55
+
56
+ def self.is_ios
57
+ SolaraSettingsManager.instance.platform.downcase == IOS.downcase
58
+ end
59
+
60
+ def self.is_android
61
+ SolaraSettingsManager.instance.platform.downcase == Android.downcase
62
+ end
63
+
64
+ def self.is_flutter
65
+ SolaraSettingsManager.instance.platform.downcase == Flutter.downcase
66
+ end
67
+ end
68
+
69
+ def init(platform)
70
+ unless Platform.all.include?(platform)
71
+ raise ArgumentError, "Invalid platform. Please use one of: #{Platform.all.join(', ')}"
72
+ end
73
+ end
@@ -0,0 +1,55 @@
1
+ class SolaraStatusManager
2
+
3
+ def start
4
+ current_brand_draft
5
+ puts
6
+ brands_list
7
+ doctor
8
+ end
9
+
10
+ def current_brand_draft
11
+ current_brand = BrandsManager.instance.current_brand
12
+ unless current_brand
13
+ return
14
+ end
15
+
16
+ Solara.logger.title("Current Brand Status")
17
+
18
+ content_changed = current_brand['content_changed']
19
+
20
+ unless content_changed
21
+ Solara.logger.info("Current brand configurations are up-to-date")
22
+ return
23
+ end
24
+
25
+ Solara.logger.warn("Changes for the current brand have been drafted!")
26
+ Solara.logger.info("To apply these changes, please use one of the following methods:")
27
+ message = <<-MESSAGE
28
+ 1. Run this command in your terminal:
29
+
30
+ solara switch -k #{current_brand['key']}
31
+
32
+ 2. Alternatively, open the dashboard by executing this command in your terminal:
33
+
34
+ solara dashboard -k #{current_brand['key']}
35
+
36
+ Then, click the "Apply Changes" button.
37
+ MESSAGE
38
+ Solara.logger.info(message)
39
+ end
40
+
41
+ def brands_list
42
+ Solara.logger.title("Brands List")
43
+
44
+ brands_list = BrandsManager.instance.brands_list.each_with_index.map { |brand, index|
45
+ "#{index + 1}. Key: #{brand['key']}\n Name: #{brand['name']}\n"
46
+ }
47
+ brands_list.each do |brand|
48
+ Solara.logger.info(brand)
49
+ end
50
+ end
51
+
52
+ def doctor
53
+ SolaraManager.new.doctor
54
+ end
55
+ end
@@ -0,0 +1,42 @@
1
+ require 'singleton'
2
+
3
+ require 'singleton'
4
+ require 'json'
5
+
6
+ class SolaraVersionManager
7
+ include Singleton
8
+
9
+ def sversion
10
+ json['solaraVersion']
11
+ end
12
+
13
+ def version=(value)
14
+ new_json = json
15
+ new_json['solaraVersion'] = value
16
+ save(new_json)
17
+ end
18
+
19
+ def value(key)
20
+ json[key]
21
+ end
22
+
23
+ def add(key, value)
24
+ new_json = json
25
+ new_json[key] = value
26
+ save(new_json)
27
+ end
28
+
29
+ private
30
+
31
+ def json
32
+ path = FilePath.solara_version
33
+ FileManager.create_file_if_not_exist(path)
34
+ JSON.parse(File.read(path))
35
+ rescue JSON::ParserError => e
36
+ {}
37
+ end
38
+
39
+ def save(json)
40
+ File.write(FilePath.solara_version, JSON.pretty_generate(json))
41
+ end
42
+ end
@@ -0,0 +1,22 @@
1
+ require 'rexml/document'
2
+
3
+ class StringsXmlManager
4
+ def initialize(file_path)
5
+ @file_path = file_path
6
+ @xml_content = File.read(@file_path)
7
+ @doc = REXML::Document.new(@xml_content)
8
+ end
9
+
10
+ def delete_app_name
11
+ app_name_element = @doc.elements['resources/string[@name="app_name"]']
12
+ app_name_element.remove if app_name_element
13
+ save_changes
14
+ Solara.logger.debug("Removed app_name from #{@file_path} to avoid duplication with artifacts/strings.xml.")
15
+ end
16
+
17
+ private
18
+
19
+ def save_changes
20
+ File.write(@file_path, @doc.to_s)
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ class TerminalInputManager
2
+
3
+ def get_validated(message)
4
+ loop do
5
+ print message.green
6
+ user_input = STDIN.gets.chomp
7
+
8
+ if yield(user_input)
9
+ return user_input
10
+ end
11
+ end
12
+ end
13
+
14
+ def get(message)
15
+ loop do
16
+ print message.green
17
+ user_input = STDIN.gets.chomp
18
+ return user_input unless user_input.nil? || user_input.empty?
19
+ end
20
+ end
21
+
22
+ end