@nclamvn/vibecode-cli 1.5.0 → 1.7.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.
- package/.vibecode/learning/fixes.json +1 -0
- package/.vibecode/learning/preferences.json +1 -0
- package/bin/vibecode.js +86 -3
- package/docs-site/README.md +41 -0
- package/docs-site/blog/2019-05-28-first-blog-post.md +12 -0
- package/docs-site/blog/2019-05-29-long-blog-post.md +44 -0
- package/docs-site/blog/2021-08-01-mdx-blog-post.mdx +24 -0
- package/docs-site/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- package/docs-site/blog/2021-08-26-welcome/index.md +29 -0
- package/docs-site/blog/authors.yml +25 -0
- package/docs-site/blog/tags.yml +19 -0
- package/docs-site/docs/commands/agent.md +162 -0
- package/docs-site/docs/commands/assist.md +71 -0
- package/docs-site/docs/commands/build.md +53 -0
- package/docs-site/docs/commands/config.md +30 -0
- package/docs-site/docs/commands/debug.md +173 -0
- package/docs-site/docs/commands/doctor.md +34 -0
- package/docs-site/docs/commands/go.md +128 -0
- package/docs-site/docs/commands/index.md +79 -0
- package/docs-site/docs/commands/init.md +42 -0
- package/docs-site/docs/commands/learn.md +82 -0
- package/docs-site/docs/commands/lock.md +33 -0
- package/docs-site/docs/commands/plan.md +29 -0
- package/docs-site/docs/commands/review.md +31 -0
- package/docs-site/docs/commands/snapshot.md +34 -0
- package/docs-site/docs/commands/start.md +32 -0
- package/docs-site/docs/commands/status.md +37 -0
- package/docs-site/docs/commands/undo.md +83 -0
- package/docs-site/docs/configuration.md +72 -0
- package/docs-site/docs/faq.md +83 -0
- package/docs-site/docs/getting-started.md +119 -0
- package/docs-site/docs/guides/agent-mode.md +94 -0
- package/docs-site/docs/guides/debug-mode.md +83 -0
- package/docs-site/docs/guides/magic-mode.md +107 -0
- package/docs-site/docs/installation.md +98 -0
- package/docs-site/docs/intro.md +67 -0
- package/docs-site/docusaurus.config.ts +141 -0
- package/docs-site/package-lock.json +18039 -0
- package/docs-site/package.json +48 -0
- package/docs-site/sidebars.ts +70 -0
- package/docs-site/src/components/HomepageFeatures/index.tsx +72 -0
- package/docs-site/src/components/HomepageFeatures/styles.module.css +16 -0
- package/docs-site/src/css/custom.css +30 -0
- package/docs-site/src/pages/index.module.css +23 -0
- package/docs-site/src/pages/index.tsx +44 -0
- package/docs-site/src/pages/markdown-page.md +7 -0
- package/docs-site/src/theme/Footer/index.tsx +127 -0
- package/docs-site/src/theme/Footer/styles.module.css +285 -0
- package/docs-site/static/.nojekyll +0 -0
- package/docs-site/static/img/docusaurus-social-card.jpg +0 -0
- package/docs-site/static/img/docusaurus.png +0 -0
- package/docs-site/static/img/favicon.ico +0 -0
- package/docs-site/static/img/logo.svg +1 -0
- package/docs-site/static/img/undraw_docusaurus_mountain.svg +171 -0
- package/docs-site/static/img/undraw_docusaurus_react.svg +170 -0
- package/docs-site/static/img/undraw_docusaurus_tree.svg +40 -0
- package/docs-site/tsconfig.json +8 -0
- package/package.json +5 -2
- package/src/agent/orchestrator.js +104 -35
- package/src/commands/build.js +13 -3
- package/src/commands/debug.js +109 -1
- package/src/commands/git.js +923 -0
- package/src/commands/go.js +9 -2
- package/src/commands/learn.js +294 -0
- package/src/commands/shell.js +486 -0
- package/src/commands/undo.js +281 -0
- package/src/commands/watch.js +556 -0
- package/src/commands/wizard.js +322 -0
- package/src/core/backup.js +325 -0
- package/src/core/learning.js +295 -0
- package/src/debug/image-analyzer.js +304 -0
- package/src/debug/index.js +30 -1
- package/src/index.js +50 -0
- package/src/ui/__tests__/error-translator.test.js +390 -0
- package/src/ui/dashboard.js +364 -0
- package/src/ui/error-translator.js +775 -0
- package/src/utils/image.js +222 -0
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Error Translator
|
|
3
|
+
// Phase H3: Human-Friendly Error Messages
|
|
4
|
+
// Technical errors → Vietnamese explanations + fix suggestions
|
|
5
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Error patterns and their Vietnamese translations
|
|
11
|
+
* Each pattern has:
|
|
12
|
+
* - pattern: RegExp to match error
|
|
13
|
+
* - translate: Function that returns translated error info
|
|
14
|
+
*
|
|
15
|
+
* IMPORTANT: Patterns should handle variations:
|
|
16
|
+
* - Quote types: single ('), double ("), escaped (\"), or none
|
|
17
|
+
* - Case variations
|
|
18
|
+
* - Prefix variations (TypeError:, Error:, etc.)
|
|
19
|
+
*/
|
|
20
|
+
const ERROR_PATTERNS = [
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
// JavaScript/Node.js Runtime Errors
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
// Cannot read properties of undefined/null - handles both quote types (new style)
|
|
26
|
+
{
|
|
27
|
+
pattern: /Cannot read propert(?:y|ies) of (undefined|null)(?: \(reading ['"]?(\w+)['"]?\))?/i,
|
|
28
|
+
translate: (match) => ({
|
|
29
|
+
title: match[1] === 'null' ? 'Biến là null' : 'Biến chưa được định nghĩa',
|
|
30
|
+
description: `Code đang cố truy cập thuộc tính '${match[2] || 'unknown'}' của một biến ${match[1]}`,
|
|
31
|
+
suggestions: [
|
|
32
|
+
'Kiểm tra biến có được khởi tạo trước khi sử dụng',
|
|
33
|
+
'Thêm optional chaining (?.) khi truy cập thuộc tính',
|
|
34
|
+
'Kiểm tra null/undefined với if statement'
|
|
35
|
+
],
|
|
36
|
+
category: 'RUNTIME'
|
|
37
|
+
})
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
// Cannot read property X of undefined (old style - Node < 16)
|
|
41
|
+
{
|
|
42
|
+
pattern: /Cannot read property ['"]?(\w+)['"]? of (undefined|null)/i,
|
|
43
|
+
translate: (match) => ({
|
|
44
|
+
title: match[2] === 'null' ? 'Biến là null' : 'Biến chưa được định nghĩa',
|
|
45
|
+
description: `Code đang cố truy cập thuộc tính '${match[1]}' của một biến ${match[2]}`,
|
|
46
|
+
suggestions: [
|
|
47
|
+
'Kiểm tra biến có được khởi tạo trước khi sử dụng',
|
|
48
|
+
'Thêm optional chaining (?.) khi truy cập thuộc tính',
|
|
49
|
+
'Kiểm tra null/undefined với if statement'
|
|
50
|
+
],
|
|
51
|
+
category: 'RUNTIME'
|
|
52
|
+
})
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
// Module not found: Can't resolve (webpack/bundler - BUILD error)
|
|
56
|
+
// MUST come before generic "Cannot find module" pattern
|
|
57
|
+
{
|
|
58
|
+
pattern: /Module not found:?\s*Can'?t resolve ['"]?([^'">\s]+)['"]?/i,
|
|
59
|
+
translate: (match) => ({
|
|
60
|
+
title: 'Module không tìm thấy khi build',
|
|
61
|
+
description: `Bundler không thể resolve: '${match[1]}'`,
|
|
62
|
+
suggestions: [
|
|
63
|
+
'Kiểm tra path import có đúng không',
|
|
64
|
+
'Chạy npm install nếu thiếu package',
|
|
65
|
+
'Kiểm tra tsconfig paths nếu dùng alias'
|
|
66
|
+
],
|
|
67
|
+
category: 'BUILD'
|
|
68
|
+
})
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// Cannot find module - handles quotes and various formats (runtime error)
|
|
72
|
+
{
|
|
73
|
+
pattern: /(?:Cannot find module|Module not found)[:\s]+['"]?([^'">\s]+)['"]?/i,
|
|
74
|
+
translate: (match) => ({
|
|
75
|
+
title: 'Không tìm thấy module',
|
|
76
|
+
description: `Module '${match[1]}' không tồn tại hoặc chưa được cài đặt`,
|
|
77
|
+
suggestions: [
|
|
78
|
+
match[1].startsWith('.') || match[1].startsWith('@/')
|
|
79
|
+
? 'Kiểm tra đường dẫn import có chính xác'
|
|
80
|
+
: `Chạy: npm install ${match[1]}`,
|
|
81
|
+
'Kiểm tra file có tồn tại không',
|
|
82
|
+
'Kiểm tra tsconfig paths nếu dùng alias'
|
|
83
|
+
],
|
|
84
|
+
category: 'MODULE'
|
|
85
|
+
})
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// SyntaxError: Unexpected token - handles various formats
|
|
89
|
+
{
|
|
90
|
+
pattern: /SyntaxError:?\s*Unexpected token\s*['"]?(.{1,10})['"]?/i,
|
|
91
|
+
translate: (match) => ({
|
|
92
|
+
title: 'Lỗi cú pháp',
|
|
93
|
+
description: `Có lỗi cú pháp trong code - token không mong đợi: '${match[1] || 'unknown'}'`,
|
|
94
|
+
suggestions: [
|
|
95
|
+
'Kiểm tra dấu ngoặc đóng/mở có đủ không',
|
|
96
|
+
'Kiểm tra dấu phẩy, chấm phẩy',
|
|
97
|
+
'Kiểm tra cú pháp arrow function, object'
|
|
98
|
+
],
|
|
99
|
+
category: 'SYNTAX'
|
|
100
|
+
})
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
// SyntaxError: Unexpected end of input
|
|
104
|
+
{
|
|
105
|
+
pattern: /SyntaxError:?\s*Unexpected end of/i,
|
|
106
|
+
translate: () => ({
|
|
107
|
+
title: 'Lỗi cú pháp - Thiếu đóng ngoặc',
|
|
108
|
+
description: 'Code kết thúc đột ngột, có thể thiếu dấu ngoặc hoặc dấu quote',
|
|
109
|
+
suggestions: [
|
|
110
|
+
'Kiểm tra tất cả dấu { } ( ) [ ] có đủ cặp',
|
|
111
|
+
'Kiểm tra string có đóng quote đúng không',
|
|
112
|
+
'Sử dụng editor có highlight để tìm lỗi'
|
|
113
|
+
],
|
|
114
|
+
category: 'SYNTAX'
|
|
115
|
+
})
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
// ReferenceError: X is not defined
|
|
119
|
+
{
|
|
120
|
+
pattern: /ReferenceError:?\s*(\w+) is not defined/i,
|
|
121
|
+
translate: (match) => ({
|
|
122
|
+
title: 'Biến chưa được khai báo',
|
|
123
|
+
description: `Biến '${match[1]}' được sử dụng nhưng chưa được khai báo`,
|
|
124
|
+
suggestions: [
|
|
125
|
+
`Khai báo biến: const ${match[1]} = ...`,
|
|
126
|
+
'Kiểm tra import có đúng không',
|
|
127
|
+
'Kiểm tra scope của biến'
|
|
128
|
+
],
|
|
129
|
+
category: 'REFERENCE'
|
|
130
|
+
})
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
// TypeError: X is not a function
|
|
134
|
+
{
|
|
135
|
+
pattern: /TypeError:?\s*['"]?(\w+)['"]? is not a function/i,
|
|
136
|
+
translate: (match) => ({
|
|
137
|
+
title: 'Không phải hàm',
|
|
138
|
+
description: `'${match[1]}' không phải là một function, không thể gọi được`,
|
|
139
|
+
suggestions: [
|
|
140
|
+
'Kiểm tra import có đúng không',
|
|
141
|
+
'Kiểm tra tên function có chính xác',
|
|
142
|
+
'Kiểm tra object có method này không'
|
|
143
|
+
],
|
|
144
|
+
category: 'TYPE'
|
|
145
|
+
})
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
// TypeError: X is not iterable
|
|
149
|
+
{
|
|
150
|
+
pattern: /TypeError:?\s*(.+?) is not iterable/i,
|
|
151
|
+
translate: (match) => ({
|
|
152
|
+
title: 'Không thể lặp qua dữ liệu',
|
|
153
|
+
description: `'${match[1]}' không phải là array hoặc iterable object`,
|
|
154
|
+
suggestions: [
|
|
155
|
+
'Kiểm tra data có phải array không',
|
|
156
|
+
'Thêm Array.isArray() check trước khi loop',
|
|
157
|
+
'Kiểm tra API response format'
|
|
158
|
+
],
|
|
159
|
+
category: 'TYPE'
|
|
160
|
+
})
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
// Maximum call stack size exceeded
|
|
164
|
+
{
|
|
165
|
+
pattern: /Maximum call stack size exceeded/i,
|
|
166
|
+
translate: () => ({
|
|
167
|
+
title: 'Stack Overflow',
|
|
168
|
+
description: 'Có recursion vô hạn hoặc quá sâu trong code',
|
|
169
|
+
suggestions: [
|
|
170
|
+
'Kiểm tra recursive function có base case',
|
|
171
|
+
'Kiểm tra circular dependency',
|
|
172
|
+
'Sử dụng iteration thay vì recursion'
|
|
173
|
+
],
|
|
174
|
+
category: 'RUNTIME'
|
|
175
|
+
})
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
179
|
+
// File System Errors
|
|
180
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
// ENOENT - handles with or without quotes
|
|
183
|
+
{
|
|
184
|
+
pattern: /ENOENT:?\s*no such file or directory[,:]?\s*(?:open\s+)?['"]?([^'">\s]+)['"]?/i,
|
|
185
|
+
translate: (match) => ({
|
|
186
|
+
title: 'File không tồn tại',
|
|
187
|
+
description: `Không tìm thấy file hoặc thư mục: '${match[1] || 'unknown'}'`,
|
|
188
|
+
suggestions: [
|
|
189
|
+
'Kiểm tra đường dẫn file có chính xác',
|
|
190
|
+
'Tạo file/thư mục nếu cần',
|
|
191
|
+
'Kiểm tra quyền truy cập'
|
|
192
|
+
],
|
|
193
|
+
category: 'FILE'
|
|
194
|
+
})
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
// ENOENT - simple format fallback
|
|
198
|
+
{
|
|
199
|
+
pattern: /ENOENT/i,
|
|
200
|
+
translate: () => ({
|
|
201
|
+
title: 'File không tồn tại',
|
|
202
|
+
description: 'Không tìm thấy file hoặc thư mục được yêu cầu',
|
|
203
|
+
suggestions: [
|
|
204
|
+
'Kiểm tra đường dẫn file có chính xác',
|
|
205
|
+
'Tạo file/thư mục nếu cần',
|
|
206
|
+
'Kiểm tra quyền truy cập'
|
|
207
|
+
],
|
|
208
|
+
category: 'FILE'
|
|
209
|
+
})
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
// EACCES
|
|
213
|
+
{
|
|
214
|
+
pattern: /EACCES:?\s*permission denied/i,
|
|
215
|
+
translate: () => ({
|
|
216
|
+
title: 'Không có quyền truy cập',
|
|
217
|
+
description: 'Không có quyền để thực hiện thao tác này',
|
|
218
|
+
suggestions: [
|
|
219
|
+
'Chạy với sudo (cẩn thận!)',
|
|
220
|
+
'Kiểm tra quyền của file/thư mục',
|
|
221
|
+
'Đổi ownership: chown -R $USER:$USER .'
|
|
222
|
+
],
|
|
223
|
+
category: 'PERMISSION'
|
|
224
|
+
})
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
// EADDRINUSE
|
|
228
|
+
{
|
|
229
|
+
pattern: /EADDRINUSE.*?:?(\d+)/i,
|
|
230
|
+
translate: (match) => ({
|
|
231
|
+
title: 'Port đang được sử dụng',
|
|
232
|
+
description: `Port ${match[1]} đã có process khác đang chạy`,
|
|
233
|
+
suggestions: [
|
|
234
|
+
`Kill process: lsof -ti:${match[1]} | xargs kill`,
|
|
235
|
+
'Dùng port khác trong config',
|
|
236
|
+
'Tìm và tắt ứng dụng đang dùng port'
|
|
237
|
+
],
|
|
238
|
+
category: 'NETWORK'
|
|
239
|
+
})
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
// Database connection refused (common ports) - MUST come before generic ECONNREFUSED
|
|
243
|
+
{
|
|
244
|
+
pattern: /ECONNREFUSED.*?(?:3306|5432|27017|6379)/i,
|
|
245
|
+
translate: () => ({
|
|
246
|
+
title: 'Database không chạy',
|
|
247
|
+
description: 'Không thể kết nối database - service có thể chưa start',
|
|
248
|
+
suggestions: [
|
|
249
|
+
'Start database service',
|
|
250
|
+
'Kiểm tra Docker container nếu dùng Docker',
|
|
251
|
+
'Kiểm tra connection string trong .env'
|
|
252
|
+
],
|
|
253
|
+
category: 'DATABASE'
|
|
254
|
+
})
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
// ECONNREFUSED (generic - for non-database ports)
|
|
258
|
+
{
|
|
259
|
+
pattern: /ECONNREFUSED.*?(?::(\d+)|(\d+\.\d+\.\d+\.\d+))/i,
|
|
260
|
+
translate: (match) => ({
|
|
261
|
+
title: 'Không thể kết nối',
|
|
262
|
+
description: `Không thể kết nối đến ${match[1] ? 'port ' + match[1] : 'server'} - service có thể chưa chạy`,
|
|
263
|
+
suggestions: [
|
|
264
|
+
'Kiểm tra service đã start chưa',
|
|
265
|
+
'Kiểm tra firewall settings',
|
|
266
|
+
'Kiểm tra URL/port trong config'
|
|
267
|
+
],
|
|
268
|
+
category: 'NETWORK'
|
|
269
|
+
})
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
273
|
+
// NPM Errors
|
|
274
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
// npm ERR! code ERESOLVE
|
|
277
|
+
{
|
|
278
|
+
pattern: /npm ERR!?\s*(?:code\s+)?ERESOLVE/i,
|
|
279
|
+
translate: () => ({
|
|
280
|
+
title: 'Xung đột phiên bản dependency',
|
|
281
|
+
description: 'Các package yêu cầu phiên bản dependency khác nhau',
|
|
282
|
+
suggestions: [
|
|
283
|
+
'Chạy: npm install --legacy-peer-deps',
|
|
284
|
+
'Chạy: npm install --force',
|
|
285
|
+
'Kiểm tra và update các package xung đột'
|
|
286
|
+
],
|
|
287
|
+
category: 'NPM'
|
|
288
|
+
})
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
// npm ERR! code ENOENT
|
|
292
|
+
{
|
|
293
|
+
pattern: /npm ERR!?\s*(?:code\s+)?ENOENT/i,
|
|
294
|
+
translate: () => ({
|
|
295
|
+
title: 'npm không tìm thấy file',
|
|
296
|
+
description: 'npm không tìm thấy package.json hoặc file cần thiết',
|
|
297
|
+
suggestions: [
|
|
298
|
+
'Kiểm tra đang ở đúng thư mục project',
|
|
299
|
+
'Chạy: npm init nếu chưa có package.json',
|
|
300
|
+
'Kiểm tra file/thư mục có tồn tại'
|
|
301
|
+
],
|
|
302
|
+
category: 'NPM'
|
|
303
|
+
})
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
// npm ERR! code E4XX/E5XX
|
|
307
|
+
{
|
|
308
|
+
pattern: /npm ERR!?\s*(?:code\s+)?E(\d{3})/i,
|
|
309
|
+
translate: (match) => ({
|
|
310
|
+
title: 'Lỗi npm registry',
|
|
311
|
+
description: `npm gặp lỗi HTTP ${match[1]} khi tải package`,
|
|
312
|
+
suggestions: [
|
|
313
|
+
'Kiểm tra kết nối internet',
|
|
314
|
+
'Thử lại sau vài phút',
|
|
315
|
+
'Kiểm tra npm registry có hoạt động không'
|
|
316
|
+
],
|
|
317
|
+
category: 'NPM'
|
|
318
|
+
})
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
// npm peer dep missing
|
|
322
|
+
{
|
|
323
|
+
pattern: /npm ERR!?\s*peer dep(?:endency)? missing/i,
|
|
324
|
+
translate: () => ({
|
|
325
|
+
title: 'Thiếu peer dependency',
|
|
326
|
+
description: 'Một package yêu cầu dependency khác mà bạn chưa cài',
|
|
327
|
+
suggestions: [
|
|
328
|
+
'Chạy: npm install --legacy-peer-deps',
|
|
329
|
+
'Cài thủ công package được yêu cầu',
|
|
330
|
+
'Kiểm tra version compatibility'
|
|
331
|
+
],
|
|
332
|
+
category: 'NPM'
|
|
333
|
+
})
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
337
|
+
// Next.js / React Errors
|
|
338
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
339
|
+
|
|
340
|
+
// Functions cannot be passed to Client Components
|
|
341
|
+
{
|
|
342
|
+
pattern: /Functions cannot be passed directly to Client Components/i,
|
|
343
|
+
translate: () => ({
|
|
344
|
+
title: 'Lỗi Server/Client Component (Next.js)',
|
|
345
|
+
description: 'Đang truyền function vào Client Component - không được phép trong App Router',
|
|
346
|
+
suggestions: [
|
|
347
|
+
'Chuyển function thành serializable data',
|
|
348
|
+
'Thêm "use client" vào component con',
|
|
349
|
+
'Di chuyển logic vào Client Component'
|
|
350
|
+
],
|
|
351
|
+
category: 'NEXTJS'
|
|
352
|
+
})
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
// Hydration failed
|
|
356
|
+
{
|
|
357
|
+
pattern: /Hydration failed|Text content does not match/i,
|
|
358
|
+
translate: () => ({
|
|
359
|
+
title: 'Lỗi Hydration (React/Next.js)',
|
|
360
|
+
description: 'HTML từ server không khớp với client - thường do render khác nhau',
|
|
361
|
+
suggestions: [
|
|
362
|
+
'Tránh dùng Date, random trong initial render',
|
|
363
|
+
'Sử dụng useEffect cho browser-only code',
|
|
364
|
+
'Kiểm tra conditional rendering logic'
|
|
365
|
+
],
|
|
366
|
+
category: 'REACT'
|
|
367
|
+
})
|
|
368
|
+
},
|
|
369
|
+
|
|
370
|
+
// Invalid hook call
|
|
371
|
+
{
|
|
372
|
+
pattern: /Invalid hook call/i,
|
|
373
|
+
translate: () => ({
|
|
374
|
+
title: 'Lỗi React Hook',
|
|
375
|
+
description: 'Hook được gọi sai cách - có thể ngoài component hoặc trong condition',
|
|
376
|
+
suggestions: [
|
|
377
|
+
'Chỉ gọi hook trong function component',
|
|
378
|
+
'Không gọi hook trong if/loop',
|
|
379
|
+
'Kiểm tra có duplicate React không'
|
|
380
|
+
],
|
|
381
|
+
category: 'REACT'
|
|
382
|
+
})
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
386
|
+
// TypeScript Errors
|
|
387
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
388
|
+
|
|
389
|
+
// TS Error codes (TSxxxx)
|
|
390
|
+
{
|
|
391
|
+
pattern: /(?:error\s+)?TS(\d{4,5}):\s*(.{10,80})/i,
|
|
392
|
+
translate: (match) => ({
|
|
393
|
+
title: `Lỗi TypeScript (TS${match[1]})`,
|
|
394
|
+
description: match[2].substring(0, 60) + (match[2].length > 60 ? '...' : ''),
|
|
395
|
+
suggestions: [
|
|
396
|
+
'Kiểm tra kiểu dữ liệu của biến',
|
|
397
|
+
'Thêm type annotation nếu cần',
|
|
398
|
+
`Tìm hiểu thêm: typescript.tv/errors/#TS${match[1]}`
|
|
399
|
+
],
|
|
400
|
+
category: 'TYPESCRIPT'
|
|
401
|
+
})
|
|
402
|
+
},
|
|
403
|
+
|
|
404
|
+
// Type X is not assignable to type Y
|
|
405
|
+
{
|
|
406
|
+
pattern: /Type ['"]?([^'"]+?)['"]? is not assignable to type ['"]?([^'"]+?)['"]?/i,
|
|
407
|
+
translate: (match) => ({
|
|
408
|
+
title: 'Lỗi TypeScript - Type không khớp',
|
|
409
|
+
description: `Type '${match[1].substring(0, 20)}' không thể gán cho '${match[2].substring(0, 20)}'`,
|
|
410
|
+
suggestions: [
|
|
411
|
+
'Kiểm tra kiểu dữ liệu của biến',
|
|
412
|
+
'Thêm type assertion nếu chắc chắn',
|
|
413
|
+
'Sửa type definition'
|
|
414
|
+
],
|
|
415
|
+
category: 'TYPESCRIPT'
|
|
416
|
+
})
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
// Property X does not exist on type
|
|
420
|
+
{
|
|
421
|
+
pattern: /Property ['"]?(\w+)['"]? does not exist on type/i,
|
|
422
|
+
translate: (match) => ({
|
|
423
|
+
title: 'Lỗi TypeScript - Property không tồn tại',
|
|
424
|
+
description: `Property '${match[1]}' không có trong type definition`,
|
|
425
|
+
suggestions: [
|
|
426
|
+
'Thêm property vào interface/type',
|
|
427
|
+
'Kiểm tra tên property có đúng không',
|
|
428
|
+
'Sử dụng type assertion hoặc any (tạm thời)'
|
|
429
|
+
],
|
|
430
|
+
category: 'TYPESCRIPT'
|
|
431
|
+
})
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
// Argument of type X is not assignable
|
|
435
|
+
{
|
|
436
|
+
pattern: /Argument of type ['"]?([^'"]+?)['"]? is not assignable/i,
|
|
437
|
+
translate: (match) => ({
|
|
438
|
+
title: 'Lỗi TypeScript - Argument không khớp',
|
|
439
|
+
description: `Argument '${match[1].substring(0, 30)}' không đúng type`,
|
|
440
|
+
suggestions: [
|
|
441
|
+
'Kiểm tra function signature',
|
|
442
|
+
'Cast type nếu cần',
|
|
443
|
+
'Sửa data truyền vào cho đúng type'
|
|
444
|
+
],
|
|
445
|
+
category: 'TYPESCRIPT'
|
|
446
|
+
})
|
|
447
|
+
},
|
|
448
|
+
|
|
449
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
450
|
+
// Database Errors
|
|
451
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
452
|
+
|
|
453
|
+
// Prisma errors
|
|
454
|
+
{
|
|
455
|
+
pattern: /Prisma(?:Client)?.*?P(\d+)/i,
|
|
456
|
+
translate: (match) => ({
|
|
457
|
+
title: 'Lỗi Prisma Database',
|
|
458
|
+
description: `Prisma error code P${match[1]}`,
|
|
459
|
+
suggestions: [
|
|
460
|
+
'Chạy: npx prisma db push',
|
|
461
|
+
'Chạy: npx prisma generate',
|
|
462
|
+
'Kiểm tra DATABASE_URL trong .env'
|
|
463
|
+
],
|
|
464
|
+
category: 'DATABASE'
|
|
465
|
+
})
|
|
466
|
+
},
|
|
467
|
+
|
|
468
|
+
// Prisma invalid invocation
|
|
469
|
+
{
|
|
470
|
+
pattern: /Invalid [`']?prisma\.(\w+)\.(\w+)\(\)/i,
|
|
471
|
+
translate: (match) => ({
|
|
472
|
+
title: 'Lỗi Prisma Query',
|
|
473
|
+
description: `Lỗi khi gọi prisma.${match[1]}.${match[2]}()`,
|
|
474
|
+
suggestions: [
|
|
475
|
+
'Kiểm tra model tồn tại trong schema.prisma',
|
|
476
|
+
'Chạy: npx prisma generate',
|
|
477
|
+
'Kiểm tra syntax của query'
|
|
478
|
+
],
|
|
479
|
+
category: 'DATABASE'
|
|
480
|
+
})
|
|
481
|
+
},
|
|
482
|
+
|
|
483
|
+
// MySQL/PostgreSQL access denied
|
|
484
|
+
{
|
|
485
|
+
pattern: /ER_ACCESS_DENIED_ERROR|FATAL:\s*password authentication failed/i,
|
|
486
|
+
translate: () => ({
|
|
487
|
+
title: 'Lỗi truy cập Database',
|
|
488
|
+
description: 'Sai username/password hoặc không có quyền truy cập database',
|
|
489
|
+
suggestions: [
|
|
490
|
+
'Kiểm tra DATABASE_URL trong .env',
|
|
491
|
+
'Kiểm tra user có quyền access database',
|
|
492
|
+
'Reset password nếu cần'
|
|
493
|
+
],
|
|
494
|
+
category: 'DATABASE'
|
|
495
|
+
})
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
// SQLITE_ERROR
|
|
499
|
+
{
|
|
500
|
+
pattern: /SQLITE_ERROR:?\s*(.+)/i,
|
|
501
|
+
translate: (match) => ({
|
|
502
|
+
title: 'Lỗi SQLite',
|
|
503
|
+
description: match[1].substring(0, 60),
|
|
504
|
+
suggestions: [
|
|
505
|
+
'Kiểm tra file database có tồn tại',
|
|
506
|
+
'Kiểm tra table/column name có đúng',
|
|
507
|
+
'Chạy migration nếu schema thay đổi'
|
|
508
|
+
],
|
|
509
|
+
category: 'DATABASE'
|
|
510
|
+
})
|
|
511
|
+
},
|
|
512
|
+
|
|
513
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
514
|
+
// Git Errors
|
|
515
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
516
|
+
|
|
517
|
+
// Not a git repository
|
|
518
|
+
{
|
|
519
|
+
pattern: /fatal:?\s*not a git repository/i,
|
|
520
|
+
translate: () => ({
|
|
521
|
+
title: 'Không phải Git repository',
|
|
522
|
+
description: 'Thư mục hiện tại chưa được khởi tạo git',
|
|
523
|
+
suggestions: [
|
|
524
|
+
'Chạy: git init',
|
|
525
|
+
'Đảm bảo đang ở đúng thư mục project',
|
|
526
|
+
'Kiểm tra thư mục .git có tồn tại'
|
|
527
|
+
],
|
|
528
|
+
category: 'GIT'
|
|
529
|
+
})
|
|
530
|
+
},
|
|
531
|
+
|
|
532
|
+
// Failed to push
|
|
533
|
+
{
|
|
534
|
+
pattern: /error:?\s*failed to push some refs/i,
|
|
535
|
+
translate: () => ({
|
|
536
|
+
title: 'Không thể push lên remote',
|
|
537
|
+
description: 'Remote có commits mới mà local chưa có',
|
|
538
|
+
suggestions: [
|
|
539
|
+
'Chạy: git pull --rebase',
|
|
540
|
+
'Resolve conflicts nếu có',
|
|
541
|
+
'Force push (cẩn thận!): git push -f'
|
|
542
|
+
],
|
|
543
|
+
category: 'GIT'
|
|
544
|
+
})
|
|
545
|
+
},
|
|
546
|
+
|
|
547
|
+
// Merge conflict
|
|
548
|
+
{
|
|
549
|
+
pattern: /CONFLICT.*?Merge conflict in (.+)/i,
|
|
550
|
+
translate: (match) => ({
|
|
551
|
+
title: 'Git Merge Conflict',
|
|
552
|
+
description: `Có conflict trong file: ${match[1]}`,
|
|
553
|
+
suggestions: [
|
|
554
|
+
'Mở file và resolve conflicts thủ công',
|
|
555
|
+
'Sử dụng git mergetool',
|
|
556
|
+
'Sau khi resolve: git add . && git commit'
|
|
557
|
+
],
|
|
558
|
+
category: 'GIT'
|
|
559
|
+
})
|
|
560
|
+
},
|
|
561
|
+
|
|
562
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
563
|
+
// Build/Webpack Errors
|
|
564
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
565
|
+
|
|
566
|
+
// Module build failed
|
|
567
|
+
{
|
|
568
|
+
pattern: /Module build failed/i,
|
|
569
|
+
translate: () => ({
|
|
570
|
+
title: 'Build module thất bại',
|
|
571
|
+
description: 'Webpack/bundler không thể build một module',
|
|
572
|
+
suggestions: [
|
|
573
|
+
'Kiểm tra syntax của file được báo lỗi',
|
|
574
|
+
'Kiểm tra loader config nếu dùng custom',
|
|
575
|
+
'Clear cache: rm -rf .next node_modules/.cache'
|
|
576
|
+
],
|
|
577
|
+
category: 'BUILD'
|
|
578
|
+
})
|
|
579
|
+
},
|
|
580
|
+
|
|
581
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
582
|
+
// Memory/Performance Errors
|
|
583
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
584
|
+
|
|
585
|
+
// JavaScript heap out of memory
|
|
586
|
+
{
|
|
587
|
+
pattern: /JavaScript heap out of memory|FATAL ERROR:?\s*(?:Ineffective mark-compacts|CALL_AND_RETRY_LAST)/i,
|
|
588
|
+
translate: () => ({
|
|
589
|
+
title: 'Hết bộ nhớ',
|
|
590
|
+
description: 'Node.js hết RAM khi chạy - thường do data quá lớn hoặc memory leak',
|
|
591
|
+
suggestions: [
|
|
592
|
+
'Tăng memory: NODE_OPTIONS="--max-old-space-size=4096"',
|
|
593
|
+
'Kiểm tra có memory leak không',
|
|
594
|
+
'Xử lý data theo batch thay vì load all'
|
|
595
|
+
],
|
|
596
|
+
category: 'MEMORY'
|
|
597
|
+
})
|
|
598
|
+
}
|
|
599
|
+
];
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Fallback for unknown errors
|
|
603
|
+
*/
|
|
604
|
+
const UNKNOWN_ERROR = {
|
|
605
|
+
title: 'Lỗi không xác định',
|
|
606
|
+
description: 'Đã xảy ra lỗi trong quá trình thực thi',
|
|
607
|
+
suggestions: [
|
|
608
|
+
'Chạy vibecode debug --auto để AI phân tích',
|
|
609
|
+
'Kiểm tra console log chi tiết',
|
|
610
|
+
'Chạy vibecode assist để được hỗ trợ'
|
|
611
|
+
],
|
|
612
|
+
category: 'UNKNOWN'
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Translate an error to human-friendly Vietnamese
|
|
617
|
+
* @param {Error|string} error - The error to translate
|
|
618
|
+
* @param {Object} options - Options
|
|
619
|
+
* @param {boolean} options.debug - Enable debug mode
|
|
620
|
+
* @returns {Object} Translated error info
|
|
621
|
+
*/
|
|
622
|
+
export function translateError(error, options = {}) {
|
|
623
|
+
const errorMessage = typeof error === 'string' ? error : error.message || String(error);
|
|
624
|
+
|
|
625
|
+
if (options.debug) {
|
|
626
|
+
console.log('[DEBUG] translateError input:', errorMessage.substring(0, 100));
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
for (let i = 0; i < ERROR_PATTERNS.length; i++) {
|
|
630
|
+
const { pattern, translate } = ERROR_PATTERNS[i];
|
|
631
|
+
|
|
632
|
+
if (options.debug) {
|
|
633
|
+
console.log(`[DEBUG] Testing pattern ${i}:`, pattern.toString().substring(0, 50));
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const match = errorMessage.match(pattern);
|
|
637
|
+
|
|
638
|
+
if (match) {
|
|
639
|
+
if (options.debug) {
|
|
640
|
+
console.log('[DEBUG] Matched! Groups:', match.slice(0, 3));
|
|
641
|
+
}
|
|
642
|
+
const result = translate(match);
|
|
643
|
+
return result;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (options.debug) {
|
|
648
|
+
console.log('[DEBUG] No pattern matched');
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return {
|
|
652
|
+
...UNKNOWN_ERROR,
|
|
653
|
+
originalMessage: errorMessage.substring(0, 100)
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Format translated error as a pretty box
|
|
659
|
+
* @param {Error|string} error - The error to format
|
|
660
|
+
* @param {Object} options - Formatting options
|
|
661
|
+
* @returns {string} Formatted error string
|
|
662
|
+
*/
|
|
663
|
+
export function formatTranslatedError(error, options = {}) {
|
|
664
|
+
const translated = translateError(error);
|
|
665
|
+
const errorMessage = typeof error === 'string' ? error : error.message || String(error);
|
|
666
|
+
|
|
667
|
+
// Extract location if available
|
|
668
|
+
const locationMatch = errorMessage.match(/at\s+(?:\w+\s+)?\(?([^:]+):(\d+)(?::\d+)?\)?/);
|
|
669
|
+
const location = locationMatch ? `${locationMatch[1]} dòng ${locationMatch[2]}` : null;
|
|
670
|
+
|
|
671
|
+
const lines = [];
|
|
672
|
+
|
|
673
|
+
// Header
|
|
674
|
+
lines.push(chalk.red('╭────────────────────────────────────────────────────────────────────╮'));
|
|
675
|
+
lines.push(chalk.red('│') + ` ❌ ${chalk.bold.red('LỖI:')} ${chalk.white(translated.title)}`.padEnd(76) + chalk.red('│'));
|
|
676
|
+
lines.push(chalk.red('│') + ''.padEnd(68) + chalk.red('│'));
|
|
677
|
+
|
|
678
|
+
// Description (wrap if too long)
|
|
679
|
+
const desc = translated.description;
|
|
680
|
+
if (desc.length <= 55) {
|
|
681
|
+
lines.push(chalk.red('│') + ` ${chalk.gray('Vấn đề:')} ${chalk.white(desc)}`.padEnd(76) + chalk.red('│'));
|
|
682
|
+
} else {
|
|
683
|
+
lines.push(chalk.red('│') + ` ${chalk.gray('Vấn đề:')} ${chalk.white(desc.substring(0, 55))}`.padEnd(76) + chalk.red('│'));
|
|
684
|
+
lines.push(chalk.red('│') + ` ${chalk.white(desc.substring(55, 110))}`.padEnd(76) + chalk.red('│'));
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Location
|
|
688
|
+
if (location) {
|
|
689
|
+
lines.push(chalk.red('│') + ''.padEnd(68) + chalk.red('│'));
|
|
690
|
+
lines.push(chalk.red('│') + ` 📍 ${chalk.gray('Vị trí:')} ${chalk.yellow(location)}`.padEnd(76) + chalk.red('│'));
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Suggestions
|
|
694
|
+
lines.push(chalk.red('│') + ''.padEnd(68) + chalk.red('│'));
|
|
695
|
+
lines.push(chalk.red('│') + ` 💡 ${chalk.gray('Gợi ý:')}`.padEnd(76) + chalk.red('│'));
|
|
696
|
+
|
|
697
|
+
for (const suggestion of translated.suggestions.slice(0, 3)) {
|
|
698
|
+
const truncated = suggestion.length > 58 ? suggestion.substring(0, 55) + '...' : suggestion;
|
|
699
|
+
lines.push(chalk.red('│') + ` ${chalk.cyan('•')} ${chalk.white(truncated)}`.padEnd(76) + chalk.red('│'));
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Debug hint
|
|
703
|
+
lines.push(chalk.red('│') + ''.padEnd(68) + chalk.red('│'));
|
|
704
|
+
lines.push(chalk.red('│') + ` ${chalk.gray('Chạy')} ${chalk.cyan('vibecode debug --auto')} ${chalk.gray('để AI phân tích chi tiết')}`.padEnd(76) + chalk.red('│'));
|
|
705
|
+
lines.push(chalk.red('│') + ''.padEnd(68) + chalk.red('│'));
|
|
706
|
+
lines.push(chalk.red('╰────────────────────────────────────────────────────────────────────╯'));
|
|
707
|
+
|
|
708
|
+
// Original error (verbose mode)
|
|
709
|
+
if (options.verbose && options.showOriginal !== false) {
|
|
710
|
+
lines.push('');
|
|
711
|
+
lines.push(chalk.gray('Original error:'));
|
|
712
|
+
lines.push(chalk.gray(errorMessage.substring(0, 300)));
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
return lines.join('\n');
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Show error in formatted box (console.log wrapper)
|
|
720
|
+
* @param {Error|string} error - The error to show
|
|
721
|
+
* @param {Object} options - Display options
|
|
722
|
+
*/
|
|
723
|
+
export function showError(error, options = {}) {
|
|
724
|
+
console.log(formatTranslatedError(error, options));
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Get inline error message (short, single line)
|
|
729
|
+
* @param {Error|string} error - The error to translate
|
|
730
|
+
* @returns {string} Short error message
|
|
731
|
+
*/
|
|
732
|
+
export function inlineError(error) {
|
|
733
|
+
const translated = translateError(error);
|
|
734
|
+
return chalk.red(`❌ ${translated.title}: ${translated.description.substring(0, 50)}${translated.description.length > 50 ? '...' : ''}`);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Get error category for filtering/grouping
|
|
739
|
+
* @param {Error|string} error - The error
|
|
740
|
+
* @returns {string} Error category
|
|
741
|
+
*/
|
|
742
|
+
export function getErrorCategory(error) {
|
|
743
|
+
const translated = translateError(error);
|
|
744
|
+
return translated.category || 'UNKNOWN';
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Check if error is of a specific category
|
|
749
|
+
* @param {Error|string} error - The error
|
|
750
|
+
* @param {string} category - Category to check
|
|
751
|
+
* @returns {boolean}
|
|
752
|
+
*/
|
|
753
|
+
export function isErrorCategory(error, category) {
|
|
754
|
+
return getErrorCategory(error) === category;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Get all pattern categories for documentation
|
|
759
|
+
* @returns {string[]} List of categories
|
|
760
|
+
*/
|
|
761
|
+
export function getCategories() {
|
|
762
|
+
const categories = new Set();
|
|
763
|
+
for (const { translate } of ERROR_PATTERNS) {
|
|
764
|
+
// Call translate with empty match to get category
|
|
765
|
+
try {
|
|
766
|
+
const result = translate(['', '', '']);
|
|
767
|
+
if (result.category) {
|
|
768
|
+
categories.add(result.category);
|
|
769
|
+
}
|
|
770
|
+
} catch {
|
|
771
|
+
// Some translates may fail with empty match, ignore
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return [...categories];
|
|
775
|
+
}
|