codesummary 1.0.2 → 1.1.1

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.
@@ -1,343 +1,478 @@
1
- import chalk from 'chalk';
2
- import path from 'path';
3
- import fs from 'fs';
4
- import os from 'os';
5
-
6
- /**
7
- * Error Handler and Validation Utilities for CodeSummary
8
- * Centralized error handling and validation logic
9
- */
10
- export class ErrorHandler {
11
- /**
12
- * Handle and format CLI errors consistently
13
- * @param {Error} error - The error object
14
- * @param {string} context - Context where error occurred
15
- * @param {boolean} exit - Whether to exit process
16
- */
17
- static handleError(error, context = 'Unknown', exit = true) {
18
- console.error(chalk.red('ERROR:'), chalk.white(error.message));
19
-
20
- if (process.env.NODE_ENV === 'development' || process.env.DEBUG) {
21
- console.error(chalk.gray('Context:'), context);
22
- console.error(chalk.gray('Stack:'), error.stack);
23
- }
24
-
25
- if (exit) {
26
- process.exit(1);
27
- }
28
- }
29
-
30
- /**
31
- * Handle configuration validation errors
32
- * @param {Error} error - Configuration error
33
- * @param {string} configPath - Path to config file
34
- */
35
- static handleConfigError(error, configPath) {
36
- console.error(chalk.red('CONFIGURATION ERROR'));
37
- console.error(chalk.gray(`Config file: ${configPath}`));
38
- console.error(chalk.white(error.message));
39
- console.error(chalk.yellow('\nTry running: codesummary --reset-config'));
40
- process.exit(1);
41
- }
42
-
43
- /**
44
- * Handle file system errors with helpful messages
45
- * @param {Error} error - File system error
46
- * @param {string} operation - The operation being performed
47
- * @param {string} filePath - Path involved in the operation
48
- */
49
- static handleFileSystemError(error, operation, filePath) {
50
- let message = `Failed to ${operation}`;
51
-
52
- switch (error.code) {
53
- case 'ENOENT':
54
- message = `File or directory not found: ${filePath}`;
55
- break;
56
- case 'EACCES':
57
- case 'EPERM':
58
- message = `Permission denied: ${filePath}`;
59
- console.error(chalk.yellow('SUGGESTION: Try running with elevated privileges or check file permissions'));
60
- break;
61
- case 'ENOSPC':
62
- message = 'No space left on device';
63
- break;
64
- case 'EMFILE':
65
- case 'ENFILE':
66
- message = 'Too many open files';
67
- break;
68
- default:
69
- message = `${message}: ${error.message}`;
70
- }
71
-
72
- console.error(chalk.red('FILE SYSTEM ERROR:'), chalk.white(message));
73
-
74
- if (error.code === 'EACCES' || error.code === 'EPERM') {
75
- console.error(chalk.yellow('SUGGESTIONS:'));
76
- console.error(chalk.gray(' - Check file/directory permissions'));
77
- console.error(chalk.gray(' - Try running as administrator/sudo'));
78
- console.error(chalk.gray(' - Ensure the file is not locked by another process'));
79
- }
80
- }
81
-
82
- /**
83
- * Handle PDF generation errors
84
- * @param {Error} error - PDF generation error
85
- * @param {string} outputPath - Intended output path
86
- */
87
- static handlePDFError(error, outputPath) {
88
- console.error(chalk.red('PDF GENERATION FAILED'));
89
- console.error(chalk.gray(`Output path: ${outputPath}`));
90
-
91
- if (error.message.includes('ENOSPC')) {
92
- console.error(chalk.white('Not enough disk space to generate PDF'));
93
- console.error(chalk.yellow('SUGGESTION: Try freeing up disk space or using a different output location'));
94
- } else if (error.message.includes('EACCES')) {
95
- console.error(chalk.white('Permission denied writing to output location'));
96
- console.error(chalk.yellow('SUGGESTION: Check permissions or try a different output directory'));
97
- } else {
98
- console.error(chalk.white(error.message));
99
- }
100
-
101
- process.exit(1);
102
- }
103
-
104
- /**
105
- * Validate file path for security and validity
106
- * @param {string} filePath - Path to validate
107
- * @param {object} options - Validation options
108
- * @returns {boolean} True if valid
109
- */
110
- static validatePath(filePath, options = {}) {
111
- const {
112
- mustExist = false,
113
- mustBeAbsolute = false,
114
- allowedExtensions = null,
115
- preventTraversal = true
116
- } = options;
117
-
118
- if (!filePath || typeof filePath !== 'string') {
119
- throw new Error('Invalid file path: must be a non-empty string');
120
- }
121
-
122
- // Prevent path traversal attacks
123
- if (preventTraversal && (filePath.includes('..') || filePath.includes('\0'))) {
124
- throw new Error('Invalid file path: path traversal detected');
125
- }
126
-
127
- // Check if path should be absolute
128
- if (mustBeAbsolute && !path.isAbsolute(filePath)) {
129
- throw new Error('Path must be absolute');
130
- }
131
-
132
- // Check if file must exist
133
- if (mustExist && !fs.existsSync(filePath)) {
134
- throw new Error(`Path does not exist: ${filePath}`);
135
- }
136
-
137
- // Check file extension if specified
138
- if (allowedExtensions && allowedExtensions.length > 0) {
139
- const ext = path.extname(filePath).toLowerCase();
140
- if (!allowedExtensions.includes(ext)) {
141
- throw new Error(`Invalid file extension. Allowed: ${allowedExtensions.join(', ')}`);
142
- }
143
- }
144
-
145
- return true;
146
- }
147
-
148
- /**
149
- * Validate configuration object structure
150
- * @param {object} config - Configuration to validate
151
- * @returns {boolean} True if valid
152
- */
153
- static validateConfig(config) {
154
- if (!config || typeof config !== 'object') {
155
- throw new Error('Configuration must be an object');
156
- }
157
-
158
- // Validate output section
159
- if (!config.output || typeof config.output !== 'object') {
160
- throw new Error('Configuration missing output section');
161
- }
162
-
163
- if (!config.output.mode || !['relative', 'fixed'].includes(config.output.mode)) {
164
- throw new Error('Output mode must be either "relative" or "fixed"');
165
- }
166
-
167
- if (config.output.mode === 'fixed' && !config.output.fixedPath) {
168
- throw new Error('Fixed output mode requires fixedPath');
169
- }
170
-
171
- // Validate allowedExtensions
172
- if (!Array.isArray(config.allowedExtensions)) {
173
- throw new Error('allowedExtensions must be an array');
174
- }
175
-
176
- if (config.allowedExtensions.length === 0) {
177
- throw new Error('At least one file extension must be allowed');
178
- }
179
-
180
- // Validate excludeDirs
181
- if (!Array.isArray(config.excludeDirs)) {
182
- throw new Error('excludeDirs must be an array');
183
- }
184
-
185
- // Validate styles section
186
- if (!config.styles || typeof config.styles !== 'object') {
187
- throw new Error('Configuration missing styles section');
188
- }
189
-
190
- // Validate settings section
191
- if (!config.settings || typeof config.settings !== 'object') {
192
- throw new Error('Configuration missing settings section');
193
- }
194
-
195
- if (typeof config.settings.maxFilesBeforePrompt !== 'number' ||
196
- config.settings.maxFilesBeforePrompt < 1) {
197
- throw new Error('maxFilesBeforePrompt must be a positive number');
198
- }
199
-
200
- return true;
201
- }
202
-
203
- /**
204
- * Sanitize user input to prevent injection attacks
205
- * @param {string} input - User input to sanitize
206
- * @returns {string} Sanitized input
207
- */
208
- static sanitizeInput(input) {
209
- if (typeof input !== 'string') {
210
- return '';
211
- }
212
-
213
- return input
214
- .replace(/[<>]/g, '') // Remove potential HTML/XML tags
215
- .replace(/[\x00-\x1f\x7f-\x9f]/g, '') // Remove control characters
216
- .trim()
217
- .substring(0, 1000); // Limit length
218
- }
219
-
220
- /**
221
- * Validate file content before processing
222
- * @param {string} filePath - Path to file
223
- * @param {Buffer} content - File content buffer
224
- * @returns {boolean} True if content appears to be text
225
- */
226
- static validateFileContent(filePath, content) {
227
- // Check for null bytes (common in binary files)
228
- if (content.includes(0)) {
229
- console.warn(chalk.yellow(`WARNING: Skipping potentially binary file: ${filePath}`));
230
- return false;
231
- }
232
-
233
- // Check file size (warn for very large files)
234
- const maxSize = 10 * 1024 * 1024; // 10MB
235
- if (content.length > maxSize) {
236
- console.warn(chalk.yellow(`WARNING: Large file detected (${Math.round(content.length / 1024 / 1024)}MB): ${filePath}`));
237
- console.warn(chalk.gray('This may affect PDF generation performance'));
238
- }
239
-
240
- return true;
241
- }
242
-
243
- /**
244
- * Graceful shutdown handler
245
- * @param {string} signal - Signal received
246
- */
247
- static gracefulShutdown(signal) {
248
- console.log(chalk.yellow(`\nWARNING: Received ${signal}. Shutting down gracefully...`));
249
-
250
- // Cleanup operations could go here
251
- // For example: close open file handles, cleanup temp files, etc.
252
-
253
- console.log(chalk.green('SUCCESS: Cleanup completed'));
254
- process.exit(0);
255
- }
256
-
257
- /**
258
- * Setup global error handlers
259
- */
260
- static setupGlobalHandlers() {
261
- // Handle uncaught exceptions
262
- process.on('uncaughtException', (error) => {
263
- console.error(chalk.red('UNCAUGHT EXCEPTION:'));
264
- console.error(error.stack);
265
- console.error(chalk.yellow('Please report this error to: https://github.com/skamoll/CodeSummary/issues'));
266
- process.exit(1);
267
- });
268
-
269
- // Handle unhandled promise rejections
270
- process.on('unhandledRejection', (reason, promise) => {
271
- console.error(chalk.red('UNHANDLED PROMISE REJECTION:'));
272
- console.error('Promise:', promise);
273
- console.error('Reason:', reason);
274
- console.error(chalk.yellow('Please report this error to: https://github.com/skamoll/CodeSummary/issues'));
275
- process.exit(1);
276
- });
277
-
278
- // Handle graceful shutdown signals
279
- process.on('SIGINT', () => ErrorHandler.gracefulShutdown('SIGINT'));
280
- process.on('SIGTERM', () => ErrorHandler.gracefulShutdown('SIGTERM'));
281
-
282
- // Handle warnings
283
- process.on('warning', (warning) => {
284
- if (process.env.NODE_ENV === 'development') {
285
- console.warn(chalk.yellow('WARNING:'), warning.message);
286
- }
287
- });
288
- }
289
-
290
- /**
291
- * Create context-aware error wrapper
292
- * @param {string} context - Context description
293
- * @returns {Function} Error wrapper function
294
- */
295
- static createErrorWrapper(context) {
296
- return (error) => {
297
- if (error.name === 'AbortError') {
298
- console.log(chalk.yellow('\nWARNING: Operation cancelled by user'));
299
- process.exit(0);
300
- }
301
-
302
- ErrorHandler.handleError(error, context);
303
- };
304
- }
305
-
306
- /**
307
- * Validate environment and dependencies
308
- */
309
- static validateEnvironment() {
310
- // Check Node.js version
311
- const nodeVersion = process.version;
312
- const majorVersion = parseInt(nodeVersion.split('.')[0].substring(1));
313
-
314
- if (majorVersion < 18) {
315
- console.error(chalk.red('ERROR: Node.js version requirement not met'));
316
- console.error(chalk.white(`Current version: ${nodeVersion}`));
317
- console.error(chalk.white('Required version: >=18.0.0'));
318
- console.error(chalk.yellow('Please upgrade Node.js: https://nodejs.org/'));
319
- process.exit(1);
320
- }
321
-
322
- // Check available memory
323
- const totalMemory = os.totalmem();
324
- const freeMemory = os.freemem();
325
- const memoryUsage = process.memoryUsage();
326
-
327
- if (freeMemory < 100 * 1024 * 1024) { // Less than 100MB free
328
- console.warn(chalk.yellow('WARNING: Low system memory detected'));
329
- console.warn(chalk.gray('PDF generation may be slower for large projects'));
330
- }
331
-
332
- // Check platform compatibility
333
- const platform = process.platform;
334
- const supportedPlatforms = ['win32', 'darwin', 'linux'];
335
-
336
- if (!supportedPlatforms.includes(platform)) {
337
- console.warn(chalk.yellow(`WARNING: Untested platform: ${platform}`));
338
- console.warn(chalk.gray('CodeSummary may not work correctly on this platform'));
339
- }
340
- }
341
- }
342
-
1
+ import chalk from 'chalk';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import os from 'os';
5
+
6
+ /**
7
+ * Error Handler and Validation Utilities for CodeSummary
8
+ * Centralized error handling and validation logic
9
+ */
10
+ export class ErrorHandler {
11
+ // Static cleanup registry for resources that need cleanup before exit
12
+ static cleanupTasks = [];
13
+ static isCleaningUp = false;
14
+
15
+ /**
16
+ * Register a cleanup task to be executed before process exit
17
+ * @param {Function} cleanupFn - Function to call during cleanup
18
+ * @param {string} description - Description of the cleanup task
19
+ */
20
+ static registerCleanup(cleanupFn, description = 'cleanup task') {
21
+ if (typeof cleanupFn !== 'function') {
22
+ console.warn(`WARNING: Invalid cleanup task: ${description}`);
23
+ return;
24
+ }
25
+
26
+ this.cleanupTasks.push({ fn: cleanupFn, description });
27
+ }
28
+
29
+ /**
30
+ * Execute all registered cleanup tasks
31
+ * @param {boolean} verbose - Whether to log cleanup progress
32
+ */
33
+ static async executeCleanup(verbose = false) {
34
+ if (this.isCleaningUp) {
35
+ return; // Prevent recursive cleanup
36
+ }
37
+
38
+ this.isCleaningUp = true;
39
+
40
+ if (verbose && this.cleanupTasks.length > 0) {
41
+ console.log(chalk.yellow(`Cleaning up ${this.cleanupTasks.length} resources...`));
42
+ }
43
+
44
+ for (const task of this.cleanupTasks) {
45
+ try {
46
+ if (verbose) {
47
+ console.log(chalk.gray(`- ${task.description}`));
48
+ }
49
+ await task.fn();
50
+ } catch (error) {
51
+ if (verbose) {
52
+ console.warn(chalk.yellow(`WARNING: Cleanup failed for ${task.description}: ${error.message}`));
53
+ }
54
+ }
55
+ }
56
+
57
+ this.cleanupTasks = [];
58
+ this.isCleaningUp = false;
59
+ }
60
+
61
+ /**
62
+ * Safe process exit with cleanup
63
+ * @param {number} code - Exit code
64
+ * @param {string} reason - Reason for exit
65
+ */
66
+ static async safeExit(code = 0, reason = 'Process completed') {
67
+ try {
68
+ await this.executeCleanup(process.env.NODE_ENV === 'development');
69
+ if (process.env.NODE_ENV === 'development') {
70
+ console.log(chalk.green(`✓ Cleanup completed. ${reason}`));
71
+ }
72
+ } catch (error) {
73
+ console.error(chalk.red(`ERROR during cleanup: ${error.message}`));
74
+ } finally {
75
+ process.exit(code);
76
+ }
77
+ }
78
+ /**
79
+ * Handle and format CLI errors consistently
80
+ * @param {Error} error - The error object
81
+ * @param {string} context - Context where error occurred
82
+ * @param {boolean} exit - Whether to exit process
83
+ */
84
+ static async handleError(error, context = 'Unknown', exit = true) {
85
+ console.error(chalk.red('ERROR:'), chalk.white(error.message));
86
+
87
+ if (process.env.NODE_ENV === 'development' || process.env.DEBUG) {
88
+ console.error(chalk.gray('Context:'), context);
89
+ console.error(chalk.gray('Stack:'), error.stack);
90
+ }
91
+
92
+ if (exit) {
93
+ await this.safeExit(1, `Error in ${context}`);
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Handle configuration validation errors
99
+ * @param {Error} error - Configuration error
100
+ * @param {string} configPath - Path to config file
101
+ */
102
+ static async handleConfigError(error, configPath) {
103
+ console.error(chalk.red('CONFIGURATION ERROR'));
104
+ console.error(chalk.gray(`Config file: ${configPath}`));
105
+ console.error(chalk.white(error.message));
106
+ console.error(chalk.yellow('\nTry running: codesummary --reset-config'));
107
+ await this.safeExit(1, 'Configuration error');
108
+ }
109
+
110
+ /**
111
+ * Handle file system errors with helpful messages
112
+ * @param {Error} error - File system error
113
+ * @param {string} operation - The operation being performed
114
+ * @param {string} filePath - Path involved in the operation
115
+ */
116
+ static handleFileSystemError(error, operation, filePath) {
117
+ let message = `Failed to ${operation}`;
118
+
119
+ switch (error.code) {
120
+ case 'ENOENT':
121
+ message = `File or directory not found: ${filePath}`;
122
+ break;
123
+ case 'EACCES':
124
+ case 'EPERM':
125
+ message = `Permission denied: ${filePath}`;
126
+ console.error(chalk.yellow('SUGGESTION: Try running with elevated privileges or check file permissions'));
127
+ break;
128
+ case 'ENOSPC':
129
+ message = 'No space left on device';
130
+ break;
131
+ case 'EMFILE':
132
+ case 'ENFILE':
133
+ message = 'Too many open files';
134
+ break;
135
+ default:
136
+ message = `${message}: ${error.message}`;
137
+ }
138
+
139
+ console.error(chalk.red('FILE SYSTEM ERROR:'), chalk.white(message));
140
+
141
+ if (error.code === 'EACCES' || error.code === 'EPERM') {
142
+ console.error(chalk.yellow('SUGGESTIONS:'));
143
+ console.error(chalk.gray(' - Check file/directory permissions'));
144
+ console.error(chalk.gray(' - Try running as administrator/sudo'));
145
+ console.error(chalk.gray(' - Ensure the file is not locked by another process'));
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Handle PDF generation errors
151
+ * @param {Error} error - PDF generation error
152
+ * @param {string} outputPath - Intended output path
153
+ */
154
+ static async handlePDFError(error, outputPath) {
155
+ console.error(chalk.red('PDF GENERATION FAILED'));
156
+ console.error(chalk.gray(`Output path: ${outputPath}`));
157
+
158
+ if (error.message.includes('ENOSPC')) {
159
+ console.error(chalk.white('Not enough disk space to generate PDF'));
160
+ console.error(chalk.yellow('SUGGESTION: Try freeing up disk space or using a different output location'));
161
+ } else if (error.message.includes('EACCES')) {
162
+ console.error(chalk.white('Permission denied writing to output location'));
163
+ console.error(chalk.yellow('SUGGESTION: Check permissions or try a different output directory'));
164
+ } else {
165
+ console.error(chalk.white(error.message));
166
+ }
167
+
168
+ await this.safeExit(1, 'PDF generation failed');
169
+ }
170
+
171
+ /**
172
+ * Validate file path for security and validity
173
+ * @param {string} filePath - Path to validate
174
+ * @param {object} options - Validation options
175
+ * @returns {boolean} True if valid
176
+ */
177
+ static validatePath(filePath, options = {}) {
178
+ const {
179
+ mustExist = false,
180
+ mustBeAbsolute = false,
181
+ allowedExtensions = null,
182
+ preventTraversal = true
183
+ } = options;
184
+
185
+ if (!filePath || typeof filePath !== 'string') {
186
+ throw new Error('Invalid file path: must be a non-empty string');
187
+ }
188
+
189
+ // Normalize path to handle different separators
190
+ const normalizedPath = path.normalize(filePath);
191
+
192
+ // Enhanced path traversal prevention
193
+ if (preventTraversal) {
194
+ // Check for various path traversal patterns
195
+ const dangerousPatterns = [
196
+ /\.\./, // Standard traversal
197
+ /\0/, // Null bytes
198
+ /[<>"|?*]/, // Invalid Windows characters
199
+ /\\\\[^\\]/, // UNC paths
200
+ /\/(etc|proc|sys|dev)\//i // Sensitive Unix directories
201
+ ];
202
+
203
+ // Only block absolute paths if mustBeAbsolute is false
204
+ if (!mustBeAbsolute) {
205
+ dangerousPatterns.push(/^[A-Z]:\\/i); // Absolute Windows paths when not allowed
206
+ dangerousPatterns.push(/^\/[^\/]/); // Absolute Unix paths when not allowed
207
+ }
208
+
209
+ for (const pattern of dangerousPatterns) {
210
+ if (pattern.test(normalizedPath)) {
211
+ throw new Error('Invalid file path: contains dangerous characters or patterns');
212
+ }
213
+ }
214
+
215
+ // Additional Windows-specific checks
216
+ if (process.platform === 'win32') {
217
+ const reservedNames = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4',
218
+ 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3',
219
+ 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'];
220
+
221
+ const baseName = path.basename(normalizedPath, path.extname(normalizedPath)).toUpperCase();
222
+ if (reservedNames.includes(baseName)) {
223
+ throw new Error('Invalid file path: uses reserved Windows device name');
224
+ }
225
+ }
226
+
227
+ // Check path length limits
228
+ if (normalizedPath.length > 260 && process.platform === 'win32') {
229
+ throw new Error('Path too long for Windows filesystem (>260 characters)');
230
+ }
231
+ if (normalizedPath.length > 4096) {
232
+ throw new Error('Path too long (>4096 characters)');
233
+ }
234
+ }
235
+
236
+ // Check if path should be absolute
237
+ if (mustBeAbsolute && !path.isAbsolute(normalizedPath)) {
238
+ throw new Error('Path must be absolute');
239
+ }
240
+
241
+ // Check if file must exist
242
+ if (mustExist && !fs.existsSync(normalizedPath)) {
243
+ throw new Error(`Path does not exist: ${normalizedPath}`);
244
+ }
245
+
246
+ // Check file extension if specified
247
+ if (allowedExtensions && allowedExtensions.length > 0) {
248
+ const ext = path.extname(normalizedPath).toLowerCase();
249
+ if (!allowedExtensions.includes(ext)) {
250
+ throw new Error(`Invalid file extension. Allowed: ${allowedExtensions.join(', ')}`);
251
+ }
252
+ }
253
+
254
+ return true;
255
+ }
256
+
257
+ /**
258
+ * Validate configuration object structure
259
+ * @param {object} config - Configuration to validate
260
+ * @returns {boolean} True if valid
261
+ */
262
+ static validateConfig(config) {
263
+ if (!config || typeof config !== 'object') {
264
+ throw new Error('Configuration must be an object');
265
+ }
266
+
267
+ // Validate output section
268
+ if (!config.output || typeof config.output !== 'object') {
269
+ throw new Error('Configuration missing output section');
270
+ }
271
+
272
+ if (!config.output.mode || !['relative', 'fixed'].includes(config.output.mode)) {
273
+ throw new Error('Output mode must be either "relative" or "fixed"');
274
+ }
275
+
276
+ if (config.output.mode === 'fixed' && !config.output.fixedPath) {
277
+ throw new Error('Fixed output mode requires fixedPath');
278
+ }
279
+
280
+ // Validate allowedExtensions
281
+ if (!Array.isArray(config.allowedExtensions)) {
282
+ throw new Error('allowedExtensions must be an array');
283
+ }
284
+
285
+ if (config.allowedExtensions.length === 0) {
286
+ throw new Error('At least one file extension must be allowed');
287
+ }
288
+
289
+ // Validate excludeDirs
290
+ if (!Array.isArray(config.excludeDirs)) {
291
+ throw new Error('excludeDirs must be an array');
292
+ }
293
+
294
+ // Validate excludeFiles (optional field for backward compatibility)
295
+ if (config.excludeFiles && !Array.isArray(config.excludeFiles)) {
296
+ throw new Error('excludeFiles must be an array if provided');
297
+ }
298
+
299
+ // Validate styles section
300
+ if (!config.styles || typeof config.styles !== 'object') {
301
+ throw new Error('Configuration missing styles section');
302
+ }
303
+
304
+ // Validate settings section
305
+ if (!config.settings || typeof config.settings !== 'object') {
306
+ throw new Error('Configuration missing settings section');
307
+ }
308
+
309
+ if (typeof config.settings.maxFilesBeforePrompt !== 'number' ||
310
+ config.settings.maxFilesBeforePrompt < 1) {
311
+ throw new Error('maxFilesBeforePrompt must be a positive number');
312
+ }
313
+
314
+ return true;
315
+ }
316
+
317
+ /**
318
+ * Sanitize user input to prevent injection attacks
319
+ * @param {string} input - User input to sanitize
320
+ * @param {object} options - Sanitization options
321
+ * @returns {string} Sanitized input
322
+ */
323
+ static sanitizeInput(input, options = {}) {
324
+ if (typeof input !== 'string') {
325
+ return '';
326
+ }
327
+
328
+ const {
329
+ maxLength = 1000,
330
+ allowPath = false,
331
+ strictMode = true
332
+ } = options;
333
+
334
+ let sanitized = input;
335
+
336
+ if (strictMode) {
337
+ // Remove or replace dangerous characters
338
+ sanitized = sanitized
339
+ .replace(/[<>]/g, '') // Remove potential HTML/XML tags
340
+ .replace(/[\x00-\x1f\x7f-\x9f]/g, '') // Remove control characters
341
+ .replace(/[`${}]/g, '') // Remove template literal and variable expansion chars
342
+ .replace(/[;&|]/g, ''); // Remove command injection chars
343
+ }
344
+
345
+ // For path inputs, allow specific characters
346
+ if (allowPath) {
347
+ // Allow path separators and basic path characters (more permissive for legitimate paths)
348
+ sanitized = sanitized.replace(/[^a-zA-Z0-9\-_.\\/\\:\s()]/g, '');
349
+ } else {
350
+ // For non-path inputs, be more restrictive
351
+ sanitized = sanitized.replace(/[^a-zA-Z0-9\-_.\s]/g, '');
352
+ }
353
+
354
+ return sanitized
355
+ .trim()
356
+ .substring(0, maxLength);
357
+ }
358
+
359
+ /**
360
+ * Validate file content before processing
361
+ * @param {string} filePath - Path to file
362
+ * @param {Buffer} content - File content buffer
363
+ * @returns {boolean} True if content appears to be text
364
+ */
365
+ static validateFileContent(filePath, content) {
366
+ // Check for null bytes (common in binary files)
367
+ if (content.includes(0)) {
368
+ console.warn(chalk.yellow(`WARNING: Skipping potentially binary file: ${filePath}`));
369
+ return false;
370
+ }
371
+
372
+ // Check file size (warn for very large files)
373
+ const maxSize = 10 * 1024 * 1024; // 10MB
374
+ if (content.length > maxSize) {
375
+ console.warn(chalk.yellow(`WARNING: Large file detected (${Math.round(content.length / 1024 / 1024)}MB): ${filePath}`));
376
+ console.warn(chalk.gray('This may affect PDF generation performance'));
377
+ }
378
+
379
+ return true;
380
+ }
381
+
382
+ /**
383
+ * Graceful shutdown handler
384
+ * @param {string} signal - Signal received
385
+ */
386
+ static async gracefulShutdown(signal) {
387
+ console.log(chalk.yellow(`\nWARNING: Received ${signal}. Shutting down gracefully...`));
388
+ await this.safeExit(0, `Received ${signal}`);
389
+ }
390
+
391
+ /**
392
+ * Setup global error handlers
393
+ */
394
+ static setupGlobalHandlers() {
395
+ // Handle uncaught exceptions
396
+ process.on('uncaughtException', async (error) => {
397
+ console.error(chalk.red('UNCAUGHT EXCEPTION:'));
398
+ console.error(error.stack);
399
+ console.error(chalk.yellow('Please report this error to: https://github.com/skamoll/CodeSummary/issues'));
400
+ await this.safeExit(1, 'Uncaught exception');
401
+ });
402
+
403
+ // Handle unhandled promise rejections
404
+ process.on('unhandledRejection', async (reason, promise) => {
405
+ console.error(chalk.red('UNHANDLED PROMISE REJECTION:'));
406
+ console.error('Promise:', promise);
407
+ console.error('Reason:', reason);
408
+ console.error(chalk.yellow('Please report this error to: https://github.com/skamoll/CodeSummary/issues'));
409
+ await this.safeExit(1, 'Unhandled promise rejection');
410
+ });
411
+
412
+ // Handle graceful shutdown signals
413
+ process.on('SIGINT', () => ErrorHandler.gracefulShutdown('SIGINT'));
414
+ process.on('SIGTERM', () => ErrorHandler.gracefulShutdown('SIGTERM'));
415
+
416
+ // Handle warnings
417
+ process.on('warning', (warning) => {
418
+ if (process.env.NODE_ENV === 'development') {
419
+ console.warn(chalk.yellow('WARNING:'), warning.message);
420
+ }
421
+ });
422
+ }
423
+
424
+ /**
425
+ * Create context-aware error wrapper
426
+ * @param {string} context - Context description
427
+ * @returns {Function} Error wrapper function
428
+ */
429
+ static createErrorWrapper(context) {
430
+ return async (error) => {
431
+ if (error.name === 'AbortError') {
432
+ console.log(chalk.yellow('\nWARNING: Operation cancelled by user'));
433
+ await this.safeExit(0, 'Operation cancelled by user');
434
+ return;
435
+ }
436
+
437
+ await ErrorHandler.handleError(error, context);
438
+ };
439
+ }
440
+
441
+ /**
442
+ * Validate environment and dependencies
443
+ */
444
+ static async validateEnvironment() {
445
+ // Check Node.js version
446
+ const nodeVersion = process.version;
447
+ const majorVersion = parseInt(nodeVersion.split('.')[0].substring(1));
448
+
449
+ if (majorVersion < 18) {
450
+ console.error(chalk.red('ERROR: Node.js version requirement not met'));
451
+ console.error(chalk.white(`Current version: ${nodeVersion}`));
452
+ console.error(chalk.white('Required version: >=18.0.0'));
453
+ console.error(chalk.yellow('Please upgrade Node.js: https://nodejs.org/'));
454
+ await this.safeExit(1, 'Node.js version incompatible');
455
+ }
456
+
457
+ // Check available memory
458
+ const totalMemory = os.totalmem();
459
+ const freeMemory = os.freemem();
460
+ const memoryUsage = process.memoryUsage();
461
+
462
+ if (freeMemory < 100 * 1024 * 1024) { // Less than 100MB free
463
+ console.warn(chalk.yellow('WARNING: Low system memory detected'));
464
+ console.warn(chalk.gray('PDF generation may be slower for large projects'));
465
+ }
466
+
467
+ // Check platform compatibility
468
+ const platform = process.platform;
469
+ const supportedPlatforms = ['win32', 'darwin', 'linux'];
470
+
471
+ if (!supportedPlatforms.includes(platform)) {
472
+ console.warn(chalk.yellow(`WARNING: Untested platform: ${platform}`));
473
+ console.warn(chalk.gray('CodeSummary may not work correctly on this platform'));
474
+ }
475
+ }
476
+ }
477
+
343
478
  export default ErrorHandler;