@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,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LiquidJS Engine Setup
|
|
3
|
+
* Configures LiquidJS with all O2VEND filters and custom tags
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { Liquid } = require('liquidjs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const LiquidHelperService = require('./liquid-filters');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create and configure LiquidJS engine
|
|
13
|
+
* @param {string} themePath - Path to theme directory
|
|
14
|
+
* @param {Object} options - Engine options
|
|
15
|
+
* @returns {Liquid} Configured LiquidJS engine
|
|
16
|
+
*/
|
|
17
|
+
function createLiquidEngine(themePath, options = {}) {
|
|
18
|
+
const {
|
|
19
|
+
cache = false, // Disable cache in dev mode
|
|
20
|
+
extname = '.liquid'
|
|
21
|
+
} = options;
|
|
22
|
+
|
|
23
|
+
// Create Liquid engine
|
|
24
|
+
const liquid = new Liquid({
|
|
25
|
+
root: themePath,
|
|
26
|
+
extname: extname,
|
|
27
|
+
cache: cache,
|
|
28
|
+
strictFilters: false,
|
|
29
|
+
strictVariables: false,
|
|
30
|
+
outputEscape: 'escape',
|
|
31
|
+
ownPropertyOnly: false
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Register all filters from LiquidHelperService
|
|
35
|
+
const liquidHelperService = new LiquidHelperService();
|
|
36
|
+
const filters = liquidHelperService.getFilters();
|
|
37
|
+
|
|
38
|
+
Object.keys(filters).forEach(filterName => {
|
|
39
|
+
liquid.registerFilter(filterName, filters[filterName]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Register custom tags
|
|
43
|
+
registerCustomTags(liquid, themePath);
|
|
44
|
+
|
|
45
|
+
return liquid;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Register custom Liquid tags (section, hook, schema)
|
|
50
|
+
* @param {Liquid} liquid - LiquidJS engine instance
|
|
51
|
+
* @param {string} themePath - Theme path
|
|
52
|
+
*/
|
|
53
|
+
function registerCustomTags(liquid, themePath) {
|
|
54
|
+
// Register section tag
|
|
55
|
+
liquid.registerTag('section', {
|
|
56
|
+
parse: function(tagToken, remainTokens) {
|
|
57
|
+
this.sectionPath = tagToken.args.trim().replace(/^['"]|['"]$/g, '');
|
|
58
|
+
},
|
|
59
|
+
render: async function(scope, hash) {
|
|
60
|
+
const sectionPath = this.sectionPath;
|
|
61
|
+
if (!sectionPath) {
|
|
62
|
+
console.error('[SECTION] No section path provided');
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const sectionFile = path.join(themePath, 'sections', `${sectionPath}.liquid`);
|
|
68
|
+
if (fs.existsSync(sectionFile)) {
|
|
69
|
+
const sectionContent = fs.readFileSync(sectionFile, 'utf8');
|
|
70
|
+
const scopeContexts = Array.isArray(scope?.contexts) ? scope.contexts : [];
|
|
71
|
+
const primaryScope = scopeContexts.length > 0
|
|
72
|
+
? scopeContexts[0]
|
|
73
|
+
: (scope?.environments || scope?.context || {});
|
|
74
|
+
|
|
75
|
+
const context = {
|
|
76
|
+
...primaryScope,
|
|
77
|
+
section: primaryScope.section || {}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return await liquid.parseAndRender(sectionContent, context);
|
|
81
|
+
} else {
|
|
82
|
+
console.warn(`[SECTION] Section file not found: ${sectionFile}`);
|
|
83
|
+
return '';
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error(`[SECTION] Error rendering section ${sectionPath}:`, error.message);
|
|
87
|
+
return '';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Register hook tag (simplified for CLI - plugins not supported)
|
|
93
|
+
liquid.registerTag('hook', {
|
|
94
|
+
parse: function(tagToken, remainTokens) {
|
|
95
|
+
const args = tagToken.args;
|
|
96
|
+
if (typeof args === 'string') {
|
|
97
|
+
this.hookName = args.trim().replace(/^['"]|['"]$/g, '');
|
|
98
|
+
} else {
|
|
99
|
+
this.hookName = String(args || '').trim().replace(/^['"]|['"]$/g, '');
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
render: async function(scope, hash) {
|
|
103
|
+
// In CLI, hooks are not supported (no plugin system)
|
|
104
|
+
// Return empty string
|
|
105
|
+
return '';
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Schema tag is handled by LiquidJS as a block tag
|
|
110
|
+
// No special registration needed
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Render template with layout support
|
|
115
|
+
* @param {Liquid} liquid - LiquidJS engine
|
|
116
|
+
* @param {string} templatePath - Template path (relative to theme, e.g., 'templates/index')
|
|
117
|
+
* @param {Object} context - Template context
|
|
118
|
+
* @param {string} themePath - Theme path
|
|
119
|
+
* @returns {Promise<string>} Rendered HTML
|
|
120
|
+
*/
|
|
121
|
+
async function renderWithLayout(liquid, templatePath, context, themePath) {
|
|
122
|
+
try {
|
|
123
|
+
const fullTemplatePath = path.join(themePath, `${templatePath}.liquid`);
|
|
124
|
+
|
|
125
|
+
if (!fs.existsSync(fullTemplatePath)) {
|
|
126
|
+
throw new Error(`Template not found: ${fullTemplatePath}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const templateContent = fs.readFileSync(fullTemplatePath, 'utf-8');
|
|
130
|
+
|
|
131
|
+
// Check for layout directive
|
|
132
|
+
const layoutMatch = templateContent.match(/\{%\s*layout\s+['"](.+?)['"]\s*%\}/);
|
|
133
|
+
|
|
134
|
+
if (layoutMatch) {
|
|
135
|
+
const layoutPath = layoutMatch[1]; // e.g., 'theme'
|
|
136
|
+
|
|
137
|
+
// Remove layout directive from template
|
|
138
|
+
const contentWithoutLayout = templateContent.replace(/\{%\s*layout\s+['"](.+?)['"]\s*%\}/, '').trim();
|
|
139
|
+
|
|
140
|
+
// Render template content first
|
|
141
|
+
const renderedContent = await liquid.parseAndRender(contentWithoutLayout, context);
|
|
142
|
+
|
|
143
|
+
// Render layout with content
|
|
144
|
+
const layoutContext = {
|
|
145
|
+
...context,
|
|
146
|
+
content: renderedContent
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const layoutFile = path.join(themePath, 'layout', `${layoutPath}.liquid`);
|
|
150
|
+
if (fs.existsSync(layoutFile)) {
|
|
151
|
+
const layoutContent = fs.readFileSync(layoutFile, 'utf-8');
|
|
152
|
+
return await liquid.parseAndRender(layoutContent, layoutContext);
|
|
153
|
+
} else {
|
|
154
|
+
throw new Error(`Layout not found: ${layoutFile}`);
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
// No layout directive, render template directly
|
|
158
|
+
return await liquid.parseAndRender(templateContent, context);
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error('Error rendering template with layout:', error);
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = {
|
|
167
|
+
createLiquidEngine,
|
|
168
|
+
renderWithLayout
|
|
169
|
+
};
|