@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.
- package/README.md +211 -0
- package/bin/o2vend +34 -0
- package/config/widget-map.json +45 -0
- package/package.json +64 -0
- package/src/commands/check.js +201 -0
- package/src/commands/generate.js +33 -0
- package/src/commands/init.js +302 -0
- package/src/commands/optimize.js +216 -0
- package/src/commands/package.js +208 -0
- package/src/commands/serve.js +105 -0
- package/src/commands/validate.js +191 -0
- package/src/lib/api-client.js +339 -0
- package/src/lib/dev-server.js +482 -0
- package/src/lib/file-watcher.js +80 -0
- package/src/lib/hot-reload.js +106 -0
- package/src/lib/liquid-engine.js +169 -0
- package/src/lib/liquid-filters.js +589 -0
- package/src/lib/mock-api-server.js +225 -0
- package/src/lib/mock-data.js +290 -0
- package/src/lib/widget-service.js +293 -0
|
@@ -0,0 +1,339 @@
|
|
|
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
|
+
console.error('[O2VEND API] Network error: Unable to reach API');
|
|
109
|
+
}
|
|
110
|
+
return Promise.reject(error);
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Generic GET request with caching
|
|
117
|
+
* @param {string} endpoint - API endpoint
|
|
118
|
+
* @param {Object} params - Query parameters
|
|
119
|
+
* @param {boolean} useCache - Whether to use cache (default: false)
|
|
120
|
+
* @returns {Promise<Object>} API response data
|
|
121
|
+
*/
|
|
122
|
+
async get(endpoint, params = {}, useCache = false) {
|
|
123
|
+
const cacheKey = `${endpoint}-${JSON.stringify(params)}`;
|
|
124
|
+
|
|
125
|
+
if (useCache && this.cache.has(cacheKey)) {
|
|
126
|
+
return this.cache.get(cacheKey);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const response = await this.client.get(endpoint, { params });
|
|
131
|
+
const data = response.data;
|
|
132
|
+
|
|
133
|
+
if (useCache) {
|
|
134
|
+
this.cache.set(cacheKey, data);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return data;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error(`[O2VEND API] GET ${endpoint} failed:`, error.message);
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Generic POST request
|
|
146
|
+
* @param {string} endpoint - API endpoint
|
|
147
|
+
* @param {Object} data - Request body
|
|
148
|
+
* @param {Object} config - Additional axios config
|
|
149
|
+
* @returns {Promise<Object>} API response data
|
|
150
|
+
*/
|
|
151
|
+
async post(endpoint, data = {}, config = {}) {
|
|
152
|
+
try {
|
|
153
|
+
const response = await this.client.post(endpoint, data, config);
|
|
154
|
+
return response.data;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error(`[O2VEND API] POST ${endpoint} failed:`, error.message);
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Generic PUT request
|
|
163
|
+
* @param {string} endpoint - API endpoint
|
|
164
|
+
* @param {Object} data - Request body
|
|
165
|
+
* @param {Object} config - Additional axios config
|
|
166
|
+
* @returns {Promise<Object>} API response data
|
|
167
|
+
*/
|
|
168
|
+
async put(endpoint, data = {}, config = {}) {
|
|
169
|
+
try {
|
|
170
|
+
const response = await this.client.put(endpoint, data, config);
|
|
171
|
+
return response.data;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error(`[O2VEND API] PUT ${endpoint} failed:`, error.message);
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Generic DELETE request
|
|
180
|
+
* @param {string} endpoint - API endpoint
|
|
181
|
+
* @param {Object} config - Additional axios config
|
|
182
|
+
* @returns {Promise<Object>} API response data
|
|
183
|
+
*/
|
|
184
|
+
async delete(endpoint, config = {}) {
|
|
185
|
+
try {
|
|
186
|
+
const response = await this.client.delete(endpoint, config);
|
|
187
|
+
return response.data;
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error(`[O2VEND API] DELETE ${endpoint} failed:`, error.message);
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ==================== CORE ENDPOINTS ====================
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get store information
|
|
198
|
+
* @param {boolean} useCache - Whether to use cache
|
|
199
|
+
* @returns {Promise<Object>} Store information
|
|
200
|
+
*/
|
|
201
|
+
async getStoreInfo(useCache = false) {
|
|
202
|
+
return this.get('/shopfront/api/v2/storeinfo', {}, useCache);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get store settings
|
|
207
|
+
* @param {boolean} useCache - Whether to use cache
|
|
208
|
+
* @returns {Promise<Object>} Store settings
|
|
209
|
+
*/
|
|
210
|
+
async getSettings(useCache = false) {
|
|
211
|
+
return this.get('/shopfront/api/v2/settings', {}, useCache);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ==================== PRODUCT ENDPOINTS ====================
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get products
|
|
218
|
+
* @param {Object} productQueryRequest - Query parameters
|
|
219
|
+
* @returns {Promise<Object>} Products response
|
|
220
|
+
*/
|
|
221
|
+
async getProducts(productQueryRequest = {}) {
|
|
222
|
+
return this.post('/shopfront/api/v2/products', productQueryRequest);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Get product by ID
|
|
227
|
+
* @param {string} id - Product ID
|
|
228
|
+
* @param {boolean} useCache - Whether to use cache
|
|
229
|
+
* @returns {Promise<Object>} Product data
|
|
230
|
+
*/
|
|
231
|
+
async getProductById(id, useCache = false) {
|
|
232
|
+
return this.get(`/shopfront/api/v2/products/${id}`, {}, useCache);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Search products
|
|
237
|
+
* @param {Object} searchRequest - Search parameters
|
|
238
|
+
* @returns {Promise<Object>} Search results
|
|
239
|
+
*/
|
|
240
|
+
async searchProducts(searchRequest = {}) {
|
|
241
|
+
return this.post('/shopfront/api/v2/products/search', searchRequest);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ==================== CATEGORY ENDPOINTS ====================
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Get categories
|
|
248
|
+
* @param {Object} categoryQueryRequest - Query parameters
|
|
249
|
+
* @returns {Promise<Object>} Categories response
|
|
250
|
+
*/
|
|
251
|
+
async getCategories(categoryQueryRequest = {}) {
|
|
252
|
+
return this.post('/shopfront/api/v2/categories', categoryQueryRequest);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ==================== BRAND ENDPOINTS ====================
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get brands
|
|
259
|
+
* @param {Object} brandQueryRequest - Query parameters
|
|
260
|
+
* @returns {Promise<Object>} Brands response
|
|
261
|
+
*/
|
|
262
|
+
async getBrands(brandQueryRequest = {}) {
|
|
263
|
+
return this.post('/shopfront/api/v2/brands', brandQueryRequest);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ==================== WIDGET ENDPOINTS ====================
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Get widgets
|
|
270
|
+
* @param {Object} params - Widget query parameters (section, pageId, etc.)
|
|
271
|
+
* @returns {Promise<Object>} Widgets response
|
|
272
|
+
*/
|
|
273
|
+
async getWidgets(params = {}) {
|
|
274
|
+
return this.post('/shopfront/api/v2/widgets', params);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Get widgets by section
|
|
279
|
+
* @param {string} pageId - Page ID (optional)
|
|
280
|
+
* @param {string} section - Section name
|
|
281
|
+
* @returns {Promise<Array>} Widgets array
|
|
282
|
+
*/
|
|
283
|
+
async getWidgetsBySection(pageId, section) {
|
|
284
|
+
const params = {
|
|
285
|
+
section,
|
|
286
|
+
status: 'active'
|
|
287
|
+
};
|
|
288
|
+
if (pageId) {
|
|
289
|
+
params.pageId = pageId;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const response = await this.getWidgets(params);
|
|
293
|
+
return response.widgets || response.data || [];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ==================== CART ENDPOINTS ====================
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Get cart
|
|
300
|
+
* @param {string} cartId - Cart ID
|
|
301
|
+
* @returns {Promise<Object>} Cart data
|
|
302
|
+
*/
|
|
303
|
+
async getCart(cartId) {
|
|
304
|
+
return this.get(`/shopfront/api/v2/cart/${cartId}`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Add item to cart
|
|
309
|
+
* @param {string} cartId - Cart ID
|
|
310
|
+
* @param {Object} item - Cart item
|
|
311
|
+
* @returns {Promise<Object>} Updated cart
|
|
312
|
+
*/
|
|
313
|
+
async addToCart(cartId, item) {
|
|
314
|
+
return this.post(`/shopfront/api/v2/cart/${cartId}/items`, item);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Update cart item
|
|
319
|
+
* @param {string} cartId - Cart ID
|
|
320
|
+
* @param {string} itemId - Item ID
|
|
321
|
+
* @param {Object} update - Update data
|
|
322
|
+
* @returns {Promise<Object>} Updated cart
|
|
323
|
+
*/
|
|
324
|
+
async updateCartItem(cartId, itemId, update) {
|
|
325
|
+
return this.put(`/shopfront/api/v2/cart/${cartId}/items/${itemId}`, update);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Remove cart item
|
|
330
|
+
* @param {string} cartId - Cart ID
|
|
331
|
+
* @param {string} itemId - Item ID
|
|
332
|
+
* @returns {Promise<Object>} Updated cart
|
|
333
|
+
*/
|
|
334
|
+
async removeCartItem(cartId, itemId) {
|
|
335
|
+
return this.delete(`/shopfront/api/v2/cart/${cartId}/items/${itemId}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
module.exports = O2VendApiClient;
|