@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,357 @@
1
+ /**
2
+ * O2VEND API Client
3
+ * Standalone implementation - copied and adapted from webstore solution
4
+ * Update manually when webstore solution changes
5
+ *
6
+ * Source: o2vend-webstore/services/o2vend-api-client.js
7
+ */
8
+
9
+ const axios = require('axios');
10
+ const NodeCache = require('node-cache');
11
+ const crypto = require('crypto');
12
+
13
+ /**
14
+ * O2VEND Storefront API Client
15
+ * Handles all API communication with the O2VEND Storefront API
16
+ *
17
+ * Note: The API returns all field names in lowercase (camelCase), not PascalCase.
18
+ */
19
+ class O2VendApiClient {
20
+ constructor(tenantId, apiKey, baseUrl = null, checkoutSignature = null) {
21
+ this.tenantId = tenantId;
22
+ this.apiKey = apiKey;
23
+ this.baseUrl = baseUrl || process.env.O2VEND_API_BASE_URL;
24
+ this.timeout = parseInt(process.env.O2VEND_API_TIMEOUT) || 10000;
25
+ this.checkoutSignature = checkoutSignature;
26
+
27
+ // Initialize cache with 5 minute TTL
28
+ this.cache = new NodeCache({
29
+ stdTTL: 300, // 5 minutes
30
+ checkperiod: 60 // Check for expired keys every minute
31
+ });
32
+
33
+ // Create axios instance with default config
34
+ this.client = axios.create({
35
+ baseURL: this.baseUrl && this.baseUrl.endsWith('/') ? this.baseUrl.slice(0, -1) : this.baseUrl,
36
+ timeout: this.timeout,
37
+ headers: {
38
+ 'X-O2VEND-SHOPFRONT-API-KEY': this.apiKey,
39
+ 'Content-Type': 'application/json',
40
+ 'User-Agent': 'O2VEND-Theme-CLI/1.0'
41
+ }
42
+ });
43
+
44
+ // Setup interceptors
45
+ this.setupInterceptors();
46
+ }
47
+
48
+ /**
49
+ * Normalize object keys from PascalCase to camelCase recursively
50
+ * @param {*} obj - Object to normalize
51
+ * @returns {*} Object with normalized keys
52
+ */
53
+ normalizeKeys(obj) {
54
+ if (obj === null || obj === undefined) {
55
+ return obj;
56
+ }
57
+
58
+ if (Array.isArray(obj)) {
59
+ return obj.map(item => this.normalizeKeys(item));
60
+ }
61
+
62
+ if (typeof obj === 'object') {
63
+ const normalized = {};
64
+ for (const key in obj) {
65
+ if (obj.hasOwnProperty(key)) {
66
+ const normalizedKey = key.charAt(0).toLowerCase() + key.slice(1);
67
+ normalized[normalizedKey] = this.normalizeKeys(obj[key]);
68
+ }
69
+ }
70
+ return normalized;
71
+ }
72
+
73
+ return obj;
74
+ }
75
+
76
+ /**
77
+ * Setup axios interceptors for logging and error handling
78
+ */
79
+ setupInterceptors() {
80
+ // Request interceptor
81
+ this.client.interceptors.request.use(
82
+ (config) => {
83
+ return config;
84
+ },
85
+ (error) => {
86
+ console.error('[O2VEND API] Request error:', error.message);
87
+ return Promise.reject(error);
88
+ }
89
+ );
90
+
91
+ // Response interceptor - normalize all response data
92
+ this.client.interceptors.response.use(
93
+ (response) => {
94
+ if (response.data) {
95
+ response.data = this.normalizeKeys(response.data);
96
+ }
97
+ return response;
98
+ },
99
+ (error) => {
100
+ if (error.response) {
101
+ const status = error.response.status;
102
+ if (status === 404 && process.env.DEBUG_API_ERRORS !== 'true') {
103
+ // Don't log 404s unless debug mode
104
+ } else {
105
+ console.error(`[O2VEND API] Response error: ${status}`, error.message);
106
+ }
107
+ } else if (error.request) {
108
+ // Don't log timeout errors for mock API (they're expected during startup)
109
+ if (!error.message.includes('timeout')) {
110
+ console.error('[O2VEND API] Network error: Unable to reach API');
111
+ }
112
+ }
113
+ return Promise.reject(error);
114
+ }
115
+ );
116
+ }
117
+
118
+ /**
119
+ * Generic GET request with caching
120
+ * @param {string} endpoint - API endpoint
121
+ * @param {Object} params - Query parameters
122
+ * @param {boolean} useCache - Whether to use cache (default: false)
123
+ * @returns {Promise<Object>} API response data
124
+ */
125
+ async get(endpoint, params = {}, useCache = false) {
126
+ const cacheKey = `${endpoint}-${JSON.stringify(params)}`;
127
+
128
+ if (useCache && this.cache.has(cacheKey)) {
129
+ return this.cache.get(cacheKey);
130
+ }
131
+
132
+ try {
133
+ const response = await this.client.get(endpoint, { params });
134
+ const data = response.data;
135
+
136
+ if (useCache) {
137
+ this.cache.set(cacheKey, data);
138
+ }
139
+
140
+ return data;
141
+ } catch (error) {
142
+ // Only log non-timeout errors (timeouts are expected during startup)
143
+ if (!error.message.includes('timeout')) {
144
+ console.error(`[O2VEND API] GET ${endpoint} failed:`, error.message);
145
+ }
146
+ throw error;
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Generic POST request
152
+ * @param {string} endpoint - API endpoint
153
+ * @param {Object} data - Request body
154
+ * @param {Object} config - Additional axios config
155
+ * @returns {Promise<Object>} API response data
156
+ */
157
+ async post(endpoint, data = {}, config = {}) {
158
+ try {
159
+ const response = await this.client.post(endpoint, data, config);
160
+ const result = response.data;
161
+ // Log successful responses for debugging (only for widgets and products)
162
+ if (endpoint.includes('widgets') || endpoint.includes('products')) {
163
+ const count = result.widgets?.length || result.products?.length || result.data?.widgets?.length || result.data?.products?.length || 0;
164
+ console.log(`[O2VEND API] POST ${endpoint} success: ${count} items`);
165
+ }
166
+ return result;
167
+ } catch (error) {
168
+ // Only log non-timeout errors (timeouts are expected during startup)
169
+ if (!error.message.includes('timeout')) {
170
+ console.error(`[O2VEND API] POST ${endpoint} failed:`, error.message);
171
+ if (error.response) {
172
+ console.error(`[O2VEND API] Response status: ${error.response.status}`, error.response.data);
173
+ }
174
+ }
175
+ throw error;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Generic PUT request
181
+ * @param {string} endpoint - API endpoint
182
+ * @param {Object} data - Request body
183
+ * @param {Object} config - Additional axios config
184
+ * @returns {Promise<Object>} API response data
185
+ */
186
+ async put(endpoint, data = {}, config = {}) {
187
+ try {
188
+ const response = await this.client.put(endpoint, data, config);
189
+ return response.data;
190
+ } catch (error) {
191
+ console.error(`[O2VEND API] PUT ${endpoint} failed:`, error.message);
192
+ throw error;
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Generic DELETE request
198
+ * @param {string} endpoint - API endpoint
199
+ * @param {Object} config - Additional axios config
200
+ * @returns {Promise<Object>} API response data
201
+ */
202
+ async delete(endpoint, config = {}) {
203
+ try {
204
+ const response = await this.client.delete(endpoint, config);
205
+ return response.data;
206
+ } catch (error) {
207
+ console.error(`[O2VEND API] DELETE ${endpoint} failed:`, error.message);
208
+ throw error;
209
+ }
210
+ }
211
+
212
+ // ==================== CORE ENDPOINTS ====================
213
+
214
+ /**
215
+ * Get store information
216
+ * @param {boolean} useCache - Whether to use cache
217
+ * @returns {Promise<Object>} Store information
218
+ */
219
+ async getStoreInfo(useCache = false) {
220
+ return this.get('/shopfront/api/v2/storeinfo', {}, useCache);
221
+ }
222
+
223
+ /**
224
+ * Get store settings
225
+ * @param {boolean} useCache - Whether to use cache
226
+ * @returns {Promise<Object>} Store settings
227
+ */
228
+ async getSettings(useCache = false) {
229
+ return this.get('/shopfront/api/v2/settings', {}, useCache);
230
+ }
231
+
232
+ // ==================== PRODUCT ENDPOINTS ====================
233
+
234
+ /**
235
+ * Get products
236
+ * @param {Object} productQueryRequest - Query parameters
237
+ * @returns {Promise<Object>} Products response
238
+ */
239
+ async getProducts(productQueryRequest = {}) {
240
+ return this.post('/shopfront/api/v2/products', productQueryRequest);
241
+ }
242
+
243
+ /**
244
+ * Get product by ID
245
+ * @param {string} id - Product ID
246
+ * @param {boolean} useCache - Whether to use cache
247
+ * @returns {Promise<Object>} Product data
248
+ */
249
+ async getProductById(id, useCache = false) {
250
+ return this.get(`/shopfront/api/v2/products/${id}`, {}, useCache);
251
+ }
252
+
253
+ /**
254
+ * Search products
255
+ * @param {Object} searchRequest - Search parameters
256
+ * @returns {Promise<Object>} Search results
257
+ */
258
+ async searchProducts(searchRequest = {}) {
259
+ return this.post('/shopfront/api/v2/products/search', searchRequest);
260
+ }
261
+
262
+ // ==================== CATEGORY ENDPOINTS ====================
263
+
264
+ /**
265
+ * Get categories
266
+ * @param {Object} categoryQueryRequest - Query parameters
267
+ * @returns {Promise<Object>} Categories response
268
+ */
269
+ async getCategories(categoryQueryRequest = {}) {
270
+ return this.post('/shopfront/api/v2/categories', categoryQueryRequest);
271
+ }
272
+
273
+ // ==================== BRAND ENDPOINTS ====================
274
+
275
+ /**
276
+ * Get brands
277
+ * @param {Object} brandQueryRequest - Query parameters
278
+ * @returns {Promise<Object>} Brands response
279
+ */
280
+ async getBrands(brandQueryRequest = {}) {
281
+ return this.post('/shopfront/api/v2/brands', brandQueryRequest);
282
+ }
283
+
284
+ // ==================== WIDGET ENDPOINTS ====================
285
+
286
+ /**
287
+ * Get widgets
288
+ * @param {Object} params - Widget query parameters (section, pageId, etc.)
289
+ * @returns {Promise<Object>} Widgets response
290
+ */
291
+ async getWidgets(params = {}) {
292
+ return this.post('/shopfront/api/v2/widgets', params);
293
+ }
294
+
295
+ /**
296
+ * Get widgets by section
297
+ * @param {string} pageId - Page ID (optional)
298
+ * @param {string} section - Section name
299
+ * @returns {Promise<Array>} Widgets array
300
+ */
301
+ async getWidgetsBySection(pageId, section) {
302
+ const params = {
303
+ section,
304
+ status: 'active'
305
+ };
306
+ if (pageId) {
307
+ params.pageId = pageId;
308
+ }
309
+
310
+ const response = await this.getWidgets(params);
311
+ return response.widgets || response.data || [];
312
+ }
313
+
314
+ // ==================== CART ENDPOINTS ====================
315
+
316
+ /**
317
+ * Get cart
318
+ * @param {string} cartId - Cart ID
319
+ * @returns {Promise<Object>} Cart data
320
+ */
321
+ async getCart(cartId) {
322
+ return this.get(`/shopfront/api/v2/cart/${cartId}`);
323
+ }
324
+
325
+ /**
326
+ * Add item to cart
327
+ * @param {string} cartId - Cart ID
328
+ * @param {Object} item - Cart item
329
+ * @returns {Promise<Object>} Updated cart
330
+ */
331
+ async addToCart(cartId, item) {
332
+ return this.post(`/shopfront/api/v2/cart/${cartId}/items`, item);
333
+ }
334
+
335
+ /**
336
+ * Update cart item
337
+ * @param {string} cartId - Cart ID
338
+ * @param {string} itemId - Item ID
339
+ * @param {Object} update - Update data
340
+ * @returns {Promise<Object>} Updated cart
341
+ */
342
+ async updateCartItem(cartId, itemId, update) {
343
+ return this.put(`/shopfront/api/v2/cart/${cartId}/items/${itemId}`, update);
344
+ }
345
+
346
+ /**
347
+ * Remove cart item
348
+ * @param {string} cartId - Cart ID
349
+ * @param {string} itemId - Item ID
350
+ * @returns {Promise<Object>} Updated cart
351
+ */
352
+ async removeCartItem(cartId, itemId) {
353
+ return this.delete(`/shopfront/api/v2/cart/${cartId}/items/${itemId}`);
354
+ }
355
+ }
356
+
357
+ module.exports = O2VendApiClient;