solara 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 714797cd507e354e49c4a1981a516f6ad3e659797ba40b6040c7e7a6f30f11aa
4
- data.tar.gz: 3b3d4ed6c116abea9dd15c2db4b5df308320649be4381623e3c7a3c863ef0385
3
+ metadata.gz: acd7dd411828a00b018a7ead20fc5f21853c45682a7b21f419377385b7e5cbb4
4
+ data.tar.gz: 187461f358743a466bcd31971622a32091fc4368264f51f73c7ef73b048ae6c3
5
5
  SHA512:
6
- metadata.gz: b4c46c3f893d95eddbde530e7d83fe340ce36698974880073b3517779bb8e39b31813863c13f7743e821936b814e6d7f5d42337c10b3ef37a4cd6767732f5a20
7
- data.tar.gz: 750e7851fa9584bb85b00b5f01e48ea246892914f1bbfafeb7a2b18db8e138d0da5f8fd2a2253434bed1dd6b6d9b0c6911dcaec908714e96ff69c894653490b8
6
+ metadata.gz: f3e85254b364be2888f9e9a760967bf64e4672df8ae4dde907b6f8a8928afa7266ad9a26f1ff35867ae8b7fb2fc362aa0be6f6a70c7aafb3b29e83fa2c456e72
7
+ data.tar.gz: ee5abac576555d1415bcaa33cec324bf21dcf0519b28eefc1687b6b129b6a74c1f160eb5f2b30f3037facda8ca163e0867c4a7acd8ecdfe2ef2ab88fcf84e330
@@ -12,7 +12,7 @@ class BrandOnboarder
12
12
  end
13
13
 
14
14
  def onboard
15
- if @clone_brand_key.nil? || @clone_brand_key.empty?
15
+ if @clone_brand_key.nil? || @clone_brand_key.strip.empty?
16
16
  generate_brand_template
17
17
  else
18
18
  clone_brand
@@ -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
- @manifest_path = FilePath.brand_json_dir(brand_key)
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
- Solara.logger.start_step("Process JSON manifest: #{@manifest_path}")
93
+ with_logging do
94
+ process_manifests
95
+ end
96
+ end
76
97
 
98
+ private
77
99
 
