baseguard 1.0.3 → 1.0.4

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 (167) hide show
  1. package/.baseguardrc.example.json +63 -63
  2. package/.eslintrc.json +24 -24
  3. package/.prettierrc +7 -7
  4. package/CHANGELOG.md +195 -195
  5. package/DEPLOYMENT.md +624 -624
  6. package/DEPLOYMENT_CHECKLIST.md +239 -239
  7. package/DEPLOYMENT_SUMMARY_v1.0.2.md +202 -202
  8. package/QUICK_START.md +134 -134
  9. package/README.md +488 -488
  10. package/RELEASE_NOTES_v1.0.2.md +434 -434
  11. package/bin/base.js +627 -627
  12. package/dist/ai/fix-manager.d.ts.map +1 -1
  13. package/dist/ai/fix-manager.js +1 -1
  14. package/dist/ai/fix-manager.js.map +1 -1
  15. package/dist/ai/gemini-analyzer.d.ts.map +1 -1
  16. package/dist/ai/gemini-analyzer.js +29 -35
  17. package/dist/ai/gemini-analyzer.js.map +1 -1
  18. package/dist/ai/gemini-code-fixer.d.ts.map +1 -1
  19. package/dist/ai/gemini-code-fixer.js +58 -58
  20. package/dist/ai/gemini-code-fixer.js.map +1 -1
  21. package/dist/ai/jules-implementer.d.ts +3 -0
  22. package/dist/ai/jules-implementer.d.ts.map +1 -1
  23. package/dist/ai/jules-implementer.js +63 -32
  24. package/dist/ai/jules-implementer.js.map +1 -1
  25. package/dist/ai/unified-code-fixer.js.map +1 -1
  26. package/dist/commands/check.d.ts.map +1 -1
  27. package/dist/commands/check.js +1 -1
  28. package/dist/commands/check.js.map +1 -1
  29. package/dist/commands/config.js +2 -1
  30. package/dist/commands/config.js.map +1 -1
  31. package/dist/commands/fix.d.ts.map +1 -1
  32. package/dist/commands/fix.js +44 -15
  33. package/dist/commands/fix.js.map +1 -1
  34. package/dist/core/api-key-manager.js +2 -2
  35. package/dist/core/api-key-manager.js.map +1 -1
  36. package/dist/core/baseguard.d.ts +1 -0
  37. package/dist/core/baseguard.d.ts.map +1 -1
  38. package/dist/core/baseguard.js +13 -10
  39. package/dist/core/baseguard.js.map +1 -1
  40. package/dist/core/baseline-checker.d.ts.map +1 -1
  41. package/dist/core/baseline-checker.js +2 -1
  42. package/dist/core/baseline-checker.js.map +1 -1
  43. package/dist/core/configuration-recovery.d.ts.map +1 -1
  44. package/dist/core/configuration-recovery.js +1 -1
  45. package/dist/core/configuration-recovery.js.map +1 -1
  46. package/dist/core/debug-logger.d.ts.map +1 -1
  47. package/dist/core/debug-logger.js +1 -1
  48. package/dist/core/debug-logger.js.map +1 -1
  49. package/dist/core/error-handler.d.ts.map +1 -1
  50. package/dist/core/error-handler.js +2 -1
  51. package/dist/core/error-handler.js.map +1 -1
  52. package/dist/core/gitignore-manager.js +5 -5
  53. package/dist/core/graceful-degradation-manager.d.ts.map +1 -1
  54. package/dist/core/graceful-degradation-manager.js +16 -16
  55. package/dist/core/graceful-degradation-manager.js.map +1 -1
  56. package/dist/core/lazy-loader.d.ts.map +1 -1
  57. package/dist/core/lazy-loader.js +9 -2
  58. package/dist/core/lazy-loader.js.map +1 -1
  59. package/dist/core/memory-manager.d.ts +0 -3
  60. package/dist/core/memory-manager.d.ts.map +1 -1
  61. package/dist/core/memory-manager.js.map +1 -1
  62. package/dist/core/parser-worker.d.ts +2 -0
  63. package/dist/core/parser-worker.d.ts.map +1 -0
  64. package/dist/core/parser-worker.js +19 -0
  65. package/dist/core/parser-worker.js.map +1 -0
  66. package/dist/core/startup-optimizer.d.ts.map +1 -1
  67. package/dist/core/startup-optimizer.js +4 -8
  68. package/dist/core/startup-optimizer.js.map +1 -1
  69. package/dist/core/system-error-handler.d.ts.map +1 -1
  70. package/dist/core/system-error-handler.js.map +1 -1
  71. package/dist/git/automation-engine.d.ts.map +1 -1
  72. package/dist/git/automation-engine.js +5 -4
  73. package/dist/git/automation-engine.js.map +1 -1
  74. package/dist/git/github-manager.d.ts.map +1 -1
  75. package/dist/git/github-manager.js.map +1 -1
  76. package/dist/git/hook-manager.js +5 -5
  77. package/dist/git/hook-manager.js.map +1 -1
  78. package/dist/parsers/parser-manager.d.ts.map +1 -1
  79. package/dist/parsers/parser-manager.js +1 -1
  80. package/dist/parsers/parser-manager.js.map +1 -1
  81. package/dist/parsers/svelte-parser.js +1 -1
  82. package/dist/parsers/svelte-parser.js.map +1 -1
  83. package/dist/parsers/vanilla-parser.d.ts.map +1 -1
  84. package/dist/parsers/vanilla-parser.js.map +1 -1
  85. package/dist/parsers/vue-parser.d.ts.map +1 -1
  86. package/dist/parsers/vue-parser.js.map +1 -1
  87. package/dist/ui/components.d.ts +1 -1
  88. package/dist/ui/components.d.ts.map +1 -1
  89. package/dist/ui/components.js +11 -11
  90. package/dist/ui/components.js.map +1 -1
  91. package/dist/ui/terminal-header.js +14 -14
  92. package/package.json +105 -105
  93. package/src/ai/__tests__/gemini-analyzer.test.ts +180 -180
  94. package/src/ai/agentkit-orchestrator.ts +533 -533
  95. package/src/ai/fix-manager.ts +362 -362
  96. package/src/ai/gemini-analyzer.ts +665 -671
  97. package/src/ai/gemini-code-fixer.ts +539 -540
  98. package/src/ai/index.ts +3 -3
  99. package/src/ai/jules-implementer.ts +504 -460
  100. package/src/ai/unified-code-fixer.ts +347 -347
  101. package/src/commands/automation.ts +343 -343
  102. package/src/commands/check.ts +298 -299
  103. package/src/commands/config.ts +584 -583
  104. package/src/commands/fix.ts +264 -238
  105. package/src/commands/index.ts +6 -6
  106. package/src/commands/init.ts +155 -155
  107. package/src/commands/status.ts +306 -306
  108. package/src/core/api-key-manager.ts +298 -298
  109. package/src/core/baseguard.ts +757 -756
  110. package/src/core/baseline-checker.ts +564 -563
  111. package/src/core/cache-manager.ts +271 -271
  112. package/src/core/configuration-recovery.ts +672 -673
  113. package/src/core/configuration.ts +595 -595
  114. package/src/core/debug-logger.ts +590 -590
  115. package/src/core/directory-filter.ts +420 -420
  116. package/src/core/error-handler.ts +518 -517
  117. package/src/core/file-processor.ts +337 -337
  118. package/src/core/gitignore-manager.ts +168 -168
  119. package/src/core/graceful-degradation-manager.ts +596 -596
  120. package/src/core/index.ts +16 -16
  121. package/src/core/lazy-loader.ts +317 -307
  122. package/src/core/memory-manager.ts +290 -295
  123. package/src/core/parser-worker.ts +33 -0
  124. package/src/core/startup-optimizer.ts +246 -255
  125. package/src/core/system-error-handler.ts +755 -756
  126. package/src/git/automation-engine.ts +361 -361
  127. package/src/git/github-manager.ts +190 -192
  128. package/src/git/hook-manager.ts +210 -210
  129. package/src/git/index.ts +3 -3
  130. package/src/index.ts +7 -7
  131. package/src/parsers/feature-validator.ts +558 -558
  132. package/src/parsers/index.ts +7 -7
  133. package/src/parsers/parser-manager.ts +418 -419
  134. package/src/parsers/parser.ts +25 -25
  135. package/src/parsers/react-parser-optimized.ts +160 -160
  136. package/src/parsers/react-parser.ts +358 -358
  137. package/src/parsers/svelte-parser.ts +510 -510
  138. package/src/parsers/vanilla-parser.ts +685 -686
  139. package/src/parsers/vue-parser.ts +476 -478
  140. package/src/types/index.ts +95 -95
  141. package/src/ui/components.ts +567 -567
  142. package/src/ui/help.ts +192 -192
  143. package/src/ui/index.ts +3 -3
  144. package/src/ui/prompts.ts +680 -680
  145. package/src/ui/terminal-header.ts +58 -58
  146. package/test-build.js +40 -40
  147. package/test-config-commands.js +55 -55
  148. package/test-header-simple.js +32 -32
  149. package/test-terminal-header.js +11 -11
  150. package/test-ui.js +28 -28
  151. package/tests/e2e/baseguard.e2e.test.ts +515 -515
  152. package/tests/e2e/cross-platform.e2e.test.ts +419 -419
  153. package/tests/e2e/git-integration.e2e.test.ts +486 -486
  154. package/tests/fixtures/react-project/package.json +13 -13
  155. package/tests/fixtures/react-project/src/App.css +75 -75
  156. package/tests/fixtures/react-project/src/App.tsx +76 -76
  157. package/tests/fixtures/svelte-project/package.json +10 -10
  158. package/tests/fixtures/svelte-project/src/App.svelte +368 -368
  159. package/tests/fixtures/vanilla-project/index.html +75 -75
  160. package/tests/fixtures/vanilla-project/script.js +330 -330
  161. package/tests/fixtures/vanilla-project/styles.css +358 -358
  162. package/tests/fixtures/vue-project/package.json +11 -11
  163. package/tests/fixtures/vue-project/src/App.vue +215 -215
  164. package/tsconfig.json +34 -34
  165. package/vitest.config.ts +11 -11
  166. package/dist/terminal-header.d.ts +0 -12
  167. package/dist/terminal-header.js +0 -45
