@o2vend/theme-cli 1.0.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.

Potentially problematic release.


This version of @o2vend/theme-cli might be problematic. Click here for more details.

@@ -0,0 +1,293 @@
1
+ /**
2
+ * O2VEND Widget Service
3
+ * Standalone implementation - copied and adapted from webstore solution
4
+ * Update manually when webstore solution changes
5
+ *
6
+ * Source: o2vend-webstore/services/widget-service.js
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const NodeCache = require('node-cache');
12
+
13
+ /**
14
+ * Widget Service for O2VEND API
15
+ * Manages widgets for liquid theming and dynamic content
16
+ */
17
+ class WidgetService {
18
+ constructor(apiClient, options = {}) {
19
+ this.apiClient = apiClient;
20
+ this.themeName = options.theme || 'default';
21
+ this.themePath = options.themePath || process.cwd();
22
+ this.cache = new NodeCache({
23
+ stdTTL: 300, // 5 minutes cache
24
+ checkperiod: 60
25
+ });
26
+ this.widgetTemplateMap = this.loadWidgetTemplateMap();
27
+ }
28
+
29
+ /**
30
+ * Load widget template mapping from config
31
+ * @returns {Object} widget type to template slug map
32
+ */
33
+ loadWidgetTemplateMap() {
34
+ try {
35
+ // Try to load from CLI config directory first
36
+ const configPath = path.join(__dirname, '../../config/widget-map.json');
37
+ if (fs.existsSync(configPath)) {
38
+ const fileContent = fs.readFileSync(configPath, 'utf8');
39
+ const map = JSON.parse(fileContent);
40
+ console.log(`[WidgetService] Loaded widget map with ${Object.keys(map).length} entries`);
41
+ return map;
42
+ }
43
+ console.warn('[WidgetService] Widget map file not found at:', configPath);
44
+ } catch (error) {
45
+ console.warn('[WidgetService] Failed to load widget map:', error.message);
46
+ }
47
+ return {};
48
+ }
49
+
50
+ /**
51
+ * Normalize keys recursively from PascalCase to camelCase
52
+ * @param {*} obj - Object to normalize
53
+ * @returns {*} Object with normalized keys
54
+ */
55
+ normalizeContentKeys(obj) {
56
+ if (obj === null || obj === undefined) {
57
+ return obj;
58
+ }
59
+
60
+ if (Array.isArray(obj)) {
61
+ return obj.map(item => this.normalizeContentKeys(item));
62
+ }
63
+
64
+ if (typeof obj === 'object') {
65
+ const normalized = {};
66
+ for (const key in obj) {
67
+ if (obj.hasOwnProperty(key)) {
68
+ const normalizedKey = key.charAt(0).toLowerCase() + key.slice(1);
69
+ normalized[normalizedKey] = this.normalizeContentKeys(obj[key]);
70
+ // Keep original PascalCase for compatibility
71
+ if (key !== normalizedKey) {
72
+ normalized[key] = normalized[normalizedKey];
73
+ }
74
+ }
75
+ }
76
+ return normalized;
77
+ }
78
+
79
+ return obj;
80
+ }
81
+
82
+ /**
83
+ * Normalize widget object
84
+ * @param {Object} widget
85
+ * @returns {Object}
86
+ */
87
+ normalizeWidget(widget) {
88
+ if (!widget || typeof widget !== 'object') {
89
+ return widget;
90
+ }
91
+
92
+ // Normalize widget ID
93
+ if (!widget.id) {
94
+ if (widget._id) {
95
+ widget.id = typeof widget._id === 'object' && widget._id.$oid
96
+ ? widget._id.$oid
97
+ : String(widget._id);
98
+ } else if (widget.Id) {
99
+ widget.id = String(widget.Id);
100
+ } else if (widget.WidgetInstanceId) {
101
+ widget.id = String(widget.WidgetInstanceId);
102
+ } else {
103
+ widget.id = `${widget.type || 'widget'}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
104
+ }
105
+ } else {
106
+ widget.id = String(widget.id);
107
+ }
108
+
109
+ // Initialize settings and data
110
+ if (!widget.settings || typeof widget.settings !== 'object') {
111
+ widget.settings = {};
112
+ }
113
+
114
+ if (!widget.data || typeof widget.data !== 'object') {
115
+ widget.data = widget.payload || {};
116
+ }
117
+
118
+ // Parse widget.content if it's a JSON string
119
+ let parsedContent = null;
120
+ if (widget.content) {
121
+ if (typeof widget.content === 'string') {
122
+ try {
123
+ parsedContent = JSON.parse(widget.content);
124
+ parsedContent = this.normalizeContentKeys(parsedContent);
125
+ } catch (error) {
126
+ console.warn(`[WidgetService] Failed to parse widget.content JSON:`, error.message);
127
+ parsedContent = {};
128
+ }
129
+ } else if (typeof widget.content === 'object') {
130
+ parsedContent = this.normalizeContentKeys(widget.content);
131
+ }
132
+ } else {
133
+ parsedContent = {};
134
+ }
135
+
136
+ // Move Title/Subtitle to settings
137
+ if (widget.Title && !widget.settings.title) {
138
+ widget.settings.title = widget.Title;
139
+ }
140
+ if (widget.title && !widget.settings.title) {
141
+ widget.settings.title = widget.title;
142
+ }
143
+ if (widget.Subtitle && !widget.settings.subtitle) {
144
+ widget.settings.subtitle = widget.Subtitle;
145
+ }
146
+ if (widget.subtitle && !widget.settings.subtitle) {
147
+ widget.settings.subtitle = widget.subtitle;
148
+ }
149
+
150
+ // Store content
151
+ widget.data.content = parsedContent;
152
+ widget.content = parsedContent; // Legacy support
153
+
154
+ // Handle HtmlContent
155
+ if (widget.htmlContent !== undefined && widget.htmlContent !== null && widget.htmlContent !== '') {
156
+ widget.data.htmlContent = widget.htmlContent;
157
+ } else if (widget.HtmlContent !== undefined && widget.HtmlContent !== null && widget.HtmlContent !== '') {
158
+ widget.data.htmlContent = widget.HtmlContent;
159
+ widget.htmlContent = widget.HtmlContent;
160
+ }
161
+
162
+ // Set section
163
+ if (!widget.section) {
164
+ widget.section = widget.sectionName || widget.area || 'content';
165
+ }
166
+ widget.section = String(widget.section || 'content').toLowerCase();
167
+
168
+ // Set template path
169
+ widget.template = this.getTemplateSlug(widget.type);
170
+ widget.template_path = `widgets/${widget.template}`;
171
+ widget.handle = `${widget.section}-${widget.template}-${widget.id}`.replace(/\s+/g, '-').toLowerCase();
172
+
173
+ return widget;
174
+ }
175
+
176
+ /**
177
+ * Get template slug for widget type
178
+ * @param {string} widgetType - Widget type
179
+ * @returns {string} Template slug
180
+ */
181
+ getTemplateSlug(widgetType) {
182
+ if (!widgetType) return 'default';
183
+
184
+ const slug = this.widgetTemplateMap[widgetType] ||
185
+ this.widgetTemplateMap[widgetType + 'Widget'] ||
186
+ widgetType.toLowerCase().replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
187
+
188
+ return slug;
189
+ }
190
+
191
+ /**
192
+ * Normalize array of widgets
193
+ * @param {Array} widgets
194
+ * @returns {Array}
195
+ */
196
+ normalizeWidgetArray(widgets = []) {
197
+ if (!Array.isArray(widgets)) {
198
+ return widgets;
199
+ }
200
+
201
+ const normalized = widgets.map(widget => this.normalizeWidget(widget));
202
+
203
+ // Sort by Position, order, or priority
204
+ normalized.sort((a, b) => {
205
+ const positionA = a.Position !== undefined ? a.Position : (a.position !== undefined ? a.position : (a.order || a.priority || 0));
206
+ const positionB = b.Position !== undefined ? b.Position : (b.position !== undefined ? b.position : (b.order || b.priority || 0));
207
+ return positionA - positionB;
208
+ });
209
+
210
+ return normalized;
211
+ }
212
+
213
+ /**
214
+ * Get widgets by section
215
+ * @param {string} section - Section name
216
+ * @param {Object} options - Options (pageId, etc.)
217
+ * @returns {Promise<Array>} Widgets array
218
+ */
219
+ async getWidgetsBySection(section, options = {}) {
220
+ try {
221
+ if (!this.apiClient) {
222
+ // If no API client, return empty array (mock mode will handle)
223
+ return [];
224
+ }
225
+
226
+ const params = {
227
+ section,
228
+ status: 'active',
229
+ ...options
230
+ };
231
+
232
+ const response = await this.apiClient.getWidgetsBySection(options.pageId, section);
233
+ const widgets = Array.isArray(response) ? response : (response.widgets || []);
234
+
235
+ return this.normalizeWidgetArray(widgets);
236
+ } catch (error) {
237
+ console.warn(`[WidgetService] Error getting widgets for section ${section}:`, error.message);
238
+ return [];
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Get all widgets organized by section
244
+ * @param {Object} options - Options (pageId, etc.)
245
+ * @returns {Promise<Object>} Widgets organized by section
246
+ */
247
+ async getWidgetsBySections(options = {}) {
248
+ try {
249
+ if (!this.apiClient) {
250
+ return {};
251
+ }
252
+
253
+ const params = {
254
+ status: 'active',
255
+ ...options
256
+ };
257
+
258
+ const response = await this.apiClient.getWidgets(params);
259
+ const widgets = Array.isArray(response) ? response : (response.widgets || []);
260
+
261
+ const normalized = this.normalizeWidgetArray(widgets);
262
+
263
+ // Organize by section
264
+ const widgetsBySection = {};
265
+ normalized.forEach(widget => {
266
+ const section = widget.section || 'content';
267
+ if (!widgetsBySection[section]) {
268
+ widgetsBySection[section] = [];
269
+ }
270
+ widgetsBySection[section].push(widget);
271
+ });
272
+
273
+ return widgetsBySection;
274
+ } catch (error) {
275
+ console.warn('[WidgetService] Error getting widgets:', error.message);
276
+ return {};
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Check if widget template exists in theme
282
+ * @param {string} widgetType - Widget type
283
+ * @param {string} themePath - Theme path
284
+ * @returns {boolean}
285
+ */
286
+ widgetTemplateExists(widgetType, themePath = null) {
287
+ const templateSlug = this.getTemplateSlug(widgetType);
288
+ const widgetPath = path.join(themePath || this.themePath, 'widgets', `${templateSlug}.liquid`);
289
+ return fs.existsSync(widgetPath);
290
+ }
291
+ }
292
+
293
+ module.exports = WidgetService;