codesummary 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,343 @@
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
+
343
+ export default ErrorHandler;
package/src/index.js ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+
3
+ import CLI from './cli.js';
4
+ import ErrorHandler from './errorHandler.js';
5
+
6
+ /**
7
+ * CodeSummary - Main Entry Point
8
+ * A cross-platform CLI tool for generating PDF documentation from source code
9
+ */
10
+
11
+ async function main() {
12
+ try {
13
+ // Setup global error handlers and validate environment
14
+ ErrorHandler.setupGlobalHandlers();
15
+ ErrorHandler.validateEnvironment();
16
+
17
+ const cli = new CLI();
18
+ const args = process.argv.slice(2);
19
+ await cli.run(args);
20
+ } catch (error) {
21
+ ErrorHandler.handleError(error, 'Main Application');
22
+ }
23
+ }
24
+
25
+ // Execute main function
26
+ main();