solara 0.6.0 → 0.7.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 +4 -4
- data/solara/lib/.DS_Store +0 -0
- data/solara/lib/core/.DS_Store +0 -0
- data/solara/lib/core/brands/brand_onboarder.rb +29 -25
- data/solara/lib/core/brands/brand_switcher.rb +1 -0
- data/solara/lib/core/dashboard/brand/BrandDetailController.js +1 -1
- data/solara/lib/core/dashboard/brand/SectionsFormManager.js +3 -3
- data/solara/lib/core/dashboard/brand/brand.html +2 -2
- data/solara/lib/core/dashboard/brand/source/BrandLocalSource.js +2 -2
- data/solara/lib/core/dashboard/brand/source/BrandRemoteSource.js +19 -3
- data/solara/lib/core/dashboard/handler/edit_section_handler.rb +5 -17
- data/solara/lib/core/doctor/schema/brand_configurations.json +2 -2
- data/solara/lib/core/doctor/validator/template/android_template_validation_config.yml +3 -3
- data/solara/lib/core/doctor/validator/template/flutter_template_validation_config.yml +2 -2
- data/solara/lib/core/doctor/validator/template/ios_template_validation_config.yml +3 -3
- data/solara/lib/core/scripts/brand_config_updater.rb +29 -0
- data/solara/lib/core/scripts/brand_configurations_manager.rb +76 -14
- data/solara/lib/core/scripts/brand_importer.rb +9 -20
- data/solara/lib/core/scripts/code_generator.rb +1 -1
- data/solara/lib/core/scripts/gitignore_manager.rb +2 -1
- data/solara/lib/core/scripts/json_manifest_processor.rb +1 -1
- data/solara/lib/core/scripts/resource_manifest_processor.rb +2 -1
- data/solara/lib/core/scripts/string_case.rb +18 -0
- data/solara/lib/core/template/.DS_Store +0 -0
- data/solara/lib/core/template/brands/json/json_manifest.json +1 -1
- data/solara/lib/core/template/config/android_template_config.json +6 -6
- data/solara/lib/core/template/config/flutter_template_config.json +4 -4
- data/solara/lib/core/template/config/ios_template_config.json +6 -6
- data/solara/lib/core/template/configurations.json +34 -21
- data/solara/lib/core/template/project_template_generator.rb +119 -13
- data/solara/lib/solara/version.rb +1 -1
- data/solara/lib/solara_manager.rb +5 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee9572ad8162826e8ebd8ed6b9f5e6a0ba5c2ef23ecec56ed610206becc3e80f
|
4
|
+
data.tar.gz: d0cfbf2ba68d160fdf531f3148e216662063d2a9182c341ea22e1266e283bcf5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e2e2c181701aa9af6099694beedc3f68e926446e8a1d67b4841e66b5ecf1da8fad76c9b479a52738f0bf7a48551328b7058c0bbafcc1574a68b9b388b37035a
|
7
|
+
data.tar.gz: e3e2d47d57ed66593435bb6294013c99711177bfadb4ae25ea74d2ebb1d511abc0eb98dd7fa9f4579106cf7ec26cf76393294b7d4fd9b6c89aa0c679343ef8e3
|
data/solara/lib/.DS_Store
CHANGED
Binary file
|
data/solara/lib/core/.DS_Store
CHANGED
Binary file
|
@@ -5,43 +5,47 @@ Dir[File.expand_path('platform/ios/*.rb', __dir__)].each { |file| require_relati
|
|
5
5
|
Dir[File.expand_path('platform/flutter/*.rb', __dir__)].each { |file| require_relative file }
|
6
6
|
|
7
7
|
class BrandOnboarder
|
8
|
-
def initialize(brand_key, brand_name, clone_brand_key: nil)
|
9
|
-
@brand_key = brand_key
|
10
|
-
@brand_name = brand_name
|
11
|
-
@clone_brand_key = clone_brand_key
|
12
|
-
end
|
13
8
|
|
14
|
-
def onboard
|
15
|
-
if
|
16
|
-
|
9
|
+
def onboard(brand_key, brand_name, clone_brand_key: nil)
|
10
|
+
if clone_brand_key.nil? || clone_brand_key.strip.empty?
|
11
|
+
generate_from_template(brand_key)
|
17
12
|
else
|
18
|
-
clone_brand
|
13
|
+
clone_brand(brand_key, clone_brand_key: clone_brand_key)
|
19
14
|
end
|
20
|
-
add_to_brands_list
|
15
|
+
add_to_brands_list(brand_key, brand_name)
|
21
16
|
end
|
22
17
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
template_dir = FilePath.template_brands
|
27
|
-
target_dir = FilePath.brand(@brand_key)
|
28
|
-
config_file = FilePath.template_config
|
29
|
-
|
30
|
-
generator = ProjectTemplateGenerator.new(template_dir, target_dir, config_file)
|
31
|
-
generator.create_project
|
18
|
+
def sync_with_template(brand_key)
|
19
|
+
generator = template_generator(brand_key)
|
20
|
+
generator.sync_with_template
|
32
21
|
end
|
33
22
|
|
34
|
-
def clone_brand
|
35
|
-
Solara.logger.debug("Cloning #{
|
36
|
-
source = FilePath.brand(
|
37
|
-
destination = FilePath.brand(
|
23
|
+
def clone_brand(brand_key, clone_brand_key:)
|
24
|
+
Solara.logger.debug("Cloning #{clone_brand_key} to #{brand_key}")
|
25
|
+
source = FilePath.brand(clone_brand_key)
|
26
|
+
destination = FilePath.brand(brand_key)
|
38
27
|
|
39
28
|
FileManager.delete_if_exists(destination)
|
40
29
|
FolderCopier.new(source, destination).copy
|
41
30
|
end
|
42
31
|
|
43
|
-
def add_to_brands_list
|
44
|
-
BrandsManager.instance.add_brand(
|
32
|
+
def add_to_brands_list(brand_key, brand_name)
|
33
|
+
BrandsManager.instance.add_brand(brand_name, brand_key)
|
34
|
+
end
|
35
|
+
|
36
|
+
def generate_from_template(brand_key)
|
37
|
+
Solara.logger.debug("Onboarding #{brand_key} from template.")
|
38
|
+
|
39
|
+
generator = template_generator(brand_key)
|
40
|
+
generator.create_project
|
41
|
+
end
|
42
|
+
|
43
|
+
def template_generator(brand_key)
|
44
|
+
template_dir = FilePath.template_brands
|
45
|
+
target_dir = FilePath.brand(brand_key)
|
46
|
+
config_file = FilePath.template_config
|
47
|
+
|
48
|
+
ProjectTemplateGenerator.new(template_dir, target_dir, config_file)
|
45
49
|
end
|
46
50
|
|
47
51
|
end
|
@@ -158,7 +158,7 @@ class BrandDetailController {
|
|
158
158
|
if (this.model.isRemote()) return
|
159
159
|
|
160
160
|
try {
|
161
|
-
await this.model.saveSection(container.dataset.
|
161
|
+
await this.model.saveSection(container.dataset.filename, section.content);
|
162
162
|
await this.checkBrandHealth();
|
163
163
|
if (this.model.isCurrentBrand) {
|
164
164
|
await this.switchToBrand(true)
|
@@ -22,7 +22,7 @@ class SectionsFormManager {
|
|
22
22
|
const sectionElement = document.createElement('div');
|
23
23
|
sectionElement.className = 'section';
|
24
24
|
|
25
|
-
sectionElement.dataset.
|
25
|
+
sectionElement.dataset.filename = section.filename
|
26
26
|
sectionElement.dataset.name = section.name
|
27
27
|
|
28
28
|
const titleContainer = document.createElement('div');
|
@@ -35,7 +35,7 @@ class SectionsFormManager {
|
|
35
35
|
|
36
36
|
sectionElement.appendChild(titleContainer);
|
37
37
|
|
38
|
-
sectionElement.id = section.
|
38
|
+
sectionElement.id = section.filename;
|
39
39
|
|
40
40
|
this.sectionsContainer.appendChild(sectionElement);
|
41
41
|
|
@@ -61,7 +61,7 @@ class SectionItemManager {
|
|
61
61
|
displayJSONCards() {
|
62
62
|
let cardContent = this.container.querySelector('.card-content')
|
63
63
|
if (cardContent !== null) this.container.innerHTML = ''
|
64
|
-
this.container.appendChild(this.createCard(this.section.content, 'root', null, this.section.
|
64
|
+
this.container.appendChild(this.createCard(this.section.content, 'root', null, this.section.filename));
|
65
65
|
}
|
66
66
|
|
67
67
|
createCard(obj, key, parent, cardTitle) {
|
@@ -90,7 +90,7 @@
|
|
90
90
|
.right {
|
91
91
|
width: 15%;
|
92
92
|
position: fixed;
|
93
|
-
max-height:
|
93
|
+
max-height: 95%;
|
94
94
|
overflow-y: auto;
|
95
95
|
padding: 7px;
|
96
96
|
right: 0;
|
@@ -253,7 +253,7 @@
|
|
253
253
|
#error-button {
|
254
254
|
position: fixed;
|
255
255
|
bottom: 14px;
|
256
|
-
|
256
|
+
left: 14px;
|
257
257
|
background-color: #ff4136;
|
258
258
|
color: white;
|
259
259
|
border: none;
|
@@ -43,13 +43,13 @@ class BrandLocalSource {
|
|
43
43
|
}
|
44
44
|
}
|
45
45
|
|
46
|
-
async saveSection(
|
46
|
+
async saveSection(filename, configuration, brandKey) {
|
47
47
|
if (this.savingInProgress) return;
|
48
48
|
this.savingInProgress = true;
|
49
49
|
|
50
50
|
const dataToSend = {
|
51
51
|
brand_key: brandKey,
|
52
|
-
|
52
|
+
filename: filename,
|
53
53
|
data: configuration
|
54
54
|
};
|
55
55
|
|
@@ -5,7 +5,7 @@ class BrandRemoteSource {
|
|
5
5
|
}
|
6
6
|
|
7
7
|
async createNewBrandConfigurations() {
|
8
|
-
const url = 'https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/
|
8
|
+
const url = 'https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/main/solara/lib/core/template/configurations.json';
|
9
9
|
|
10
10
|
try {
|
11
11
|
const response = await fetch(url);
|
@@ -21,8 +21,8 @@ class BrandRemoteSource {
|
|
21
21
|
}
|
22
22
|
const content = await contentResponse.json();
|
23
23
|
return {
|
24
|
-
|
25
|
-
name: config.
|
24
|
+
filename: config.filename,
|
25
|
+
name: this.snakeToCapitalizedSpaced(config.filename, 'ios'),
|
26
26
|
content: content
|
27
27
|
};
|
28
28
|
});
|
@@ -36,6 +36,22 @@ class BrandRemoteSource {
|
|
36
36
|
}
|
37
37
|
}
|
38
38
|
|
39
|
+
// TODO: should be ecnapsulated
|
40
|
+
snakeToCapitalizedSpaced(
|
41
|
+
snakeCaseString,
|
42
|
+
exclude = '',
|
43
|
+
transform = (item) => item.charAt(0).toUpperCase() + item.slice(1)
|
44
|
+
) {
|
45
|
+
// Split by underscores
|
46
|
+
const parts = snakeCaseString.split('_').map(item => {
|
47
|
+
// Return the item as-is if it matches the exclude value
|
48
|
+
return item === exclude ? item : transform(item);
|
49
|
+
});
|
50
|
+
|
51
|
+
// Join the parts with a space
|
52
|
+
return parts.join(' ');
|
53
|
+
}
|
54
|
+
|
39
55
|
async getBrandConfigurationsJsonFromDirectory(dirHandle) {
|
40
56
|
const schema = await this.fetchBrandConfigurationsSchema();
|
41
57
|
|
@@ -7,14 +7,14 @@ class EditSectionHandler < BaseHandler
|
|
7
7
|
begin
|
8
8
|
request_payload = JSON.parse(req.body)
|
9
9
|
brand_key = request_payload['brand_key']
|
10
|
-
|
10
|
+
filename = request_payload['filename']
|
11
11
|
data = request_payload['data']
|
12
12
|
|
13
|
-
update_section(
|
13
|
+
update_section(filename, data, brand_key)
|
14
14
|
set_current_brand_content_changed(brand_key, true)
|
15
15
|
|
16
16
|
res.status = 200
|
17
|
-
res.body = JSON.generate({ success: true, message: "Configuration for #{
|
17
|
+
res.body = JSON.generate({ success: true, message: "Configuration for #{filename} updated successfully" })
|
18
18
|
res.content_type = 'application/json'
|
19
19
|
rescue JSON::ParserError => e
|
20
20
|
handle_error(res, e, "Invalid JSON in request body", 400)
|
@@ -27,20 +27,8 @@ class EditSectionHandler < BaseHandler
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
def update_section(
|
31
|
-
|
32
|
-
|
33
|
-
path = template[:path]
|
34
|
-
|
35
|
-
if File.exist?(path)
|
36
|
-
File.write(path, JSON.pretty_generate(data))
|
37
|
-
Solara.logger.debug("Updated Config for #{path}: #{data}")
|
38
|
-
else
|
39
|
-
raise "Config file not found: #{path}"
|
40
|
-
end
|
41
|
-
rescue StandardError => e
|
42
|
-
Solara.logger.failure("Error updating section: #{e.message}")
|
43
|
-
raise
|
30
|
+
def update_section(filename, data, brand_key)
|
31
|
+
BrandConfigUpdater.new.update(filename, data, brand_key)
|
44
32
|
end
|
45
33
|
|
46
34
|
def set_current_brand_content_changed(brand_key, changed)
|
@@ -28,7 +28,7 @@ structure:
|
|
28
28
|
json:
|
29
29
|
type: directory
|
30
30
|
contents:
|
31
|
-
|
31
|
+
android_json_manifest.json:
|
32
32
|
type: file
|
33
33
|
validations:
|
34
34
|
- type: valid_json
|
@@ -63,7 +63,7 @@ structure:
|
|
63
63
|
json:
|
64
64
|
type: directory
|
65
65
|
contents:
|
66
|
-
|
66
|
+
ios_json_manifest.json:
|
67
67
|
type: file
|
68
68
|
validations:
|
69
69
|
- type: valid_json
|
@@ -94,7 +94,7 @@ structure:
|
|
94
94
|
json:
|
95
95
|
type: directory
|
96
96
|
contents:
|
97
|
-
|
97
|
+
global_json_manifest.json:
|
98
98
|
type: file
|
99
99
|
validations:
|
100
100
|
- type: valid_json
|
@@ -5,7 +5,7 @@ structure:
|
|
5
5
|
json:
|
6
6
|
type: directory
|
7
7
|
contents:
|
8
|
-
|
8
|
+
flutter_json_manifest.json:
|
9
9
|
type: file
|
10
10
|
validations:
|
11
11
|
- type: valid_json
|
@@ -89,7 +89,7 @@ structure:
|
|
89
89
|
json:
|
90
90
|
type: directory
|
91
91
|
contents:
|
92
|
-
|
92
|
+
global_json_manifest.json:
|
93
93
|
type: file
|
94
94
|
validations:
|
95
95
|
- type: valid_json
|
@@ -28,7 +28,7 @@ structure:
|
|
28
28
|
json:
|
29
29
|
type: directory
|
30
30
|
contents:
|
31
|
-
|
31
|
+
android_json_manifest.json:
|
32
32
|
type: file
|
33
33
|
validations:
|
34
34
|
- type: valid_json
|
@@ -63,7 +63,7 @@ structure:
|
|
63
63
|
json:
|
64
64
|
type: directory
|
65
65
|
contents:
|
66
|
-
|
66
|
+
ios_json_manifest.json:
|
67
67
|
type: file
|
68
68
|
validations:
|
69
69
|
- type: valid_json
|
@@ -94,7 +94,7 @@ structure:
|
|
94
94
|
json:
|
95
95
|
type: directory
|
96
96
|
contents:
|
97
|
-
|
97
|
+
global_json_manifest.json:
|
98
98
|
type: file
|
99
99
|
validations:
|
100
100
|
- type: valid_json
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class BrandConfigUpdater
|
2
|
+
|
3
|
+
def update(filename, data, brand_key)
|
4
|
+
template = BrandConfigurationsManager.new(brand_key).template_with_filename(filename)
|
5
|
+
if template.nil?
|
6
|
+
Solara.logger.debug("Can't find tempate for filename: #{filename}, with brand key: #{brand_key}")
|
7
|
+
return
|
8
|
+
end
|
9
|
+
|
10
|
+
path = template[:path]
|
11
|
+
json = JSON.pretty_generate(data)
|
12
|
+
|
13
|
+
begin
|
14
|
+
# Check if the file exists
|
15
|
+
if File.exist?(path)
|
16
|
+
# If it exists, update the file
|
17
|
+
File.write(path, json)
|
18
|
+
Solara.logger.debug("Updated Config for #{path}: #{data}")
|
19
|
+
else
|
20
|
+
# If it doesn't exist, create the file with the data
|
21
|
+
File.write(path, json)
|
22
|
+
Solara.logger.debug("Created Config for #{path}: #{data}")
|
23
|
+
end
|
24
|
+
rescue StandardError => e
|
25
|
+
Solara.logger.failure("Error updating #{brand_key} config file #{path}: #{e.message}")
|
26
|
+
raise
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -4,19 +4,12 @@ class BrandConfigurationsManager
|
|
4
4
|
@brand_key = brand_key
|
5
5
|
end
|
6
6
|
|
7
|
-
def
|
8
|
-
templates.select { |
|
7
|
+
def template_with_filename(filename)
|
8
|
+
templates.select { |template| template[:filename] === filename }.first
|
9
9
|
end
|
10
10
|
|
11
11
|
def templates
|
12
|
-
|
13
|
-
configurations.map do |config|
|
14
|
-
{
|
15
|
-
key: config['key'],
|
16
|
-
name: config['name'],
|
17
|
-
path: "#{FilePath.brand(@brand_key)}/#{config['filePath']}"
|
18
|
-
}
|
19
|
-
end
|
12
|
+
TemplateManager.new(@brand_key).templates
|
20
13
|
end
|
21
14
|
|
22
15
|
def create
|
@@ -24,18 +17,87 @@ class BrandConfigurationsManager
|
|
24
17
|
|
25
18
|
config_templates.map do |template|
|
26
19
|
create_config_item(
|
27
|
-
template[:
|
20
|
+
template[:filename],
|
28
21
|
template[:name],
|
29
22
|
template[:path]
|
30
23
|
)
|
31
24
|
end
|
32
25
|
end
|
33
26
|
|
34
|
-
def create_config_item(
|
27
|
+
def create_config_item(filename, name, path)
|
35
28
|
{
|
36
|
-
|
29
|
+
filename: filename,
|
37
30
|
name: name,
|
38
31
|
content: JSON.parse(File.read(path)),
|
39
32
|
}
|
40
33
|
end
|
41
|
-
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class TemplateManager
|
37
|
+
|
38
|
+
def initialize(brand_key)
|
39
|
+
@brand_key = brand_key
|
40
|
+
end
|
41
|
+
|
42
|
+
def templates
|
43
|
+
result = parse_configurations
|
44
|
+
collect_json_templates.each do |template|
|
45
|
+
result << template unless result.any? { |r| r['filename'] == template['filename'] }
|
46
|
+
end
|
47
|
+
result.compact
|
48
|
+
rescue StandardError => e
|
49
|
+
Solara.logger.error("Failed to generate templates: #{e.message}")
|
50
|
+
[]
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def parse_configurations
|
56
|
+
configurations = JSON.parse(File.read(FilePath.brand_configurations))['configurations']
|
57
|
+
configurations.map do |config|
|
58
|
+
path = build_path(config['filePath'])
|
59
|
+
next unless File.exist?(path)
|
60
|
+
|
61
|
+
filename_without_extension = File.basename(config['filename'], '.json')
|
62
|
+
|
63
|
+
{
|
64
|
+
filename: File.basename(config['filename']),
|
65
|
+
name: StringCase.snake_to_capitalized_spaced(filename_without_extension, exclude: "ios"),
|
66
|
+
path: path
|
67
|
+
}
|
68
|
+
end
|
69
|
+
rescue StandardError => e
|
70
|
+
Solara.logger.error("Failed to parse configurations: #{e.message}")
|
71
|
+
[]
|
72
|
+
end
|
73
|
+
|
74
|
+
def collect_json_templates
|
75
|
+
directories = [
|
76
|
+
FilePath.brand_global_json_dir,
|
77
|
+
FilePath.brand_json_dir(@brand_key)
|
78
|
+
]
|
79
|
+
|
80
|
+
directories.flat_map do |dir|
|
81
|
+
get_json_files(dir).map do |file|
|
82
|
+
filename_without_extension = File.basename(file, '.json')
|
83
|
+
|
84
|
+
{
|
85
|
+
filename: File.basename(file),
|
86
|
+
name: StringCase.snake_to_capitalized_spaced(filename_without_extension, exclude: "ios"),
|
87
|
+
path: file
|
88
|
+
}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def get_json_files(dir)
|
94
|
+
Dir.glob(File.join(dir, '**', '*.json'))
|
95
|
+
rescue StandardError => e
|
96
|
+
Solara.logger.error("Error reading directory #{dir}: #{e.message}")
|
97
|
+
[]
|
98
|
+
end
|
99
|
+
|
100
|
+
def build_path(file_path)
|
101
|
+
"#{FilePath.brand(@brand_key)}/#{file_path}"
|
102
|
+
end
|
103
|
+
end
|
@@ -26,8 +26,8 @@ class BrandImporter
|
|
26
26
|
validate_json(configurations_path)
|
27
27
|
validate_json_schema(configurations_path)
|
28
28
|
|
29
|
-
|
30
|
-
brand =
|
29
|
+
configurations = JSON.parse(File.read(configurations_path))
|
30
|
+
brand = configurations['brand']
|
31
31
|
brand_key = brand['key'] # Ensure to use 'key' instead of 'brand_key'
|
32
32
|
|
33
33
|
exists = BrandsManager.instance.exists(brand_key)
|
@@ -35,32 +35,21 @@ class BrandImporter
|
|
35
35
|
SolaraManager.new.onboard(brand_key, brand['name'], open_dashboard: false)
|
36
36
|
end
|
37
37
|
|
38
|
-
|
38
|
+
SolaraManager.new.sync_brand_with_template(brand_key) if exists
|
39
|
+
update_brand(brand_key, configurations)
|
39
40
|
|
40
41
|
message_suffix = exists ? "The existing brand '#{brand_key}' has been updated." : "A new brand with the key '#{brand_key}' has been onboarded."
|
41
42
|
Solara.logger.success("Successfully imported (#{configurations_path}). #{message_suffix}")
|
42
43
|
end
|
43
44
|
|
44
|
-
def update_brand(brand_key,
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
file_path = find_file_in_subdirectories(brand_path, file_name)
|
50
|
-
|
51
|
-
# Create or replace the contents of the configuration file
|
52
|
-
if file_path
|
53
|
-
File.write(file_path, JSON.pretty_generate(configuration['content']))
|
54
|
-
else
|
55
|
-
Solara.logger.failure("File #{file_name} not found in #{brand_path}, ignoring importing it!")
|
56
|
-
end
|
45
|
+
def update_brand(brand_key, configurations)
|
46
|
+
configurations['configurations'].each do |configuration|
|
47
|
+
filename = configuration['filename']
|
48
|
+
data = configuration['content']
|
49
|
+
BrandConfigUpdater.new.update(filename, data, brand_key)
|
57
50
|
end
|
58
51
|
end
|
59
52
|
|
60
|
-
def find_file_in_subdirectories(base_path, file_name)
|
61
|
-
Dir.glob(File.join(base_path, '**', file_name)).first
|
62
|
-
end
|
63
|
-
|
64
53
|
def validate_json(configurations_path)
|
65
54
|
begin
|
66
55
|
JsonFileValidator.new([configurations_path]).validate
|
@@ -27,11 +27,12 @@ class GitignoreManager
|
|
27
27
|
|
28
28
|
def add_items(items)
|
29
29
|
items.each do |item|
|
30
|
-
add_item(FileManager.get_relative_path_to_root(item))
|
30
|
+
add_item(item.start_with?('/') ? item : FileManager.get_relative_path_to_root(item))
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
34
|
def add_item(item)
|
35
|
+
puts item
|
35
36
|
existing_items = read_gitignore
|
36
37
|
|
37
38
|
if existing_items.include?(item)
|
@@ -19,7 +19,7 @@ class JsonManifestProcessor
|
|
19
19
|
private
|
20
20
|
|
21
21
|
def read_manifest
|
22
|
-
manifest_path = File.join(@json_path,
|
22
|
+
manifest_path = File.join(@json_path, "#{SolaraSettingsManager.instance.platform}_json_manifest.json")
|
23
23
|
JSON.parse(File.read(manifest_path))
|
24
24
|
rescue JSON::ParserError => e
|
25
25
|
Solara.logger.debug("Error parsing manifest JSON: #{e.message}")
|
@@ -89,7 +89,8 @@ class ResourceManifestProcessor
|
|
89
89
|
|
90
90
|
def git_ignore(files)
|
91
91
|
files.each do |file|
|
92
|
-
|
92
|
+
path = FileManager.get_relative_path_to_root(file)
|
93
|
+
GitignoreManager.new(FilePath.project_root).add_items(["/#{path}"])
|
93
94
|
end
|
94
95
|
end
|
95
96
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class StringCase
|
2
|
+
|
3
|
+
def self.capitalize(string)
|
4
|
+
"#{string[0].upcase}#{string[1..-1]}"
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.snake_to_capitalized_spaced(snake_case_string, exclude: '', transform: ->(item) { StringCase.capitalize(item) })
|
8
|
+
# Split by underscores, then apply the transformation to each part
|
9
|
+
parts = snake_case_string.split('_').map do |item|
|
10
|
+
# Return the item as-is if it matches the exclude value
|
11
|
+
item == exclude ? item : transform.call(item)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Join the parts with a space
|
15
|
+
parts.join(' ')
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
Binary file
|
@@ -72,22 +72,22 @@
|
|
72
72
|
},
|
73
73
|
{
|
74
74
|
"source": "json/json_manifest.json",
|
75
|
-
"target": "android/json/",
|
75
|
+
"target": "android/json/android_json_manifest.json",
|
76
76
|
"condition": "true"
|
77
77
|
},
|
78
78
|
{
|
79
79
|
"source": "json/json_manifest.json",
|
80
|
-
"target": "ios/json/",
|
80
|
+
"target": "ios/json/ios_json_manifest.json",
|
81
81
|
"condition": "true"
|
82
82
|
},
|
83
83
|
{
|
84
84
|
"source": "json/Json-Manifest.md",
|
85
|
-
"target": "android/json/",
|
85
|
+
"target": "android/json/Json-Manifest.md",
|
86
86
|
"condition": "true"
|
87
87
|
},
|
88
88
|
{
|
89
89
|
"source": "json/Json-Manifest.md",
|
90
|
-
"target": "ios/json/",
|
90
|
+
"target": "ios/json/Json-Manifest.md",
|
91
91
|
"condition": "true"
|
92
92
|
},
|
93
93
|
{
|
@@ -97,12 +97,12 @@
|
|
97
97
|
},
|
98
98
|
{
|
99
99
|
"source": "json/json_manifest.json",
|
100
|
-
"target": "../../global/json",
|
100
|
+
"target": "../../global/json/global_json_manifest.json",
|
101
101
|
"condition": "true"
|
102
102
|
},
|
103
103
|
{
|
104
104
|
"source": "json/Json-Manifest.md",
|
105
|
-
"target": "../../global/json",
|
105
|
+
"target": "../../global/json/Json-Manifest.md",
|
106
106
|
"condition": "true"
|
107
107
|
},
|
108
108
|
{
|
@@ -72,12 +72,12 @@
|
|
72
72
|
},
|
73
73
|
{
|
74
74
|
"source": "json/json_manifest.json",
|
75
|
-
"target": "flutter/json/",
|
75
|
+
"target": "flutter/json/flutter_json_manifest.json",
|
76
76
|
"condition": "true"
|
77
77
|
},
|
78
78
|
{
|
79
79
|
"source": "json/Json-Manifest.md",
|
80
|
-
"target": "flutter/json/",
|
80
|
+
"target": "flutter/json/Json-Manifest.md",
|
81
81
|
"condition": "true"
|
82
82
|
},
|
83
83
|
{
|
@@ -87,12 +87,12 @@
|
|
87
87
|
},
|
88
88
|
{
|
89
89
|
"source": "json/json_manifest.json",
|
90
|
-
"target": "../../global/json",
|
90
|
+
"target": "../../global/json/global_json_manifest.json",
|
91
91
|
"condition": "true"
|
92
92
|
},
|
93
93
|
{
|
94
94
|
"source": "json/Json-Manifest.md",
|
95
|
-
"target": "../../global/json",
|
95
|
+
"target": "../../global/json/Json-Manifest.md",
|
96
96
|
"condition": "true"
|
97
97
|
},
|
98
98
|
{
|
@@ -72,22 +72,22 @@
|
|
72
72
|
},
|
73
73
|
{
|
74
74
|
"source": "json/json_manifest.json",
|
75
|
-
"target": "android/json/",
|
75
|
+
"target": "android/json/android_json_manifest.json",
|
76
76
|
"condition": "true"
|
77
77
|
},
|
78
78
|
{
|
79
79
|
"source": "json/json_manifest.json",
|
80
|
-
"target": "ios/json/",
|
80
|
+
"target": "ios/json/ios_json_manifest.json",
|
81
81
|
"condition": "true"
|
82
82
|
},
|
83
83
|
{
|
84
84
|
"source": "json/Json-Manifest.md",
|
85
|
-
"target": "android/json/",
|
85
|
+
"target": "android/json/Json-Manifest.md",
|
86
86
|
"condition": "true"
|
87
87
|
},
|
88
88
|
{
|
89
89
|
"source": "json/Json-Manifest.md",
|
90
|
-
"target": "ios/json/",
|
90
|
+
"target": "ios/json/Json-Manifest.md",
|
91
91
|
"condition": "true"
|
92
92
|
},
|
93
93
|
{
|
@@ -97,12 +97,12 @@
|
|
97
97
|
},
|
98
98
|
{
|
99
99
|
"source": "json/json_manifest.json",
|
100
|
-
"target": "../../global/json",
|
100
|
+
"target": "../../global/json/global_json_manifest.json",
|
101
101
|
"condition": "true"
|
102
102
|
},
|
103
103
|
{
|
104
104
|
"source": "json/Json-Manifest.md",
|
105
|
-
"target": "../../global/json",
|
105
|
+
"target": "../../global/json/Json-Manifest.md",
|
106
106
|
"condition": "true"
|
107
107
|
},
|
108
108
|
{
|
@@ -1,46 +1,59 @@
|
|
1
1
|
{
|
2
2
|
"configurations": [
|
3
3
|
{
|
4
|
-
"
|
5
|
-
"name": "Brand Configuration",
|
4
|
+
"filename": "brand_config.json",
|
6
5
|
"filePath": "shared/brand_config.json",
|
7
|
-
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/
|
6
|
+
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/main/solara/lib/core/template/brands/shared/brand_config.json"
|
8
7
|
},
|
9
8
|
{
|
10
|
-
"
|
11
|
-
"name": "Theme Configuration",
|
9
|
+
"filename": "theme.json",
|
12
10
|
"filePath": "shared/theme.json",
|
13
|
-
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/
|
11
|
+
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/main/solara/lib/core/template/brands/shared/theme.json"
|
14
12
|
},
|
15
13
|
{
|
16
|
-
"
|
17
|
-
"name": "Android Configuration",
|
14
|
+
"filename": "android_config.json",
|
18
15
|
"filePath": "android/android_config.json",
|
19
|
-
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/
|
16
|
+
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/main/solara/lib/core/template/brands/android/android_config.json"
|
20
17
|
},
|
21
18
|
{
|
22
|
-
"
|
23
|
-
"name": "Android Signing",
|
19
|
+
"filename": "android_signing.json",
|
24
20
|
"filePath": "android/android_signing.json",
|
25
|
-
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/
|
21
|
+
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/main/solara/lib/core/template/brands/android/android_signing.json"
|
26
22
|
},
|
27
23
|
{
|
28
|
-
"
|
29
|
-
"name": "iOS Configuration",
|
24
|
+
"filename": "ios_config.json",
|
30
25
|
"filePath": "ios/ios_config.json",
|
31
|
-
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/
|
26
|
+
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/main/solara/lib/core/template/brands/ios/ios_config.json"
|
32
27
|
},
|
33
28
|
{
|
34
|
-
"
|
35
|
-
"name": "iOS Signing",
|
29
|
+
"filename": "ios_signing.json",
|
36
30
|
"filePath": "ios/ios_signing.json",
|
37
|
-
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/
|
31
|
+
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/main/solara/lib/core/template/brands/ios/ios_signing.json"
|
38
32
|
},
|
39
33
|
{
|
40
|
-
"
|
41
|
-
"name": "InfoPlist.xcstrings",
|
34
|
+
"filename": "InfoPlist.xcstrings",
|
42
35
|
"filePath": "ios/InfoPlist.xcstrings",
|
43
|
-
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/
|
36
|
+
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/main/solara/lib/core/template/brands/ios/InfoPlist.xcstrings"
|
37
|
+
},
|
38
|
+
{
|
39
|
+
"filename": "ios_json_manifest.json",
|
40
|
+
"filePath": "ios/json/ios_json_manifest.json",
|
41
|
+
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/main/solara/lib/core/template/brands/json/json_manifest.json"
|
42
|
+
},
|
43
|
+
{
|
44
|
+
"filename": "android_json_manifest.json",
|
45
|
+
"filePath": "android/json/android_json_manifest.json",
|
46
|
+
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/main/solara/lib/core/template/brands/json/json_manifest.json"
|
47
|
+
},
|
48
|
+
{
|
49
|
+
"filename": "flutter_json_manifest.json",
|
50
|
+
"filePath": "flutter/json/flutter_json_manifest.json",
|
51
|
+
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/main/solara/lib/core/template/brands/json/json_manifest.json"
|
52
|
+
},
|
53
|
+
{
|
54
|
+
"filename": "global_json_manifest.json",
|
55
|
+
"filePath": "../../global/json/global_json_manifest.json",
|
56
|
+
"url": "https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/main/solara/lib/core/template/brands/json/json_manifest.json"
|
44
57
|
}
|
45
58
|
]
|
46
59
|
}
|
@@ -7,28 +7,125 @@ class ProjectTemplateGenerator
|
|
7
7
|
@target_dir = target_dir
|
8
8
|
@config_file = config_file
|
9
9
|
@config = read_config
|
10
|
+
@file_mappings = build_file_mappings
|
10
11
|
end
|
11
12
|
|
12
13
|
def create_project
|
13
14
|
@config["files"].each do |file|
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
next unless evaluate_condition(file["condition"], @config["variables"])
|
16
|
+
source_path = File.join(@template_dir, file["source"])
|
17
|
+
target_path = File.join(@target_dir, file["target"])
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
19
|
+
if file["source"].nil? || file["source"].empty? || !File.exist?(source_path)
|
20
|
+
# Create the target directory if no source path is provided
|
21
|
+
FileUtils.mkdir_p(target_path)
|
22
|
+
else
|
23
|
+
copy_content = file.fetch("copy_content", true)
|
24
|
+
copy_item(source_path, target_path, copy_content, file)
|
25
|
+
replace_variables(target_path, @config["variables"]) if copy_content
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def sync_with_template
|
31
|
+
@config["files"].each do |file|
|
32
|
+
next unless evaluate_condition(file["condition"], @config["variables"])
|
33
|
+
next if file["source"].nil? || file["source"].empty?
|
34
|
+
|
35
|
+
source_path = File.join(@template_dir, file["source"])
|
36
|
+
target_path = File.join(@target_dir, file["target"])
|
37
|
+
|
38
|
+
next unless File.exist?(source_path)
|
39
|
+
|
40
|
+
if File.directory?(source_path)
|
41
|
+
FileUtils.mkdir_p(target_path) unless Dir.exist?(target_path)
|
42
|
+
sync_directory(source_path, target_path, file)
|
43
|
+
elsif !File.exist?(target_path) && should_copy_path?(source_path)
|
44
|
+
FileUtils.mkdir_p(File.dirname(target_path))
|
45
|
+
FileUtils.cp(source_path, target_path)
|
46
|
+
copy_content = file.fetch("copy_content", true)
|
47
|
+
replace_variables(target_path, @config["variables"]) if copy_content
|
26
48
|
end
|
27
49
|
end
|
28
50
|
end
|
29
51
|
|
30
52
|
private
|
31
53
|
|
54
|
+
# This method:
|
55
|
+
# - Creates a hash of source paths to their targets
|
56
|
+
# - Removes leading slashes for consistency
|
57
|
+
# - Handles directories differently from files
|
58
|
+
# - For directories: stores true to indicate it's a directory that should be copied
|
59
|
+
# For files: stores the specific target path
|
60
|
+
def build_file_mappings
|
61
|
+
mappings = {}
|
62
|
+
@config["files"].each do |file|
|
63
|
+
source_path = file["source"].sub(/^\//, '') # Removes leading slashes for consistency
|
64
|
+
if File.directory?(File.join(@template_dir, source_path))
|
65
|
+
mappings[source_path] = true # Directories are marked as true
|
66
|
+
else
|
67
|
+
mappings[source_path] = file["target"] # Files store their target path
|
68
|
+
end
|
69
|
+
end
|
70
|
+
mappings
|
71
|
+
end
|
72
|
+
|
73
|
+
# This method:
|
74
|
+
# - Converts the full path to a relative path
|
75
|
+
# - Checks if the path matches any configured source
|
76
|
+
# - For directories (ending with '/'): checks if the path is within that directory
|
77
|
+
# - For files: checks for exact matches
|
78
|
+
# - Returns false if no match is found
|
79
|
+
def should_copy_path?(path)
|
80
|
+
relative_path = path.sub(@template_dir, '').sub(/^\//, '')
|
81
|
+
|
82
|
+
@file_mappings.each do |source, target|
|
83
|
+
if source.end_with?('/')
|
84
|
+
return true if relative_path.start_with?(source)
|
85
|
+
else
|
86
|
+
return true if relative_path == source
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
false
|
91
|
+
end
|
92
|
+
|
93
|
+
def get_target_path(source_path)
|
94
|
+
relative_path = source_path.sub(@template_dir, '').sub(/^\//, '')
|
95
|
+
@config["files"].each do |file|
|
96
|
+
if file["source"] == relative_path
|
97
|
+
return File.join(@target_dir, file["target"])
|
98
|
+
end
|
99
|
+
end
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
|
103
|
+
def sync_directory(source_dir, target_dir, config_entry)
|
104
|
+
return unless File.directory?(source_dir)
|
105
|
+
|
106
|
+
Dir.foreach(source_dir) do |item|
|
107
|
+
next if item == '.' || item == '..'
|
108
|
+
|
109
|
+
source_path = File.join(source_dir, item)
|
110
|
+
|
111
|
+
if specific_target = get_target_path(source_path)
|
112
|
+
target_path = specific_target
|
113
|
+
else
|
114
|
+
target_path = File.join(target_dir, item)
|
115
|
+
end
|
116
|
+
|
117
|
+
next unless should_copy_path?(source_path)
|
118
|
+
|
119
|
+
if File.directory?(source_path)
|
120
|
+
FileUtils.mkdir_p(target_path) unless Dir.exist?(target_path)
|
121
|
+
sync_directory(source_path, target_path, config_entry)
|
122
|
+
elsif !File.exist?(target_path)
|
123
|
+
FileUtils.mkdir_p(File.dirname(target_path))
|
124
|
+
FileUtils.cp(source_path, target_path)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
32
129
|
def read_config
|
33
130
|
JSON.parse(File.read(@config_file))
|
34
131
|
end
|
@@ -37,12 +134,21 @@ class ProjectTemplateGenerator
|
|
37
134
|
true
|
38
135
|
end
|
39
136
|
|
40
|
-
def copy_item(source, target, copy_content)
|
137
|
+
def copy_item(source, target, copy_content, config_entry)
|
41
138
|
if File.directory?(source)
|
42
139
|
FileUtils.mkdir_p(target)
|
43
140
|
Dir.foreach(source) do |item|
|
44
141
|
next if item == '.' || item == '..'
|
45
|
-
|
142
|
+
source_path = File.join(source, item)
|
143
|
+
|
144
|
+
if specific_target = get_target_path(source_path)
|
145
|
+
target_path = specific_target
|
146
|
+
else
|
147
|
+
target_path = File.join(target, item)
|
148
|
+
end
|
149
|
+
|
150
|
+
next unless should_copy_path?(source_path)
|
151
|
+
copy_item(source_path, target_path, copy_content, config_entry)
|
46
152
|
end
|
47
153
|
else
|
48
154
|
FileUtils.mkdir_p(File.dirname(target))
|
@@ -40,7 +40,7 @@ class SolaraManager
|
|
40
40
|
return
|
41
41
|
end
|
42
42
|
|
43
|
-
BrandOnboarder.new(brand_key, brand_name, clone_brand_key: clone_brand_key)
|
43
|
+
BrandOnboarder.new.onboard(brand_key, brand_name, clone_brand_key: clone_brand_key)
|
44
44
|
|
45
45
|
switch(brand_key, ignore_health_check: true)
|
46
46
|
|
@@ -78,4 +78,8 @@ class SolaraManager
|
|
78
78
|
DoctorManager.new.visit_brands(keys, print_logs: print_logs)
|
79
79
|
end
|
80
80
|
|
81
|
+
def sync_brand_with_template(brand_key)
|
82
|
+
BrandOnboarder.new.sync_with_template(brand_key)
|
83
|
+
end
|
84
|
+
|
81
85
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solara
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Malek Kamel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-10-
|
11
|
+
date: 2024-10-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -239,6 +239,7 @@ files:
|
|
239
239
|
- solara/lib/core/doctor/validator/template/template_validator.rb
|
240
240
|
- solara/lib/core/doctor/validator/validation_strategy.rb
|
241
241
|
- solara/lib/core/scripts/brand_config_manager.rb
|
242
|
+
- solara/lib/core/scripts/brand_config_updater.rb
|
242
243
|
- solara/lib/core/scripts/brand_configurations_manager.rb
|
243
244
|
- solara/lib/core/scripts/brand_exporter.rb
|
244
245
|
- solara/lib/core/scripts/brand_importer.rb
|
@@ -270,11 +271,13 @@ files:
|
|
270
271
|
- solara/lib/core/scripts/solara_settings_manager.rb
|
271
272
|
- solara/lib/core/scripts/solara_status_manager.rb
|
272
273
|
- solara/lib/core/scripts/solara_version_manager.rb
|
274
|
+
- solara/lib/core/scripts/string_case.rb
|
273
275
|
- solara/lib/core/scripts/strings_xml_manager.rb
|
274
276
|
- solara/lib/core/scripts/terminal_input_manager.rb
|
275
277
|
- solara/lib/core/scripts/theme_generator.rb
|
276
278
|
- solara/lib/core/scripts/yaml_manager.rb
|
277
279
|
- solara/lib/core/solara_configurator.rb
|
280
|
+
- solara/lib/core/template/.DS_Store
|
278
281
|
- solara/lib/core/template/brands/android/android_config.json
|
279
282
|
- solara/lib/core/template/brands/android/android_signing.json
|
280
283
|
- solara/lib/core/template/brands/android/res/.DS_Store
|