78
- case SolaraSettingsManager.instance.platform
79
- when Platform::Flutter
80
- process_maifest(Language::Dart, FilePath.flutter_lib_artifacts)
81
- process_maifest(Language::Dart, FilePath.brand_global_json_dir)
82
- when Platform::IOS
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
- Solara.logger.end_step("Process JSON manifest: #{@manifest_path}")
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 process_maifest(language, output_path)
96
- processor = JsonManifestProcessor.new(
97
- @manifest_path,
128
+ def process_manifest(language, manifest_path, output_path)
129
+ JsonManifestProcessor.new(
130
+ manifest_path,
98
131
  language,
99
132
  output_path
100
- )
101
- processor.process
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.key} (${configuraationsResult.brand.name})`);
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
- await this.switchToBrand(true)
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);
@@ -10,6 +10,7 @@ class BrandDetailModel {
10
10
  constructor() {
11
11
  this.localSource = new BrandLocalSource();
12
12
  this.remoteSource = new BrandRemoteSource();
13
+ this.isCurrentBrand = false;
13
14
 
14
15
  this.brandKey = this.getQueryFromUrl('brand_key');
15
16
 
@@ -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
- for (const [k, v] of Object.entries(obj)) {
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 (this.isColorValue(v)) {
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
- // Check if the value is a valid color (hex with opacity, RGBA, or RGB)
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
- obj[key] = JSON.parse(value);
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=" 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);">
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=" animation-delay: 0.5s;">Upload JSON</button>
595
- <div class="button-message" style=" animation-delay: 0.7s;">Alternatively, upload from a folder that includes the brand's JSON files.</div>
596
- <button id="uploadBrandBtn" style=" animation-delay: 0.9s;">Upload Folder</button>
597
- <div class="button-message" style=" animation-delay: 1.1s;">You also have the option to create new brand configurations.</div>
598
- <button id="newBrandBtn" style=" animation-delay: 1.3s;">New Brand</button>
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": "object",
14
- "properties": {
15
- "enabled": {
16
- "type": "boolean"
17
- },
18
- "className": {
19
- "type": "string"
20
- },
21
- "customClassNames": {
22
- "type": "array",
23
- "items": {
24
- "type": "object",
25
- "properties": {
26
- "generatedName": {
27
- "type": "string"
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
- "required": [
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
- if Dir.exist?(source_path)
9
- Dir.glob(source_path.join('*')).each do |item|
10
- relative_path = Pathname.new(item).relative_path_from(source_path).to_s
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
- Solara.logger.debug("🚗 Copied #{relative_path} \n\t↑ From: #{source_path} \n\t↓ To: #{destination_path}")
22
- end
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
- Solara.logger.failure("#{source_path} not found!")
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
- manifest = read_json
13
- process_files(manifest)
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 process_files(manifest)
19
- manifest['files'].each do |file|
20
- generate_code(file)
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 generate_code(file)
25
- return unless file['generate']['enabled']
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
- file_name = file['fileName']
28
- class_name = file['generate']['className']
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
- puts "generate_code: file_name = #{file_name}, class_name = #{class_name}"
31
- return if file_name.empty? || class_name.empty?
32
- puts "generate_code: file_name = #{file_name}, class_name = #{class_name}"
68
+ class_name = derive_class_name(file_name)
69
+ process_json_file(file_path, class_name, {}, false)
70
+ end
71
+ end
33
72
 
34
- custom_class_names = convert_to_map(file['generate']['customClassNames'])
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
- file_path = File.join(@json_path, file_name)
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: JSON.parse(File.read(file_path)),
39
- language: @language ,
84
+ json: json_content,
85
+ language: @language,
40
86
  parent_class_name: class_name,
41
- custom_types: custom_class_names
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 gnerated_filename(class_name)
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("Genrated #{output}")
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("Unexpected error: #{e.message}")
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, dst)
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, dst)
108
+ def check_mandatory_file(item, src)
109
+ return if @ignore_health_check
110
+
108
111
  if item['mandatory'] && !File.exist?(src)
109
- Solara.logger.fatal("Mandatory resource file/folder not found for #{@brand_key}: #{src}. Please add the resource or mark it as not mandatory in #{FilePath.resources_manifest}.")
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
- Solara.logger.fatal("Brand switch copy manifest not found for #{@brand_key}: #{@manifest_file}")
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
- Solara.logger.fatal("Invalid brand switch copy manifest for #{@brand_key}: #{e.message}")
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
- "enabled": true,
26
- "className": "",
27
- "customClassNames": [
28
- {
29
- "generatedName": "",
30
- "customName": ""
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
- "enabled": true,
8
- "className": "",
9
- "customClassNames": [
10
- {
11
- "generatedName": "",
12
- "customName": ""
13
- }
14
- ]
15
- }
6
+ "generate": true,
7
+ "parentClassName": "",
8
+ "customClassNames": [
9
+ {
10
+ "originalName": "",
11
+ "customName": ""
12
+ }
13
+ ]
16
14
  }
17
15
  ]
18
16
  }
@@ -1,3 +1,3 @@
1
1
  module Solara
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -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
- Solara.logger.header("Onboarding #{brand_key}")
35
+ begin
36
+ Solara.logger.header("Onboarding #{brand_key}")
36
37
 
37
- if !init && BrandsManager.instance.exists(brand_key)
38
- Solara.logger.fatal("Brand with key (#{brand_key}) already added to brands!")
39
- return
40
- end
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
- BrandOnboarder.new(brand_key, brand_name, clone_brand_key: clone_brand_key).onboard
45
+ switch(brand_key, ignore_health_check: true)
43
46
 
44
- switch(brand_key, ignore_health_check: true)
45
47
 
46
- clone_message = clone_brand_key.nil? || clone_brand_key.empty? ? '.' : ", cloned from #{clone_brand_key}."
47
- message = success_message || "Onboarded #{brand_key} successfully#{clone_message}"
48
- Solara.logger.success(message)
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
- if open_dashboard
51
- Solara.logger.success("Openning the dashboard for #{brand_key} to complete its details.")
52
- dashboard(brand_key)
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.5.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-18 00:00:00.000000000 Z
11
+ date: 2024-10-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor