@o2vend/theme-cli 1.0.32

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.
Files changed (116) hide show
  1. package/README.md +425 -0
  2. package/assets/Logo_o2vend.png +0 -0
  3. package/assets/favicon.png +0 -0
  4. package/assets/logo-white.png +0 -0
  5. package/bin/o2vend +42 -0
  6. package/config/widget-map.json +50 -0
  7. package/lib/commands/check.js +201 -0
  8. package/lib/commands/generate.js +33 -0
  9. package/lib/commands/init.js +214 -0
  10. package/lib/commands/optimize.js +216 -0
  11. package/lib/commands/package.js +208 -0
  12. package/lib/commands/serve.js +105 -0
  13. package/lib/commands/validate.js +191 -0
  14. package/lib/lib/api-client.js +357 -0
  15. package/lib/lib/dev-server.js +2618 -0
  16. package/lib/lib/file-watcher.js +80 -0
  17. package/lib/lib/hot-reload.js +106 -0
  18. package/lib/lib/liquid-engine.js +822 -0
  19. package/lib/lib/liquid-filters.js +671 -0
  20. package/lib/lib/mock-api-server.js +989 -0
  21. package/lib/lib/mock-data.js +1468 -0
  22. package/lib/lib/widget-service.js +321 -0
  23. package/package.json +70 -0
  24. package/test-theme/README.md +27 -0
  25. package/test-theme/assets/async-sections.js +446 -0
  26. package/test-theme/assets/cart-drawer.js +463 -0
  27. package/test-theme/assets/cart-manager.js +223 -0
  28. package/test-theme/assets/checkout-price-handler.js +368 -0
  29. package/test-theme/assets/components.css +4629 -0
  30. package/test-theme/assets/delivery-zone.css +299 -0
  31. package/test-theme/assets/delivery-zone.js +396 -0
  32. package/test-theme/assets/logo.png +0 -0
  33. package/test-theme/assets/sections.css +48 -0
  34. package/test-theme/assets/theme.css +3500 -0
  35. package/test-theme/assets/theme.js +3745 -0
  36. package/test-theme/config/settings_data.json +292 -0
  37. package/test-theme/config/settings_schema.json +1050 -0
  38. package/test-theme/layout/theme.liquid +195 -0
  39. package/test-theme/locales/en.default.json +260 -0
  40. package/test-theme/sections/content-fallback.liquid +53 -0
  41. package/test-theme/sections/content.liquid +57 -0
  42. package/test-theme/sections/footer-fallback.liquid +328 -0
  43. package/test-theme/sections/footer.liquid +278 -0
  44. package/test-theme/sections/header-fallback.liquid +1805 -0
  45. package/test-theme/sections/header.liquid +1145 -0
  46. package/test-theme/sections/hero-fallback.liquid +212 -0
  47. package/test-theme/sections/hero.liquid +136 -0
  48. package/test-theme/snippets/account-sidebar.liquid +200 -0
  49. package/test-theme/snippets/add-to-cart-modal.liquid +484 -0
  50. package/test-theme/snippets/breadcrumbs.liquid +134 -0
  51. package/test-theme/snippets/cart-drawer.liquid +467 -0
  52. package/test-theme/snippets/delivery-zone-city-selector.liquid +79 -0
  53. package/test-theme/snippets/delivery-zone-modal.liquid +337 -0
  54. package/test-theme/snippets/delivery-zone-search.liquid +78 -0
  55. package/test-theme/snippets/icon.liquid +105 -0
  56. package/test-theme/snippets/login-modal.liquid +346 -0
  57. package/test-theme/snippets/mega-menu.liquid +812 -0
  58. package/test-theme/snippets/news-thumbnail.liquid +187 -0
  59. package/test-theme/snippets/pagination.liquid +120 -0
  60. package/test-theme/snippets/price.liquid +92 -0
  61. package/test-theme/snippets/product-card-related.liquid +78 -0
  62. package/test-theme/snippets/product-card-simple.liquid +41 -0
  63. package/test-theme/snippets/product-card.liquid +697 -0
  64. package/test-theme/snippets/rating.liquid +85 -0
  65. package/test-theme/snippets/skeleton-collection-grid.liquid +114 -0
  66. package/test-theme/snippets/skeleton-product-card.liquid +124 -0
  67. package/test-theme/snippets/skeleton-product-grid.liquid +34 -0
  68. package/test-theme/snippets/social-sharing.liquid +185 -0
  69. package/test-theme/templates/account/dashboard.liquid +401 -0
  70. package/test-theme/templates/account/loyalty-redemption.liquid +405 -0
  71. package/test-theme/templates/account/loyalty.liquid +588 -0
  72. package/test-theme/templates/account/order-detail.liquid +230 -0
  73. package/test-theme/templates/account/orders.liquid +349 -0
  74. package/test-theme/templates/account/profile.liquid +758 -0
  75. package/test-theme/templates/account/register.liquid +232 -0
  76. package/test-theme/templates/account/return-orders.liquid +348 -0
  77. package/test-theme/templates/account/store-credit.liquid +464 -0
  78. package/test-theme/templates/account/subscriptions.liquid +601 -0
  79. package/test-theme/templates/account/wishlist.liquid +419 -0
  80. package/test-theme/templates/address-book.liquid +1092 -0
  81. package/test-theme/templates/categories.liquid +452 -0
  82. package/test-theme/templates/checkout.liquid +4511 -0
  83. package/test-theme/templates/error.liquid +384 -0
  84. package/test-theme/templates/index.liquid +11 -0
  85. package/test-theme/templates/login.liquid +185 -0
  86. package/test-theme/templates/order-confirmation.liquid +720 -0
  87. package/test-theme/templates/page.liquid +297 -0
  88. package/test-theme/templates/product-detail.liquid +4363 -0
  89. package/test-theme/templates/products.liquid +518 -0
  90. package/test-theme/templates/search.liquid +922 -0
  91. package/test-theme/theme.json.example +19 -0
  92. package/test-theme/widgets/brand-carousel.liquid +676 -0
  93. package/test-theme/widgets/brand.liquid +245 -0
  94. package/test-theme/widgets/carousel.liquid +843 -0
  95. package/test-theme/widgets/category-list-carousel.liquid +656 -0
  96. package/test-theme/widgets/category-list.liquid +340 -0
  97. package/test-theme/widgets/category.liquid +475 -0
  98. package/test-theme/widgets/discount-time.liquid +176 -0
  99. package/test-theme/widgets/footer-menu.liquid +695 -0
  100. package/test-theme/widgets/footer.liquid +179 -0
  101. package/test-theme/widgets/gallery.liquid +271 -0
  102. package/test-theme/widgets/header-menu.liquid +932 -0
  103. package/test-theme/widgets/header.liquid +159 -0
  104. package/test-theme/widgets/html.liquid +214 -0
  105. package/test-theme/widgets/news.liquid +217 -0
  106. package/test-theme/widgets/product-canvas.liquid +235 -0
  107. package/test-theme/widgets/product-carousel.liquid +502 -0
  108. package/test-theme/widgets/product.liquid +45 -0
  109. package/test-theme/widgets/recently-viewed.liquid +26 -0
  110. package/test-theme/widgets/shared/product-grid.liquid +339 -0
  111. package/test-theme/widgets/simple-product.liquid +42 -0
  112. package/test-theme/widgets/single-product.liquid +610 -0
  113. package/test-theme/widgets/spacebar-carousel.liquid +663 -0
  114. package/test-theme/widgets/spacebar.liquid +279 -0
  115. package/test-theme/widgets/splash.liquid +378 -0
  116. package/test-theme/widgets/testimonial-carousel.liquid +709 -0
@@ -0,0 +1,321 @@
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.templatePath = widget.template_path; // Also set camelCase version
172
+ widget.handle = `${widget.section}-${widget.template}-${widget.id}`.replace(/\s+/g, '-').toLowerCase();
173
+
174
+ // Ensure template_path is always set for rendering
175
+ if (!widget.template_path) {
176
+ widget.template_path = `widgets/${widget.template}`;
177
+ }
178
+
179
+ return widget;
180
+ }
181
+
182
+ /**
183
+ * Get template slug for widget type
184
+ * @param {string} widgetType - Widget type
185
+ * @returns {string} Template slug
186
+ */
187
+ getTemplateSlug(widgetType) {
188
+ if (!widgetType) return 'default';
189
+
190
+ const slug = this.widgetTemplateMap[widgetType] ||
191
+ this.widgetTemplateMap[widgetType + 'Widget'] ||
192
+ widgetType.toLowerCase().replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
193
+
194
+ return slug;
195
+ }
196
+
197
+ /**
198
+ * Normalize array of widgets
199
+ * @param {Array} widgets
200
+ * @returns {Array}
201
+ */
202
+ normalizeWidgetArray(widgets = []) {
203
+ if (!Array.isArray(widgets)) {
204
+ return widgets;
205
+ }
206
+
207
+ const normalized = widgets.map(widget => this.normalizeWidget(widget));
208
+
209
+ // Sort by Position, order, or priority
210
+ normalized.sort((a, b) => {
211
+ const positionA = a.Position !== undefined ? a.Position : (a.position !== undefined ? a.position : (a.order || a.priority || 0));
212
+ const positionB = b.Position !== undefined ? b.Position : (b.position !== undefined ? b.position : (b.order || b.priority || 0));
213
+ return positionA - positionB;
214
+ });
215
+
216
+ return normalized;
217
+ }
218
+
219
+ /**
220
+ * Get widgets by section
221
+ * @param {string} section - Section name
222
+ * @param {Object} options - Options (pageId, etc.)
223
+ * @returns {Promise<Array>} Widgets array
224
+ */
225
+ async getWidgetsBySection(section, options = {}) {
226
+ try {
227
+ if (!this.apiClient) {
228
+ // If no API client, return empty array (mock mode will handle)
229
+ return [];
230
+ }
231
+
232
+ const params = {
233
+ section,
234
+ status: 'active',
235
+ ...options
236
+ };
237
+
238
+ const response = await this.apiClient.getWidgetsBySection(options.pageId, section);
239
+ const widgets = Array.isArray(response) ? response : (response.widgets || []);
240
+
241
+ return this.normalizeWidgetArray(widgets);
242
+ } catch (error) {
243
+ console.warn(`[WidgetService] Error getting widgets for section ${section}:`, error.message);
244
+ return [];
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Get all widgets organized by section
250
+ * @param {Object} options - Options (pageId, etc.)
251
+ * @returns {Promise<Object>} Widgets organized by section
252
+ */
253
+ async getWidgetsBySections(options = {}) {
254
+ try {
255
+ if (!this.apiClient) {
256
+ return {};
257
+ }
258
+
259
+ const params = {
260
+ status: 'active',
261
+ ...options
262
+ };
263
+
264
+ const response = await this.apiClient.getWidgets(params);
265
+ const widgets = Array.isArray(response) ? response : (response.widgets || []);
266
+
267
+ console.log(`[WidgetService] Received ${widgets.length} widgets from API`);
268
+
269
+ const normalized = this.normalizeWidgetArray(widgets);
270
+
271
+ // DEBUG: Verify widgets have template_path after normalization
272
+ if (normalized.length > 0) {
273
+ const sampleWidget = normalized[0];
274
+ console.log(`[WidgetService DEBUG] Sample widget after normalization:`);
275
+ console.log(` - Type: ${sampleWidget.type}`);
276
+ console.log(` - ID: ${sampleWidget.id}`);
277
+ console.log(` - Template: ${sampleWidget.template || 'N/A'}`);
278
+ console.log(` - Template path: ${sampleWidget.template_path || 'N/A'}`);
279
+ console.log(` - Section: ${sampleWidget.section || 'N/A'}`);
280
+
281
+ // Check if template file exists
282
+ if (sampleWidget.template_path) {
283
+ const templatePath = path.join(this.themePath, `${sampleWidget.template_path}.liquid`);
284
+ const templateExists = fs.existsSync(templatePath);
285
+ console.log(` - Template file exists: ${templateExists} (${templatePath})`);
286
+ }
287
+ }
288
+
289
+ // Organize by section
290
+ const widgetsBySection = {};
291
+ normalized.forEach(widget => {
292
+ const section = (widget.section || widget.sectionName || 'content').toLowerCase();
293
+ if (!widgetsBySection[section]) {
294
+ widgetsBySection[section] = [];
295
+ }
296
+ widgetsBySection[section].push(widget);
297
+ });
298
+
299
+ console.log(`[WidgetService] Organized widgets into sections: ${Object.keys(widgetsBySection).join(', ')}`);
300
+
301
+ return widgetsBySection;
302
+ } catch (error) {
303
+ console.warn('[WidgetService] Error getting widgets:', error.message);
304
+ return {};
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Check if widget template exists in theme
310
+ * @param {string} widgetType - Widget type
311
+ * @param {string} themePath - Theme path
312
+ * @returns {boolean}
313
+ */
314
+ widgetTemplateExists(widgetType, themePath = null) {
315
+ const templateSlug = this.getTemplateSlug(widgetType);
316
+ const widgetPath = path.join(themePath || this.themePath, 'widgets', `${templateSlug}.liquid`);
317
+ return fs.existsSync(widgetPath);
318
+ }
319
+ }
320
+
321
+ module.exports = WidgetService;
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@o2vend/theme-cli",
3
+ "version": "1.0.32",
4
+ "description": "O2VEND Theme Development CLI - Standalone tool for local theme development",
5
+ "bin": {
6
+ "o2vend": "./bin/o2vend"
7
+ },
8
+ "prepublishOnly": "npm run build",
9
+ "keywords": [
10
+ "o2vend",
11
+ "theme",
12
+ "cli",
13
+ "ecommerce",
14
+ "liquid"
15
+ ],
16
+ "scripts": {
17
+ "test": "echo \"Error: no test specified\" && exit 1",
18
+ "build": "node scripts/build.js",
19
+ "deprecate-old": "node scripts/deprecate-old-versions.js",
20
+ "unpublish-all": "node scripts/unpublish-all-versions.js",
21
+ "republish-clean": "node scripts/republish-clean.js",
22
+ "unpublish-and-republish": "node scripts/unpublish-and-republish.js"
23
+ },
24
+ "dependencies": {
25
+ "commander": "^11.0.0",
26
+ "express": "^4.18.2",
27
+ "liquidjs": "^10.7.0",
28
+ "axios": "^1.6.0",
29
+ "lodash": "^4.17.21",
30
+ "moment": "^2.29.4",
31
+ "slugify": "^1.6.6",
32
+ "chokidar": "^3.5.3",
33
+ "socket.io": "^4.7.0",
34
+ "chalk": "^4.1.2",
35
+ "inquirer": "^9.2.0",
36
+ "ora": "^5.4.1",
37
+ "boxen": "^7.1.1",
38
+ "table": "^6.8.3",
39
+ "listr": "^0.14.3",
40
+ "dotenv": "^16.3.1",
41
+ "archiver": "^6.0.1",
42
+ "glob": "^10.3.10",
43
+ "fs-extra": "^11.1.1",
44
+ "node-cache": "^5.1.2",
45
+ "http-proxy-middleware": "^2.0.6",
46
+ "open": "^8.4.2"
47
+ },
48
+ "engines": {
49
+ "node": ">=18.0.0"
50
+ },
51
+ "files": [
52
+ "bin/**/*",
53
+ "lib/**/*",
54
+ "config/**/*",
55
+ "assets/**/*",
56
+ "test-theme/**/*",
57
+ "README.md"
58
+ ],
59
+ "repository": {
60
+ "type": "git",
61
+ "url": "https://github.com/Jeyan-Technologies/o2vend-theme-dev-tools.git",
62
+ "directory": "packages/cli"
63
+ },
64
+ "publishConfig": {
65
+ "access": "public",
66
+ "registry": "https://registry.npmjs.org"
67
+ },
68
+ "author": "Jeyan Technologies Private Limited",
69
+ "license": "MIT"
70
+ }
@@ -0,0 +1,27 @@
1
+ # test-theme
2
+
3
+ O2VEND Theme
4
+
5
+ ## Getting Started
6
+
7
+ 1. Start development server:
8
+ ```bash
9
+ o2vend serve
10
+ ```
11
+
12
+ 2. Edit theme files in this directory
13
+
14
+ 3. Package for marketplace:
15
+ ```bash
16
+ o2vend package
17
+ ```
18
+
19
+ ## Structure
20
+
21
+ - `layout/` - Layout templates
22
+ - `templates/` - Page templates
23
+ - `sections/` - Section components
24
+ - `widgets/` - Widget templates
25
+ - `snippets/` - Reusable snippets
26
+ - `assets/` - CSS, JavaScript, images
27
+ - `config/` - Theme configuration