solara 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/solara +18 -0
- data/solara/lib/.DS_Store +0 -0
- data/solara/lib/core/.DS_Store +0 -0
- data/solara/lib/core/aliases/alias_generator.rb +128 -0
- data/solara/lib/core/aliases/alias_generator_manager.rb +28 -0
- data/solara/lib/core/aliases/solara_terminal_setup.rb +103 -0
- data/solara/lib/core/brands/brand_onboarder.rb +46 -0
- data/solara/lib/core/brands/brand_switcher.rb +204 -0
- data/solara/lib/core/brands/brands_manager.rb +154 -0
- data/solara/lib/core/dashboard/.DS_Store +0 -0
- data/solara/lib/core/dashboard/brand/.DS_Store +0 -0
- data/solara/lib/core/dashboard/brand/BrandDetail.js +11 -0
- data/solara/lib/core/dashboard/brand/BrandDetailController.js +361 -0
- data/solara/lib/core/dashboard/brand/BrandDetailModel.js +155 -0
- data/solara/lib/core/dashboard/brand/BrandDetailView.js +245 -0
- data/solara/lib/core/dashboard/brand/brand.html +477 -0
- data/solara/lib/core/dashboard/brand/source/BrandLocalSource.js +123 -0
- data/solara/lib/core/dashboard/brand/source/BrandRemoteSource.js +260 -0
- data/solara/lib/core/dashboard/brands/Brands.js +10 -0
- data/solara/lib/core/dashboard/brands/BrandsController.js +155 -0
- data/solara/lib/core/dashboard/brands/BrandsModel.js +136 -0
- data/solara/lib/core/dashboard/brands/BrandsView.js +136 -0
- data/solara/lib/core/dashboard/brands/brands.html +345 -0
- data/solara/lib/core/dashboard/component/AddFieldSheet.js +212 -0
- data/solara/lib/core/dashboard/component/AliasesBottomSheet.js +128 -0
- data/solara/lib/core/dashboard/component/BrandOptionsBottomSheet.js +130 -0
- data/solara/lib/core/dashboard/component/ConfirmationDialog.js +103 -0
- data/solara/lib/core/dashboard/component/MessageBottomSheet.js +80 -0
- data/solara/lib/core/dashboard/component/OnboardBrandBottomSheet.js +214 -0
- data/solara/lib/core/dashboard/dashboard_manager.rb +19 -0
- data/solara/lib/core/dashboard/dashboard_server.rb +132 -0
- data/solara/lib/core/dashboard/handler/base_handler.rb +25 -0
- data/solara/lib/core/dashboard/handler/brand_alisases_handler.rb +33 -0
- data/solara/lib/core/dashboard/handler/brand_configurations_handler.rb +18 -0
- data/solara/lib/core/dashboard/handler/brand_configurations_manager.rb +73 -0
- data/solara/lib/core/dashboard/handler/brand_icon_handler.rb +20 -0
- data/solara/lib/core/dashboard/handler/brand_section_handler.rb +20 -0
- data/solara/lib/core/dashboard/handler/brands_handler.rb +14 -0
- data/solara/lib/core/dashboard/handler/current_brand_handler.rb +18 -0
- data/solara/lib/core/dashboard/handler/doctor_handler.rb +39 -0
- data/solara/lib/core/dashboard/handler/edit_section_handler.rb +55 -0
- data/solara/lib/core/dashboard/handler/offboard_brand_handler.rb +34 -0
- data/solara/lib/core/dashboard/handler/onboard_brand_handler.rb +53 -0
- data/solara/lib/core/dashboard/handler/redirect_handler.rb +12 -0
- data/solara/lib/core/dashboard/handler/switch_handler.rb +25 -0
- data/solara/lib/core/dashboard/index.html +36 -0
- data/solara/lib/core/dashboard/local.html +41 -0
- data/solara/lib/core/dashboard/res/favicon/android-chrome-192x192.png +0 -0
- data/solara/lib/core/dashboard/res/favicon/android-chrome-512x512.png +0 -0
- data/solara/lib/core/dashboard/res/favicon/apple-touch-icon.png +0 -0
- data/solara/lib/core/dashboard/res/favicon/favicon-16x16.png +0 -0
- data/solara/lib/core/dashboard/res/favicon/favicon-32x32.png +0 -0
- data/solara/lib/core/dashboard/res/favicon/favicon.ico +0 -0
- data/solara/lib/core/dashboard/res/favicon/site.webmanifest +1 -0
- data/solara/lib/core/dashboard/solara.png +0 -0
- data/solara/lib/core/doctor/brand_doctor.rb +94 -0
- data/solara/lib/core/doctor/doctor_manager.rb +35 -0
- data/solara/lib/core/doctor/project_doctor.rb +8 -0
- data/solara/lib/core/doctor/schema/brand_configurations.json +60 -0
- data/solara/lib/core/doctor/schema/platform/android/android_config.json +23 -0
- data/solara/lib/core/doctor/schema/platform/android/android_signing.json +23 -0
- data/solara/lib/core/doctor/schema/platform/ios/ios_config.json +27 -0
- data/solara/lib/core/doctor/schema/platform/ios/ios_signing.json +27 -0
- data/solara/lib/core/doctor/schema/platform/shared/theme.json +48 -0
- data/solara/lib/core/doctor/validator/brand_settings_validator.rb +55 -0
- data/solara/lib/core/doctor/validator/brand_settings_validator_manager.rb +82 -0
- data/solara/lib/core/doctor/validator/directory_structure_validator.rb +38 -0
- data/solara/lib/core/doctor/validator/file_structure_validator.rb +37 -0
- data/solara/lib/core/doctor/validator/json_file_validator.rb +21 -0
- data/solara/lib/core/doctor/validator/json_schema_validator.rb +32 -0
- data/solara/lib/core/doctor/validator/project_filesystem_validator.rb +70 -0
- data/solara/lib/core/doctor/validator/template/android_template_validation_config.yml +51 -0
- data/solara/lib/core/doctor/validator/template/flutter_template_validation_config.yml +53 -0
- data/solara/lib/core/doctor/validator/template/ios_template_validation_config.yml +51 -0
- data/solara/lib/core/doctor/validator/template/template_validator.rb +108 -0
- data/solara/lib/core/doctor/validator/validation_strategy.rb +7 -0
- data/solara/lib/core/scripts/brand_config_generator.rb +245 -0
- data/solara/lib/core/scripts/brand_config_manager.rb +90 -0
- data/solara/lib/core/scripts/brand_exporter.rb +38 -0
- data/solara/lib/core/scripts/brand_importer.rb +84 -0
- data/solara/lib/core/scripts/brand_offboarder.rb +19 -0
- data/solara/lib/core/scripts/brand_resources_manager.rb +77 -0
- data/solara/lib/core/scripts/directory_creator.rb +22 -0
- data/solara/lib/core/scripts/file_manager.rb +90 -0
- data/solara/lib/core/scripts/file_path.rb +327 -0
- data/solara/lib/core/scripts/folder_copier.rb +41 -0
- data/solara/lib/core/scripts/gitignore_manager.rb +54 -0
- data/solara/lib/core/scripts/interactive_file_system_validator.rb +110 -0
- data/solara/lib/core/scripts/platform/android/android_manifest_switcher.rb +24 -0
- data/solara/lib/core/scripts/platform/android/android_strings_switcher.rb +39 -0
- data/solara/lib/core/scripts/platform/android/gradle_switcher.rb +233 -0
- data/solara/lib/core/scripts/platform/android/properties_generator.rb +31 -0
- data/solara/lib/core/scripts/platform/ios/ios_file_path_manager.rb +109 -0
- data/solara/lib/core/scripts/platform/ios/ios_plist_manager.rb +42 -0
- data/solara/lib/core/scripts/platform/ios/xcconfig_generator.rb +44 -0
- data/solara/lib/core/scripts/platform/ios/xcode_asset_manager.rb +56 -0
- data/solara/lib/core/scripts/platform/ios/xcode_project_manager.rb +82 -0
- data/solara/lib/core/scripts/platform/ios/xcode_project_switcher.rb +130 -0
- data/solara/lib/core/scripts/project_settings_manager.rb +39 -0
- data/solara/lib/core/scripts/solara_logger.rb +103 -0
- data/solara/lib/core/scripts/solara_settings_manager.rb +73 -0
- data/solara/lib/core/scripts/solara_status_manager.rb +55 -0
- data/solara/lib/core/scripts/solara_version_manager.rb +42 -0
- data/solara/lib/core/scripts/strings_xml_manager.rb +22 -0
- data/solara/lib/core/scripts/terminal_input_manager.rb +22 -0
- data/solara/lib/core/scripts/theme_generator.rb +250 -0
- data/solara/lib/core/scripts/yaml_manager.rb +72 -0
- data/solara/lib/core/solara_configurator.rb +15 -0
- data/solara/lib/core/template/brands/android/android_config.json +8 -0
- data/solara/lib/core/template/brands/android/android_signing.json +6 -0
- data/solara/lib/core/template/brands/android/res/.DS_Store +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-hdpi/ic_launcher.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-mdpi/ic_launcher.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-xhdpi/ic_launcher.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- data/solara/lib/core/template/brands/brands.json +4 -0
- data/solara/lib/core/template/brands/ios/assets/.DS_Store +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/100.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/102.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/1024.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/114.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/120.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/128.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/144.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/152.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/16.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/167.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/172.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/180.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/196.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/20.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/216.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/256.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/29.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/32.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/40.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/48.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/50.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/512.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/55.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/57.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/58.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/60.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/64.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/66.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/72.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/76.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/80.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/87.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/88.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/92.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/Contents.json +1 -0
- data/solara/lib/core/template/brands/ios/ios_config.json +7 -0
- data/solara/lib/core/template/brands/ios/ios_signing.json +7 -0
- data/solara/lib/core/template/brands/shared/.DS_Store +0 -0
- data/solara/lib/core/template/brands/shared/brand_config.json +2 -0
- data/solara/lib/core/template/brands/shared/theme.json +46 -0
- data/solara/lib/core/template/config/android_template_config.json +57 -0
- data/solara/lib/core/template/config/flutter_template_config.json +62 -0
- data/solara/lib/core/template/config/ios_template_config.json +57 -0
- data/solara/lib/core/template/project_template_generator.rb +63 -0
- data/solara/lib/platform_detector.rb +84 -0
- data/solara/lib/solara/cli.rb +5 -0
- data/solara/lib/solara/version.rb +3 -0
- data/solara/lib/solara.rb +238 -0
- data/solara/lib/solara_initializer.rb +44 -0
- data/solara/lib/solara_manager.rb +73 -0
- metadata +346 -0
@@ -0,0 +1,110 @@
|
|
1
|
+
Dir.glob("#{__dir__}/*.rb").each { |file| require file }
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
class InteractiveFileSystemValidator
|
7
|
+
def initialize(project_root, settings_manager)
|
8
|
+
@project_root = project_root
|
9
|
+
@settings_manager = settings_manager
|
10
|
+
end
|
11
|
+
|
12
|
+
def start(file_system)
|
13
|
+
validate(file_system)
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate(file_system)
|
17
|
+
file_system.each do |item|
|
18
|
+
key = item[:key]
|
19
|
+
name = item[:name]
|
20
|
+
type = item[:type]
|
21
|
+
platform = item[:platform]
|
22
|
+
item_path = item.fetch(:path, '')
|
23
|
+
recursive = item.fetch(:recursive, true)
|
24
|
+
|
25
|
+
value = @settings_manager.value(key, platform)
|
26
|
+
|
27
|
+
if value
|
28
|
+
# Check if the item exists and is of the correct type
|
29
|
+
if type == 'file' && !File.file?(value)
|
30
|
+
Solara.logger.failure("Missing file: #{key} (#{value})")
|
31
|
+
validate_required_item(item)
|
32
|
+
elsif type == 'folder' && !File.directory?(value)
|
33
|
+
Solara.logger.failure("Missing folder: #{key} (#{value})")
|
34
|
+
validate_required_item(item)
|
35
|
+
end
|
36
|
+
else
|
37
|
+
ignored = %w[solara/ Artifacts/ Pods/ build/]
|
38
|
+
|
39
|
+
root = File.join(@project_root, item_path)
|
40
|
+
paths = if recursive
|
41
|
+
FileManager.find_files_by_name(root, name)
|
42
|
+
else
|
43
|
+
Dir.glob(File.join(root, name)).select { |path| File.file?(path) || File.directory?(path) }
|
44
|
+
end
|
45
|
+
|
46
|
+
paths = paths.map { |path| FileManager.get_relative_path(@project_root, path) }
|
47
|
+
.reject { |path| ignored.any? { |ignored_path| path.include?(ignored_path) } }
|
48
|
+
|
49
|
+
case paths.size
|
50
|
+
when 0
|
51
|
+
Solara.logger.failure("Missing #{type}: #{key}")
|
52
|
+
validate_required_item(item)
|
53
|
+
when 1
|
54
|
+
@settings_manager.add(key, paths.first, platform)
|
55
|
+
|
56
|
+
Solara.logger.debug("Added #{type}: #{key} (#{paths.first})")
|
57
|
+
else
|
58
|
+
Solara.logger.failure("Found multiple paths for #{key}:\n\t- #{paths.join("\n\t- ")}")
|
59
|
+
validate_required_item(item)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def validate_required_item(item)
|
68
|
+
item_key = item[:key]
|
69
|
+
item_type = item[:type]
|
70
|
+
platform = item[:platform]
|
71
|
+
item_path = ''
|
72
|
+
|
73
|
+
loop do
|
74
|
+
item_path = get_path_from_user(item_key, item_type)
|
75
|
+
break if validate_item(item_path, item_key, item_type)
|
76
|
+
end
|
77
|
+
value = FileManager.get_relative_path(@project_root, item_path)
|
78
|
+
@settings_manager.add(item_key, value, platform)
|
79
|
+
end
|
80
|
+
|
81
|
+
def validate_item(item_path, item_name, item_type)
|
82
|
+
return false if item_path.nil? || !item_path.end_with?(item_name)
|
83
|
+
|
84
|
+
case item_type
|
85
|
+
when 'file'
|
86
|
+
if File.file?(item_path)
|
87
|
+
Solara.logger.debug("File exists: #{item_path}")
|
88
|
+
true
|
89
|
+
else
|
90
|
+
Solara.logger.failure("File does not exist: #{item_path}")
|
91
|
+
false
|
92
|
+
end
|
93
|
+
when 'folder'
|
94
|
+
if File.directory?(item_path)
|
95
|
+
true
|
96
|
+
else
|
97
|
+
Solara.logger.failure("Folder does not exist: #{item_path}")
|
98
|
+
false
|
99
|
+
end
|
100
|
+
else
|
101
|
+
Solara.logger.failure("Invalid item type: #{item_type}")
|
102
|
+
false
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def get_path_from_user(item_key, item_type)
|
107
|
+
print "Enter the relative path for #{item_key}): "
|
108
|
+
STDIN.gets.chomp
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class AndroidManifestSwitcher
|
2
|
+
def initialize
|
3
|
+
end
|
4
|
+
|
5
|
+
def update_manifest(config)
|
6
|
+
Solara.logger.start_step("Update AndroidManifest")
|
7
|
+
manifest_file = FilePath.android_manifest
|
8
|
+
if File.exist?(manifest_file)
|
9
|
+
manifest_content = File.read(manifest_file)
|
10
|
+
updated_manifest = update_app_name(manifest_content, config)
|
11
|
+
File.write(manifest_file, updated_manifest)
|
12
|
+
Solara.logger.debug("Updated #{FilePath.android_manifest} to use string resource for app name")
|
13
|
+
else
|
14
|
+
Solara.logger.debug("❌ #{FilePath.android_manifest} not found. Skipping manifest update.")
|
15
|
+
end
|
16
|
+
Solara.logger.end_step("Update AndroidManifest")
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def update_app_name(manifest_content, config)
|
22
|
+
manifest_content.gsub(/android:label="[^"]+"/, 'android:label="@string/app_name"')
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class AndroidStringsSwitcher
|
2
|
+
def initialize
|
3
|
+
end
|
4
|
+
|
5
|
+
def update(config)
|
6
|
+
Solara.logger.start_step("Generate artifacts/strings.xml")
|
7
|
+
strings_file = FilePath.android_artifacts_strings
|
8
|
+
|
9
|
+
# Create the file if it doesn't exist
|
10
|
+
unless File.exist?(strings_file)
|
11
|
+
FileUtils.mkdir_p(File.dirname(strings_file))
|
12
|
+
end
|
13
|
+
|
14
|
+
strings_content = generate_strings_xml_content(config)
|
15
|
+
File.write(strings_file, strings_content)
|
16
|
+
Solara.logger.debug("Updated #{strings_file} with name: \"#{config['brandName']}\"")
|
17
|
+
|
18
|
+
remove_app_name_from_strings
|
19
|
+
Solara.logger.end_step("Generate artifacts/strings.xml")
|
20
|
+
end
|
21
|
+
|
22
|
+
# It's important to delete app_name to avoid duplicate resources
|
23
|
+
def remove_app_name_from_strings
|
24
|
+
file_path = FilePath.android_strings
|
25
|
+
manager = StringsXmlManager.new(file_path)
|
26
|
+
manager.delete_app_name
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def generate_strings_xml_content(config)
|
32
|
+
<<-XML
|
33
|
+
<?xml version="1.0" encoding="utf-8"?>
|
34
|
+
<resources>
|
35
|
+
<string name="app_name">#{config['brandName']}</string>
|
36
|
+
</resources>
|
37
|
+
XML
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class GradleSwitcher
|
5
|
+
KOTLIN_IMPORTS = <<-KOTLIN
|
6
|
+
import java.io.FileInputStream
|
7
|
+
import java.util.Properties
|
8
|
+
|
9
|
+
KOTLIN
|
10
|
+
|
11
|
+
KOTLIN_PROPERTIES_LOADER = <<-KOTLIN
|
12
|
+
val brandProperties = Properties().apply {
|
13
|
+
load(FileInputStream(file("../artifacts/brand.properties")))
|
14
|
+
}
|
15
|
+
KOTLIN
|
16
|
+
|
17
|
+
GROOVY_PROPERTIES_LOADER = <<-GROOVY
|
18
|
+
project.ext {
|
19
|
+
brandProperties = new Properties()
|
20
|
+
brandProperties.load(new FileInputStream(file("../artifacts/brand.properties")))
|
21
|
+
}
|
22
|
+
GROOVY
|
23
|
+
|
24
|
+
KOTLIN_APPLICATION_ID = 'applicationId = brandProperties.getProperty("applicationId")'
|
25
|
+
GROOVY_APPLICATION_ID = "applicationId = project.ext.brandProperties.getProperty('applicationId')"
|
26
|
+
|
27
|
+
KOTLIN_VERSION_NAME = 'versionName = brandProperties.getProperty("versionName")'
|
28
|
+
GROOVY_VERSION_NAME = "versionName = project.ext.brandProperties.getProperty('versionName')"
|
29
|
+
|
30
|
+
KOTLIN_VERSION_CODE = 'versionCode = brandProperties.getProperty("versionCode").toInt()'
|
31
|
+
GROOVY_VERSION_CODE = "versionCode = project.ext.brandProperties.getProperty('versionCode').toInteger()"
|
32
|
+
|
33
|
+
DEFAULT_SOURCE_SETS = %w[src/main/res src/main/artifacts]
|
34
|
+
|
35
|
+
def initialize(brand_key)
|
36
|
+
@brand_key = brand_key
|
37
|
+
@is_kotlin_gradle = FilePath.is_koltin_gradle
|
38
|
+
@brand_config = JSON.parse(File.read(FilePath.android_config(brand_key)))
|
39
|
+
@source_sets = (@brand_config['sourceSets'] || []).concat(DEFAULT_SOURCE_SETS).uniq
|
40
|
+
end
|
41
|
+
|
42
|
+
def update_build_gradle
|
43
|
+
Solara.logger.start_step("Update app/build.gradle")
|
44
|
+
gradle_file = FilePath.android_app_gradle
|
45
|
+
gradle_content = File.read(gradle_file)
|
46
|
+
|
47
|
+
update_gradle(gradle_file, gradle_content)
|
48
|
+
add_source_sets(gradle_file)
|
49
|
+
update_keystore_config(gradle_file)
|
50
|
+
Solara.logger.end_step("Update app/build.gradle")
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def update_gradle(gradle_file, gradle_content)
|
56
|
+
properties_loader = @is_kotlin_gradle ? KOTLIN_PROPERTIES_LOADER : GROOVY_PROPERTIES_LOADER
|
57
|
+
|
58
|
+
if @is_kotlin_gradle
|
59
|
+
# Add imports for Kotlin
|
60
|
+
unless gradle_content.include?("import java.io.FileInputStream")
|
61
|
+
insert_position = gradle_content.index(/\s*(plugins|android)\s*{/)
|
62
|
+
if insert_position.nil?
|
63
|
+
raise "Could not find a suitable position to insert imports in #{FilePath.gradle_name}"
|
64
|
+
end
|
65
|
+
gradle_content.insert(insert_position, KOTLIN_IMPORTS)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
insert_position = gradle_content.index(/\s*android\s*{/)
|
70
|
+
if insert_position.nil?
|
71
|
+
raise "Could not find android block in #{FilePath.gradle_name}"
|
72
|
+
end
|
73
|
+
|
74
|
+
unless gradle_content.include?(@is_kotlin_gradle ? "val brandProperties" : "brandProperties = new Properties")
|
75
|
+
gradle_content.insert(insert_position + 1, properties_loader)
|
76
|
+
end
|
77
|
+
|
78
|
+
android_block_regex = /(android\s*\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\})/m
|
79
|
+
updated_android_block = gradle_content.match(android_block_regex)[1]
|
80
|
+
.gsub(
|
81
|
+
/applicationId\s+=\s+.*/,
|
82
|
+
@is_kotlin_gradle ? KOTLIN_APPLICATION_ID : GROOVY_APPLICATION_ID
|
83
|
+
)
|
84
|
+
.gsub(
|
85
|
+
/versionName\s+=\s+.*/,
|
86
|
+
@is_kotlin_gradle ? KOTLIN_VERSION_NAME : GROOVY_VERSION_NAME
|
87
|
+
).gsub(
|
88
|
+
/versionCode\s+=\s+.*/,
|
89
|
+
@is_kotlin_gradle ? KOTLIN_VERSION_CODE : GROOVY_VERSION_CODE
|
90
|
+
)
|
91
|
+
|
92
|
+
gradle_content.sub!(android_block_regex, updated_android_block)
|
93
|
+
File.write(gradle_file, gradle_content)
|
94
|
+
Solara.logger.debug("Updated #{gradle_file} (#{@is_kotlin_gradle ? 'Kotlin' : 'Groovy'}) to use brand.properties")
|
95
|
+
end
|
96
|
+
|
97
|
+
def add_source_sets(gradle_file)
|
98
|
+
content = File.read(gradle_file)
|
99
|
+
|
100
|
+
source_sets_string = @source_sets.map { |dir| "\"#{dir}\"" }.join(', ')
|
101
|
+
kotlin_pattern = /(sourceSets\s*\{\s*getByName\s*\(\s*"main"\s*\)\s*\{\s*res\s*\.\s*srcDirs\s*\(.*?\)\s*\}\s*\})/m
|
102
|
+
groovy_pattern = /(sourceSets\s*\{\s*main\s*\{\s*res\s*\.\s*srcDirs\s*=.*?\s*\}\s*\})/m
|
103
|
+
|
104
|
+
new_config = generate_source_sets(source_sets_string)
|
105
|
+
|
106
|
+
modified_content = if @is_kotlin_gradle
|
107
|
+
if content.match?(kotlin_pattern)
|
108
|
+
content.gsub(kotlin_pattern) do |match|
|
109
|
+
indent = match[/^\s*/]
|
110
|
+
"#{indent}#{new_config.strip}"
|
111
|
+
end
|
112
|
+
else
|
113
|
+
content.sub(/(\s*android\s*\{)/) { "#{$1}\n #{new_config.strip}" }
|
114
|
+
end
|
115
|
+
else
|
116
|
+
if content.match?(groovy_pattern)
|
117
|
+
content.gsub(groovy_pattern) do |match|
|
118
|
+
indent = match[/^\s*/]
|
119
|
+
"#{indent}#{new_config.strip}"
|
120
|
+
end
|
121
|
+
else
|
122
|
+
content.sub(/(\s*android\s*\{)/) { "#{$1}\n #{new_config.strip}" }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
File.write(gradle_file, modified_content)
|
127
|
+
Solara.logger.debug("Source sets configuration updated successfully.")
|
128
|
+
end
|
129
|
+
|
130
|
+
def generate_source_sets(source_sets_string)
|
131
|
+
if @is_kotlin_gradle
|
132
|
+
<<-KOTLIN
|
133
|
+
sourceSets {
|
134
|
+
getByName("main") {
|
135
|
+
res.srcDirs(listOf(#{source_sets_string}))
|
136
|
+
}
|
137
|
+
}
|
138
|
+
KOTLIN
|
139
|
+
else
|
140
|
+
<<-GROOVY
|
141
|
+
sourceSets {
|
142
|
+
main {
|
143
|
+
res.srcDirs = [#{source_sets_string}]
|
144
|
+
}
|
145
|
+
}
|
146
|
+
GROOVY
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def update_keystore_config(gradle_file)
|
151
|
+
# We need to apply code signing only if the user has provided its config
|
152
|
+
path = FilePath.brand_signing(@brand_key, Platform::Android)
|
153
|
+
signing = JSON.parse(File.read(path))
|
154
|
+
if signing['storeFile'].empty?
|
155
|
+
return
|
156
|
+
end
|
157
|
+
|
158
|
+
content = File.read(gradle_file)
|
159
|
+
|
160
|
+
# Check if the configuration is already applied
|
161
|
+
if content.include?('brandProperties.getProperty("keystore.storeFile")') ||
|
162
|
+
content.include?('project.ext.brandProperties.getProperty("keystore.storeFile")')
|
163
|
+
Solara.logger.debug("Keystore configuration already applied. Skipping update.")
|
164
|
+
return
|
165
|
+
end
|
166
|
+
|
167
|
+
new_config = generate_keystore_config
|
168
|
+
|
169
|
+
signing_config_pattern = /(signingConfigs\s*\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\})/m
|
170
|
+
build_types_pattern = /(buildTypes\s*\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\})/m
|
171
|
+
|
172
|
+
modified_content = content.dup
|
173
|
+
|
174
|
+
# Remove existing buildTypes block if it exists
|
175
|
+
modified_content.gsub!(build_types_pattern, '')
|
176
|
+
|
177
|
+
# Update or add signingConfigs and buildTypes
|
178
|
+
if modified_content.match?(signing_config_pattern)
|
179
|
+
modified_content.gsub!(signing_config_pattern, new_config)
|
180
|
+
else
|
181
|
+
modified_content.sub!(/(\s*android\s*\{)/) { "#{$1}\n #{new_config.strip}" }
|
182
|
+
end
|
183
|
+
|
184
|
+
if content != modified_content
|
185
|
+
File.write(gradle_file, modified_content)
|
186
|
+
Solara.logger.debug("Keystore configuration updated successfully.")
|
187
|
+
else
|
188
|
+
Solara.logger.debug("No changes were necessary for keystore configuration.")
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def generate_keystore_config
|
193
|
+
if @is_kotlin_gradle
|
194
|
+
<<-KOTLIN
|
195
|
+
signingConfigs {
|
196
|
+
create("release") {
|
197
|
+
storeFile = file(brandProperties.getProperty("storeFile"))
|
198
|
+
storePassword = brandProperties.getProperty("storePassword")
|
199
|
+
keyAlias = brandProperties.getProperty("keyAlias")
|
200
|
+
keyPassword = brandProperties.getProperty("keyPassword")
|
201
|
+
}
|
202
|
+
}
|
203
|
+
buildTypes {
|
204
|
+
getByName("release") {
|
205
|
+
signingConfig = signingConfigs.getByName("release")
|
206
|
+
}
|
207
|
+
getByName("debug") {
|
208
|
+
isDebuggable = true
|
209
|
+
}
|
210
|
+
}
|
211
|
+
KOTLIN
|
212
|
+
else
|
213
|
+
<<-GROOVY
|
214
|
+
signingConfigs {
|
215
|
+
release {
|
216
|
+
storeFile file(project.ext.brandProperties.getProperty("storeFile"))
|
217
|
+
storePassword project.ext.brandProperties.getProperty("storePassword")
|
218
|
+
keyAlias project.ext.brandProperties.getProperty("keyAlias")
|
219
|
+
keyPassword project.ext.brandProperties.getProperty("keyPassword")
|
220
|
+
}
|
221
|
+
}
|
222
|
+
buildTypes {
|
223
|
+
release {
|
224
|
+
signingConfig signingConfigs.release
|
225
|
+
}
|
226
|
+
debug {
|
227
|
+
debuggable true
|
228
|
+
}
|
229
|
+
}
|
230
|
+
GROOVY
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class PropertiesGenerator
|
2
|
+
def initialize(brand_key)
|
3
|
+
@brand_key = brand_key
|
4
|
+
end
|
5
|
+
|
6
|
+
def generate
|
7
|
+
Solara.logger.start_step("Generate brand.properties for Android")
|
8
|
+
output_file = FilePath.android_generated_properties
|
9
|
+
content = "# Generated by Solara\n"
|
10
|
+
|
11
|
+
load_config.each do |key, value|
|
12
|
+
content << "#{key}=#{value}\n"
|
13
|
+
end
|
14
|
+
|
15
|
+
File.write(output_file, content)
|
16
|
+
Solara.logger.debug("🎉 Generated properties file: #{output_file}, content below ⬇️")
|
17
|
+
Solara.logger.debug("--------------\n#{content}--------------")
|
18
|
+
Solara.logger.end_step("Generate brand.properties for Android")
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_config
|
22
|
+
config_path = FilePath.android_brand_config(@brand_key)
|
23
|
+
signing_path = FilePath.brand_signing(@brand_key, Platform::Android)
|
24
|
+
config = JSON.parse(File.read(config_path))
|
25
|
+
signing_config = JSON.parse(File.read(signing_path))
|
26
|
+
|
27
|
+
config.merge!(signing_config)
|
28
|
+
|
29
|
+
config
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
Dir.glob("#{__dir__}/*.rb").each { |file| require file }
|
2
|
+
require 'singleton'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
class IOSFilePathManager
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@platform = SolaraSettingsManager.instance.platform
|
10
|
+
@project_root = SolaraSettingsManager.instance.project_root
|
11
|
+
end
|
12
|
+
|
13
|
+
def xcode_project
|
14
|
+
path = ProjectSettingsManager.instance.value('xcodeproj', Platform::IOS)
|
15
|
+
File.join(@project_root, path)
|
16
|
+
end
|
17
|
+
|
18
|
+
def xcode_project_directory
|
19
|
+
Pathname.new(xcode_project).parent.to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
def brand_assets(brand_key)
|
23
|
+
File.join(FilePath.brands, brand_key, FilePath.ios, 'assets')
|
24
|
+
end
|
25
|
+
|
26
|
+
def brand_xcconfig
|
27
|
+
File.join(artifacts, 'Brand.xcconfig')
|
28
|
+
end
|
29
|
+
|
30
|
+
def artifacts
|
31
|
+
case @platform
|
32
|
+
when Platform::Flutter
|
33
|
+
return File.join(@project_root, FilePath.ios, 'Flutter', 'Artifacts')
|
34
|
+
when Platform::IOS
|
35
|
+
return File.join(xcode_project_directory, 'Artifacts')
|
36
|
+
else
|
37
|
+
raise ArgumentError, "Invalid platform: #{@platform}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def info_plist
|
42
|
+
path = ProjectSettingsManager.instance.value('Info.plist', Platform::IOS)
|
43
|
+
File.join(@project_root, path)
|
44
|
+
end
|
45
|
+
|
46
|
+
def assets
|
47
|
+
path = ProjectSettingsManager.instance.value('Assets.xcassets', Platform::IOS)
|
48
|
+
File.join(@project_root, path)
|
49
|
+
end
|
50
|
+
|
51
|
+
def assets_directory
|
52
|
+
File.dirname(assets)
|
53
|
+
end
|
54
|
+
|
55
|
+
def assets_artifacts
|
56
|
+
File.join(assets_directory, 'Assets.xcassets', 'Artifacts')
|
57
|
+
end
|
58
|
+
|
59
|
+
def assets_artifcats
|
60
|
+
File.join(assets, 'Artifacts')
|
61
|
+
end
|
62
|
+
|
63
|
+
def app_xcconfig(name)
|
64
|
+
case @platform
|
65
|
+
when Platform::Flutter
|
66
|
+
return File.join(@project_root, FilePath.ios, 'Flutter', name)
|
67
|
+
when Platform::IOS
|
68
|
+
return File.join(xcode_project_directory, 'XCConfig', name)
|
69
|
+
else
|
70
|
+
raise ArgumentError, "Invalid platform: #{@platform}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def app_xcconfig_directory
|
75
|
+
Pathname.new(app_xcconfig('Debug.xcconfig')).parent.to_s
|
76
|
+
end
|
77
|
+
|
78
|
+
def brand_app_icon(brand_key)
|
79
|
+
File.join(FilePath.brands, brand_key, FilePath.ios, 'assets', 'AppIcon.appiconset')
|
80
|
+
end
|
81
|
+
|
82
|
+
def brand_app_icon_image(brand_key)
|
83
|
+
appicon_set_path = brand_app_icon(brand_key)
|
84
|
+
|
85
|
+
if appicon_set_path.nil?
|
86
|
+
raise "Error: AppIcon.appiconset not found for brand #{brand_key}"
|
87
|
+
end
|
88
|
+
|
89
|
+
contents_json_path = File.join(appicon_set_path, 'Contents.json')
|
90
|
+
|
91
|
+
unless File.exist?(contents_json_path)
|
92
|
+
raise "Error: Contents.json not found in AppIcon.appiconset for brand #{brand_key}"
|
93
|
+
end
|
94
|
+
|
95
|
+
contents = JSON.parse(File.read(contents_json_path))
|
96
|
+
|
97
|
+
largest_image = contents['images'].max_by do |img|
|
98
|
+
size = img['size'].scan(/(\d+)x(\d+)/).first&.map(&:to_i)
|
99
|
+
size ? size[0] * size[1] : 0
|
100
|
+
end
|
101
|
+
|
102
|
+
if largest_image
|
103
|
+
File.join(appicon_set_path, largest_image['filename'])
|
104
|
+
else
|
105
|
+
raise "No images found in Contents.json for brand #{brand_key}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'xcodeproj'
|
2
|
+
|
3
|
+
class IOSPlistManager
|
4
|
+
def initialize(project, info_plist_path)
|
5
|
+
@project = project
|
6
|
+
@info_plist_path = info_plist_path
|
7
|
+
end
|
8
|
+
|
9
|
+
def create_and_add_info_plist
|
10
|
+
# add_info_plist_to_project
|
11
|
+
set_info_plist_in_build_settings
|
12
|
+
save_project
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def add_info_plist_to_project
|
18
|
+
file_ref = @project.files.select { |f| f.path == @info_plist_path }.first
|
19
|
+
if file_ref
|
20
|
+
Solara.logger.debug("Info.plist file reference already exists in the project. Skipping this step.")
|
21
|
+
else
|
22
|
+
file_ref = XcodeProjectManager.new.add_single_file_to_group(@project, @project.main_group, @info_plist_path)
|
23
|
+
Solara.logger.debug("Info.plist file created. file_ref = #{file_ref}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_info_plist_in_build_settings
|
28
|
+
path = FileManager.get_relative_path(IOSFilePathManager.instance.xcode_project_directory, @info_plist_path)
|
29
|
+
main_target.build_configurations.each do |config|
|
30
|
+
config.build_settings['INFOPLIST_FILE'] = path
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def save_project
|
35
|
+
@project.save
|
36
|
+
end
|
37
|
+
|
38
|
+
def main_target
|
39
|
+
@project.targets.first
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class XcconfigGenerator
|
2
|
+
def initialize(brand_key)
|
3
|
+
@brand_key = brand_key
|
4
|
+
@platform = SolaraSettingsManager.instance.platform
|
5
|
+
end
|
6
|
+
|
7
|
+
def generate
|
8
|
+
Solara.logger.start_step("Generate Brand.xcconfig for iOS")
|
9
|
+
destination = IOSFilePathManager.instance.brand_xcconfig
|
10
|
+
content = generate_xcconfig_content
|
11
|
+
File.write(destination, content)
|
12
|
+
Solara.logger.debug("🎉 Generated #{IOSFilePathManager.instance.brand_xcconfig}. Content below ⬇️")
|
13
|
+
Solara.logger.debug("--------------\n#{content}--------------")
|
14
|
+
Solara.logger.end_step("Generate Brand.xcconfig for iOS")
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def generate_xcconfig_content
|
20
|
+
content = <<~XCCONFIG
|
21
|
+
ASSETCATALOG_COMPILER_APPICON_NAME = Artifacts/AppIcon
|
22
|
+
#{load_config.map { |key, value| "#{key.upcase} = #{value}" }.join("\n")}
|
23
|
+
XCCONFIG
|
24
|
+
|
25
|
+
case @platform
|
26
|
+
when Platform::Flutter
|
27
|
+
# "../Generated.xcconfig" is the config related to Flutter itself, we must include it here.
|
28
|
+
<<~XCCONFIG
|
29
|
+
#include "../Generated.xcconfig"
|
30
|
+
|
31
|
+
#{content}
|
32
|
+
XCCONFIG
|
33
|
+
when Platform::IOS
|
34
|
+
content
|
35
|
+
else
|
36
|
+
raise ArgumentError, "Invalid platform: #{@platform}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def load_config
|
41
|
+
config_path = FilePath.ios_config(@brand_key)
|
42
|
+
JSON.parse(File.read(config_path))
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class XcodeAssetManager
|
5
|
+
def initialize(asset_catalog_path)
|
6
|
+
@asset_catalog_path = asset_catalog_path
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
def add(source)
|
11
|
+
# Fetches only files in the specified source directory
|
12
|
+
Dir.glob(File.join(source, '*.{png,jpg,jpeg,svg,heic,pdf}')).each do |file_path|
|
13
|
+
if File.file?(file_path) # Ensure it's a file and not a directory
|
14
|
+
filename_without_extension = File.basename(file_path, File.extname(file_path))
|
15
|
+
add_image(filename_without_extension, file_path, '1x')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_image(image_name, image_path, scale = '1x')
|
21
|
+
# Create the image set directory if it doesn't exist
|
22
|
+
image_set_path = File.join(@asset_catalog_path, "#{image_name}.imageset")
|
23
|
+
FileUtils.mkdir_p(image_set_path)
|
24
|
+
|
25
|
+
# Copy the image file to the image set directory
|
26
|
+
destination_path = File.join(image_set_path, "#{image_name}@#{scale}.png")
|
27
|
+
FileUtils.cp(image_path, destination_path)
|
28
|
+
|
29
|
+
# Update or create the Contents.json file
|
30
|
+
contents_json_path = File.join(image_set_path, 'Contents.json')
|
31
|
+
contents = if File.exist?(contents_json_path)
|
32
|
+
JSON.parse(File.read(contents_json_path))
|
33
|
+
else
|
34
|
+
{ "images" => [], "info" => { "version" => 1, "author" => "solara" } }
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add or update the image entry
|
38
|
+
image_entry = {
|
39
|
+
"idiom" => "universal",
|
40
|
+
"filename" => "#{image_name}@#{scale}.png",
|
41
|
+
"scale" => scale
|
42
|
+
}
|
43
|
+
|
44
|
+
existing_entry = contents["images"].find { |img| img["scale"] == scale }
|
45
|
+
if existing_entry
|
46
|
+
existing_entry.merge!(image_entry)
|
47
|
+
else
|
48
|
+
contents["images"] << image_entry
|
49
|
+
end
|
50
|
+
|
51
|
+
# Write the updated Contents.json
|
52
|
+
File.write(contents_json_path, JSON.pretty_generate(contents))
|
53
|
+
|
54
|
+
puts "Image '#{image_name}' added successfully at scale #{scale}."
|
55
|
+
end
|
56
|
+
end
|