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 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