@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,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O2VEND Theme CLI - Package Command
|
|
3
|
+
* Package theme for marketplace upload with proper theme.json structure
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { Command } = require('commander');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs-extra');
|
|
10
|
+
const archiver = require('archiver');
|
|
11
|
+
const glob = require('glob');
|
|
12
|
+
|
|
13
|
+
const packageCommand = new Command('package');
|
|
14
|
+
|
|
15
|
+
packageCommand
|
|
16
|
+
.description('Package theme for marketplace upload with theme.json manifest')
|
|
17
|
+
.option('-o, --output <path>', 'Output ZIP file path', 'dist/theme.zip')
|
|
18
|
+
.option('--exclude <patterns>', 'Files to exclude', '.env,.git,node_modules,.vscode')
|
|
19
|
+
.option('--validate', 'Run validation before packaging', false)
|
|
20
|
+
.option('--cwd <path>', 'Working directory (theme path)', process.cwd())
|
|
21
|
+
.option('--theme-id <id>', 'Theme ID for theme.json', (value, prev) => prev || path.basename(process.cwd()))
|
|
22
|
+
.option('--theme-name <name>', 'Theme name for theme.json', 'My Theme')
|
|
23
|
+
.option('--theme-version <version>', 'Theme version for theme.json', '1.0.0')
|
|
24
|
+
.option('--author <author>', 'Author for theme.json', 'Theme Developer')
|
|
25
|
+
.action(async (options) => {
|
|
26
|
+
try {
|
|
27
|
+
console.log(chalk.cyan('š¦ Packaging theme for marketplace...\n'));
|
|
28
|
+
|
|
29
|
+
const themePath = path.resolve(options.cwd);
|
|
30
|
+
const outputPath = path.resolve(options.output);
|
|
31
|
+
const outputDir = path.dirname(outputPath);
|
|
32
|
+
|
|
33
|
+
// Ensure output directory exists
|
|
34
|
+
await fs.ensureDir(outputDir);
|
|
35
|
+
|
|
36
|
+
// Check if theme.json.example exists
|
|
37
|
+
const themeJsonExamplePath = path.join(themePath, 'theme.json.example');
|
|
38
|
+
const themeJsonPath = path.join(themePath, 'theme.json');
|
|
39
|
+
|
|
40
|
+
// Validate theme structure
|
|
41
|
+
if (options.validate) {
|
|
42
|
+
console.log(chalk.yellow('š Validating theme structure...'));
|
|
43
|
+
|
|
44
|
+
// Basic validation
|
|
45
|
+
const errors = [];
|
|
46
|
+
const requiredDirs = ['layout', 'templates', 'sections', 'snippets', 'assets', 'config'];
|
|
47
|
+
requiredDirs.forEach(dir => {
|
|
48
|
+
const dirPath = path.join(themePath, dir);
|
|
49
|
+
if (!fs.existsSync(dirPath)) {
|
|
50
|
+
errors.push(`Required directory missing: ${dir}/`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const layoutPath = path.join(themePath, 'layout', 'theme.liquid');
|
|
55
|
+
if (!fs.existsSync(layoutPath)) {
|
|
56
|
+
errors.push('Required file missing: layout/theme.liquid');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (errors.length > 0) {
|
|
60
|
+
console.log(chalk.red('\nā Validation failed:\n'));
|
|
61
|
+
errors.forEach(error => console.log(chalk.red(` - ${error}`)));
|
|
62
|
+
console.log('');
|
|
63
|
+
throw new Error('Theme validation failed');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(chalk.green('ā
Validation passed\n'));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Generate or read theme.json
|
|
70
|
+
let themeJson;
|
|
71
|
+
if (await fs.pathExists(themeJsonPath)) {
|
|
72
|
+
// Use existing theme.json
|
|
73
|
+
themeJson = await fs.readJson(themeJsonPath);
|
|
74
|
+
console.log(chalk.green('ā
Found theme.json\n'));
|
|
75
|
+
} else if (await fs.pathExists(themeJsonExamplePath)) {
|
|
76
|
+
// Copy from example and fill in values
|
|
77
|
+
themeJson = await fs.readJson(themeJsonExamplePath);
|
|
78
|
+
themeJson.id = options.themeId;
|
|
79
|
+
themeJson.name = options.themeName;
|
|
80
|
+
themeJson.version = options.themeVersion;
|
|
81
|
+
themeJson.author = options.author;
|
|
82
|
+
console.log(chalk.yellow('ā ļø Using theme.json.example as template\n'));
|
|
83
|
+
} else {
|
|
84
|
+
// Generate minimal theme.json
|
|
85
|
+
themeJson = {
|
|
86
|
+
id: options.themeId,
|
|
87
|
+
name: options.themeName,
|
|
88
|
+
version: options.themeVersion,
|
|
89
|
+
author: options.author,
|
|
90
|
+
description: `${options.themeName} - O2VEND Theme`,
|
|
91
|
+
migration: {
|
|
92
|
+
files: {
|
|
93
|
+
modified: [],
|
|
94
|
+
added: [],
|
|
95
|
+
deleted: []
|
|
96
|
+
},
|
|
97
|
+
script: null
|
|
98
|
+
},
|
|
99
|
+
compatibility: {
|
|
100
|
+
minO2VENDVersion: '1.0.0',
|
|
101
|
+
dependencies: []
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
console.log(chalk.yellow('ā ļø Generated theme.json from options\n'));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Validate required fields
|
|
108
|
+
const requiredFields = ['id', 'name', 'version', 'author', 'description'];
|
|
109
|
+
const missingFields = requiredFields.filter(field => !themeJson[field]);
|
|
110
|
+
if (missingFields.length > 0) {
|
|
111
|
+
throw new Error(`Missing required fields in theme.json: ${missingFields.join(', ')}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Ensure migration object exists
|
|
115
|
+
if (!themeJson.migration) {
|
|
116
|
+
themeJson.migration = {
|
|
117
|
+
files: {
|
|
118
|
+
modified: [],
|
|
119
|
+
added: [],
|
|
120
|
+
deleted: []
|
|
121
|
+
},
|
|
122
|
+
script: null
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Ensure compatibility object exists
|
|
127
|
+
if (!themeJson.compatibility) {
|
|
128
|
+
themeJson.compatibility = {
|
|
129
|
+
minO2VENDVersion: '1.0.0',
|
|
130
|
+
dependencies: []
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log(chalk.cyan('š Theme metadata:'));
|
|
135
|
+
console.log(chalk.gray(` ID: ${themeJson.id}`));
|
|
136
|
+
console.log(chalk.gray(` Name: ${themeJson.name}`));
|
|
137
|
+
console.log(chalk.gray(` Version: ${themeJson.version}`));
|
|
138
|
+
console.log(chalk.gray(` Author: ${themeJson.author}\n`));
|
|
139
|
+
|
|
140
|
+
// Create ZIP file
|
|
141
|
+
console.log(chalk.cyan('š¦ Creating ZIP package...'));
|
|
142
|
+
|
|
143
|
+
const output = fs.createWriteStream(outputPath);
|
|
144
|
+
const archive = archiver('zip', {
|
|
145
|
+
zlib: { level: 9 } // Maximum compression
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
return new Promise((resolve, reject) => {
|
|
149
|
+
output.on('close', () => {
|
|
150
|
+
const sizeMB = (archive.pointer() / 1024 / 1024).toFixed(2);
|
|
151
|
+
console.log(chalk.green(`ā
Package created successfully!`));
|
|
152
|
+
console.log(chalk.gray(` File: ${outputPath}`));
|
|
153
|
+
console.log(chalk.gray(` Size: ${sizeMB} MB\n`));
|
|
154
|
+
resolve();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
archive.on('error', (error) => {
|
|
158
|
+
reject(error);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
archive.pipe(output);
|
|
162
|
+
|
|
163
|
+
// Add theme.json first
|
|
164
|
+
archive.append(JSON.stringify(themeJson, null, 2), {
|
|
165
|
+
name: 'theme.json'
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Get all files to include
|
|
169
|
+
const excludePatterns = options.exclude.split(',').map(p => p.trim());
|
|
170
|
+
const ignorePatterns = [
|
|
171
|
+
'**/node_modules/**',
|
|
172
|
+
'**/.git/**',
|
|
173
|
+
'**/.vscode/**',
|
|
174
|
+
'**/.env*',
|
|
175
|
+
'**/dist/**',
|
|
176
|
+
'**/.o2vend-cache/**',
|
|
177
|
+
'**/theme.json.example',
|
|
178
|
+
'**/migrations/**/*.example', // Exclude example migration scripts
|
|
179
|
+
'**/migrations/README.md', // Exclude README from migrations
|
|
180
|
+
'**/*.log',
|
|
181
|
+
...excludePatterns.map(p => `**/${p}/**`)
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
// Add all theme files
|
|
185
|
+
const files = glob.sync('**/*', {
|
|
186
|
+
cwd: themePath,
|
|
187
|
+
ignore: ignorePatterns,
|
|
188
|
+
nodir: true,
|
|
189
|
+
dot: false
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
files.forEach(file => {
|
|
193
|
+
const filePath = path.join(themePath, file);
|
|
194
|
+
archive.file(filePath, { name: file });
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
archive.finalize();
|
|
198
|
+
});
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error(chalk.red('ā Error packaging theme:'), error.message);
|
|
201
|
+
if (error.stack && process.env.DEBUG) {
|
|
202
|
+
console.error(error.stack);
|
|
203
|
+
}
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
module.exports = packageCommand;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O2VEND Theme CLI - Serve Command
|
|
3
|
+
* Start local development server with hot reload
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { Command } = require('commander');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const DevServer = require('../lib/dev-server');
|
|
11
|
+
require('dotenv').config();
|
|
12
|
+
|
|
13
|
+
const serveCommand = new Command('serve')
|
|
14
|
+
.alias('s'); // Allow 'o2vend s' as shorthand
|
|
15
|
+
|
|
16
|
+
serveCommand
|
|
17
|
+
.description('Start development server with hot reload')
|
|
18
|
+
.option('-m, --mode <mode>', 'API mode (mock|real)', process.env.API_MODE || 'mock')
|
|
19
|
+
.option('-p, --port <port>', 'Server port', process.env.PORT || '3000')
|
|
20
|
+
.option('--host <host>', 'Server host', process.env.HOST || 'localhost')
|
|
21
|
+
.option('--cwd <path>', 'Working directory (theme path)', process.cwd())
|
|
22
|
+
.option('--open', 'Open browser automatically', process.env.OPEN_BROWSER !== 'false')
|
|
23
|
+
.option('--no-open', 'Don\'t open browser')
|
|
24
|
+
.option('--mock-api-port <port>', 'Mock API port', process.env.MOCK_API_PORT || '3001')
|
|
25
|
+
.action(async (options) => {
|
|
26
|
+
try {
|
|
27
|
+
console.log(chalk.cyan('š Starting O2VEND Theme Development Server\n'));
|
|
28
|
+
|
|
29
|
+
// Resolve theme path
|
|
30
|
+
const themePath = path.resolve(options.cwd);
|
|
31
|
+
|
|
32
|
+
// Check if theme path exists
|
|
33
|
+
if (!fs.existsSync(themePath)) {
|
|
34
|
+
console.error(chalk.red(`ā Theme path does not exist: ${themePath}`));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check for theme structure
|
|
39
|
+
const layoutPath = path.join(themePath, 'layout', 'theme.liquid');
|
|
40
|
+
if (!fs.existsSync(layoutPath)) {
|
|
41
|
+
console.warn(chalk.yellow(`ā ļø Theme layout not found: ${layoutPath}`));
|
|
42
|
+
console.warn(chalk.yellow(' Creating basic theme structure...'));
|
|
43
|
+
// Create basic structure
|
|
44
|
+
fs.ensureDirSync(path.join(themePath, 'layout'));
|
|
45
|
+
fs.ensureDirSync(path.join(themePath, 'templates'));
|
|
46
|
+
fs.writeFileSync(layoutPath, '<!DOCTYPE html>\n<html><head><title>{{ shop.name }}</title></head><body>{{ content }}</body></html>');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Create dev server
|
|
50
|
+
const devServer = new DevServer({
|
|
51
|
+
themePath: themePath,
|
|
52
|
+
port: parseInt(options.port),
|
|
53
|
+
host: options.host,
|
|
54
|
+
mode: options.mode,
|
|
55
|
+
mockApiPort: parseInt(options.mockApiPort),
|
|
56
|
+
open: options.open !== false
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Handle graceful shutdown
|
|
60
|
+
process.on('SIGINT', async () => {
|
|
61
|
+
console.log(chalk.yellow('\n\nš Shutting down server...'));
|
|
62
|
+
await devServer.stop();
|
|
63
|
+
if (devServer.mockApi) {
|
|
64
|
+
await devServer.mockApi.stop();
|
|
65
|
+
}
|
|
66
|
+
process.exit(0);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
process.on('SIGTERM', async () => {
|
|
70
|
+
await devServer.stop();
|
|
71
|
+
if (devServer.mockApi) {
|
|
72
|
+
await devServer.mockApi.stop();
|
|
73
|
+
}
|
|
74
|
+
process.exit(0);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Handle 'q' key press in terminal
|
|
78
|
+
if (process.stdin.isTTY) {
|
|
79
|
+
process.stdin.setRawMode(true);
|
|
80
|
+
process.stdin.resume();
|
|
81
|
+
process.stdin.setEncoding('utf8');
|
|
82
|
+
process.stdin.on('data', async (key) => {
|
|
83
|
+
if (key === 'q' || key === '\u0003') {
|
|
84
|
+
console.log(chalk.yellow('\n\nš Shutting down server...'));
|
|
85
|
+
await devServer.stop();
|
|
86
|
+
if (devServer.mockApi) {
|
|
87
|
+
await devServer.mockApi.stop();
|
|
88
|
+
}
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Start server
|
|
95
|
+
await devServer.start();
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(chalk.red('ā Error starting server:'), error.message);
|
|
98
|
+
if (error.stack && process.env.DEBUG) {
|
|
99
|
+
console.error(error.stack);
|
|
100
|
+
}
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
module.exports = serveCommand;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O2VEND Theme CLI - Validate Command
|
|
3
|
+
* Validate theme structure and configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { Command } = require('commander');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const fs = require('fs-extra');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
|
|
11
|
+
const validateCommand = new Command('validate');
|
|
12
|
+
|
|
13
|
+
validateCommand
|
|
14
|
+
.description('Validate theme structure and configuration')
|
|
15
|
+
.option('--strict', 'Enable strict validation')
|
|
16
|
+
.option('--fix', 'Auto-fix issues where possible')
|
|
17
|
+
.option('--json', 'Output as JSON')
|
|
18
|
+
.option('--report <path>', 'Save report to file')
|
|
19
|
+
.option('--cwd <path>', 'Working directory (theme path)', process.cwd())
|
|
20
|
+
.action(async (options) => {
|
|
21
|
+
try {
|
|
22
|
+
console.log(chalk.cyan('š Validating theme...\n'));
|
|
23
|
+
|
|
24
|
+
const themePath = path.resolve(options.cwd);
|
|
25
|
+
const errors = [];
|
|
26
|
+
const warnings = [];
|
|
27
|
+
|
|
28
|
+
// Check if theme directory exists
|
|
29
|
+
if (!fs.existsSync(themePath)) {
|
|
30
|
+
errors.push(`Theme directory does not exist: ${themePath}`);
|
|
31
|
+
outputResults(errors, warnings, options);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Required directories
|
|
36
|
+
const requiredDirs = ['layout', 'templates', 'sections', 'snippets', 'assets', 'config'];
|
|
37
|
+
requiredDirs.forEach(dir => {
|
|
38
|
+
const dirPath = path.join(themePath, dir);
|
|
39
|
+
if (!fs.existsSync(dirPath)) {
|
|
40
|
+
errors.push(`Required directory missing: ${dir}/`);
|
|
41
|
+
} else if (!fs.statSync(dirPath).isDirectory()) {
|
|
42
|
+
errors.push(`${dir}/ exists but is not a directory`);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Required files
|
|
47
|
+
const layoutPath = path.join(themePath, 'layout', 'theme.liquid');
|
|
48
|
+
if (!fs.existsSync(layoutPath)) {
|
|
49
|
+
errors.push('Required file missing: layout/theme.liquid');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const indexTemplatePath = path.join(themePath, 'templates', 'index.liquid');
|
|
53
|
+
if (!fs.existsSync(indexTemplatePath)) {
|
|
54
|
+
warnings.push('Recommended file missing: templates/index.liquid (homepage template)');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Validate settings_schema.json
|
|
58
|
+
const settingsSchemaPath = path.join(themePath, 'config', 'settings_schema.json');
|
|
59
|
+
if (fs.existsSync(settingsSchemaPath)) {
|
|
60
|
+
try {
|
|
61
|
+
const schema = await fs.readJson(settingsSchemaPath);
|
|
62
|
+
if (!schema.name) {
|
|
63
|
+
warnings.push('settings_schema.json missing "name" field');
|
|
64
|
+
}
|
|
65
|
+
if (!Array.isArray(schema.settings)) {
|
|
66
|
+
warnings.push('settings_schema.json should have "settings" array');
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
errors.push(`Invalid JSON in settings_schema.json: ${error.message}`);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
warnings.push('settings_schema.json not found (optional but recommended)');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Validate settings_data.json if exists
|
|
76
|
+
const settingsDataPath = path.join(themePath, 'config', 'settings_data.json');
|
|
77
|
+
if (fs.existsSync(settingsDataPath)) {
|
|
78
|
+
try {
|
|
79
|
+
await fs.readJson(settingsDataPath);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
errors.push(`Invalid JSON in settings_data.json: ${error.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Validate theme.json if exists
|
|
86
|
+
const themeJsonPath = path.join(themePath, 'theme.json');
|
|
87
|
+
if (fs.existsSync(themeJsonPath)) {
|
|
88
|
+
try {
|
|
89
|
+
const themeJson = await fs.readJson(themeJsonPath);
|
|
90
|
+
const requiredFields = ['id', 'name', 'version', 'author', 'description'];
|
|
91
|
+
requiredFields.forEach(field => {
|
|
92
|
+
if (!themeJson[field]) {
|
|
93
|
+
errors.push(`theme.json missing required field: ${field}`);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Validate version format (semantic versioning)
|
|
98
|
+
if (themeJson.version && !/^\d+\.\d+\.\d+/.test(themeJson.version)) {
|
|
99
|
+
warnings.push(`theme.json version should follow semantic versioning (e.g., "1.0.0"): ${themeJson.version}`);
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
errors.push(`Invalid JSON in theme.json: ${error.message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check for Liquid files
|
|
107
|
+
const liquidFiles = findFiles(themePath, '.liquid');
|
|
108
|
+
if (liquidFiles.length === 0) {
|
|
109
|
+
warnings.push('No .liquid template files found');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Output results
|
|
113
|
+
outputResults(errors, warnings, options);
|
|
114
|
+
|
|
115
|
+
if (errors.length > 0) {
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error(chalk.red('ā Validation failed:'), error.message);
|
|
120
|
+
if (error.stack && process.env.DEBUG) {
|
|
121
|
+
console.error(error.stack);
|
|
122
|
+
}
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Find files by extension
|
|
129
|
+
*/
|
|
130
|
+
function findFiles(dir, ext) {
|
|
131
|
+
const files = [];
|
|
132
|
+
if (!fs.existsSync(dir)) return files;
|
|
133
|
+
|
|
134
|
+
const items = fs.readdirSync(dir);
|
|
135
|
+
items.forEach(item => {
|
|
136
|
+
const itemPath = path.join(dir, item);
|
|
137
|
+
const stat = fs.statSync(itemPath);
|
|
138
|
+
if (stat.isDirectory()) {
|
|
139
|
+
files.push(...findFiles(itemPath, ext));
|
|
140
|
+
} else if (item.endsWith(ext)) {
|
|
141
|
+
files.push(itemPath);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return files;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Output validation results
|
|
150
|
+
*/
|
|
151
|
+
function outputResults(errors, warnings, options) {
|
|
152
|
+
const result = {
|
|
153
|
+
valid: errors.length === 0,
|
|
154
|
+
errors,
|
|
155
|
+
warnings,
|
|
156
|
+
errorCount: errors.length,
|
|
157
|
+
warningCount: warnings.length
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
if (options.json) {
|
|
161
|
+
console.log(JSON.stringify(result, null, 2));
|
|
162
|
+
} else {
|
|
163
|
+
if (errors.length > 0) {
|
|
164
|
+
console.log(chalk.red(`\nā Found ${errors.length} error(s):\n`));
|
|
165
|
+
errors.forEach((error, index) => {
|
|
166
|
+
console.log(chalk.red(` ${index + 1}. ${error}`));
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (warnings.length > 0) {
|
|
171
|
+
console.log(chalk.yellow(`\nā ļø Found ${warnings.length} warning(s):\n`));
|
|
172
|
+
warnings.forEach((warning, index) => {
|
|
173
|
+
console.log(chalk.yellow(` ${index + 1}. ${warning}`));
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (errors.length === 0 && warnings.length === 0) {
|
|
178
|
+
console.log(chalk.green('ā
Validation passed!'));
|
|
179
|
+
} else if (errors.length === 0) {
|
|
180
|
+
console.log(chalk.green('\nā
Validation passed (with warnings)'));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Save report if requested
|
|
185
|
+
if (options.report) {
|
|
186
|
+
fs.writeJsonSync(options.report, result, { spaces: 2 });
|
|
187
|
+
console.log(chalk.gray(`\nš Report saved to: ${options.report}`));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = validateCommand;
|