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.
- package/LICENSE +674 -0
- package/README.md +370 -0
- package/RELEASE.md +412 -0
- package/bin/codesummary.js +13 -0
- package/features.md +502 -0
- package/package.json +84 -0
- package/src/cli.js +392 -0
- package/src/configManager.js +427 -0
- package/src/errorHandler.js +343 -0
- package/src/index.js +26 -0
- package/src/pdfGenerator.js +427 -0
- package/src/scanner.js +330 -0
|
@@ -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();
|