solara 0.5.0 → 0.6.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/core/brands/brand_onboarder.rb +1 -1
- data/solara/lib/core/brands/brand_switcher.rb +58 -24
- data/solara/lib/core/dashboard/brand/BrandDetailController.js +5 -2
- data/solara/lib/core/dashboard/brand/BrandDetailModel.js +1 -0
- data/solara/lib/core/dashboard/brand/SectionsFormManager.js +73 -12
- data/solara/lib/core/dashboard/brand/brand.html +45 -6
- data/solara/lib/core/dashboard/component/OnboardBrandBottomSheet.js +4 -0
- data/solara/lib/core/dashboard/handler/onboard_brand_handler.rb +0 -15
- data/solara/lib/core/doctor/schema/platform/json_manifest.json +20 -38
- data/solara/lib/core/scripts/file_manager.rb +11 -15
- data/solara/lib/core/scripts/gitignore_manager.rb +1 -3
- data/solara/lib/core/scripts/json_manifest_processor.rb +86 -45
- data/solara/lib/core/scripts/resource_manifest_processor.rb +10 -10
- data/solara/lib/core/template/brands/json/Json-Manifest.md +8 -10
- data/solara/lib/core/template/brands/json/json_manifest.json +8 -10
- data/solara/lib/solara/version.rb +1 -1
- data/solara/lib/solara_manager.rb +21 -13
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: acd7dd411828a00b018a7ead20fc5f21853c45682a7b21f419377385b7e5cbb4
|
4
|
+
data.tar.gz: 187461f358743a466bcd31971622a32091fc4368264f51f73c7ef73b048ae6c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3e85254b364be2888f9e9a760967bf64e4672df8ae4dde907b6f8a8928afa7266ad9a26f1ff35867ae8b7fb2fc362aa0be6f6a70c7aafb3b29e83fa2c456e72
|
7
|
+
data.tar.gz: ee5abac576555d1415bcaa33cec324bf21dcf0519b28eefc1687b6b129b6a74c1f160eb5f2b30f3037facda8ca163e0867c4a7acd8ecdfe2ef2ab88fcf84e330
|
@@ -30,7 +30,7 @@ class BrandSwitcher
|
|
30
30
|
def switch
|
31
31
|
BrandFontSwitcher.new(@brand_key).switch
|
32
32
|
|
33
|
-
ResourceManifestSwitcher.new(@brand_key).switch
|
33
|
+
ResourceManifestSwitcher.new(@brand_key, ignore_health_check: @ignore_health_check).switch
|
34
34
|
JsonManifestSwitcher.new(@brand_key).switch
|
35
35
|
|
36
36
|
case @platform
|
@@ -51,13 +51,14 @@ class BrandSwitcher
|
|
51
51
|
end
|
52
52
|
|
53
53
|
class ResourceManifestSwitcher
|
54
|
-
def initialize(brand_key)
|
54
|
+
def initialize(brand_key, ignore_health_check:)
|
55
55
|
@brand_key = brand_key
|
56
|
+
@ignore_health_check = ignore_health_check
|
56
57
|
end
|
57
58
|
|
58
59
|
def switch
|
59
60
|
Solara.logger.start_step("Process resource manifest: #{FilePath.resources_manifest}")
|
60
|
-
brand_resource_copier = ResourceManifestProcessor.new(@brand_key)
|
61
|
+
brand_resource_copier = ResourceManifestProcessor.new(@brand_key, ignore_health_check: @ignore_health_check)
|
61
62
|
brand_resource_copier.copy
|
62
63
|
Solara.logger.debug("#{@brand_key} resources copied successfully according to the manifest: #{FilePath.resources_manifest}.")
|
63
64
|
Solara.logger.end_step("Process resource manifest: #{FilePath.resources_manifest}")
|
@@ -66,39 +67,72 @@ class ResourceManifestSwitcher
|
|
66
67
|
end
|
67
68
|
|
68
69
|
class JsonManifestSwitcher
|
70
|
+
PLATFORM_CONFIGS = {
|
71
|
+
Platform::Flutter => {
|
72
|
+
language: Language::Dart,
|
73
|
+
output_path: :flutter_lib_artifacts
|
74
|
+
},
|
75
|
+
Platform::IOS => {
|
76
|
+
language: Language::Swift,
|
77
|
+
output_path: :ios_project_root_artifacts
|
78
|
+
},
|
79
|
+
Platform::Android => {
|
80
|
+
language: Language::Kotlin,
|
81
|
+
output_path: :android_project_java_artifacts
|
82
|
+
}
|
83
|
+
}.freeze
|
84
|
+
|
69
85
|
def initialize(brand_key)
|
70
86
|
@brand_key = brand_key
|
71
|
-
@
|
87
|
+
@brand_manifest_path = FilePath.brand_json_dir(brand_key)
|
88
|
+
@platform = SolaraSettingsManager.instance.platform
|
89
|
+
validate_inputs
|
72
90
|
end
|
73
91
|
|
74
92
|
def switch
|
75
|
-
|
93
|
+
with_logging do
|
94
|
+
process_manifests
|
95
|
+
end
|
96
|
+
end
|
76
97
|
|
98
|
+
private
|
77
99
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
process_maifest(Language::Swift, FilePath.ios_project_root_artifacts)
|
84
|
-
process_maifest(Language::Swift, FilePath.brand_global_json_dir)
|
85
|
-
when Platform::Android
|
86
|
-
process_maifest(Language::Kotlin, FilePath.android_project_java_artifacts )
|
87
|
-
process_maifest(Language::Kotlin, FilePath.brand_global_json_dir)
|
88
|
-
else
|
89
|
-
raise ArgumentError, "Invalid platform: #{@platform}"
|
90
|
-
end
|
100
|
+
def validate_inputs
|
101
|
+
raise ArgumentError, "Brand key cannot be nil" if @brand_key.nil?
|
102
|
+
raise ArgumentError, "Invalid platform: #{@platform}" unless PLATFORM_CONFIGS.key?(@platform)
|
103
|
+
raise ArgumentError, "Manifest path not found: #{@brand_manifest_path}" unless File.exist?(@brand_manifest_path)
|
104
|
+
end
|
91
105
|
|
92
|
-
|
106
|
+
def with_logging
|
107
|
+
log_message = "Process JSON manifest: #{@brand_manifest_path}"
|
108
|
+
Solara.logger.start_step(log_message)
|
109
|
+
yield
|
110
|
+
Solara.logger.end_step(log_message)
|
111
|
+
rescue StandardError => e
|
112
|
+
Solara.logger.error("Failed to process manifest: #{e.message}")
|
113
|
+
raise
|
114
|
+
end
|
115
|
+
|
116
|
+
def process_manifests
|
117
|
+
config = PLATFORM_CONFIGS[@platform]
|
118
|
+
output_path = FilePath.send(config[:output_path])
|
119
|
+
|
120
|
+
[
|
121
|
+
@brand_manifest_path,
|
122
|
+
FilePath.brand_global_json_dir
|
123
|
+
].each do |manifest_path|
|
124
|
+
process_manifest(config[:language], manifest_path, output_path)
|
125
|
+
end
|
93
126
|
end
|
94
127
|
|
95
|
-
def
|
96
|
-
|
97
|
-
|
128
|
+
def process_manifest(language, manifest_path, output_path)
|
129
|
+
JsonManifestProcessor.new(
|
130
|
+
manifest_path,
|
98
131
|
language,
|
99
132
|
output_path
|
100
|
-
)
|
101
|
-
|
133
|
+
).process
|
134
|
+
rescue StandardError => e
|
135
|
+
raise StandardError, "Failed to process #{manifest_path}: #{e.message}"
|
102
136
|
end
|
103
137
|
end
|
104
138
|
|
@@ -91,6 +91,7 @@ class BrandDetailController {
|
|
91
91
|
const response = await this.model.fetchBrandDetails();
|
92
92
|
await this.onLoadSections(response.result);
|
93
93
|
const {isCurrentBrand, contentChanged} = await this.model.fetchCurrentBrand();
|
94
|
+
this.model.isCurrentBrand = isCurrentBrand
|
94
95
|
|
95
96
|
if (isCurrentBrand) {
|
96
97
|
this.view.setupSyncBrandButton(contentChanged ? '#ff4136' : '#4A90E2');
|
@@ -109,7 +110,7 @@ class BrandDetailController {
|
|
109
110
|
try {
|
110
111
|
this.view.addBrandOverlay.style.display = 'none'
|
111
112
|
this.view.header.style.display = 'flex';
|
112
|
-
this.view.updateAppNameTitle(`${configuraationsResult.brand.
|
113
|
+
this.view.updateAppNameTitle(`${configuraationsResult.brand.name} - ${configuraationsResult.brand.key}`);
|
113
114
|
await this.showSections(configuraationsResult);
|
114
115
|
this.view.showIndex();
|
115
116
|
} catch (error) {
|
@@ -159,7 +160,9 @@ class BrandDetailController {
|
|
159
160
|
try {
|
160
161
|
await this.model.saveSection(container.dataset.key, section.content);
|
161
162
|
await this.checkBrandHealth();
|
162
|
-
|
163
|
+
if (this.model.isCurrentBrand) {
|
164
|
+
await this.switchToBrand(true)
|
165
|
+
}
|
163
166
|
} catch (error) {
|
164
167
|
console.error('Error saving section:', error);
|
165
168
|
alert(error.message);
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import '../component/EditJsonSheet.js';
|
2
2
|
|
3
3
|
class SectionsFormManager {
|
4
|
-
|
5
4
|
constructor() {
|
6
5
|
this.sections = [];
|
7
6
|
this.sectionsContainer = document.getElementById('sections');
|
@@ -49,7 +48,6 @@ class SectionsFormManager {
|
|
49
48
|
})
|
50
49
|
return Array.from(data);
|
51
50
|
}
|
52
|
-
|
53
51
|
}
|
54
52
|
|
55
53
|
class SectionItemManager {
|
@@ -113,7 +111,7 @@ class SectionItemManager {
|
|
113
111
|
|
114
112
|
const isArray = Array.isArray(obj)
|
115
113
|
|
116
|
-
|
114
|
+
for (const [k, v] of Object.entries(obj)) {
|
117
115
|
const item = document.createElement('div');
|
118
116
|
|
119
117
|
const cardValueContainer = document.createElement('div');
|
@@ -135,18 +133,62 @@ class SectionItemManager {
|
|
135
133
|
item.className = 'card-item';
|
136
134
|
itemKey.textContent = k.replace(/_/g, ' ')
|
137
135
|
|
138
|
-
if (
|
136
|
+
if (typeof v === 'boolean') {
|
137
|
+
// Create a container for the entire boolean input
|
138
|
+
const booleanContainer = document.createElement('div');
|
139
|
+
booleanContainer.className = 'boolean-container';
|
140
|
+
|
141
|
+
const checkboxContainer = document.createElement('div');
|
142
|
+
checkboxContainer.className = 'card-value checkbox-container';
|
143
|
+
|
144
|
+
const itemValue = document.createElement('input');
|
145
|
+
itemValue.type = 'checkbox';
|
146
|
+
itemValue.className = 'card-value checkbox';
|
147
|
+
itemValue.checked = v;
|
148
|
+
|
149
|
+
const valueLabel = document.createElement('span');
|
150
|
+
valueLabel.className = 'checkbox-value';
|
151
|
+
valueLabel.textContent = v.toString();
|
152
|
+
|
153
|
+
const updateValue = () => {
|
154
|
+
const newValue = !itemValue.checked;
|
155
|
+
itemValue.checked = newValue;
|
156
|
+
valueLabel.textContent = newValue.toString();
|
157
|
+
this.updateValue(obj, k, newValue, typeof v);
|
158
|
+
};
|
159
|
+
|
160
|
+
// Add click handlers to both container and checkbox
|
161
|
+
booleanContainer.onclick = (e) => {
|
162
|
+
if (e.target !== itemValue) { // Prevent double-toggle when clicking checkbox
|
163
|
+
updateValue();
|
164
|
+
}
|
165
|
+
};
|
166
|
+
|
167
|
+
itemValue.onchange = () => {
|
168
|
+
valueLabel.textContent = itemValue.checked.toString();
|
169
|
+
this.updateValue(obj, k, itemValue.checked, typeof v);
|
170
|
+
};
|
171
|
+
|
172
|
+
checkboxContainer.appendChild(itemValue);
|
173
|
+
checkboxContainer.appendChild(valueLabel);
|
174
|
+
|
175
|
+
// Move the key inside the boolean container
|
176
|
+
booleanContainer.appendChild(itemKey);
|
177
|
+
booleanContainer.appendChild(checkboxContainer);
|
178
|
+
|
179
|
+
cardValueContainer.appendChild(booleanContainer);
|
180
|
+
} else if (this.isColorValue(v)) {
|
139
181
|
const itemValue = document.createElement('input');
|
140
182
|
itemValue.type = 'color';
|
141
183
|
itemValue.className = 'card-value';
|
142
184
|
itemValue.value = v;
|
143
|
-
itemValue.onchange = () => this.updateValue(obj, k, itemValue.value);
|
185
|
+
itemValue.onchange = () => this.updateValue(obj, k, itemValue.value, typeof v);
|
144
186
|
cardValueContainer.appendChild(itemValue);
|
145
187
|
} else {
|
146
188
|
const itemValue = document.createElement('textarea');
|
147
189
|
itemValue.className = 'card-value';
|
148
190
|
itemValue.value = v;
|
149
|
-
itemValue.onchange = () => this.updateValue(obj, k, itemValue.value);
|
191
|
+
itemValue.onchange = () => this.updateValue(obj, k, itemValue.value, typeof v);
|
150
192
|
cardValueContainer.appendChild(itemValue);
|
151
193
|
}
|
152
194
|
|
@@ -165,8 +207,7 @@ class SectionItemManager {
|
|
165
207
|
}
|
166
208
|
|
167
209
|
isColorValue(value) {
|
168
|
-
|
169
|
-
const hexPattern = /^#([0-9A-F]{3}){1,2}([0-9A-F]{2})?$/i; // 3, 6, or 8 hex digits
|
210
|
+
const hexPattern = /^#([0-9A-F]{3}){1,2}([0-9A-F]{2})?$/i;
|
170
211
|
const rgbaPattern = /^rgba?\(\s*(\d{1,3}\s*,\s*){2}\d{1,3}\s*,?\s*(0|1|0?\.\d+|1?\.\d+)\s*\)$/;
|
171
212
|
|
172
213
|
return hexPattern.test(value) || rgbaPattern.test(value);
|
@@ -182,14 +223,35 @@ class SectionItemManager {
|
|
182
223
|
this.notifyChange()
|
183
224
|
}
|
184
225
|
|
185
|
-
updateValue(obj, key, value) {
|
226
|
+
updateValue(obj, key, value, originalType) {
|
186
227
|
try {
|
187
|
-
|
228
|
+
// Handle different types based on the original value's type
|
229
|
+
switch (originalType) {
|
230
|
+
case 'string':
|
231
|
+
obj[key] = String(value);
|
232
|
+
break;
|
233
|
+
case 'number':
|
234
|
+
// If the original was a number, keep it number
|
235
|
+
if (!isNaN(value) || value === '') {
|
236
|
+
obj[key] = Number(value);
|
237
|
+
}
|
238
|
+
break;
|
239
|
+
case 'boolean':
|
240
|
+
obj[key] = value.toLowerCase() === 'true';
|
241
|
+
break;
|
242
|
+
default:
|
243
|
+
// Try to parse as JSON, fallback to string if it fails
|
244
|
+
try {
|
245
|
+
obj[key] = JSON.parse(value);
|
246
|
+
} catch {
|
247
|
+
obj[key] = value;
|
248
|
+
}
|
249
|
+
}
|
188
250
|
} catch {
|
189
251
|
obj[key] = value;
|
190
252
|
}
|
191
253
|
this.displayJSONCards();
|
192
|
-
this.notifyChange()
|
254
|
+
this.notifyChange();
|
193
255
|
}
|
194
256
|
|
195
257
|
confirmDeleteProperty(obj, key) {
|
@@ -228,5 +290,4 @@ class SectionItemManager {
|
|
228
290
|
}
|
229
291
|
}
|
230
292
|
|
231
|
-
|
232
293
|
export default SectionsFormManager;
|
@@ -515,6 +515,45 @@
|
|
515
515
|
background-color: var(--background-color);
|
516
516
|
color: var(--text-color);
|
517
517
|
}
|
518
|
+
.boolean-container {
|
519
|
+
flex-direction: row;
|
520
|
+
display: flex;
|
521
|
+
align-items: center;
|
522
|
+
|
523
|
+
cursor: pointer;
|
524
|
+
padding: 0px;
|
525
|
+
flex: 1;
|
526
|
+
}
|
527
|
+
|
528
|
+
.boolean-container:hover {
|
529
|
+
background-color: var(--background-color));
|
530
|
+
}
|
531
|
+
|
532
|
+
.card-value.checkbox-container {
|
533
|
+
flex-direction: row;
|
534
|
+
display: flex;
|
535
|
+
align-items: start;
|
536
|
+
gap: 0px;
|
537
|
+
padding-bottom: 10px;
|
538
|
+
margin-left: 10px;
|
539
|
+
}
|
540
|
+
|
541
|
+
.card-value.checkbox {
|
542
|
+
width: auto;
|
543
|
+
height: auto;
|
544
|
+
margin: 0;
|
545
|
+
flex: 0;
|
546
|
+
cursor: pointer;
|
547
|
+
}
|
548
|
+
|
549
|
+
.checkbox-value {
|
550
|
+
font-size: 1.2em;
|
551
|
+
color: var(--text-color);
|
552
|
+
user-select: none;
|
553
|
+
flex: 0;
|
554
|
+
margin-left: 10px;
|
555
|
+
}
|
556
|
+
|
518
557
|
.card-actions {
|
519
558
|
display: flex;
|
520
559
|
align-items: center;
|
@@ -556,7 +595,7 @@
|
|
556
595
|
<img class="loading-overlay-logo" src="../solara.png" alt="Loading Logo">
|
557
596
|
</div>
|
558
597
|
|
559
|
-
<div id="toast" style="
|
598
|
+
<div id="toast" style=" display: none; position: fixed; top: 10%; left: 50%; transform: translate(-50%, 10%); background-color: #4CAF50; color: white; padding: 16px; border-radius: 5px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);">
|
560
599
|
<!-- Message will be set dynamically -->
|
561
600
|
</div>
|
562
601
|
|
@@ -591,11 +630,11 @@
|
|
591
630
|
<img src="../solara.png" alt="Solara Logo">
|
592
631
|
<h2>Solara simplifies the management of your brand configurations, allowing you to access and update them anytime, anywhere.</h2>
|
593
632
|
<div class="button-message">You can select a JSON file containing brand configurations that were exported using Solara.</div>
|
594
|
-
<button id="uploadJsonBtn" style="
|
595
|
-
<div class="button-message" style="
|
596
|
-
<button id="uploadBrandBtn" style="
|
597
|
-
<div class="button-message" style="
|
598
|
-
<button id="newBrandBtn" style="
|
633
|
+
<button id="uploadJsonBtn" style=" animation-delay: 0.5s;">Upload JSON</button>
|
634
|
+
<div class="button-message" style=" animation-delay: 0.7s;">Alternatively, upload from a folder that includes the brand's JSON files.</div>
|
635
|
+
<button id="uploadBrandBtn" style=" animation-delay: 0.9s;">Upload Folder</button>
|
636
|
+
<div class="button-message" style=" animation-delay: 1.1s;">You also have the option to create new brand configurations.</div>
|
637
|
+
<button id="newBrandBtn" style=" animation-delay: 1.3s;">New Brand</button>
|
599
638
|
</div>
|
600
639
|
|
601
640
|
</div>
|
@@ -72,6 +72,10 @@ class OnboardBrandBottomSheet extends HTMLElement {
|
|
72
72
|
border-radius: 3.5px;
|
73
73
|
font-size: 11.2px;
|
74
74
|
background-color: var(--background-color);
|
75
|
+
<<<<<<< HEAD
|
76
|
+
=======
|
77
|
+
color: var(--text-color);
|
78
|
+
>>>>>>> develop
|
75
79
|
}
|
76
80
|
.tooltip {
|
77
81
|
position: relative;
|
@@ -26,18 +26,6 @@ class OnboardBrandHandler < BaseHandler
|
|
26
26
|
end
|
27
27
|
res.content_type = 'application/json'
|
28
28
|
end
|
29
|
-
|
30
|
-
def onboard_brand(brand_name, brand_key, clone_brand_key = nil)
|
31
|
-
if BrandsManager.instance.exists(brand_key)
|
32
|
-
return { success: false, message: "Brand with key (#{brand_key}) already added!" }
|
33
|
-
end
|
34
|
-
SolaraManager.new.onboard(brand_key, brand_name, clone_brand_key: clone_brand_key, open_dashboard: false)
|
35
|
-
{ success: true, message: "Brand added successfully" }
|
36
|
-
rescue StandardError => e
|
37
|
-
Solara.logger.failure("Error adding brand: #{e.message}")
|
38
|
-
raise
|
39
|
-
end
|
40
|
-
|
41
29
|
end
|
42
30
|
|
43
31
|
def onboard_brand(brand_name, brand_key, clone_brand_key = nil)
|
@@ -46,8 +34,5 @@ class OnboardBrandHandler < BaseHandler
|
|
46
34
|
end
|
47
35
|
SolaraManager.new.onboard(brand_key, brand_name, clone_brand_key: clone_brand_key, open_dashboard: false)
|
48
36
|
{ success: true, message: "Brand added successfully" }
|
49
|
-
rescue StandardError => e
|
50
|
-
Solara.logger.failure("Error adding brand: #{e.message}")
|
51
|
-
raise
|
52
37
|
end
|
53
38
|
end
|
@@ -10,48 +10,30 @@
|
|
10
10
|
"type": "string"
|
11
11
|
},
|
12
12
|
"generate": {
|
13
|
-
"type": "
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
"
|
22
|
-
|
23
|
-
"
|
24
|
-
"type": "
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
},
|
29
|
-
"customName": {
|
30
|
-
"type": "string"
|
31
|
-
}
|
32
|
-
},
|
33
|
-
"required": [
|
34
|
-
"generatedName",
|
35
|
-
"customName"
|
36
|
-
]
|
13
|
+
"type": "boolean"
|
14
|
+
},
|
15
|
+
"parentClassName": {
|
16
|
+
"type": "string"
|
17
|
+
},
|
18
|
+
"customClassNames": {
|
19
|
+
"type": "array",
|
20
|
+
"items": {
|
21
|
+
"type": "object",
|
22
|
+
"properties": {
|
23
|
+
"originalName": {
|
24
|
+
"type": "string"
|
25
|
+
},
|
26
|
+
"customName": {
|
27
|
+
"type": "string"
|
37
28
|
}
|
38
|
-
}
|
39
|
-
|
40
|
-
|
41
|
-
"enabled",
|
42
|
-
"className",
|
43
|
-
"customClassNames"
|
44
|
-
]
|
29
|
+
},
|
30
|
+
"required": ["originalName", "customName"]
|
31
|
+
}
|
45
32
|
}
|
46
33
|
},
|
47
|
-
"required": [
|
48
|
-
"fileName",
|
49
|
-
"generate"
|
50
|
-
]
|
34
|
+
"required": ["fileName", "generate", "parentClassName", "customClassNames"]
|
51
35
|
}
|
52
36
|
}
|
53
37
|
},
|
54
|
-
"required": [
|
55
|
-
"files"
|
56
|
-
]
|
38
|
+
"required": ["files"]
|
57
39
|
}
|
@@ -5,23 +5,19 @@ class FileManager
|
|
5
5
|
source_path = Pathname.new(source_dir).expand_path
|
6
6
|
destination_path = Pathname.new(destination_dir).expand_path
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
destination_item_path = destination_path.join(relative_path)
|
12
|
-
|
13
|
-
if File.directory?(item)
|
14
|
-
FileUtils.mkdir_p(destination_item_path)
|
15
|
-
FileUtils.cp_r(item + '/.', destination_item_path) # Ensure to copy contents
|
16
|
-
else
|
17
|
-
FileUtils.mkdir_p(destination_item_path.dirname) # Create parent directory
|
18
|
-
FileUtils.cp(item, destination_item_path)
|
19
|
-
end
|
8
|
+
Dir.glob(source_path.join('*')).each do |item|
|
9
|
+
relative_path = Pathname.new(item).relative_path_from(source_path).to_s
|
10
|
+
destination_item_path = destination_path.join(relative_path)
|
20
11
|
|
21
|
-
|
22
|
-
|
12
|
+
if File.directory?(item)
|
13
|
+
FileUtils.mkdir_p(destination_item_path)
|
14
|
+
FileUtils.cp_r(item + '/.', destination_item_path) # Ensure to copy contents
|
23
15
|
else
|
24
|
-
|
16
|
+
FileUtils.mkdir_p(destination_item_path.dirname) # Create parent directory
|
17
|
+
FileUtils.cp(item, destination_item_path)
|
18
|
+
end
|
19
|
+
|
20
|
+
Solara.logger.debug("🚗 Copied #{relative_path} \n\t↑ From: #{source_path} \n\t↓ To: #{destination_path}")
|
25
21
|
end
|
26
22
|
end
|
27
23
|
|
@@ -18,9 +18,7 @@ class GitignoreManager
|
|
18
18
|
]
|
19
19
|
|
20
20
|
if Platform.is_flutter || Platform.is_ios
|
21
|
-
items << FileManager.get_relative_path_to_root(FilePath.project_infoplist_string_catalog)
|
22
|
-
# The excluded InfoPlist.xcstrings maybe at the root. In this case we have to avoid ignoring the brands files.
|
23
|
-
items << '!solara/brand/brands/**/InfoPlist.xcstrings'
|
21
|
+
items << "/#{FileManager.get_relative_path_to_root(FilePath.project_infoplist_string_catalog)}"
|
24
22
|
end
|
25
23
|
|
26
24
|
GitignoreManager.new(FilePath.project_root).add_items(items)
|
@@ -1,53 +1,116 @@
|
|
1
1
|
require 'json'
|
2
2
|
|
3
3
|
class JsonManifestProcessor
|
4
|
-
|
5
4
|
def initialize(json_path, language, output_path)
|
6
5
|
@json_path = json_path
|
7
6
|
@language = language
|
8
7
|
@output_path = output_path
|
8
|
+
@manifest = read_manifest
|
9
9
|
end
|
10
10
|
|
11
11
|
def process
|
12
|
-
|
13
|
-
|
12
|
+
# First process files specified in manifest
|
13
|
+
process_manifest_files if @manifest && @manifest['files']
|
14
|
+
|
15
|
+
# Then process remaining JSON files
|
16
|
+
process_remaining_files
|
14
17
|
end
|
15
18
|
|
16
19
|
private
|
17
20
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
+
def read_manifest
|
22
|
+
manifest_path = File.join(@json_path, 'json_manifest.json')
|
23
|
+
JSON.parse(File.read(manifest_path))
|
24
|
+
rescue JSON::ParserError => e
|
25
|
+
Solara.logger.debug("Error parsing manifest JSON: #{e.message}")
|
26
|
+
nil
|
27
|
+
rescue Errno::ENOENT => e
|
28
|
+
Solara.logger.debug("Manifest file not found: #{e.message}")
|
29
|
+
nil
|
30
|
+
rescue StandardError => e
|
31
|
+
Solara.logger.debug("Unexpected error reading manifest: #{e.message}")
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def process_manifest_files
|
36
|
+
@manifest['files'].each do |file|
|
37
|
+
process_manifest_file(file)
|
21
38
|
end
|
22
39
|
end
|
23
40
|
|
24
|
-
def
|
25
|
-
|
41
|
+
def process_manifest_file(file)
|
42
|
+
return unless file['generate']
|
43
|
+
|
44
|
+
file_name = file['fileName']
|
45
|
+
class_name = file['parentClassName']
|
46
|
+
|
47
|
+
return if file_name.empty? || class_name.empty?
|
48
|
+
|
49
|
+
custom_class_names = convert_to_map(file['customClassNames'])
|
50
|
+
process_json_file(
|
51
|
+
File.join(@json_path, file_name),
|
52
|
+
class_name,
|
53
|
+
custom_class_names,
|
54
|
+
true
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
def process_remaining_files
|
59
|
+
manifest_files = @manifest&.dig('files')&.map { |f| f['fileName'] } || []
|
26
60
|
|
27
|
-
|
28
|
-
|
61
|
+
json_files = get_json_files
|
62
|
+
json_files.each do |file_path|
|
63
|
+
file_name = File.basename(file_path)
|
64
|
+
# Skip files that were already processed via manifest
|
65
|
+
next if manifest_files.include?(file_name)
|
66
|
+
next if file_name == 'json_manifest.json'
|
29
67
|
|
30
|
-
|
31
|
-
|
32
|
-
|
68
|
+
class_name = derive_class_name(file_name)
|
69
|
+
process_json_file(file_path, class_name, {}, false)
|
70
|
+
end
|
71
|
+
end
|
33
72
|
|
34
|
-
|
73
|
+
def get_json_files
|
74
|
+
Dir.glob(File.join(@json_path, '**', '*.json'))
|
75
|
+
rescue StandardError => e
|
76
|
+
Solara.logger.debug("Error reading directory #{@json_path}: #{e.message}")
|
77
|
+
[]
|
78
|
+
end
|
35
79
|
|
36
|
-
|
80
|
+
def process_json_file(file_path, class_name, custom_types, is_manifest_file)
|
81
|
+
begin
|
82
|
+
json_content = JSON.parse(File.read(file_path))
|
37
83
|
code_generator = CodeGenerator.new(
|
38
|
-
json:
|
39
|
-
language: @language
|
84
|
+
json: json_content,
|
85
|
+
language: @language,
|
40
86
|
parent_class_name: class_name,
|
41
|
-
custom_types:
|
87
|
+
custom_types: custom_types
|
42
88
|
)
|
43
89
|
|
44
90
|
generated_code = code_generator.generate
|
45
|
-
|
46
|
-
output_path = File.join(@output_path, gnerated_filename(class_name))
|
91
|
+
output_path = File.join(@output_path, generated_filename(class_name))
|
47
92
|
write_to_file(output_path, generated_code)
|
93
|
+
rescue JSON::ParserError => e
|
94
|
+
Solara.logger.debug("Error parsing JSON file #{File.basename(file_path)}: #{e.message}")
|
95
|
+
rescue StandardError => e
|
96
|
+
Solara.logger.debug("Error processing file #{File.basename(file_path)}: #{e.message}")
|
97
|
+
end
|
48
98
|
end
|
49
99
|
|
50
|
-
def
|
100
|
+
def derive_class_name(file_name)
|
101
|
+
# Remove .json extension and convert to PascalCase
|
102
|
+
base_name = File.basename(file_name, '.json')
|
103
|
+
base_name.split('_').map(&:capitalize).join
|
104
|
+
end
|
105
|
+
|
106
|
+
def convert_to_map(custom_class_names)
|
107
|
+
return {} unless custom_class_names
|
108
|
+
custom_class_names.each_with_object({}) do |item, result|
|
109
|
+
result[item['originalName']] = item['customName']
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def generated_filename(class_name)
|
51
114
|
case SolaraSettingsManager.instance.platform
|
52
115
|
when Platform::Flutter
|
53
116
|
"#{to_snake_case(class_name)}.dart"
|
@@ -65,31 +128,9 @@ class JsonManifestProcessor
|
|
65
128
|
end
|
66
129
|
|
67
130
|
def write_to_file(output, content)
|
68
|
-
puts "generate_code: output = #{output}, content = #{content}"
|
69
131
|
File.write(output, content)
|
70
|
-
Solara.logger.debug("
|
71
|
-
rescue StandardError => e
|
72
|
-
Solara.logger.debug("Error writing to file #{file_name}: #{e.message}")
|
73
|
-
end
|
74
|
-
|
75
|
-
def convert_to_map(custom_class_names)
|
76
|
-
custom_class_names.each_with_object({}) do |item, result|
|
77
|
-
result[item['generatedName']] = item['customName']
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def read_json
|
82
|
-
mainfest_path = File.join(@json_path, 'json_manifest.json')
|
83
|
-
JSON.parse(File.read(mainfest_path))
|
84
|
-
rescue JSON::ParserError => e
|
85
|
-
Solara.logger.debug("Error parsing JSON: #{e.message}")
|
86
|
-
{}
|
87
|
-
rescue Errno::ENOENT => e
|
88
|
-
Solara.logger.debug("Error reading file: #{e.message}")
|
89
|
-
{}
|
132
|
+
Solara.logger.debug("Generated #{output}")
|
90
133
|
rescue StandardError => e
|
91
|
-
Solara.logger.debug("
|
92
|
-
{}
|
134
|
+
Solara.logger.debug("Error writing to file #{output}: #{e.message}")
|
93
135
|
end
|
94
|
-
|
95
136
|
end
|
@@ -2,8 +2,9 @@ require 'json'
|
|
2
2
|
require 'fileutils'
|
3
3
|
|
4
4
|
class ResourceManifestProcessor
|
5
|
-
def initialize(brand_key)
|
5
|
+
def initialize(brand_key, ignore_health_check:)
|
6
6
|
@brand_key = brand_key
|
7
|
+
@ignore_health_check = ignore_health_check
|
7
8
|
@manifest_file = FilePath.resources_manifest
|
8
9
|
@config = load_manifest_file
|
9
10
|
end
|
@@ -33,7 +34,7 @@ class ResourceManifestProcessor
|
|
33
34
|
|
34
35
|
return skip_empty_paths(item) if empty_paths?(item)
|
35
36
|
|
36
|
-
check_mandatory_file(item, src
|
37
|
+
check_mandatory_file(item, src)
|
37
38
|
|
38
39
|
if File.exist?(src)
|
39
40
|
copy_file(src, dst)
|
@@ -88,7 +89,7 @@ class ResourceManifestProcessor
|
|
88
89
|
|
89
90
|
def git_ignore(files)
|
90
91
|
files.each do |file|
|
91
|
-
GitignoreManager.new(FilePath.project_root).add_items([file])
|
92
|
+
GitignoreManager.new(FilePath.project_root).add_items(["/#{file}"])
|
92
93
|
end
|
93
94
|
end
|
94
95
|
|
@@ -104,10 +105,11 @@ class ResourceManifestProcessor
|
|
104
105
|
Solara.logger.debug("Skipped (empty source or destination) for #{@brand_key}: #{item['source']} -> #{item['destination']}")
|
105
106
|
end
|
106
107
|
|
107
|
-
def check_mandatory_file(item, src
|
108
|
+
def check_mandatory_file(item, src)
|
109
|
+
return if @ignore_health_check
|
110
|
+
|
108
111
|
if item['mandatory'] && !File.exist?(src)
|
109
|
-
|
110
|
-
exit 1
|
112
|
+
raise "Mandatory resource file/folder not found for #{@brand_key}: #{src}. Please add the resource or mark it as not mandatory in #{FilePath.resources_manifest}."
|
111
113
|
end
|
112
114
|
|
113
115
|
end
|
@@ -134,8 +136,7 @@ class ResourceManifestProcessor
|
|
134
136
|
|
135
137
|
def validate_manifest_file_existence
|
136
138
|
unless File.exist?(@manifest_file)
|
137
|
-
|
138
|
-
exit 1
|
139
|
+
raise "Resources manifest not found for #{@brand_key}: #{@manifest_file}"
|
139
140
|
end
|
140
141
|
end
|
141
142
|
|
@@ -143,8 +144,7 @@ class ResourceManifestProcessor
|
|
143
144
|
begin
|
144
145
|
JSON.parse(File.read(@manifest_file))
|
145
146
|
rescue JSON::ParserError => e
|
146
|
-
|
147
|
-
exit 1
|
147
|
+
raise "Invalid resources manifest for #{@brand_key}: #{e.message}"
|
148
148
|
end
|
149
149
|
end
|
150
150
|
|
@@ -21,16 +21,14 @@ Each directory contains a `json_manifest.json` file with the following structure
|
|
21
21
|
"files": [
|
22
22
|
{
|
23
23
|
"fileName": "",
|
24
|
-
"generate":
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
]
|
33
|
-
}
|
24
|
+
"generate": true,
|
25
|
+
"parentClassName": "",
|
26
|
+
"customClassNames": [
|
27
|
+
{
|
28
|
+
"originalName": "",
|
29
|
+
"customName": ""
|
30
|
+
}
|
31
|
+
]
|
34
32
|
}
|
35
33
|
]
|
36
34
|
}
|
@@ -3,16 +3,14 @@
|
|
3
3
|
"files": [
|
4
4
|
{
|
5
5
|
"fileName": "",
|
6
|
-
"generate":
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
]
|
15
|
-
}
|
6
|
+
"generate": true,
|
7
|
+
"parentClassName": "",
|
8
|
+
"customClassNames": [
|
9
|
+
{
|
10
|
+
"originalName": "",
|
11
|
+
"customName": ""
|
12
|
+
}
|
13
|
+
]
|
16
14
|
}
|
17
15
|
]
|
18
16
|
}
|
@@ -32,24 +32,32 @@ class SolaraManager
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def onboard(brand_key, brand_name, init: false, clone_brand_key: nil, open_dashboard: true, success_message: nil)
|
35
|
-
|
35
|
+
begin
|
36
|
+
Solara.logger.header("Onboarding #{brand_key}")
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
if !init && BrandsManager.instance.exists(brand_key)
|
39
|
+
Solara.logger.fatal("Brand with key (#{brand_key}) already added to brands!")
|
40
|
+
return
|
41
|
+
end
|
42
|
+
|
43
|
+
BrandOnboarder.new(brand_key, brand_name, clone_brand_key: clone_brand_key).onboard
|
41
44
|
|
42
|
-
|
45
|
+
switch(brand_key, ignore_health_check: true)
|
43
46
|
|
44
|
-
switch(brand_key, ignore_health_check: true)
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
|
48
|
+
clone_message = clone_brand_key.nil? || clone_brand_key.empty? ? '.' : ", cloned from #{clone_brand_key}."
|
49
|
+
message = success_message || "Onboarded #{brand_key} successfully#{clone_message}"
|
50
|
+
Solara.logger.success(message)
|
49
51
|
|
50
|
-
|
51
|
-
|
52
|
-
|
52
|
+
if open_dashboard
|
53
|
+
Solara.logger.success("Openning the dashboard for #{brand_key} to complete its details.")
|
54
|
+
dashboard(brand_key)
|
55
|
+
end
|
56
|
+
rescue => e
|
57
|
+
# Rollback this brand
|
58
|
+
offboard(brand_key, confirm: false)
|
59
|
+
Solara.logger.debug("Performed rollback for (#{brand_key}).")
|
60
|
+
raise e
|
53
61
|
end
|
54
62
|
end
|
55
63
|
|
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.6.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-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|