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,53 @@
1
+ structure:
2
+ android:
3
+ type: directory
4
+ contents:
5
+ assets:
6
+ type: directory
7
+ res:
8
+ type: directory
9
+ android_config.json:
10
+ type: file
11
+ validations:
12
+ - type: valid_json
13
+ - type: json_schema
14
+ schema_path: platform/android/android_config.json
15
+ android_signing.json:
16
+ type: file
17
+ validations:
18
+ - type: valid_json
19
+ - type: json_schema
20
+ schema_path: platform/android/android_signing.json
21
+ ios:
22
+ type: directory
23
+ contents:
24
+ assets:
25
+ type: directory
26
+ ios_config.json:
27
+ type: file
28
+ validations:
29
+ - type: valid_json
30
+ - type: json_schema
31
+ schema_path: platform/ios/ios_config.json
32
+ ios_signing.json:
33
+ type: file
34
+ validations:
35
+ - type: valid_json
36
+ - type: json_schema
37
+ schema_path: platform/ios/ios_signing.json
38
+ shared:
39
+ type: directory
40
+ contents:
41
+ assets:
42
+ type: directory
43
+ brand_config.json:
44
+ type: file
45
+ validations:
46
+ - type: valid_json
47
+ theme.json:
48
+ type: file
49
+ validations:
50
+ - type: valid_json
51
+ - type: json_schema
52
+ schema_path: platform/shared/theme.json
53
+ strict: false
@@ -0,0 +1,51 @@
1
+ structure:
2
+ android:
3
+ type: directory
4
+ contents:
5
+ assets:
6
+ type: directory
7
+ res:
8
+ type: directory
9
+ android_config.json:
10
+ type: file
11
+ validations:
12
+ - type: valid_json
13
+ - type: json_schema
14
+ schema_path: platform/android/android_config.json
15
+ android_signing.json:
16
+ type: file
17
+ validations:
18
+ - type: valid_json
19
+ - type: json_schema
20
+ schema_path: platform/android/android_signing.json
21
+ ios:
22
+ type: directory
23
+ contents:
24
+ assets:
25
+ type: directory
26
+ ios_config.json:
27
+ type: file
28
+ validations:
29
+ - type: valid_json
30
+ - type: json_schema
31
+ schema_path: platform/ios/ios_config.json
32
+ ios_signing.json:
33
+ type: file
34
+ validations:
35
+ - type: valid_json
36
+ - type: json_schema
37
+ schema_path: platform/ios/ios_signing.json
38
+ shared:
39
+ type: directory
40
+ contents:
41
+ brand_config.json:
42
+ type: file
43
+ validations:
44
+ - type: valid_json
45
+ theme.json:
46
+ type: file
47
+ validations:
48
+ - type: valid_json
49
+ - type: json_schema
50
+ schema_path: platform/shared/theme.json
51
+ strict: false
@@ -0,0 +1,108 @@
1
+ Dir.glob("#{__dir__}/../../doctor/validator/*.rb").each { |file| require file }
2
+ require 'yaml'
3
+ require 'pathname'
4
+ require 'json'
5
+ require 'json-schema'
6
+
7
+ class TemplateValidator
8
+ def initialize(project_path, config_file)
9
+ @project_path = Pathname.new(project_path)
10
+ @config = YAML.load_file(config_file)
11
+ end
12
+
13
+ def validate
14
+ errors = []
15
+ validate_structure(errors, @project_path, @config['structure'])
16
+ errors
17
+ end
18
+
19
+ private
20
+
21
+ def validate_structure(errors, current_path, expected_structure)
22
+ expected_structure.each do |name, details|
23
+ path = current_path.join(name)
24
+
25
+ if details.is_a?(Hash)
26
+ case details['type']
27
+ when 'directory'
28
+ validate_directory(errors, path, details)
29
+ when 'file'
30
+ validate_file(errors, path, details)
31
+ else
32
+ errors << "Unknown type '#{details['type']}' for #{path.relative_path_from(@project_path)}"
33
+ end
34
+ end
35
+ end
36
+
37
+ validate_no_extra_files(errors, current_path, expected_structure) if @config['strict']
38
+ end
39
+
40
+ def validate_directory(errors, path, details)
41
+ unless path.directory?
42
+ errors << "Missing directory: #{path.relative_path_from(@project_path)}"
43
+ return
44
+ end
45
+ validate_structure(errors, path, details['contents']) if details['contents']
46
+ end
47
+
48
+ def validate_file(errors, path, details)
49
+ unless path.file?
50
+ errors << "Missing file: #{path.relative_path_from(@project_path)}"
51
+ return
52
+ end
53
+
54
+ content = File.read(path)
55
+
56
+ details['validations']&.each do |validation|
57
+ case validation['type']
58
+ when 'content_includes'
59
+ unless content.include?(validation['value'])
60
+ errors << "File #{path.relative_path_from(@project_path)} does not contain expected content: #{validation['value']}"
61
+ end
62
+
63
+ when 'content_matches'
64
+ unless content.match?(Regexp.new(validation['value']))
65
+ errors << "File #{path.relative_path_from(@project_path)} does not match expected pattern: #{validation['value']}"
66
+ end
67
+
68
+ when 'file_size'
69
+ size = File.size(path)
70
+ min_size = validation['min_size']
71
+ max_size = validation['max_size']
72
+ if min_size && size < min_size
73
+ errors << "File #{path.relative_path_from(@project_path)} is smaller than expected: #{size} < #{min_size} bytes"
74
+ end
75
+ if max_size && size > max_size
76
+ errors << "File #{path.relative_path_from(@project_path)} is larger than expected: #{size} > #{max_size} bytes"
77
+ end
78
+
79
+ when 'valid_json'
80
+ begin
81
+ JsonFileValidator.new([path]).validate(@project_path)
82
+ rescue StandardError => e
83
+ errors << e.message
84
+ end
85
+
86
+ when 'json_schema'
87
+ begin
88
+ schema_path = File.join(FilePath.schema, validation['schema_path'])
89
+ JsonSchemaValidator.new(schema_path, path).validate(@project_path)
90
+ rescue StandardError => e
91
+ errors << e.message
92
+ end
93
+
94
+ else
95
+ errors << "Unknown validation type '#{validation['type']}' for #{path.relative_path_from(@project_path)}"
96
+ end
97
+ end
98
+ end
99
+
100
+ def validate_no_extra_files(errors, current_path, expected_structure)
101
+ expected_names = expected_structure.keys
102
+ current_path.children.each do |child|
103
+ unless expected_names.include?(child.basename.to_s)
104
+ errors << "Unexpected #{child.directory? ? 'directory' : 'file'}: #{child.relative_path_from(@project_path)}"
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,7 @@
1
+ class ValidationStrategy
2
+ class ValidationError < StandardError; end
3
+
4
+ def validate(project_path)
5
+ raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
6
+ end
7
+ end
@@ -0,0 +1,245 @@
1
+ module Language
2
+ Kotlin = 'kotlin'
3
+ Swift = 'swift'
4
+ Dart = 'dart'
5
+
6
+ def self.all
7
+ [Kotlin, Swift, Dart]
8
+ end
9
+ end
10
+
11
+ def init(language)
12
+ if Language.all.include?(language)
13
+ # Do something with the valid language
14
+ else
15
+ raise ArgumentError, "Invalid language. Please use one of: #{Language.all.join(', ')}"
16
+ end
17
+ end
18
+
19
+ class BrandConfigGenerator
20
+ def initialize(config, output_dir, language, platform)
21
+ @output_dir = output_dir
22
+ @language = language
23
+ @platform = platform
24
+ @config = config
25
+ end
26
+
27
+ def generate
28
+ content = generate_config_content
29
+ FileManager.create_file_if_not_exist(@output_dir)
30
+ File.write(@output_dir, content)
31
+ Solara.logger.debug("Generated brand config #{@output_dir} for: #{@language}")
32
+ end
33
+
34
+ private
35
+
36
+ def generate_config_content
37
+ content = "// Generated by Solara\n"
38
+ content += language_specific_imports
39
+
40
+ classes = []
41
+ generate_classes("BrandConfig", @config, classes)
42
+
43
+ classes.reverse_each do |class_content|
44
+ content += class_content
45
+ content += "\n"
46
+ end
47
+
48
+ content
49
+ end
50
+
51
+ def language_specific_imports
52
+ case @language
53
+ when 'dart'
54
+ "import 'package:flutter/material.dart';\n\n"
55
+ when 'kotlin'
56
+ "import android.graphics.Color\n\n"
57
+ when 'swift'
58
+ "import UIKit\n\n"
59
+ end
60
+ end
61
+
62
+ def generate_classes(class_name, config, classes)
63
+ content = class_declaration(class_name)
64
+ constructor_params = []
65
+
66
+ config.each do |key, value|
67
+ type = value_type(value, key)
68
+ content += property_declaration(key, type)
69
+ constructor_params << constructor_parameter(key, type)
70
+ end
71
+
72
+ content += constructor_declaration(class_name, constructor_params)
73
+ content += instance_declaration(class_name, config)
74
+ content += class_closing
75
+
76
+ classes << content
77
+
78
+ config.each do |key, value|
79
+ if value.is_a?(Hash)
80
+ nested_class_name = "#{key.capitalize}"
81
+ generate_classes(nested_class_name, value, classes)
82
+ end
83
+ end
84
+ end
85
+
86
+ def class_declaration(class_name)
87
+ case @language
88
+ when 'dart'
89
+ "class #{class_name} {\n"
90
+ when 'kotlin'
91
+ "data class #{class_name}(\n"
92
+ when 'swift'
93
+ "struct #{class_name} {\n"
94
+ end
95
+ end
96
+
97
+ def property_declaration(key, type)
98
+ case @language
99
+ when 'dart'
100
+ " final #{type} #{key};\n"
101
+ when 'kotlin'
102
+ " val #{key}: #{type},\n"
103
+ when 'swift'
104
+ " let #{key}: #{type}\n"
105
+ end
106
+ end
107
+
108
+ def constructor_parameter(key, type)
109
+ case @language
110
+ when 'dart'
111
+ "required this.#{key}"
112
+ when 'kotlin'
113
+ "val #{key}: #{type}"
114
+ when 'swift'
115
+ "#{key}: #{type}"
116
+ end
117
+ end
118
+
119
+ def constructor_declaration(class_name, params)
120
+ case @language
121
+ when 'dart'
122
+ params.empty? ? "\n const #{class_name}();\n\n" : "\n const #{class_name}({#{params.join(', ')}});\n\n"
123
+ when 'kotlin'
124
+ ") {\n"
125
+ when 'swift'
126
+ "\n init(#{params.join(', ')}) {\n#{params.map { |p| " self.#{p.split(':').first} = #{p.split(':').first}" }.join("\n")}\n }\n\n"
127
+ end
128
+ end
129
+
130
+ def instance_declaration(class_name, config)
131
+ case @language
132
+ when 'dart'
133
+ " static const #{class_name} instance = #{class_name}(\n#{config.map { |k, v| " #{k}: #{value_for(v, k, ' ')}" }.join(",\n")}\n );\n"
134
+ when 'kotlin'
135
+ "companion object {\n val instance = #{class_name}(\n#{config.map { |k, v| " #{k} = #{value_for(v, k, ' ')}" }.join(",\n")}\n )\n}\n"
136
+ when 'swift'
137
+ " static let shared = #{class_name}(\n#{config.map { |k, v| " #{k}: #{value_for(v, k, ' ')}" }.join(",\n")}\n )\n"
138
+ end
139
+ end
140
+
141
+ def class_closing
142
+ @language == 'swift' ? "}\n" : "}\n"
143
+ end
144
+
145
+ def value_type(value, class_prefix)
146
+ case value
147
+ when String then language_specific_type('String')
148
+ when Integer then language_specific_type('Int')
149
+ when Float then language_specific_type('Double')
150
+ when TrueClass, FalseClass then language_specific_type('Bool')
151
+ when Array
152
+ if value.all? { |item| item.is_a?(String) }
153
+ language_specific_type('Array<String>')
154
+ elsif value.all? { |item| item.is_a?(Integer) }
155
+ language_specific_type('Array<Int>')
156
+ elsif value.all? { |item| item.is_a?(Float) }
157
+ language_specific_type('Array<Double>')
158
+ elsif value.all? { |item| item.is_a?(TrueClass) || item.is_a?(FalseClass) }
159
+ language_specific_type('Array<Bool>')
160
+ else
161
+ language_specific_type('Array<Any>')
162
+ end
163
+ when Hash then "#{class_prefix.capitalize}"
164
+ else
165
+ language_specific_type('Any')
166
+ end
167
+ end
168
+
169
+ def language_specific_type(type)
170
+ case @language
171
+ when 'dart'
172
+ case type
173
+ when 'String' then 'String'
174
+ when 'Int' then 'int'
175
+ when 'Double' then 'double'
176
+ when 'Bool' then 'bool'
177
+ when 'Array<String>' then 'List<String>'
178
+ when 'Array<Int>' then 'List<int>'
179
+ when 'Array<Double>' then 'List<double>'
180
+ when 'Array<Bool>' then 'List<bool>'
181
+ when 'Array<Any>' then 'List<dynamic>'
182
+ when 'Any' then 'dynamic'
183
+ else type
184
+ end
185
+ when 'kotlin'
186
+ case type
187
+ when 'Array<String>' then 'List<String>'
188
+ when 'Array<Int>' then 'List<Int>'
189
+ when 'Array<Double>' then 'List<Double>'
190
+ when 'Array<Bool>' then 'List<Boolean>'
191
+ when 'Array<Any>' then 'List<Any>'
192
+ when 'Any' then 'Any'
193
+ else type
194
+ end
195
+ when 'swift'
196
+ type
197
+ end
198
+ end
199
+
200
+ def value_for(value, class_prefix, indent)
201
+ case value
202
+ when String
203
+ if value.start_with?('#') && value.length == 7 # Assume it's a color
204
+ color_for(value)
205
+ else
206
+ value.inspect
207
+ end
208
+ when Integer, Float
209
+ value.to_s
210
+ when TrueClass, FalseClass
211
+ value.to_s
212
+ when Array
213
+ array_items = value.map { |item| value_for(item, class_prefix, indent + ' ') }.join(', ')
214
+ "[\n#{indent} #{array_items}\n#{indent}]"
215
+ when Hash
216
+ "#{class_prefix.capitalize}.instance"
217
+ else
218
+ language_specific_null
219
+ end
220
+ end
221
+
222
+ def color_for(value)
223
+ case @language
224
+ when 'dart'
225
+ "Color(0xFF#{value[1..-1]})"
226
+ when 'kotlin'
227
+ "Color.parseColor(\"#{value}\")"
228
+ when 'swift'
229
+ "UIColor(red: #{hex_to_rgb(value[:red])}, green: #{hex_to_rgb(value[:green])}, blue: #{hex_to_rgb(value[:blue])}, alpha: 1.0)"
230
+ end
231
+ end
232
+
233
+ def hex_to_rgb(hex)
234
+ hex.to_i(16) / 255.0
235
+ end
236
+
237
+ def language_specific_null
238
+ case @language
239
+ when 'dart', 'swift'
240
+ 'null'
241
+ when 'kotlin'
242
+ 'null'
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,90 @@
1
+ Dir.glob("#{__dir__}/../scripts/platform/android/*.rb").each { |file| require file }
2
+ Dir.glob("#{__dir__}/../scripts/platform/ios/*.rb").each { |file| require file }
3
+
4
+ class BrandConfigManager
5
+ def initialize(brand_key)
6
+ @brand_key = brand_key
7
+ end
8
+
9
+ def generate_brand_config(
10
+ name,
11
+ language,
12
+ platform)
13
+ Solara.logger.start_step("Generate #{language} brand config for #{platform}")
14
+ config = load_config(FilePath.brand_config(@brand_key))
15
+ add_basic_brand_info(config, platform)
16
+ config_generator = BrandConfigGenerator.new(
17
+ config,
18
+ FilePath.generated_config(name, platform),
19
+ language,
20
+ platform
21
+ )
22
+ config_generator.generate
23
+ Solara.logger.end_step("Generate #{language} brand config for #{platform}")
24
+ end
25
+
26
+ def generate_android_properties
27
+ generator = PropertiesGenerator.new(@brand_key)
28
+ generator.generate
29
+ end
30
+
31
+ def generate_ios_xcconfig
32
+ generator = XcconfigGenerator.new(@brand_key)
33
+ generator.generate
34
+ end
35
+
36
+ private
37
+
38
+ def add_basic_brand_info(config, platform)
39
+ case platform
40
+ when Platform::Flutter
41
+ add_android_info(config, prefix: 'android')
42
+ add_ios_info(config, prefix: 'iOS')
43
+ when Platform::Android
44
+ add_android_info(config)
45
+ when Platform::IOS
46
+ add_ios_info(config)
47
+ else
48
+ raise ArgumentError, "Invalid platform: #{@platform}"
49
+ end
50
+ end
51
+
52
+ def add_android_info(config, prefix: '')
53
+ android_config = load_config(FilePath.android_config(@brand_key))
54
+ config_mappings = {
55
+ 'brandName' => 'BrandName',
56
+ 'applicationId' => 'ApplicationId',
57
+ 'versionName' => 'VersionName',
58
+ 'versionCode' => 'VersionCode'
59
+ }
60
+
61
+ add_config_info(config, android_config, config_mappings, prefix)
62
+ end
63
+
64
+ def add_ios_info(config, prefix: '')
65
+ ios_config = load_config(FilePath.ios_config(@brand_key))
66
+ config_mappings = {
67
+ 'PRODUCT_NAME' => 'ProductName',
68
+ 'PRODUCT_BUNDLE_IDENTIFIER' => 'BundleIdentifier',
69
+ 'MARKETING_VERSION' => 'MarketingVersion',
70
+ 'BUNDLE_VERSION' => 'BundleVersion'
71
+ }
72
+
73
+ add_config_info(config, ios_config, config_mappings, prefix)
74
+ end
75
+
76
+ def add_config_info(config, source_config, mappings, prefix)
77
+ mappings.each do |source_key, target_key|
78
+ key = if prefix.empty?
79
+ target_key[0].downcase + target_key[1..]
80
+ else
81
+ "#{prefix}#{target_key}"
82
+ end
83
+ config[key] = source_config[source_key]
84
+ end
85
+ end
86
+
87
+ def load_config(file_path)
88
+ JSON.parse(File.read(file_path))
89
+ end
90
+ end
@@ -0,0 +1,38 @@
1
+ require 'json'
2
+ require 'fileutils'
3
+
4
+ class BrandExporter
5
+
6
+ def start(brand_keys, path)
7
+ if !path.nil? && !path.strip.empty? && !File.directory?(path)
8
+ Solara.logger.failure("#{path} is not a directory. Please specify a valid directory.")
9
+ return
10
+ end
11
+
12
+ directory = path || FilePath.project_root
13
+
14
+ brand_keys.each do |brand_key|
15
+ export(brand_key, directory)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def export(brand_key, directory)
22
+ if BrandsManager.instance.find(brand_key).nil?
23
+ Solara.logger.failure("#{brand_key} doesn't exist!")
24
+ return
25
+ end
26
+
27
+ brand = BrandsManager.instance.brand_with_configurations(brand_key)
28
+ json = JSON.pretty_generate(brand)
29
+ json_file_path = File.join(directory, "#{brand_key}-solara-configurations.json")
30
+
31
+ # Create the file if it does not exist
32
+ File.open(json_file_path, 'w') do |file|
33
+ file.write(json)
34
+ end
35
+
36
+ Solara.logger.success("Successfully exported brand #{brand_key} to: #{json_file_path}")
37
+ end
38
+ end
@@ -0,0 +1,84 @@
1
+ require 'json'
2
+ require 'fileutils'
3
+
4
+ class BrandImporter
5
+
6
+ def start(configurations_paths)
7
+
8
+ configurations_paths.each do |path|
9
+ import(path)
10
+ end
11
+
12
+ end
13
+
14
+ private
15
+
16
+ def import(configurations_path)
17
+ unless File.exist?(configurations_path)
18
+ Solara.logger.failure("#{configurations_path} doesn't exist!")
19
+ return
20
+ end
21
+
22
+ unless File.file?(configurations_path)
23
+ Solara.logger.failure("#{configurations_path} is not configurations file!")
24
+ return
25
+ end
26
+
27
+ validate_json(configurations_path)
28
+ validate_json_schema(configurations_path)
29
+
30
+ configurations_json = JSON.parse(File.read(configurations_path))
31
+ brand = configurations_json['brand']
32
+ brand_key = brand['key'] # Ensure to use 'key' instead of 'brand_key'
33
+
34
+ exists = BrandsManager.instance.exists(brand_key)
35
+ unless exists
36
+ SolaraManager.new.onboard(brand_key, brand['name'], open_dashboard: false)
37
+ end
38
+
39
+ update_brand(brand_key, configurations_json)
40
+
41
+ message_suffix = exists ? "The existing brand '#{brand_key}' has been updated." : "A new brand with the key '#{brand_key}' has been onboarded."
42
+ Solara.logger.success("Successfully imported (#{configurations_path}). #{message_suffix}")
43
+ end
44
+
45
+ def update_brand(brand_key, configurations_json)
46
+ brand_path = FilePath.brand(brand_key)
47
+
48
+ configurations_json['configurations'].each do |configuration|
49
+ file_name = "#{configuration['key']}.json"
50
+ file_path = find_file_in_subdirectories(brand_path, file_name)
51
+
52
+ # Create or replace the contents of the configuration file
53
+ if file_path
54
+ File.write(file_path, JSON.pretty_generate(configuration['content']))
55
+ else
56
+ Solara.logger.failure("File #{file_name} not found in #{brand_path}, ignoring it!")
57
+ end
58
+ end
59
+ end
60
+
61
+ def find_file_in_subdirectories(base_path, file_name)
62
+ Dir.glob(File.join(base_path, '**', file_name)).first
63
+ end
64
+
65
+ def validate_json(configurations_path)
66
+ begin
67
+ JsonFileValidator.new([configurations_path]).validate
68
+ rescue StandardError => e
69
+ Solara.logger.fatal(e.message)
70
+ exit 1
71
+ end
72
+ end
73
+
74
+ def validate_json_schema(configurations_path)
75
+ begin
76
+ schema_path = FilePath.brand_configurations_schema
77
+ JsonSchemaValidator.new(schema_path, configurations_path).validate
78
+ rescue StandardError => e
79
+ Solara.logger.fatal(e.message)
80
+ exit 1
81
+ end
82
+ end
83
+
84
+ end