solara 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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