solara 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|