@magentrix-corp/magentrix-cli 1.3.16 → 1.3.17

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 (68) hide show
  1. package/LICENSE +25 -25
  2. package/README.md +1166 -1166
  3. package/actions/autopublish.old.js +293 -293
  4. package/actions/config.js +182 -182
  5. package/actions/create.js +466 -466
  6. package/actions/help.js +164 -164
  7. package/actions/iris/buildStage.js +874 -874
  8. package/actions/iris/delete.js +256 -256
  9. package/actions/iris/dev.js +391 -391
  10. package/actions/iris/index.js +6 -6
  11. package/actions/iris/link.js +375 -375
  12. package/actions/iris/recover.js +268 -268
  13. package/actions/main.js +80 -80
  14. package/actions/publish.js +1420 -1420
  15. package/actions/pull.js +684 -684
  16. package/actions/setup.js +148 -148
  17. package/actions/status.js +17 -17
  18. package/actions/update.js +248 -248
  19. package/bin/magentrix.js +393 -393
  20. package/package.json +55 -55
  21. package/utils/assetPaths.js +158 -158
  22. package/utils/autopublishLock.js +77 -77
  23. package/utils/cacher.js +206 -206
  24. package/utils/cli/checkInstanceUrl.js +76 -74
  25. package/utils/cli/helpers/compare.js +282 -282
  26. package/utils/cli/helpers/ensureApiKey.js +63 -63
  27. package/utils/cli/helpers/ensureCredentials.js +68 -68
  28. package/utils/cli/helpers/ensureInstanceUrl.js +75 -75
  29. package/utils/cli/writeRecords.js +262 -262
  30. package/utils/compare.js +135 -135
  31. package/utils/compress.js +17 -17
  32. package/utils/config.js +527 -527
  33. package/utils/debug.js +144 -144
  34. package/utils/diagnostics/testPublishLogic.js +96 -96
  35. package/utils/diff.js +49 -49
  36. package/utils/downloadAssets.js +291 -291
  37. package/utils/filetag.js +115 -115
  38. package/utils/hash.js +14 -14
  39. package/utils/iris/backup.js +411 -411
  40. package/utils/iris/builder.js +541 -541
  41. package/utils/iris/config-reader.js +664 -664
  42. package/utils/iris/deleteHelper.js +150 -150
  43. package/utils/iris/errors.js +537 -537
  44. package/utils/iris/linker.js +601 -601
  45. package/utils/iris/lock.js +360 -360
  46. package/utils/iris/validation.js +360 -360
  47. package/utils/iris/validator.js +281 -281
  48. package/utils/iris/zipper.js +248 -248
  49. package/utils/logger.js +291 -291
  50. package/utils/magentrix/api/assets.js +220 -220
  51. package/utils/magentrix/api/auth.js +107 -107
  52. package/utils/magentrix/api/createEntity.js +61 -61
  53. package/utils/magentrix/api/deleteEntity.js +55 -55
  54. package/utils/magentrix/api/iris.js +251 -251
  55. package/utils/magentrix/api/meqlQuery.js +36 -36
  56. package/utils/magentrix/api/retrieveEntity.js +86 -86
  57. package/utils/magentrix/api/updateEntity.js +66 -66
  58. package/utils/magentrix/fetch.js +168 -168
  59. package/utils/merge.js +22 -22
  60. package/utils/permissionError.js +70 -70
  61. package/utils/preferences.js +40 -40
  62. package/utils/progress.js +469 -469
  63. package/utils/spinner.js +43 -43
  64. package/utils/template.js +52 -52
  65. package/utils/updateFileBase.js +121 -121
  66. package/utils/workspaces.js +108 -108
  67. package/vars/config.js +11 -11
  68. package/vars/global.js +50 -50