@@ -1,517 +1,518 @@
1
- import { UIComponents } from '../ui/components.js';
2
-
3
- /**
4
- * Error types for different API operations
5
- */
6
- export enum ErrorType {
7
- NETWORK = 'network',
8
- AUTHENTICATION = 'authentication',
9
- RATE_LIMIT = 'rate_limit',
10
- QUOTA_EXCEEDED = 'quota_exceeded',
11
- INVALID_REQUEST = 'invalid_request',
12
- SERVER_ERROR = 'server_error',
13
- TIMEOUT = 'timeout',
14
- CONFIGURATION = 'configuration',
15
- VALIDATION = 'validation',
16
- UNKNOWN = 'unknown'
17
- }
18
-
19
- /**
20
- * Retry configuration for different error types
21
- */
22
- export interface RetryConfig {
23
- maxRetries: number;
24
- baseDelay: number;
25
- maxDelay: number;
26
- backoffMultiplier: number;
27
- retryableErrors: ErrorType[];
28
- }
29
-
30
- /**
31
- * Default retry configuration
32
- */
33
- export const DEFAULT_RETRY_CONFIG: RetryConfig = {
34
- maxRetries: 3,
35
- baseDelay: 1000, // 1 second
36
- maxDelay: 30000, // 30 seconds
37
- backoffMultiplier: 2,
38
- retryableErrors: [
39
- ErrorType.NETWORK,
40
- ErrorType.TIMEOUT,
41
- ErrorType.SERVER_ERROR,
42
- ErrorType.RATE_LIMIT
43
- ]
44
- };
45
-
46
- /**
47
- * API operation error with context and recovery suggestions
48
- */
49
- export class APIError extends Error {
50
- public readonly type: ErrorType;
51
- public readonly statusCode?: number;
52
- public readonly retryAfter?: number;
53
- public readonly quotaReset?: Date;
54
- public readonly suggestions: string[];
55
- public readonly context: Record<string, any>;
56
-
57
- constructor(
58
- message: string,
59
- type: ErrorType,
60
- options: {
61
- statusCode?: number;
62
- retryAfter?: number;
63
- quotaReset?: Date;
64
- suggestions?: string[];
65
- context?: Record<string, any>;
66
- cause?: Error;
67
- } = {}
68
- ) {
69
- super(message);
70
- this.name = 'APIError';
71
- this.type = type;
72
- this.statusCode = options.statusCode;
73
- this.retryAfter = options.retryAfter;
74
- this.quotaReset = options.quotaReset;
75
- this.suggestions = options.suggestions || [];
76
- this.context = options.context || {};
77
-
78
- if (options.cause) {
79
- this.cause = options.cause;
80
- }
81
- }
82
- }
83
-
84
- /**
85
- * Comprehensive error handler for API operations
86
- */
87
- export class ErrorHandler {
88
- /**
89
- * Handle and classify API errors
90
- */
91
- static handleAPIError(error: any, context: Record<string, any> = {}): APIError {
92
- // If already an APIError, return as-is
93
- if (error instanceof APIError) {
94
- return error;
95
- }
96
-
97
- // Network/connection errors
98
- if (this.isNetworkError(error)) {
99
- return new APIError(
100
- 'Network connection failed. Please check your internet connection.',
101
- ErrorType.NETWORK,
102
- {
103
- suggestions: [
104
- 'Check your internet connection',
105
- 'Verify firewall settings allow outbound HTTPS connections',
106
- 'Try again in a few moments'
107
- ],
108
- context,
109
- cause: error
110
- }
111
- );
112
- }
113
-
114
- // HTTP status code errors
115
- if (error.response?.status) {
116
- return this.handleHTTPError(error, context);
117
- }
118
-
119
- // Timeout errors
120
- if (this.isTimeoutError(error)) {
121
- return new APIError(
122
- 'Request timed out. The API service may be experiencing high load.',
123
- ErrorType.TIMEOUT,
124
- {
125
- suggestions: [
126
- 'Try again in a few moments',
127
- 'Check if the API service is experiencing issues',
128
- 'Consider increasing timeout settings'
129
- ],
130
- context,
131
- cause: error
132
- }
133
- );
134
- }
135
-
136
- // Configuration errors
137
- if (this.isConfigurationError(error)) {
138
- return new APIError(
139
- 'Configuration error. Please check your API keys and settings.',
140
- ErrorType.CONFIGURATION,
141
- {
142
- suggestions: [
143
- 'Run "base config" to verify your API keys',
144
- 'Check that API keys are valid and not expired',
145
- 'Ensure you have proper permissions for the API'
146
- ],
147
- context,
148
- cause: error
149
- }
150
- );
151
- }
152
-
153
- // Generic error
154
- return new APIError(
155
- error.message || 'An unexpected error occurred',
156
- ErrorType.UNKNOWN,
157
- {
158
- suggestions: [
159
- 'Try the operation again',
160
- 'Check the BaseGuard documentation for troubleshooting',
161
- 'Report this issue if it persists'
162
- ],
163
- context,
164
- cause: error
165
- }
166
- );
167
- }
168
-
169
- /**
170
- * Handle HTTP status code errors
171
- */
172
- private static handleHTTPError(error: any, context: Record<string, any>): APIError {
173
- const status = error.response.status;
174
- const data = error.response.data;
175
-
176
- switch (status) {
177
- case 401:
178
- return new APIError(
179
- 'Authentication failed. Your API key may be invalid or expired.',
180
- ErrorType.AUTHENTICATION,
181
- {
182
- statusCode: status,
183
- suggestions: [
184
- 'Check that your API key is correct',
185
- 'Verify the API key has not expired',
186
- 'Run "base config" to update your API keys',
187
- 'Ensure you have proper permissions'
188
- ],
189
- context
190
- }
191
- );
192
-
193
- case 403:
194
- return new APIError(
195
- 'Access forbidden. You may not have permission for this operation.',
196
- ErrorType.AUTHENTICATION,
197
- {
198
- statusCode: status,
199
- suggestions: [
200
- 'Check your API key permissions',
201
- 'Verify you have access to the requested resource',
202
- 'Contact support if you believe this is an error'
203
- ],
204
- context
205
- }
206
- );
207
-
208
- case 429:
209
- const retryAfter = this.parseRetryAfter(error.response.headers);
210
- const quotaReset = this.parseQuotaReset(error.response.headers);
211
-
212
- return new APIError(
213
- 'Rate limit exceeded. Please wait before making more requests.',
214
- ErrorType.RATE_LIMIT,
215
- {
216
- statusCode: status,
217
- retryAfter,
218
- quotaReset,
219
- suggestions: [
220
- retryAfter ? `Wait ${retryAfter} seconds before retrying` : 'Wait before retrying',
221
- 'Consider reducing the frequency of requests',
222
- quotaReset ? `Quota resets at ${quotaReset.toLocaleString()}` : 'Check your quota limits'
223
- ],
224
- context
225
- }
226
- );
227
-
228
- case 400:
229
- return new APIError(
230
- data?.message || 'Invalid request. Please check your input parameters.',
231
- ErrorType.INVALID_REQUEST,
232
- {
233
- statusCode: status,
234
- suggestions: [
235
- 'Check the request parameters',
236
- 'Verify the data format is correct',
237
- 'Review the API documentation for requirements'
238
- ],
239
- context
240
- }
241
- );
242
-
243
- case 500:
244
- case 502:
245
- case 503:
246
- case 504:
247
- return new APIError(
248
- 'Server error. The API service is experiencing issues.',
249
- ErrorType.SERVER_ERROR,
250
- {
251
- statusCode: status,
252
- suggestions: [
253
- 'Try again in a few moments',
254
- 'Check the API service status page',
255
- 'Report the issue if it persists'
256
- ],
257
- context
258
- }
259
- );
260
-
261
- default:
262
- return new APIError(
263
- data?.message || `HTTP ${status}: ${error.message}`,
264
- ErrorType.UNKNOWN,
265
- {
266
- statusCode: status,
267
- suggestions: [
268
- 'Check the API documentation',
269
- 'Try the request again',
270
- 'Report this issue if it persists'
271
- ],
272
- context
273
- }
274
- );
275
- }
276
- }
277
-
278
- /**
279
- * Check if error is a network/connection error
280
- */
281
- private static isNetworkError(error: any): boolean {
282
- return (
283
- error.code === 'ECONNREFUSED' ||
284
- error.code === 'ENOTFOUND' ||
285
- error.code === 'ECONNRESET' ||
286
- error.code === 'ENETUNREACH' ||
287
- error.message?.includes('network') ||
288
- error.message?.includes('connection')
289
- );
290
- }
291
-
292
- /**
293
- * Check if error is a timeout error
294
- */
295
- private static isTimeoutError(error: any): boolean {
296
- return (
297
- error.code === 'ETIMEDOUT' ||
298
- error.code === 'TIMEOUT' ||
299
- error.message?.includes('timeout')
300
- );
301
- }
302
-
303
- /**
304
- * Check if error is a configuration error
305
- */
306
- private static isConfigurationError(error: any): boolean {
307
- return (
308
- error.message?.includes('API key') ||
309
- error.message?.includes('configuration') ||
310
- error.message?.includes('invalid key')
311
- );
312
- }
313
-
314
- /**
315
- * Parse retry-after header
316
- */
317
- private static parseRetryAfter(headers: any): number | undefined {
318
- const retryAfter = headers?.['retry-after'] || headers?.['Retry-After'];
319
- if (retryAfter) {
320
- const seconds = parseInt(retryAfter, 10);
321
- return isNaN(seconds) ? undefined : seconds;
322
- }
323
- return undefined;
324
- }
325
-
326
- /**
327
- * Parse quota reset time from headers
328
- */
329
- private static parseQuotaReset(headers: any): Date | undefined {
330
- const reset = headers?.['x-ratelimit-reset'] || headers?.['X-RateLimit-Reset'];
331
- if (reset) {
332
- const timestamp = parseInt(reset, 10);
333
- return isNaN(timestamp) ? undefined : new Date(timestamp * 1000);
334
- }
335
- return undefined;
336
- }
337
-
338
- /**
339
- * Retry operation with exponential backoff
340
- */
341
- static async withRetry<T>(
342
- operation: () => Promise<T>,
343
- config: Partial<RetryConfig> = {}
344
- ): Promise<T> {
345
- const retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config };
346
- let lastError: APIError;
347
-
348
- for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
349
- try {
350
- return await operation();
351
- } catch (error) {
352
- const apiError = this.handleAPIError(error);
353
- lastError = apiError;
354
-
355
- // Don't retry if this is the last attempt
356
- if (attempt === retryConfig.maxRetries) {
357
- break;
358
- }
359
-
360
- // Don't retry if error type is not retryable
361
- if (!retryConfig.retryableErrors.includes(apiError.type)) {
362
- break;
363
- }
364
-
365
- // Calculate delay with exponential backoff
366
- const delay = Math.min(
367
- retryConfig.baseDelay * Math.pow(retryConfig.backoffMultiplier, attempt),
368
- retryConfig.maxDelay
369
- );
370
-
371
- // Use retry-after header if available
372
- const actualDelay = apiError.retryAfter ? apiError.retryAfter * 1000 : delay;
373
-
374
- console.log(`Retrying in ${Math.ceil(actualDelay / 1000)} seconds... (attempt ${attempt + 1}/${retryConfig.maxRetries})`);
375
- await this.sleep(actualDelay);
376
- }
377
- }
378
-
379
- throw lastError!;
380
- }
381
-
382
- /**
383
- * Display error to user with helpful information
384
- */
385
- static displayError(error: APIError): void {
386
- UIComponents.showErrorBox(error.message);
387
-
388
- if (error.suggestions.length > 0) {
389
- console.log('\nSuggestions:');
390
- UIComponents.showList(error.suggestions);
391
- }
392
-
393
- // Show additional context for specific error types
394
- switch (error.type) {
395
- case ErrorType.RATE_LIMIT:
396
- if (error.retryAfter) {
397
- UIComponents.showWarningBox(`Rate limited. Retry after ${error.retryAfter} seconds.`);
398
- }
399
- if (error.quotaReset) {
400
- UIComponents.showInfoBox(`Quota resets at: ${error.quotaReset.toLocaleString()}`);
401
- }
402
- break;
403
-
404
- case ErrorType.AUTHENTICATION:
405
- UIComponents.showWarningBox('Run "base config" to update your API keys.');
406
- break;
407
-
408
- case ErrorType.NETWORK:
409
- UIComponents.showWarningBox('Check your internet connection and try again.');
410
- break;
411
- }
412
- }
413
-
414
- /**
415
- * Get fallback mode suggestions when APIs are unavailable
416
- */
417
- static getFallbackSuggestions(errorType: ErrorType): string[] {
418
- switch (errorType) {
419
- case ErrorType.NETWORK:
420
- case ErrorType.TIMEOUT:
421
- return [
422
- 'Use offline mode for basic compatibility checking',
423
- 'Review violations manually using browser compatibility tables',
424
- 'Try again when network connection is restored'
425
- ];
426
-
427
- case ErrorType.RATE_LIMIT:
428
- case ErrorType.QUOTA_EXCEEDED:
429
- return [
430
- 'Continue with basic violation detection without AI analysis',
431
- 'Review violations manually',
432
- 'Try AI features again later when quota resets'
433
- ];
434
-
435
- case ErrorType.AUTHENTICATION:
436
- return [
437
- 'Use BaseGuard without AI features',
438
- 'Update API keys when available',
439
- 'Review violations manually using web compatibility resources'
440
- ];
441
-
442
- default:
443
- return [
444
- 'Continue with basic compatibility checking',
445
- 'Review violations manually',
446
- 'Try AI features again later'
447
- ];
448
- }
449
- }
450
-
451
- /**
452
- * Check if operation should continue in fallback mode
453
- */
454
- static shouldUseFallbackMode(error: APIError): boolean {
455
- return [
456
- ErrorType.NETWORK,
457
- ErrorType.TIMEOUT,
458
- ErrorType.AUTHENTICATION,
459
- ErrorType.RATE_LIMIT,
460
- ErrorType.QUOTA_EXCEEDED
461
- ].includes(error.type);
462
- }
463
-
464
- /**
465
- * Sleep for specified milliseconds
466
- */
467
- private static sleep(ms: number): Promise<void> {
468
- return new Promise(resolve => setTimeout(resolve, ms));
469
- }
470
-
471
- /**
472
- * Validate API response structure
473
- */
474
- static validateAPIResponse(response: any, expectedFields: string[]): void {
475
- if (!response || typeof response !== 'object') {
476
- throw new APIError(
477
- 'Invalid API response format',
478
- ErrorType.VALIDATION,
479
- {
480
- suggestions: [
481
- 'Check API service status',
482
- 'Verify API version compatibility',
483
- 'Try the request again'
484
- ]
485
- }
486
- );
487
- }
488
-
489
- const missingFields = expectedFields.filter(field => !(field in response));
490
- if (missingFields.length > 0) {
491
- throw new APIError(
492
- `API response missing required fields: ${missingFields.join(', ')}`,
493
- ErrorType.VALIDATION,
494
- {
495
- suggestions: [
496
- 'Check API documentation for response format',
497
- 'Verify API version compatibility',
498
- 'Report this issue if it persists'
499
- ],
500
- context: { missingFields, response }
501
- }
502
- );
503
- }
504
- }
505
-
506
- /**
507
- * Create context object for error reporting
508
- */
509
- static createContext(operation: string, params?: Record<string, any>): Record<string, any> {
510
- return {
511
- operation,
512
- timestamp: new Date().toISOString(),
513
- params: params || {},
514
- version: process.env.npm_package_version || 'unknown'
515
- };
516
- }
517
- }
1
+ import { UIComponents } from '../ui/components.js';
2
+
3
+ /**
4
+ * Error types for different API operations
5
+ */
6
+ export enum ErrorType {
7
+ NETWORK = 'network',
8
+ AUTHENTICATION = 'authentication',
9
+ RATE_LIMIT = 'rate_limit',
10
+ QUOTA_EXCEEDED = 'quota_exceeded',
11
+ INVALID_REQUEST = 'invalid_request',
12
+ SERVER_ERROR = 'server_error',
13
+ TIMEOUT = 'timeout',
14
+ CONFIGURATION = 'configuration',
15
+ VALIDATION = 'validation',
16
+ UNKNOWN = 'unknown'
17
+ }
18
+
19
+ /**
20
+ * Retry configuration for different error types
21
+ */
22
+ export interface RetryConfig {
23
+ maxRetries: number;
24
+ baseDelay: number;
25
+ maxDelay: number;
26
+ backoffMultiplier: number;
27
+ retryableErrors: ErrorType[];
28
+ }
29
+
30
+ /**
31
+ * Default retry configuration
32
+ */
33
+ export const DEFAULT_RETRY_CONFIG: RetryConfig = {
34
+ maxRetries: 3,
35
+ baseDelay: 1000, // 1 second
36
+ maxDelay: 30000, // 30 seconds
37
+ backoffMultiplier: 2,
38
+ retryableErrors: [
39
+ ErrorType.NETWORK,
40
+ ErrorType.TIMEOUT,
41
+ ErrorType.SERVER_ERROR,
42
+ ErrorType.RATE_LIMIT
43
+ ]
44
+ };
45
+
46
+ /**
47
+ * API operation error with context and recovery suggestions
48
+ */
49
+ export class APIError extends Error {
50
+ public readonly type: ErrorType;
51
+ public readonly statusCode?: number;
52
+ public readonly retryAfter?: number;
53
+ public readonly quotaReset?: Date;
54
+ public readonly suggestions: string[];
55
+ public readonly context: Record<string, any>;
56
+
57
+ constructor(
58
+ message: string,
59
+ type: ErrorType,
60
+ options: {
61
+ statusCode?: number;
62
+ retryAfter?: number;
63
+ quotaReset?: Date;
64
+ suggestions?: string[];
65
+ context?: Record<string, any>;
66
+ cause?: Error;
67
+ } = {}
68
+ ) {
69
+ super(message);
70
+ this.name = 'APIError';
71
+ this.type = type;
72
+ this.statusCode = options.statusCode;
73
+ this.retryAfter = options.retryAfter;
74
+ this.quotaReset = options.quotaReset;
75
+ this.suggestions = options.suggestions || [];
76
+ this.context = options.context || {};
77
+
78
+ if (options.cause) {
79
+ this.cause = options.cause;
80
+ }
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Comprehensive error handler for API operations
86
+ */
87
+ export class ErrorHandler {
88
+ /**
89
+ * Handle and classify API errors
90
+ */
91
+ static handleAPIError(error: any, context: Record<string, any> = {}): APIError {
92
+ // If already an APIError, return as-is
93
+ if (error instanceof APIError) {
94
+ return error;
95
+ }
96
+
97
+ // Network/connection errors
98
+ if (this.isNetworkError(error)) {
99
+ return new APIError(
100
+ 'Network connection failed. Please check your internet connection.',
101
+ ErrorType.NETWORK,
102
+ {
103
+ suggestions: [
104
+ 'Check your internet connection',
105
+ 'Verify firewall settings allow outbound HTTPS connections',
106
+ 'Try again in a few moments'
107
+ ],
108
+ context,
109
+ cause: error
110
+ }
111
+ );
112
+ }
113
+
114
+ // HTTP status code errors
115
+ if (error.response?.status) {
116
+ return this.handleHTTPError(error, context);
117
+ }
118
+
119
+ // Timeout errors
120
+ if (this.isTimeoutError(error)) {
121
+ return new APIError(
122
+ 'Request timed out. The API service may be experiencing high load.',
123
+ ErrorType.TIMEOUT,
124
+ {
125
+ suggestions: [
126
+ 'Try again in a few moments',
127
+ 'Check if the API service is experiencing issues',
128
+ 'Consider increasing timeout settings'
129
+ ],
130
+ context,
131
+ cause: error
132
+ }
133
+ );
134
+ }
135
+
136
+ // Configuration errors
137
+ if (this.isConfigurationError(error)) {
138
+ return new APIError(
139
+ 'Configuration error. Please check your API keys and settings.',
140
+ ErrorType.CONFIGURATION,
141
+ {
142
+ suggestions: [
143
+ 'Run "base config" to verify your API keys',
144
+ 'Check that API keys are valid and not expired',
145
+ 'Ensure you have proper permissions for the API'
146
+ ],
147
+ context,
148
+ cause: error
149
+ }
150
+ );
151
+ }
152
+
153
+ // Generic error
154
+ return new APIError(
155
+ error.message || 'An unexpected error occurred',
156
+ ErrorType.UNKNOWN,
157
+ {
158
+ suggestions: [
159
+ 'Try the operation again',
160
+ 'Check the BaseGuard documentation for troubleshooting',
161
+ 'Report this issue if it persists'
162
+ ],
163
+ context,
164
+ cause: error
165
+ }
166
+ );
167
+ }
168
+
169
+ /**
170
+ * Handle HTTP status code errors
171
+ */
172
+ private static handleHTTPError(error: any, context: Record<string, any>): APIError {
173
+ const status = error.response.status;
174
+ const data = error.response.data;
175
+
176
+ switch (status) {
177
+ case 401:
178
+ return new APIError(
179
+ 'Authentication failed. Your API key may be invalid or expired.',
180
+ ErrorType.AUTHENTICATION,
181
+ {
182
+ statusCode: status,
183
+ suggestions: [
184
+ 'Check that your API key is correct',
185
+ 'Verify the API key has not expired',
186
+ 'Run "base config" to update your API keys',
187
+ 'Ensure you have proper permissions'
188
+ ],
189
+ context
190
+ }
191
+ );
192
+
193
+ case 403:
194
+ return new APIError(
195
+ 'Access forbidden. You may not have permission for this operation.',
196
+ ErrorType.AUTHENTICATION,
197
+ {
198
+ statusCode: status,
199
+ suggestions: [
200
+ 'Check your API key permissions',
201
+ 'Verify you have access to the requested resource',
202
+ 'Contact support if you believe this is an error'
203
+ ],
204
+ context
205
+ }
206
+ );
207
+
208
+ case 429: {
209
+ const retryAfter = this.parseRetryAfter(error.response.headers);
210
+ const quotaReset = this.parseQuotaReset(error.response.headers);
211
+
212
+ return new APIError(
213
+ 'Rate limit exceeded. Please wait before making more requests.',
214
+ ErrorType.RATE_LIMIT,
215
+ {
216
+ statusCode: status,
217
+ retryAfter,
218
+ quotaReset,
219
+ suggestions: [
220
+ retryAfter ? `Wait ${retryAfter} seconds before retrying` : 'Wait before retrying',
221
+ 'Consider reducing the frequency of requests',
222
+ quotaReset ? `Quota resets at ${quotaReset.toLocaleString()}` : 'Check your quota limits'
223
+ ],
224
+ context
225
+ }
226
+ );
227
+ }
228
+
229
+ case 400:
230
+ return new APIError(
231
+ data?.message || 'Invalid request. Please check your input parameters.',
232
+ ErrorType.INVALID_REQUEST,
233
+ {
234
+ statusCode: status,
235
+ suggestions: [
236
+ 'Check the request parameters',
237
+ 'Verify the data format is correct',
238
+ 'Review the API documentation for requirements'
239
+ ],
240
+ context
241
+ }
242
+ );
243
+
244
+ case 500:
245
+ case 502:
246
+ case 503:
247
+ case 504:
248
+ return new APIError(
249
+ 'Server error. The API service is experiencing issues.',
250
+ ErrorType.SERVER_ERROR,
251
+ {
252
+ statusCode: status,
253
+ suggestions: [
254
+ 'Try again in a few moments',
255
+ 'Check the API service status page',
256
+ 'Report the issue if it persists'
257
+ ],
258
+ context
259
+ }
260
+ );
261
+
262
+ default:
263
+ return new APIError(
264
+ data?.message || `HTTP ${status}: ${error.message}`,
265
+ ErrorType.UNKNOWN,
266
+ {
267
+ statusCode: status,
268
+ suggestions: [
269
+ 'Check the API documentation',
270
+ 'Try the request again',
271
+ 'Report this issue if it persists'
272
+ ],
273
+ context
274
+ }
275
+ );
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Check if error is a network/connection error
281
+ */
282
+ private static isNetworkError(error: any): boolean {
283
+ return (
284
+ error.code === 'ECONNREFUSED' ||
285
+ error.code === 'ENOTFOUND' ||
286
+ error.code === 'ECONNRESET' ||
287
+ error.code === 'ENETUNREACH' ||
288
+ error.message?.includes('network') ||
289
+ error.message?.includes('connection')
290
+ );
291
+ }
292
+
293
+ /**
294
+ * Check if error is a timeout error
295
+ */
296
+ private static isTimeoutError(error: any): boolean {
297
+ return (
298
+ error.code === 'ETIMEDOUT' ||
299
+ error.code === 'TIMEOUT' ||
300
+ error.message?.includes('timeout')
301
+ );
302
+ }
303
+
304
+ /**
305
+ * Check if error is a configuration error
306
+ */
307
+ private static isConfigurationError(error: any): boolean {
308
+ return (
309
+ error.message?.includes('API key') ||
310
+ error.message?.includes('configuration') ||
311
+ error.message?.includes('invalid key')
312
+ );
313
+ }
314
+
315
+ /**
316
+ * Parse retry-after header
317
+ */
318
+ private static parseRetryAfter(headers: any): number | undefined {
319
+ const retryAfter = headers?.['retry-after'] || headers?.['Retry-After'];
320
+ if (retryAfter) {
321
+ const seconds = parseInt(retryAfter, 10);
322
+ return isNaN(seconds) ? undefined : seconds;
323
+ }
324
+ return undefined;
325
+ }
326
+
327
+ /**
328
+ * Parse quota reset time from headers
329
+ */
330
+ private static parseQuotaReset(headers: any): Date | undefined {
331
+ const reset = headers?.['x-ratelimit-reset'] || headers?.['X-RateLimit-Reset'];
332
+ if (reset) {
333
+ const timestamp = parseInt(reset, 10);
334
+ return isNaN(timestamp) ? undefined : new Date(timestamp * 1000);
335
+ }
336
+ return undefined;
337
+ }
338
+
339
+ /**
340
+ * Retry operation with exponential backoff
341
+ */
342
+ static async withRetry<T>(
343
+ operation: () => Promise<T>,
344
+ config: Partial<RetryConfig> = {}
345
+ ): Promise<T> {
346
+ const retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config };
347
+ let lastError: APIError;
348
+
349
+ for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
350
+ try {
351
+ return await operation();
352
+ } catch (error) {
353
+ const apiError = this.handleAPIError(error);
354
+ lastError = apiError;
355
+
356
+ // Don't retry if this is the last attempt
357
+ if (attempt === retryConfig.maxRetries) {
358
+ break;
359
+ }
360
+
361
+ // Don't retry if error type is not retryable
362
+ if (!retryConfig.retryableErrors.includes(apiError.type)) {
363
+ break;
364
+ }
365
+
366
+ // Calculate delay with exponential backoff
367
+ const delay = Math.min(
368
+ retryConfig.baseDelay * Math.pow(retryConfig.backoffMultiplier, attempt),
369
+ retryConfig.maxDelay
370
+ );
371
+
372
+ // Use retry-after header if available
373
+ const actualDelay = apiError.retryAfter ? apiError.retryAfter * 1000 : delay;
374
+
375
+ console.log(`Retrying in ${Math.ceil(actualDelay / 1000)} seconds... (attempt ${attempt + 1}/${retryConfig.maxRetries})`);
376
+ await this.sleep(actualDelay);
377
+ }
378
+ }
379
+
380
+ throw lastError!;
381
+ }
382
+
383
+ /**
384
+ * Display error to user with helpful information
385
+ */
386
+ static displayError(error: APIError): void {
387
+ UIComponents.showErrorBox(error.message);
388
+
389
+ if (error.suggestions.length > 0) {
390
+ console.log('\nSuggestions:');
391
+ UIComponents.showList(error.suggestions);
392
+ }
393
+
394
+ // Show additional context for specific error types
395
+ switch (error.type) {
396
+ case ErrorType.RATE_LIMIT:
397
+ if (error.retryAfter) {
398
+ UIComponents.showWarningBox(`Rate limited. Retry after ${error.retryAfter} seconds.`);
399
+ }
400
+ if (error.quotaReset) {
401
+ UIComponents.showInfoBox(`Quota resets at: ${error.quotaReset.toLocaleString()}`);
402
+ }
403
+ break;
404
+
405
+ case ErrorType.AUTHENTICATION:
406
+ UIComponents.showWarningBox('Run "base config" to update your API keys.');
407
+ break;
408
+
409
+ case ErrorType.NETWORK:
410
+ UIComponents.showWarningBox('Check your internet connection and try again.');
411
+ break;
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Get fallback mode suggestions when APIs are unavailable
417
+ */
418
+ static getFallbackSuggestions(errorType: ErrorType): string[] {
419
+ switch (errorType) {
420
+ case ErrorType.NETWORK:
421
+ case ErrorType.TIMEOUT:
422
+ return [
423
+ 'Use offline mode for basic compatibility checking',
424
+ 'Review violations manually using browser compatibility tables',
425
+ 'Try again when network connection is restored'
426
+ ];
427
+
428
+ case ErrorType.RATE_LIMIT:
429
+ case ErrorType.QUOTA_EXCEEDED:
430
+ return [
431
+ 'Continue with basic violation detection without AI analysis',
432
+ 'Review violations manually',
433
+ 'Try AI features again later when quota resets'
434
+ ];
435
+
436
+ case ErrorType.AUTHENTICATION:
437
+ return [
438
+ 'Use BaseGuard without AI features',
439
+ 'Update API keys when available',
440
+ 'Review violations manually using web compatibility resources'
441
+ ];
442
+
443
+ default:
444
+ return [
445
+ 'Continue with basic compatibility checking',
446
+ 'Review violations manually',
447
+ 'Try AI features again later'
448
+ ];
449
+ }
450
+ }
451
+
452
+ /**
453
+ * Check if operation should continue in fallback mode
454
+ */
455
+ static shouldUseFallbackMode(error: APIError): boolean {
456
+ return [
457
+ ErrorType.NETWORK,
458
+ ErrorType.TIMEOUT,
459
+ ErrorType.AUTHENTICATION,
460
+ ErrorType.RATE_LIMIT,
461
+ ErrorType.QUOTA_EXCEEDED
462
+ ].includes(error.type);
463
+ }
464
+
465
+ /**
466
+ * Sleep for specified milliseconds
467
+ */
468
+ private static sleep(ms: number): Promise<void> {
469
+ return new Promise(resolve => setTimeout(resolve, ms));
470
+ }
471
+
472
+ /**
473
+ * Validate API response structure
474
+ */
475
+ static validateAPIResponse(response: any, expectedFields: string[]): void {
476
+ if (!response || typeof response !== 'object') {
477
+ throw new APIError(
478
+ 'Invalid API response format',
479
+ ErrorType.VALIDATION,
480
+ {
481
+ suggestions: [
482
+ 'Check API service status',
483
+ 'Verify API version compatibility',
484
+ 'Try the request again'
485
+ ]
486
+ }
487
+ );
488
+ }
489
+
490
+ const missingFields = expectedFields.filter(field => !(field in response));
491
+ if (missingFields.length > 0) {
492
+ throw new APIError(
493
+ `API response missing required fields: ${missingFields.join(', ')}`,
494
+ ErrorType.VALIDATION,
495
+ {
496
+ suggestions: [
497
+ 'Check API documentation for response format',
498
+ 'Verify API version compatibility',
499
+ 'Report this issue if it persists'
500
+ ],
501
+ context: { missingFields, response }
502
+ }
503
+ );
504
+ }
505
+ }
506
+
507
+ /**
508
+ * Create context object for error reporting
509
+ */
510
+ static createContext(operation: string, params?: Record<string, any>): Record<string, any> {
511
+ return {
512
+ operation,
513
+ timestamp: new Date().toISOString(),
514
+ params: params || {},
515
+ version: process.env.npm_package_version || 'unknown'
516
+ };
517
+ }
518
+ }