@@ -1,537 +1,537 @@
1
- /**
2
- * Error handling utilities for Iris Vue integration.
3
- * Provides user-friendly error messages with actionable solutions.
4
- */
5
-
6
- import chalk from 'chalk';
7
-
8
- /**
9
- * Error types for categorization and handling.
10
- */
11
- export const ErrorTypes = {
12
- PERMISSION: 'PERMISSION',
13
- DISK_FULL: 'DISK_FULL',
14
- FILE_LOCKED: 'FILE_LOCKED',
15
- NOT_FOUND: 'NOT_FOUND',
16
- INVALID_CONFIG: 'INVALID_CONFIG',
17
- NETWORK: 'NETWORK',
18
- VALIDATION: 'VALIDATION',
19
- CONCURRENT_OPERATION: 'CONCURRENT_OPERATION',
20
- TIMEOUT: 'TIMEOUT',
21
- CORRUPTED: 'CORRUPTED',
22
- UNKNOWN: 'UNKNOWN'
23
- };
24
-
25
- /**
26
- * Detect the type of error from an Error object or error code.
27
- *
28
- * @param {Error | string} error - The error to analyze
29
- * @returns {string} - One of ErrorTypes
30
- */
31
- export function detectErrorType(error) {
32
- const code = error?.code || error;
33
- const message = (error?.message || String(error)).toLowerCase();
34
-
35
- // Permission errors
36
- if (code === 'EACCES' || code === 'EPERM' || message.includes('permission denied')) {
37
- return ErrorTypes.PERMISSION;
38
- }
39
-
40
- // Disk full errors
41
- if (code === 'ENOSPC' || message.includes('no space left') || message.includes('disk full')) {
42
- return ErrorTypes.DISK_FULL;
43
- }
44
-
45
- // File locked errors (Windows mainly)
46
- if (code === 'EBUSY' || code === 'ENOTEMPTY' || message.includes('resource busy') ||
47
- message.includes('being used by another process')) {
48
- return ErrorTypes.FILE_LOCKED;
49
- }
50
-
51
- // Not found errors
52
- if (code === 'ENOENT' || message.includes('no such file') || message.includes('not found')) {
53
- return ErrorTypes.NOT_FOUND;
54
- }
55
-
56
- // Network errors
57
- if (code === 'ENOTFOUND' || code === 'ECONNREFUSED' || code === 'ECONNRESET' ||
58
- code === 'ETIMEDOUT' || message.includes('network') || message.includes('fetch')) {
59
- return ErrorTypes.NETWORK;
60
- }
61
-
62
- // JSON/config parsing errors
63
- if (message.includes('json') || message.includes('parse') || message.includes('syntax')) {
64
- return ErrorTypes.CORRUPTED;
65
- }
66
-
67
- // Timeout errors
68
- if (code === 'ETIMEDOUT' || message.includes('timeout')) {
69
- return ErrorTypes.TIMEOUT;
70
- }
71
-
72
- return ErrorTypes.UNKNOWN;
73
- }
74
-
75
- /**
76
- * Format a permission error with helpful guidance.
77
- *
78
- * @param {Object} options - Error context
79
- * @param {string} options.operation - What operation failed (e.g., 'write', 'delete', 'read')
80
- * @param {string} options.path - The path that caused the error
81
- * @param {string} options.platform - The platform ('win32', 'darwin', 'linux')
82
- * @returns {string} - Formatted error message
83
- */
84
- export function formatPermissionError(options) {
85
- const { operation = 'access', path = '', platform = process.platform } = options;
86
-
87
- const lines = [
88
- '',
89
- chalk.bgRed.white.bold(' Permission Error '),
90
- chalk.red('─'.repeat(50)),
91
- '',
92
- chalk.white(`Unable to ${operation} files at:`),
93
- chalk.gray(` ${path}`),
94
- '',
95
- chalk.cyan('Possible causes:'),
96
- ];
97
-
98
- if (platform === 'win32') {
99
- lines.push(
100
- chalk.gray(' 1. File is open in another program (VS Code, Explorer, antivirus)'),
101
- chalk.gray(' 2. You need to run the command as Administrator'),
102
- chalk.gray(' 3. The file is marked as read-only'),
103
- '',
104
- chalk.cyan('Solutions:'),
105
- chalk.white(' 1. Close any programs that might have the file open'),
106
- chalk.white(' 2. Run your terminal as Administrator:'),
107
- chalk.gray(' Right-click terminal > "Run as administrator"'),
108
- chalk.white(' 3. Check file properties and uncheck "Read-only"'),
109
- chalk.white(' 4. Temporarily disable antivirus real-time scanning')
110
- );
111
- } else {
112
- lines.push(
113
- chalk.gray(' 1. Files are owned by a different user'),
114
- chalk.gray(' 2. Insufficient permissions on directory'),
115
- chalk.gray(' 3. File system is mounted read-only'),
116
- '',
117
- chalk.cyan('Solutions:'),
118
- chalk.white(' 1. Change ownership of the files:'),
119
- chalk.yellow(` sudo chown -R $(whoami) "${path}"`),
120
- chalk.white(' 2. Fix directory permissions:'),
121
- chalk.yellow(` chmod -R u+rwX "${path}"`),
122
- chalk.white(' 3. Use sudo for this operation (if appropriate):'),
123
- chalk.yellow(' sudo magentrix <command>')
124
- );
125
- }
126
-
127
- lines.push('', chalk.red('─'.repeat(50)), '');
128
-
129
- return lines.join('\n');
130
- }
131
-
132
- /**
133
- * Format a disk full error with helpful guidance.
134
- *
135
- * @param {Object} options - Error context
136
- * @param {string} options.operation - What operation failed
137
- * @param {string} options.path - The path that caused the error
138
- * @returns {string} - Formatted error message
139
- */
140
- export function formatDiskFullError(options) {
141
- const { operation = 'write', path = '' } = options;
142
- const platform = process.platform;
143
-
144
- const lines = [
145
- '',
146
- chalk.bgRed.white.bold(' Disk Full '),
147
- chalk.red('─'.repeat(50)),
148
- '',
149
- chalk.white(`Cannot ${operation} - no disk space available.`),
150
- chalk.gray(` Path: ${path}`),
151
- '',
152
- chalk.cyan('Solutions:'),
153
- ];
154
-
155
- if (platform === 'win32') {
156
- lines.push(
157
- chalk.white(' 1. Free up disk space:'),
158
- chalk.gray(' - Empty the Recycle Bin'),
159
- chalk.gray(' - Run Disk Cleanup (cleanmgr.exe)'),
160
- chalk.gray(' - Uninstall unused programs'),
161
- chalk.white(' 2. Clear npm cache:'),
162
- chalk.yellow(' npm cache clean --force'),
163
- chalk.white(' 3. Check available space:'),
164
- chalk.yellow(' wmic logicaldisk get size,freespace,caption')
165
- );
166
- } else {
167
- lines.push(
168
- chalk.white(' 1. Check disk usage:'),
169
- chalk.yellow(' df -h'),
170
- chalk.white(' 2. Find large files:'),
171
- chalk.yellow(' du -sh * | sort -rh | head -20'),
172
- chalk.white(' 3. Clear npm cache:'),
173
- chalk.yellow(' npm cache clean --force'),
174
- chalk.white(' 4. Clear system logs (macOS):'),
175
- chalk.yellow(' sudo rm -rf /private/var/log/*'),
176
- chalk.white(' 5. Clear trash:'),
177
- chalk.yellow(' rm -rf ~/.Trash/*')
178
- );
179
- }
180
-
181
- lines.push('', chalk.red('─'.repeat(50)), '');
182
-
183
- return lines.join('\n');
184
- }
185
-
186
- /**
187
- * Format a file locked error with helpful guidance.
188
- *
189
- * @param {Object} options - Error context
190
- * @param {string} options.path - The path that caused the error
191
- * @returns {string} - Formatted error message
192
- */
193
- export function formatFileLockError(options) {
194
- const { path = '' } = options;
195
- const platform = process.platform;
196
-
197
- const lines = [
198
- '',
199
- chalk.bgYellow.black.bold(' File In Use '),
200
- chalk.yellow('─'.repeat(50)),
201
- '',
202
- chalk.white('Cannot modify file - it is being used by another program.'),
203
- chalk.gray(` Path: ${path}`),
204
- '',
205
- chalk.cyan('This commonly happens when:'),
206
- chalk.gray(' - Your IDE (VS Code, WebStorm) has the file open'),
207
- chalk.gray(' - A dev server is running and watching files'),
208
- chalk.gray(' - Antivirus is scanning the file'),
209
- chalk.gray(' - Another terminal has the directory open'),
210
- '',
211
- chalk.cyan('Solutions:'),
212
- ];
213
-
214
- if (platform === 'win32') {
215
- lines.push(
216
- chalk.white(' 1. Close applications that might be using the file'),
217
- chalk.white(' 2. Close any Vue/React dev servers running from this project'),
218
- chalk.white(' 3. Close and reopen VS Code'),
219
- chalk.white(' 4. Find what is using the file:'),
220
- chalk.gray(' - Open Resource Monitor (resmon.exe)'),
221
- chalk.gray(' - Go to CPU > Associated Handles'),
222
- chalk.gray(' - Search for the filename')
223
- );
224
- } else {
225
- lines.push(
226
- chalk.white(' 1. Close applications that might be using the file'),
227
- chalk.white(' 2. Stop any running dev servers'),
228
- chalk.white(' 3. Find what is using the file:'),
229
- chalk.yellow(` lsof "${path}"`),
230
- chalk.white(' 4. Wait a few seconds and try again')
231
- );
232
- }
233
-
234
- lines.push('', chalk.yellow('─'.repeat(50)), '');
235
-
236
- return lines.join('\n');
237
- }
238
-
239
- /**
240
- * Format a network error with helpful guidance.
241
- *
242
- * @param {Object} options - Error context
243
- * @param {string} options.url - The URL that failed
244
- * @param {Error} options.error - The original error
245
- * @returns {string} - Formatted error message
246
- */
247
- export function formatNetworkError(options) {
248
- const { url = '', error } = options;
249
- const code = error?.code || '';
250
- const message = error?.message || '';
251
-
252
- const lines = [
253
- '',
254
- chalk.bgRed.white.bold(' Network Error '),
255
- chalk.red('─'.repeat(50)),
256
- '',
257
- chalk.white('Failed to connect to the server.'),
258
- chalk.gray(` URL: ${url}`),
259
- chalk.gray(` Error: ${message}`),
260
- '',
261
- chalk.cyan('Possible causes:'),
262
- ];
263
-
264
- if (code === 'ENOTFOUND' || message.includes('getaddrinfo')) {
265
- lines.push(
266
- chalk.gray(' - The server hostname could not be resolved'),
267
- chalk.gray(' - DNS server issues'),
268
- chalk.gray(' - Typo in the URL'),
269
- '',
270
- chalk.cyan('Solutions:'),
271
- chalk.white(' 1. Check that the URL is correct'),
272
- chalk.white(' 2. Verify your internet connection'),
273
- chalk.white(' 3. Try accessing the URL in a browser')
274
- );
275
- } else if (code === 'ECONNREFUSED') {
276
- lines.push(
277
- chalk.gray(' - The server is not running'),
278
- chalk.gray(' - Wrong port number'),
279
- chalk.gray(' - Firewall blocking the connection'),
280
- '',
281
- chalk.cyan('Solutions:'),
282
- chalk.white(' 1. Verify the server is running'),
283
- chalk.white(' 2. Check if the URL and port are correct'),
284
- chalk.white(' 3. Check firewall settings')
285
- );
286
- } else if (code === 'ETIMEDOUT' || message.includes('timeout')) {
287
- lines.push(
288
- chalk.gray(' - Server took too long to respond'),
289
- chalk.gray(' - Network congestion'),
290
- chalk.gray(' - Server might be overloaded'),
291
- '',
292
- chalk.cyan('Solutions:'),
293
- chalk.white(' 1. Wait a moment and try again'),
294
- chalk.white(' 2. Check if you can access the server in a browser'),
295
- chalk.white(' 3. Try from a different network')
296
- );
297
- } else {
298
- lines.push(
299
- chalk.gray(' - Network connectivity issues'),
300
- chalk.gray(' - Server is unreachable'),
301
- '',
302
- chalk.cyan('Solutions:'),
303
- chalk.white(' 1. Check your internet connection'),
304
- chalk.white(' 2. Try again in a few minutes'),
305
- chalk.white(' 3. Verify the URL is correct')
306
- );
307
- }
308
-
309
- lines.push('', chalk.red('─'.repeat(50)), '');
310
-
311
- return lines.join('\n');
312
- }
313
-
314
- /**
315
- * Format a corrupted config error with helpful guidance.
316
- *
317
- * @param {Object} options - Error context
318
- * @param {string} options.configPath - Path to the corrupted config
319
- * @param {Error} options.error - The original error
320
- * @returns {string} - Formatted error message
321
- */
322
- export function formatCorruptedConfigError(options) {
323
- const { configPath = '', error } = options;
324
- const message = error?.message || 'Invalid JSON format';
325
-
326
- const lines = [
327
- '',
328
- chalk.bgRed.white.bold(' Config File Corrupted '),
329
- chalk.red('─'.repeat(50)),
330
- '',
331
- chalk.white('The configuration file could not be read due to invalid format.'),
332
- chalk.gray(` Path: ${configPath}`),
333
- chalk.gray(` Error: ${message}`),
334
- '',
335
- chalk.cyan('This can happen when:'),
336
- chalk.gray(' - A previous write was interrupted'),
337
- chalk.gray(' - The file was manually edited with invalid JSON'),
338
- chalk.gray(' - Disk errors corrupted the file'),
339
- '',
340
- chalk.cyan('Solutions:'),
341
- chalk.white(' 1. Check if a backup exists:'),
342
- chalk.yellow(` ls -la "${configPath}.bak"`),
343
- chalk.white(' 2. If backup exists, restore it:'),
344
- chalk.yellow(` cp "${configPath}.bak" "${configPath}"`),
345
- chalk.white(' 3. If no backup, you may need to delete and recreate:'),
346
- chalk.yellow(` rm "${configPath}"`),
347
- chalk.gray(' Then re-run your command to regenerate the config.'),
348
- '',
349
- chalk.bgYellow.black(' Warning '),
350
- chalk.yellow(' Deleting the config will lose your linked project history.'),
351
- chalk.yellow(' You will need to re-link your Vue projects.'),
352
- '',
353
- chalk.red('─'.repeat(50)),
354
- ''
355
- ];
356
-
357
- return lines.join('\n');
358
- }
359
-
360
- /**
361
- * Format a concurrent operation error with helpful guidance.
362
- *
363
- * @param {Object} options - Error context
364
- * @param {string} options.operation - The operation that was blocked
365
- * @param {string} options.lockHolder - Description of what holds the lock
366
- * @param {string} options.lockFile - Path to the lock file
367
- * @returns {string} - Formatted error message
368
- */
369
- export function formatConcurrentOperationError(options) {
370
- const { operation = 'this operation', lockHolder = 'another process', lockFile = '' } = options;
371
-
372
- const lines = [
373
- '',
374
- chalk.bgYellow.black.bold(' Operation Blocked '),
375
- chalk.yellow('─'.repeat(50)),
376
- '',
377
- chalk.white(`Cannot ${operation} - ${lockHolder} is currently running.`),
378
- '',
379
- chalk.cyan('Why this happens:'),
380
- chalk.gray(' Running multiple operations simultaneously can cause:'),
381
- chalk.gray(' - File corruption'),
382
- chalk.gray(' - Data loss'),
383
- chalk.gray(' - Inconsistent state'),
384
- '',
385
- chalk.cyan('Solutions:'),
386
- chalk.white(' 1. Wait for the other operation to complete'),
387
- chalk.white(' 2. If the other operation crashed, remove the lock file:'),
388
- lockFile ? chalk.yellow(` rm "${lockFile}"`) : chalk.gray(' (lock file path not available)'),
389
- chalk.white(' 3. Check for running magentrix processes:'),
390
- chalk.yellow(' ps aux | grep magentrix'),
391
- '',
392
- chalk.yellow('─'.repeat(50)),
393
- ''
394
- ];
395
-
396
- return lines.join('\n');
397
- }
398
-
399
- /**
400
- * Format a timeout error with helpful guidance.
401
- *
402
- * @param {Object} options - Error context
403
- * @param {string} options.operation - What operation timed out
404
- * @param {number} options.timeoutMs - The timeout value in milliseconds
405
- * @returns {string} - Formatted error message
406
- */
407
- export function formatTimeoutError(options) {
408
- const { operation = 'Operation', timeoutMs = 0 } = options;
409
- const seconds = Math.round(timeoutMs / 1000);
410
-
411
- const lines = [
412
- '',
413
- chalk.bgYellow.black.bold(' Operation Timed Out '),
414
- chalk.yellow('─'.repeat(50)),
415
- '',
416
- chalk.white(`${operation} did not complete within ${seconds} seconds.`),
417
- '',
418
- chalk.cyan('Possible causes:'),
419
- chalk.gray(' - The server is slow or unresponsive'),
420
- chalk.gray(' - Network latency is high'),
421
- chalk.gray(' - Large amount of data being processed'),
422
- '',
423
- chalk.cyan('Solutions:'),
424
- chalk.white(' 1. Try running the command again'),
425
- chalk.white(' 2. Check your network connection'),
426
- chalk.white(' 3. If working with large files, allow more time'),
427
- '',
428
- chalk.yellow('─'.repeat(50)),
429
- ''
430
- ];
431
-
432
- return lines.join('\n');
433
- }
434
-
435
- /**
436
- * Format any error with automatic type detection.
437
- *
438
- * @param {Error} error - The error to format
439
- * @param {Object} context - Additional context for the error
440
- * @returns {string} - Formatted error message
441
- */
442
- export function formatError(error, context = {}) {
443
- const errorType = detectErrorType(error);
444
-
445
- switch (errorType) {
446
- case ErrorTypes.PERMISSION:
447
- return formatPermissionError({
448
- operation: context.operation || 'access',
449
- path: context.path || error.path || ''
450
- });
451
-
452
- case ErrorTypes.DISK_FULL:
453
- return formatDiskFullError({
454
- operation: context.operation || 'write',
455
- path: context.path || error.path || ''
456
- });
457
-
458
- case ErrorTypes.FILE_LOCKED:
459
- return formatFileLockError({
460
- path: context.path || error.path || ''
461
- });
462
-
463
- case ErrorTypes.NETWORK:
464
- return formatNetworkError({
465
- url: context.url || '',
466
- error
467
- });
468
-
469
- case ErrorTypes.CORRUPTED:
470
- return formatCorruptedConfigError({
471
- configPath: context.configPath || context.path || '',
472
- error
473
- });
474
-
475
- case ErrorTypes.TIMEOUT:
476
- return formatTimeoutError({
477
- operation: context.operation || 'Operation',
478
- timeoutMs: context.timeoutMs || 30000
479
- });
480
-
481
- case ErrorTypes.CONCURRENT_OPERATION:
482
- return formatConcurrentOperationError({
483
- operation: context.operation || 'this operation',
484
- lockHolder: context.lockHolder || 'another process',
485
- lockFile: context.lockFile || ''
486
- });
487
-
488
- default:
489
- // Generic error formatting
490
- return [
491
- '',
492
- chalk.bgRed.white.bold(' Error '),
493
- chalk.red('─'.repeat(50)),
494
- '',
495
- chalk.white(error.message || String(error)),
496
- context.path ? chalk.gray(` Path: ${context.path}`) : '',
497
- '',
498
- chalk.red('─'.repeat(50)),
499
- ''
500
- ].filter(Boolean).join('\n');
501
- }
502
- }
503
-
504
- /**
505
- * Wrap an async function with error handling.
506
- *
507
- * @param {Function} fn - The async function to wrap
508
- * @param {Object} context - Context for error messages
509
- * @returns {Function} - Wrapped function
510
- */
511
- export function withErrorHandling(fn, context = {}) {
512
- return async (...args) => {
513
- try {
514
- return await fn(...args);
515
- } catch (error) {
516
- console.log(formatError(error, context));
517
- return { success: false, error: error.message };
518
- }
519
- };
520
- }
521
-
522
- /**
523
- * Check if an error is recoverable (can be retried).
524
- *
525
- * @param {Error} error - The error to check
526
- * @returns {boolean} - True if the error might be recoverable with retry
527
- */
528
- export function isRecoverableError(error) {
529
- const errorType = detectErrorType(error);
530
-
531
- // These error types might succeed if retried
532
- return [
533
- ErrorTypes.NETWORK,
534
- ErrorTypes.TIMEOUT,
535
- ErrorTypes.FILE_LOCKED
536
- ].includes(errorType);
537
- }
1
+ /**
2
+ * Error handling utilities for Iris Vue integration.
3
+ * Provides user-friendly error messages with actionable solutions.
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+
8
+ /**
9
+ * Error types for categorization and handling.
10
+ */
11
+ export const ErrorTypes = {
12
+ PERMISSION: 'PERMISSION',
13
+ DISK_FULL: 'DISK_FULL',
14
+ FILE_LOCKED: 'FILE_LOCKED',
15
+ NOT_FOUND: 'NOT_FOUND',
16
+ INVALID_CONFIG: 'INVALID_CONFIG',
17
+ NETWORK: 'NETWORK',
18
+ VALIDATION: 'VALIDATION',
19
+ CONCURRENT_OPERATION: 'CONCURRENT_OPERATION',
20
+ TIMEOUT: 'TIMEOUT',
21
+ CORRUPTED: 'CORRUPTED',
22
+ UNKNOWN: 'UNKNOWN'
23
+ };
24
+
25
+ /**
26
+ * Detect the type of error from an Error object or error code.
27
+ *
28
+ * @param {Error | string} error - The error to analyze
29
+ * @returns {string} - One of ErrorTypes
30
+ */
31
+ export function detectErrorType(error) {
32
+ const code = error?.code || error;
33
+ const message = (error?.message || String(error)).toLowerCase();
34
+
35
+ // Permission errors
36
+ if (code === 'EACCES' || code === 'EPERM' || message.includes('permission denied')) {
37
+ return ErrorTypes.PERMISSION;
38
+ }
39
+
40
+ // Disk full errors
41
+ if (code === 'ENOSPC' || message.includes('no space left') || message.includes('disk full')) {
42
+ return ErrorTypes.DISK_FULL;
43
+ }
44
+
45
+ // File locked errors (Windows mainly)
46
+ if (code === 'EBUSY' || code === 'ENOTEMPTY' || message.includes('resource busy') ||
47
+ message.includes('being used by another process')) {
48
+ return ErrorTypes.FILE_LOCKED;
49
+ }
50
+
51
+ // Not found errors
52
+ if (code === 'ENOENT' || message.includes('no such file') || message.includes('not found')) {
53
+ return ErrorTypes.NOT_FOUND;
54
+ }
55
+
56
+ // Network errors
57
+ if (code === 'ENOTFOUND' || code === 'ECONNREFUSED' || code === 'ECONNRESET' ||
58
+ code === 'ETIMEDOUT' || message.includes('network') || message.includes('fetch')) {
59
+ return ErrorTypes.NETWORK;
60
+ }
61
+
62
+ // JSON/config parsing errors
63
+ if (message.includes('json') || message.includes('parse') || message.includes('syntax')) {
64
+ return ErrorTypes.CORRUPTED;
65
+ }
66
+
67
+ // Timeout errors
68
+ if (code === 'ETIMEDOUT' || message.includes('timeout')) {
69
+ return ErrorTypes.TIMEOUT;
70
+ }
71
+
72
+ return ErrorTypes.UNKNOWN;
73
+ }
74
+
75
+ /**
76
+ * Format a permission error with helpful guidance.
77
+ *
78
+ * @param {Object} options - Error context
79
+ * @param {string} options.operation - What operation failed (e.g., 'write', 'delete', 'read')
80
+ * @param {string} options.path - The path that caused the error
81
+ * @param {string} options.platform - The platform ('win32', 'darwin', 'linux')
82
+ * @returns {string} - Formatted error message
83
+ */
84
+ export function formatPermissionError(options) {
85
+ const { operation = 'access', path = '', platform = process.platform } = options;
86
+
87
+ const lines = [
88
+ '',
89
+ chalk.bgRed.white.bold(' Permission Error '),
90
+ chalk.red('─'.repeat(50)),
91
+ '',
92
+ chalk.white(`Unable to ${operation} files at:`),
93
+ chalk.gray(` ${path}`),
94
+ '',
95
+ chalk.cyan('Possible causes:'),
96
+ ];
97
+
98
+ if (platform === 'win32') {
99
+ lines.push(
100
+ chalk.gray(' 1. File is open in another program (VS Code, Explorer, antivirus)'),
101
+ chalk.gray(' 2. You need to run the command as Administrator'),
102
+ chalk.gray(' 3. The file is marked as read-only'),
103
+ '',
104
+ chalk.cyan('Solutions:'),
105
+ chalk.white(' 1. Close any programs that might have the file open'),
106
+ chalk.white(' 2. Run your terminal as Administrator:'),
107
+ chalk.gray(' Right-click terminal > "Run as administrator"'),
108
+ chalk.white(' 3. Check file properties and uncheck "Read-only"'),
109
+ chalk.white(' 4. Temporarily disable antivirus real-time scanning')
110
+ );
111
+ } else {
112
+ lines.push(
113
+ chalk.gray(' 1. Files are owned by a different user'),
114
+ chalk.gray(' 2. Insufficient permissions on directory'),
115
+ chalk.gray(' 3. File system is mounted read-only'),
116
+ '',
117
+ chalk.cyan('Solutions:'),
118
+ chalk.white(' 1. Change ownership of the files:'),
119
+ chalk.yellow(` sudo chown -R $(whoami) "${path}"`),
120
+ chalk.white(' 2. Fix directory permissions:'),
121
+ chalk.yellow(` chmod -R u+rwX "${path}"`),
122
+ chalk.white(' 3. Use sudo for this operation (if appropriate):'),
123
+ chalk.yellow(' sudo magentrix <command>')
124
+ );
125
+ }
126
+
127
+ lines.push('', chalk.red('─'.repeat(50)), '');
128
+
129
+ return lines.join('\n');
130
+ }
131
+
132
+ /**
133
+ * Format a disk full error with helpful guidance.
134
+ *
135
+ * @param {Object} options - Error context
136
+ * @param {string} options.operation - What operation failed
137
+ * @param {string} options.path - The path that caused the error
138
+ * @returns {string} - Formatted error message
139
+ */
140
+ export function formatDiskFullError(options) {
141
+ const { operation = 'write', path = '' } = options;
142
+ const platform = process.platform;
143
+
144
+ const lines = [
145
+ '',
146
+ chalk.bgRed.white.bold(' Disk Full '),
147
+ chalk.red('─'.repeat(50)),
148
+ '',
149
+ chalk.white(`Cannot ${operation} - no disk space available.`),
150
+ chalk.gray(` Path: ${path}`),
151
+ '',
152
+ chalk.cyan('Solutions:'),
153
+ ];
154
+
155
+ if (platform === 'win32') {
156
+ lines.push(
157
+ chalk.white(' 1. Free up disk space:'),
158
+ chalk.gray(' - Empty the Recycle Bin'),
159
+ chalk.gray(' - Run Disk Cleanup (cleanmgr.exe)'),
160
+ chalk.gray(' - Uninstall unused programs'),
161
+ chalk.white(' 2. Clear npm cache:'),
162
+ chalk.yellow(' npm cache clean --force'),
163
+ chalk.white(' 3. Check available space:'),
164
+ chalk.yellow(' wmic logicaldisk get size,freespace,caption')
165
+ );
166
+ } else {
167
+ lines.push(
168
+ chalk.white(' 1. Check disk usage:'),
169
+ chalk.yellow(' df -h'),
170
+ chalk.white(' 2. Find large files:'),
171
+ chalk.yellow(' du -sh * | sort -rh | head -20'),
172
+ chalk.white(' 3. Clear npm cache:'),
173
+ chalk.yellow(' npm cache clean --force'),
174
+ chalk.white(' 4. Clear system logs (macOS):'),
175
+ chalk.yellow(' sudo rm -rf /private/var/log/*'),
176
+ chalk.white(' 5. Clear trash:'),
177
+ chalk.yellow(' rm -rf ~/.Trash/*')
178
+ );
179
+ }
180
+
181
+ lines.push('', chalk.red('─'.repeat(50)), '');
182
+
183
+ return lines.join('\n');
184
+ }
185
+
186
+ /**
187
+ * Format a file locked error with helpful guidance.
188
+ *
189
+ * @param {Object} options - Error context
190
+ * @param {string} options.path - The path that caused the error
191
+ * @returns {string} - Formatted error message
192
+ */
193
+ export function formatFileLockError(options) {
194
+ const { path = '' } = options;
195
+ const platform = process.platform;
196
+
197
+ const lines = [
198
+ '',
199
+ chalk.bgYellow.black.bold(' File In Use '),
200
+ chalk.yellow('─'.repeat(50)),
201
+ '',
202
+ chalk.white('Cannot modify file - it is being used by another program.'),
203
+ chalk.gray(` Path: ${path}`),
204
+ '',
205
+ chalk.cyan('This commonly happens when:'),
206
+ chalk.gray(' - Your IDE (VS Code, WebStorm) has the file open'),
207
+ chalk.gray(' - A dev server is running and watching files'),
208
+ chalk.gray(' - Antivirus is scanning the file'),
209
+ chalk.gray(' - Another terminal has the directory open'),
210
+ '',
211
+ chalk.cyan('Solutions:'),
212
+ ];
213
+
214
+ if (platform === 'win32') {
215
+ lines.push(
216
+ chalk.white(' 1. Close applications that might be using the file'),
217
+ chalk.white(' 2. Close any Vue/React dev servers running from this project'),
218
+ chalk.white(' 3. Close and reopen VS Code'),
219
+ chalk.white(' 4. Find what is using the file:'),
220
+ chalk.gray(' - Open Resource Monitor (resmon.exe)'),
221
+ chalk.gray(' - Go to CPU > Associated Handles'),
222
+ chalk.gray(' - Search for the filename')
223
+ );
224
+ } else {
225
+ lines.push(
226
+ chalk.white(' 1. Close applications that might be using the file'),
227
+ chalk.white(' 2. Stop any running dev servers'),
228
+ chalk.white(' 3. Find what is using the file:'),
229
+ chalk.yellow(` lsof "${path}"`),
230
+ chalk.white(' 4. Wait a few seconds and try again')
231
+ );
232
+ }
233
+
234
+ lines.push('', chalk.yellow('─'.repeat(50)), '');
235
+
236
+ return lines.join('\n');
237
+ }
238
+
239
+ /**
240
+ * Format a network error with helpful guidance.
241
+ *
242
+ * @param {Object} options - Error context
243
+ * @param {string} options.url - The URL that failed
244
+ * @param {Error} options.error - The original error
245
+ * @returns {string} - Formatted error message
246
+ */
247
+ export function formatNetworkError(options) {
248
+ const { url = '', error } = options;
249
+ const code = error?.code || '';
250
+ const message = error?.message || '';
251
+
252
+ const lines = [
253
+ '',
254
+ chalk.bgRed.white.bold(' Network Error '),
255
+ chalk.red('─'.repeat(50)),
256
+ '',
257
+ chalk.white('Failed to connect to the server.'),
258
+ chalk.gray(` URL: ${url}`),
259
+ chalk.gray(` Error: ${message}`),
260
+ '',
261
+ chalk.cyan('Possible causes:'),
262
+ ];
263
+
264
+ if (code === 'ENOTFOUND' || message.includes('getaddrinfo')) {
265
+ lines.push(
266
+ chalk.gray(' - The server hostname could not be resolved'),
267
+ chalk.gray(' - DNS server issues'),
268
+ chalk.gray(' - Typo in the URL'),
269
+ '',
270
+ chalk.cyan('Solutions:'),
271
+ chalk.white(' 1. Check that the URL is correct'),
272
+ chalk.white(' 2. Verify your internet connection'),
273
+ chalk.white(' 3. Try accessing the URL in a browser')
274
+ );
275
+ } else if (code === 'ECONNREFUSED') {
276
+ lines.push(
277
+ chalk.gray(' - The server is not running'),
278
+ chalk.gray(' - Wrong port number'),
279
+ chalk.gray(' - Firewall blocking the connection'),
280
+ '',
281
+ chalk.cyan('Solutions:'),
282
+ chalk.white(' 1. Verify the server is running'),
283
+ chalk.white(' 2. Check if the URL and port are correct'),
284
+ chalk.white(' 3. Check firewall settings')
285
+ );
286
+ } else if (code === 'ETIMEDOUT' || message.includes('timeout')) {
287
+ lines.push(
288
+ chalk.gray(' - Server took too long to respond'),
289
+ chalk.gray(' - Network congestion'),
290
+ chalk.gray(' - Server might be overloaded'),
291
+ '',
292
+ chalk.cyan('Solutions:'),
293
+ chalk.white(' 1. Wait a moment and try again'),
294
+ chalk.white(' 2. Check if you can access the server in a browser'),
295
+ chalk.white(' 3. Try from a different network')
296
+ );
297
+ } else {
298
+ lines.push(
299
+ chalk.gray(' - Network connectivity issues'),
300
+ chalk.gray(' - Server is unreachable'),
301
+ '',
302
+ chalk.cyan('Solutions:'),
303
+ chalk.white(' 1. Check your internet connection'),
304
+ chalk.white(' 2. Try again in a few minutes'),
305
+ chalk.white(' 3. Verify the URL is correct')
306
+ );
307
+ }
308
+
309
+ lines.push('', chalk.red('─'.repeat(50)), '');
310
+
311
+ return lines.join('\n');
312
+ }
313
+
314
+ /**
315
+ * Format a corrupted config error with helpful guidance.
316
+ *
317
+ * @param {Object} options - Error context
318
+ * @param {string} options.configPath - Path to the corrupted config
319
+ * @param {Error} options.error - The original error
320
+ * @returns {string} - Formatted error message
321
+ */
322
+ export function formatCorruptedConfigError(options) {
323
+ const { configPath = '', error } = options;
324
+ const message = error?.message || 'Invalid JSON format';
325
+
326
+ const lines = [
327
+ '',
328
+ chalk.bgRed.white.bold(' Config File Corrupted '),
329
+ chalk.red('─'.repeat(50)),
330
+ '',
331
+ chalk.white('The configuration file could not be read due to invalid format.'),
332
+ chalk.gray(` Path: ${configPath}`),
333
+ chalk.gray(` Error: ${message}`),
334
+ '',
335
+ chalk.cyan('This can happen when:'),
336
+ chalk.gray(' - A previous write was interrupted'),
337
+ chalk.gray(' - The file was manually edited with invalid JSON'),
338
+ chalk.gray(' - Disk errors corrupted the file'),
339
+ '',
340
+ chalk.cyan('Solutions:'),
341
+ chalk.white(' 1. Check if a backup exists:'),
342
+ chalk.yellow(` ls -la "${configPath}.bak"`),
343
+ chalk.white(' 2. If backup exists, restore it:'),
344
+ chalk.yellow(` cp "${configPath}.bak" "${configPath}"`),
345
+ chalk.white(' 3. If no backup, you may need to delete and recreate:'),
346
+ chalk.yellow(` rm "${configPath}"`),
347
+ chalk.gray(' Then re-run your command to regenerate the config.'),
348
+ '',
349
+ chalk.bgYellow.black(' Warning '),
350
+ chalk.yellow(' Deleting the config will lose your linked project history.'),
351
+ chalk.yellow(' You will need to re-link your Vue projects.'),
352
+ '',
353
+ chalk.red('─'.repeat(50)),
354
+ ''
355
+ ];
356
+
357
+ return lines.join('\n');
358
+ }
359
+
360
+ /**
361
+ * Format a concurrent operation error with helpful guidance.
362
+ *
363
+ * @param {Object} options - Error context
364
+ * @param {string} options.operation - The operation that was blocked
365
+ * @param {string} options.lockHolder - Description of what holds the lock
366
+ * @param {string} options.lockFile - Path to the lock file
367
+ * @returns {string} - Formatted error message
368
+ */
369
+ export function formatConcurrentOperationError(options) {
370
+ const { operation = 'this operation', lockHolder = 'another process', lockFile = '' } = options;
371
+
372
+ const lines = [
373
+ '',
374
+ chalk.bgYellow.black.bold(' Operation Blocked '),
375
+ chalk.yellow('─'.repeat(50)),
376
+ '',
377
+ chalk.white(`Cannot ${operation} - ${lockHolder} is currently running.`),
378
+ '',
379
+ chalk.cyan('Why this happens:'),
380
+ chalk.gray(' Running multiple operations simultaneously can cause:'),
381
+ chalk.gray(' - File corruption'),
382
+ chalk.gray(' - Data loss'),
383
+ chalk.gray(' - Inconsistent state'),
384
+ '',
385
+ chalk.cyan('Solutions:'),
386
+ chalk.white(' 1. Wait for the other operation to complete'),
387
+ chalk.white(' 2. If the other operation crashed, remove the lock file:'),
388
+ lockFile ? chalk.yellow(` rm "${lockFile}"`) : chalk.gray(' (lock file path not available)'),
389
+ chalk.white(' 3. Check for running magentrix processes:'),
390
+ chalk.yellow(' ps aux | grep magentrix'),
391
+ '',
392
+ chalk.yellow('─'.repeat(50)),
393
+ ''
394
+ ];
395
+
396
+ return lines.join('\n');
397
+ }
398
+
399
+ /**
400
+ * Format a timeout error with helpful guidance.
401
+ *
402
+ * @param {Object} options - Error context
403
+ * @param {string} options.operation - What operation timed out
404
+ * @param {number} options.timeoutMs - The timeout value in milliseconds
405
+ * @returns {string} - Formatted error message
406
+ */
407
+ export function formatTimeoutError(options) {
408
+ const { operation = 'Operation', timeoutMs = 0 } = options;
409
+ const seconds = Math.round(timeoutMs / 1000);
410
+
411
+ const lines = [
412
+ '',
413
+ chalk.bgYellow.black.bold(' Operation Timed Out '),
414
+ chalk.yellow('─'.repeat(50)),
415
+ '',
416
+ chalk.white(`${operation} did not complete within ${seconds} seconds.`),
417
+ '',
418
+ chalk.cyan('Possible causes:'),
419
+ chalk.gray(' - The server is slow or unresponsive'),
420
+ chalk.gray(' - Network latency is high'),
421
+ chalk.gray(' - Large amount of data being processed'),
422
+ '',
423
+ chalk.cyan('Solutions:'),
424
+ chalk.white(' 1. Try running the command again'),
425
+ chalk.white(' 2. Check your network connection'),
426
+ chalk.white(' 3. If working with large files, allow more time'),
427
+ '',
428
+ chalk.yellow('─'.repeat(50)),
429
+ ''
430
+ ];
431
+
432
+ return lines.join('\n');
433
+ }
434
+
435
+ /**
436
+ * Format any error with automatic type detection.
437
+ *
438
+ * @param {Error} error - The error to format
439
+ * @param {Object} context - Additional context for the error
440
+ * @returns {string} - Formatted error message
441
+ */
442
+ export function formatError(error, context = {}) {
443
+ const errorType = detectErrorType(error);
444
+
445
+ switch (errorType) {
446
+ case ErrorTypes.PERMISSION:
447
+ return formatPermissionError({
448
+ operation: context.operation || 'access',
449
+ path: context.path || error.path || ''
450
+ });
451
+
452
+ case ErrorTypes.DISK_FULL:
453
+ return formatDiskFullError({
454
+ operation: context.operation || 'write',
455
+ path: context.path || error.path || ''
456
+ });
457
+
458
+ case ErrorTypes.FILE_LOCKED:
459
+ return formatFileLockError({
460
+ path: context.path || error.path || ''
461
+ });
462
+
463
+ case ErrorTypes.NETWORK:
464
+ return formatNetworkError({
465
+ url: context.url || '',
466
+ error
467
+ });
468
+
469
+ case ErrorTypes.CORRUPTED:
470
+ return formatCorruptedConfigError({
471
+ configPath: context.configPath || context.path || '',
472
+ error
473
+ });
474
+
475
+ case ErrorTypes.TIMEOUT:
476
+ return formatTimeoutError({
477
+ operation: context.operation || 'Operation',
478
+ timeoutMs: context.timeoutMs || 30000
479
+ });
480
+
481
+ case ErrorTypes.CONCURRENT_OPERATION:
482
+ return formatConcurrentOperationError({
483
+ operation: context.operation || 'this operation',
484
+ lockHolder: context.lockHolder || 'another process',
485
+ lockFile: context.lockFile || ''
486
+ });
487
+
488
+ default:
489
+ // Generic error formatting
490
+ return [
491
+ '',
492
+ chalk.bgRed.white.bold(' Error '),
493
+ chalk.red('─'.repeat(50)),
494
+ '',
495
+ chalk.white(error.message || String(error)),
496
+ context.path ? chalk.gray(` Path: ${context.path}`) : '',
497
+ '',
498
+ chalk.red('─'.repeat(50)),
499
+ ''
500
+ ].filter(Boolean).join('\n');
501
+ }
502
+ }
503
+
504
+ /**
505
+ * Wrap an async function with error handling.
506
+ *
507
+ * @param {Function} fn - The async function to wrap
508
+ * @param {Object} context - Context for error messages
509
+ * @returns {Function} - Wrapped function
510
+ */
511
+ export function withErrorHandling(fn, context = {}) {
512
+ return async (...args) => {
513
+ try {
514
+ return await fn(...args);
515
+ } catch (error) {
516
+ console.log(formatError(error, context));
517
+ return { success: false, error: error.message };
518
+ }
519
+ };
520
+ }
521
+
522
+ /**
523
+ * Check if an error is recoverable (can be retried).
524
+ *
525
+ * @param {Error} error - The error to check
526
+ * @returns {boolean} - True if the error might be recoverable with retry
527
+ */
528
+ export function isRecoverableError(error) {
529
+ const errorType = detectErrorType(error);
530
+
531
+ // These error types might succeed if retried
532
+ return [
533
+ ErrorTypes.NETWORK,
534
+ ErrorTypes.TIMEOUT,
535
+ ErrorTypes.FILE_LOCKED
536
+ ].includes(errorType);
537
+ }