codesummary 1.1.1 → 1.2.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.
- package/CHANGELOG.md +234 -190
- package/LICENSE +674 -674
- package/README.md +483 -607
- package/bin/codesummary.js +12 -12
- package/features.md +418 -502
- package/package.json +95 -95
- package/rag-schema.json +113 -113
- package/src/cli.js +599 -540
- package/src/configManager.js +880 -827
- package/src/errorHandler.js +474 -477
- package/src/index.js +25 -25
- package/src/llmGenerator.js +189 -0
- package/src/pdfGenerator.js +408 -475
- package/src/ragConfig.js +369 -373
- package/src/ragGenerator.js +1739 -1757
- package/src/scanner.js +386 -467
- package/src/utils.js +139 -0
package/src/cli.js
CHANGED
|
@@ -1,541 +1,600 @@
|
|
|
1
|
-
import inquirer from 'inquirer';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import fs from 'fs-extra';
|
|
5
|
-
import ora from 'ora';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
options.
|
|
118
|
-
break;
|
|
119
|
-
case 'config':
|
|
120
|
-
options.
|
|
121
|
-
break;
|
|
122
|
-
case '--
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
case '
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
throw new Error(`
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
break;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
//
|
|
186
|
-
const
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
*
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
*
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
console.log(chalk.
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
console.log(
|
|
518
|
-
console.log();
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
}
|
|
540
|
-
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { createRequire } from 'module';
|
|
7
|
+
|
|
8
|
+
import ConfigManager from './configManager.js';
|
|
9
|
+
import Scanner from './scanner.js';
|
|
10
|
+
import PDFGenerator from './pdfGenerator.js';
|
|
11
|
+
import RagGenerator from './ragGenerator.js';
|
|
12
|
+
import LlmGenerator from './llmGenerator.js';
|
|
13
|
+
import ErrorHandler from './errorHandler.js';
|
|
14
|
+
import { formatFileSize, resolveVersionedPath } from './utils.js';
|
|
15
|
+
|
|
16
|
+
const _require = createRequire(import.meta.url);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Command Line Interface for CodeSummary
|
|
20
|
+
* Handles user interaction and orchestrates the scanning and PDF generation process
|
|
21
|
+
*/
|
|
22
|
+
export class CLI {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.configManager = new ConfigManager();
|
|
25
|
+
this.config = null;
|
|
26
|
+
this.scanner = null;
|
|
27
|
+
this.pdfGenerator = null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Main entry point for CLI execution
|
|
32
|
+
* @param {Array} args - Command line arguments
|
|
33
|
+
*/
|
|
34
|
+
async run(args = []) {
|
|
35
|
+
try {
|
|
36
|
+
// Parse command line arguments
|
|
37
|
+
const options = await this.parseArguments(args);
|
|
38
|
+
|
|
39
|
+
// Handle special commands
|
|
40
|
+
if (options.showConfig) {
|
|
41
|
+
await this.showConfig();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (options.resetConfig) {
|
|
46
|
+
await this.resetConfig();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (options.config) {
|
|
51
|
+
await this.editConfig();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Main scanning and PDF generation flow
|
|
56
|
+
await this.executeMainFlow(options);
|
|
57
|
+
|
|
58
|
+
} catch (error) {
|
|
59
|
+
ErrorHandler.handleError(error, 'CLI Operation');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Parse command line arguments
|
|
65
|
+
* @param {Array} args - Raw arguments
|
|
66
|
+
* @returns {object} Parsed options
|
|
67
|
+
*/
|
|
68
|
+
async parseArguments(args) {
|
|
69
|
+
const options = {
|
|
70
|
+
output: null,
|
|
71
|
+
showConfig: false,
|
|
72
|
+
resetConfig: false,
|
|
73
|
+
config: false,
|
|
74
|
+
help: false,
|
|
75
|
+
version: false,
|
|
76
|
+
noInteractive: false,
|
|
77
|
+
format: 'pdf'
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
for (let i = 0; i < args.length; i++) {
|
|
81
|
+
const arg = args[i];
|
|
82
|
+
|
|
83
|
+
switch (arg) {
|
|
84
|
+
case '--output':
|
|
85
|
+
case '-o':
|
|
86
|
+
if (i + 1 >= args.length) {
|
|
87
|
+
throw new Error(`Option ${arg} requires a value`);
|
|
88
|
+
}
|
|
89
|
+
i++; // Move to next argument
|
|
90
|
+
const outputPath = args[i];
|
|
91
|
+
|
|
92
|
+
// Validate output path
|
|
93
|
+
if (!outputPath || outputPath.trim().length === 0) {
|
|
94
|
+
throw new Error(`Option ${arg} requires a non-empty path`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Sanitize and validate path
|
|
98
|
+
const sanitizedPath = ErrorHandler.sanitizeInput(outputPath, {
|
|
99
|
+
allowPath: true,
|
|
100
|
+
maxLength: 500,
|
|
101
|
+
strictMode: true
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (sanitizedPath !== outputPath) {
|
|
105
|
+
console.warn(chalk.yellow(`WARNING: Output path was sanitized: ${outputPath} -> ${sanitizedPath}`));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
ErrorHandler.validatePath(sanitizedPath, {
|
|
110
|
+
preventTraversal: true,
|
|
111
|
+
mustBeAbsolute: false
|
|
112
|
+
});
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw new Error(`Invalid output path: ${error.message}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
options.output = sanitizedPath;
|
|
118
|
+
break;
|
|
119
|
+
case '--show-config':
|
|
120
|
+
options.showConfig = true;
|
|
121
|
+
break;
|
|
122
|
+
case '--reset-config':
|
|
123
|
+
options.resetConfig = true;
|
|
124
|
+
break;
|
|
125
|
+
case 'config':
|
|
126
|
+
options.config = true;
|
|
127
|
+
break;
|
|
128
|
+
case '--help':
|
|
129
|
+
case '-h':
|
|
130
|
+
options.help = true;
|
|
131
|
+
break;
|
|
132
|
+
case '--version':
|
|
133
|
+
case '-v':
|
|
134
|
+
options.version = true;
|
|
135
|
+
break;
|
|
136
|
+
case '--no-interactive':
|
|
137
|
+
options.noInteractive = true;
|
|
138
|
+
break;
|
|
139
|
+
case '--format':
|
|
140
|
+
case '-f':
|
|
141
|
+
if (i + 1 >= args.length) {
|
|
142
|
+
throw new Error(`Option ${arg} requires a value (pdf or rag)`);
|
|
143
|
+
}
|
|
144
|
+
i++;
|
|
145
|
+
const format = args[i].toLowerCase();
|
|
146
|
+
if (!['pdf', 'rag', 'both', 'llm'].includes(format)) {
|
|
147
|
+
throw new Error(`Invalid format: ${format}. Use 'pdf', 'rag', 'llm', or 'both'`);
|
|
148
|
+
}
|
|
149
|
+
options.format = format;
|
|
150
|
+
break;
|
|
151
|
+
default:
|
|
152
|
+
if (arg.startsWith('-')) {
|
|
153
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
154
|
+
}
|
|
155
|
+
// Allow non-option arguments (for future extensibility)
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (options.help) {
|
|
161
|
+
this.showHelp();
|
|
162
|
+
await ErrorHandler.safeExit(0, 'Help displayed');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (options.version) {
|
|
166
|
+
this.showVersion();
|
|
167
|
+
await ErrorHandler.safeExit(0, 'Version displayed');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return options;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Execute the main scanning and PDF generation flow
|
|
175
|
+
* @param {object} options - Parsed command line options
|
|
176
|
+
*/
|
|
177
|
+
async executeMainFlow(options) {
|
|
178
|
+
// Load or create configuration
|
|
179
|
+
this.config = await this.loadConfiguration();
|
|
180
|
+
|
|
181
|
+
// Initialize components
|
|
182
|
+
this.scanner = new Scanner(this.config);
|
|
183
|
+
this.pdfGenerator = new PDFGenerator(this.config);
|
|
184
|
+
|
|
185
|
+
// Determine scan path (default: current working directory)
|
|
186
|
+
const scanPath = process.cwd();
|
|
187
|
+
const projectName = path.basename(scanPath);
|
|
188
|
+
|
|
189
|
+
console.log(chalk.cyan(`CodeSummary - Scanning project: ${chalk.bold(projectName)}\n`));
|
|
190
|
+
|
|
191
|
+
// Scan directory
|
|
192
|
+
const spinner = ora('Scanning directory structure...').start();
|
|
193
|
+
const filesByExtension = await this.scanner.scanDirectory(scanPath);
|
|
194
|
+
spinner.succeed('Directory scan completed');
|
|
195
|
+
|
|
196
|
+
// Check if any supported files were found
|
|
197
|
+
if (Object.keys(filesByExtension).length === 0) {
|
|
198
|
+
console.log(chalk.red('ERROR: No supported files found. Nothing to document.'));
|
|
199
|
+
await ErrorHandler.safeExit(1, 'No supported files found');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Display scan summary
|
|
203
|
+
this.scanner.displayScanSummary(filesByExtension);
|
|
204
|
+
|
|
205
|
+
// Let user select extensions to include (skip prompt in non-interactive mode)
|
|
206
|
+
const selectedExtensions = (options.noInteractive || !process.stdin.isTTY)
|
|
207
|
+
? Object.keys(filesByExtension)
|
|
208
|
+
: await this.selectExtensions(filesByExtension);
|
|
209
|
+
|
|
210
|
+
if (selectedExtensions.length === 0) {
|
|
211
|
+
console.log(chalk.yellow('WARNING: No extensions selected. Exiting.'));
|
|
212
|
+
await ErrorHandler.safeExit(0, 'No extensions selected');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Check file count threshold
|
|
216
|
+
const totalFiles = this.calculateTotalFiles(filesByExtension, selectedExtensions);
|
|
217
|
+
await this.checkFileCountThreshold(totalFiles, options.noInteractive);
|
|
218
|
+
|
|
219
|
+
// Generate output based on format (--both runs pdf+rag; errors are collected)
|
|
220
|
+
const runPdf = options.format === 'pdf' || options.format === 'both';
|
|
221
|
+
const runRag = options.format === 'rag' || options.format === 'both';
|
|
222
|
+
const runLlm = options.format === 'llm';
|
|
223
|
+
const generationErrors = [];
|
|
224
|
+
|
|
225
|
+
if (runPdf) {
|
|
226
|
+
try {
|
|
227
|
+
const outputPath = this.determineOutputPath(options.output, projectName);
|
|
228
|
+
await PDFGenerator.ensureOutputDirectory(path.dirname(outputPath));
|
|
229
|
+
const pdfSpinner = ora('Generating PDF document...').start();
|
|
230
|
+
const result = await this.pdfGenerator.generatePDF(
|
|
231
|
+
filesByExtension,
|
|
232
|
+
selectedExtensions,
|
|
233
|
+
outputPath,
|
|
234
|
+
projectName
|
|
235
|
+
);
|
|
236
|
+
pdfSpinner.succeed('PDF generation completed');
|
|
237
|
+
await this.displayCompletionSummary(result.outputPath, selectedExtensions, totalFiles, result.pageCount);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
generationErrors.push(`PDF: ${error.message}`);
|
|
240
|
+
console.error(chalk.red(`ERROR generating PDF: ${error.message}`));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (runRag) {
|
|
245
|
+
try {
|
|
246
|
+
const ragOutputPath = this.determineRagOutputPath(options.output, projectName);
|
|
247
|
+
await fs.ensureDir(path.dirname(ragOutputPath));
|
|
248
|
+
const ragSpinner = ora('Generating RAG-optimized output...').start();
|
|
249
|
+
const ragGenerator = new RagGenerator();
|
|
250
|
+
const result = await ragGenerator.generateRagOutput(
|
|
251
|
+
filesByExtension,
|
|
252
|
+
selectedExtensions,
|
|
253
|
+
ragOutputPath,
|
|
254
|
+
projectName,
|
|
255
|
+
scanPath
|
|
256
|
+
);
|
|
257
|
+
ragSpinner.succeed('RAG output generation completed');
|
|
258
|
+
await this.displayRagCompletionSummary(result.outputPath, selectedExtensions, totalFiles, result.totalChunks);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
generationErrors.push(`RAG: ${error.message}`);
|
|
261
|
+
console.error(chalk.red(`ERROR generating RAG output: ${error.message}`));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (runLlm) {
|
|
266
|
+
try {
|
|
267
|
+
const llmOutputPath = this.determineLlmOutputPath(options.output, projectName);
|
|
268
|
+
await fs.ensureDir(path.dirname(llmOutputPath));
|
|
269
|
+
const llmSpinner = ora('Generating LLM-optimised Markdown...').start();
|
|
270
|
+
const llmGenerator = new LlmGenerator();
|
|
271
|
+
const result = await llmGenerator.generateLlmOutput(
|
|
272
|
+
filesByExtension,
|
|
273
|
+
selectedExtensions,
|
|
274
|
+
llmOutputPath,
|
|
275
|
+
projectName
|
|
276
|
+
);
|
|
277
|
+
llmSpinner.succeed('LLM output generation completed');
|
|
278
|
+
await this.displayLlmCompletionSummary(result.outputPath, selectedExtensions, totalFiles);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
generationErrors.push(`LLM: ${error.message}`);
|
|
281
|
+
console.error(chalk.red(`ERROR generating LLM output: ${error.message}`));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (generationErrors.length > 0) {
|
|
286
|
+
console.error(chalk.red(`\n${generationErrors.length} output(s) failed.`));
|
|
287
|
+
await ErrorHandler.safeExit(1, 'Generation errors');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Load configuration (with first-run setup if needed)
|
|
293
|
+
* @returns {object} Configuration object
|
|
294
|
+
*/
|
|
295
|
+
async loadConfiguration() {
|
|
296
|
+
let config = await this.configManager.loadConfig();
|
|
297
|
+
|
|
298
|
+
if (!config) {
|
|
299
|
+
// First run - trigger setup wizard
|
|
300
|
+
config = await this.configManager.runFirstTimeSetup();
|
|
301
|
+
} else {
|
|
302
|
+
console.log(chalk.gray(`Using configuration from ${this.configManager.configPath}`));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return config;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Let user select which extensions to include
|
|
310
|
+
* @param {object} filesByExtension - Available files by extension
|
|
311
|
+
* @returns {Array} Selected extensions
|
|
312
|
+
*/
|
|
313
|
+
async selectExtensions(filesByExtension) {
|
|
314
|
+
const extensionInfo = this.scanner.getExtensionInfo(filesByExtension);
|
|
315
|
+
|
|
316
|
+
const choices = extensionInfo.map(info => ({
|
|
317
|
+
name: `${info.extension} → ${info.description} (${info.count} files)`,
|
|
318
|
+
value: info.extension,
|
|
319
|
+
checked: true // Pre-select all detected extensions
|
|
320
|
+
}));
|
|
321
|
+
|
|
322
|
+
const { selectedExtensions } = await inquirer.prompt([{
|
|
323
|
+
type: 'checkbox',
|
|
324
|
+
name: 'selectedExtensions',
|
|
325
|
+
message: 'Select file extensions to include:',
|
|
326
|
+
choices,
|
|
327
|
+
validate: (answer) => {
|
|
328
|
+
if (answer.length === 0) {
|
|
329
|
+
return 'You must select at least one extension.';
|
|
330
|
+
}
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
}]);
|
|
334
|
+
|
|
335
|
+
return selectedExtensions;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Calculate total files for selected extensions
|
|
340
|
+
* @param {object} filesByExtension - Files by extension
|
|
341
|
+
* @param {Array} selectedExtensions - Selected extensions
|
|
342
|
+
* @returns {number} Total file count
|
|
343
|
+
*/
|
|
344
|
+
calculateTotalFiles(filesByExtension, selectedExtensions) {
|
|
345
|
+
return selectedExtensions.reduce((total, ext) => {
|
|
346
|
+
return total + (filesByExtension[ext]?.length || 0);
|
|
347
|
+
}, 0);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Check if file count exceeds threshold and prompt user
|
|
352
|
+
* @param {number} totalFiles - Total file count
|
|
353
|
+
*/
|
|
354
|
+
async checkFileCountThreshold(totalFiles, noInteractive = false) {
|
|
355
|
+
if (totalFiles > this.config.settings.maxFilesBeforePrompt) {
|
|
356
|
+
console.log(chalk.yellow(`WARNING: Found ${totalFiles} files. Generation may take a while.`));
|
|
357
|
+
|
|
358
|
+
if (noInteractive || !process.stdin.isTTY) return;
|
|
359
|
+
|
|
360
|
+
const { shouldContinue } = await inquirer.prompt([{
|
|
361
|
+
type: 'confirm',
|
|
362
|
+
name: 'shouldContinue',
|
|
363
|
+
message: 'Do you want to continue?',
|
|
364
|
+
default: true
|
|
365
|
+
}]);
|
|
366
|
+
|
|
367
|
+
if (!shouldContinue) {
|
|
368
|
+
console.log(chalk.gray('Operation cancelled by user.'));
|
|
369
|
+
await ErrorHandler.safeExit(0, 'Operation cancelled by user');
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Determine final output path for RAG format
|
|
376
|
+
* @param {string} overridePath - Optional override path from CLI
|
|
377
|
+
* @param {string} projectName - Project name
|
|
378
|
+
* @returns {string} Final output path
|
|
379
|
+
*/
|
|
380
|
+
determineRagOutputPath(overridePath, projectName) {
|
|
381
|
+
let outputDir;
|
|
382
|
+
|
|
383
|
+
if (overridePath) {
|
|
384
|
+
const sanitizedPath = ErrorHandler.sanitizeInput(overridePath, { allowPath: true });
|
|
385
|
+
ErrorHandler.validatePath(sanitizedPath, { preventTraversal: true });
|
|
386
|
+
outputDir = path.resolve(sanitizedPath);
|
|
387
|
+
} else {
|
|
388
|
+
if (this.config.output.mode === 'relative') {
|
|
389
|
+
outputDir = process.cwd();
|
|
390
|
+
} else {
|
|
391
|
+
outputDir = path.resolve(this.config.output.fixedPath);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const sanitizedProjectName = ErrorHandler.sanitizeInput(projectName);
|
|
396
|
+
return resolveVersionedPath(path.join(outputDir, `${sanitizedProjectName}_rag.json`));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Determine final output path for PDF
|
|
401
|
+
* @param {string} overridePath - Optional override path from CLI
|
|
402
|
+
* @param {string} projectName - Project name
|
|
403
|
+
* @returns {string} Final output path
|
|
404
|
+
*/
|
|
405
|
+
determineOutputPath(overridePath, projectName) {
|
|
406
|
+
let outputDir;
|
|
407
|
+
|
|
408
|
+
if (overridePath) {
|
|
409
|
+
// Validate and sanitize override path from CLI
|
|
410
|
+
const sanitizedPath = ErrorHandler.sanitizeInput(overridePath, { allowPath: true });
|
|
411
|
+
ErrorHandler.validatePath(sanitizedPath, { preventTraversal: true });
|
|
412
|
+
|
|
413
|
+
outputDir = path.resolve(sanitizedPath);
|
|
414
|
+
console.log(chalk.gray(`PDF will be saved to: ${outputDir}`));
|
|
415
|
+
} else {
|
|
416
|
+
// Use config settings
|
|
417
|
+
if (this.config.output.mode === 'relative') {
|
|
418
|
+
outputDir = process.cwd();
|
|
419
|
+
} else {
|
|
420
|
+
outputDir = path.resolve(this.config.output.fixedPath);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Sanitize project name for filename
|
|
425
|
+
const sanitizedProjectName = ErrorHandler.sanitizeInput(projectName);
|
|
426
|
+
|
|
427
|
+
return PDFGenerator.generateOutputPath(sanitizedProjectName, outputDir);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Determine final output path for LLM Markdown format
|
|
432
|
+
* @param {string} overridePath - Optional override path from CLI
|
|
433
|
+
* @param {string} projectName - Project name
|
|
434
|
+
* @returns {string} Final output path
|
|
435
|
+
*/
|
|
436
|
+
determineLlmOutputPath(overridePath, projectName) {
|
|
437
|
+
let outputDir;
|
|
438
|
+
|
|
439
|
+
if (overridePath) {
|
|
440
|
+
const sanitizedPath = ErrorHandler.sanitizeInput(overridePath, { allowPath: true });
|
|
441
|
+
ErrorHandler.validatePath(sanitizedPath, { preventTraversal: true });
|
|
442
|
+
outputDir = path.resolve(sanitizedPath);
|
|
443
|
+
} else {
|
|
444
|
+
if (this.config.output.mode === 'relative') {
|
|
445
|
+
outputDir = process.cwd();
|
|
446
|
+
} else {
|
|
447
|
+
outputDir = path.resolve(this.config.output.fixedPath);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const sanitizedProjectName = ErrorHandler.sanitizeInput(projectName);
|
|
452
|
+
return resolveVersionedPath(path.join(outputDir, `${sanitizedProjectName}_llm.md`));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Display LLM completion summary
|
|
457
|
+
* @param {string} outputPath - Generated Markdown path
|
|
458
|
+
* @param {Array} selectedExtensions - Selected extensions
|
|
459
|
+
* @param {number} totalFiles - Total files processed
|
|
460
|
+
*/
|
|
461
|
+
async displayLlmCompletionSummary(outputPath, selectedExtensions, totalFiles) {
|
|
462
|
+
const stats = await fs.stat(outputPath);
|
|
463
|
+
const fileSizeFormatted = formatFileSize(stats.size);
|
|
464
|
+
|
|
465
|
+
console.log(chalk.green('\nSUCCESS: LLM-optimised Markdown generated successfully!\n'));
|
|
466
|
+
console.log(chalk.cyan('Summary:'));
|
|
467
|
+
console.log(chalk.gray(` Output: ${outputPath}`));
|
|
468
|
+
console.log(chalk.gray(` Extensions: ${selectedExtensions.join(', ')}`));
|
|
469
|
+
console.log(chalk.gray(` Total files: ${totalFiles}`));
|
|
470
|
+
console.log(chalk.gray(` File size: ${fileSizeFormatted}`));
|
|
471
|
+
console.log(chalk.gray(` Ready to paste into any LLM chat interface`));
|
|
472
|
+
console.log();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Display RAG completion summary
|
|
477
|
+
* @param {string} outputPath - Generated RAG JSON path
|
|
478
|
+
* @param {Array} selectedExtensions - Selected extensions
|
|
479
|
+
* @param {number} totalFiles - Total files processed
|
|
480
|
+
* @param {number} totalChunks - Number of chunks generated
|
|
481
|
+
*/
|
|
482
|
+
async displayRagCompletionSummary(outputPath, selectedExtensions, totalFiles, totalChunks) {
|
|
483
|
+
const stats = await fs.stat(outputPath);
|
|
484
|
+
const fileSizeFormatted = formatFileSize(stats.size);
|
|
485
|
+
|
|
486
|
+
console.log(chalk.green('\nSUCCESS: RAG-optimized output generated successfully!\n'));
|
|
487
|
+
console.log(chalk.cyan('Summary:'));
|
|
488
|
+
console.log(chalk.gray(` Output: ${outputPath}`));
|
|
489
|
+
console.log(chalk.gray(` Extensions: ${selectedExtensions.join(', ')}`));
|
|
490
|
+
console.log(chalk.gray(` Total files: ${totalFiles}`));
|
|
491
|
+
console.log(chalk.gray(` Total chunks: ${totalChunks}`));
|
|
492
|
+
console.log(chalk.gray(` JSON size: ${fileSizeFormatted}`));
|
|
493
|
+
console.log(chalk.gray(` Ready for RAG/LLM ingestion`));
|
|
494
|
+
console.log();
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Display completion summary
|
|
499
|
+
* @param {string} outputPath - Generated PDF path
|
|
500
|
+
* @param {Array} selectedExtensions - Selected extensions
|
|
501
|
+
* @param {number} totalFiles - Total files processed
|
|
502
|
+
* @param {number|string} pageCount - Number of pages in PDF or 'N/A'
|
|
503
|
+
*/
|
|
504
|
+
async displayCompletionSummary(outputPath, selectedExtensions, totalFiles, pageCount) {
|
|
505
|
+
// Get PDF stats
|
|
506
|
+
const stats = await fs.stat(outputPath);
|
|
507
|
+
const fileSizeFormatted = formatFileSize(stats.size);
|
|
508
|
+
|
|
509
|
+
console.log(chalk.green('\nSUCCESS: PDF generation completed successfully!\n'));
|
|
510
|
+
console.log(chalk.cyan('Summary:'));
|
|
511
|
+
console.log(chalk.gray(` Output: ${outputPath}`));
|
|
512
|
+
console.log(chalk.gray(` Extensions: ${selectedExtensions.join(', ')}`));
|
|
513
|
+
console.log(chalk.gray(` Total files: ${totalFiles}`));
|
|
514
|
+
if (pageCount !== 'N/A') {
|
|
515
|
+
console.log(chalk.gray(` Total pages: ${pageCount}`));
|
|
516
|
+
}
|
|
517
|
+
console.log(chalk.gray(` PDF size: ${fileSizeFormatted}`));
|
|
518
|
+
console.log();
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Show current configuration
|
|
523
|
+
*/
|
|
524
|
+
async showConfig() {
|
|
525
|
+
const config = await this.configManager.loadConfig();
|
|
526
|
+
if (config) {
|
|
527
|
+
this.configManager.displayConfig(config);
|
|
528
|
+
} else {
|
|
529
|
+
console.log(chalk.yellow('WARNING: No configuration found. Run codesummary to set up.'));
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Reset configuration
|
|
535
|
+
*/
|
|
536
|
+
async resetConfig() {
|
|
537
|
+
await this.configManager.resetConfig();
|
|
538
|
+
console.log(chalk.green('SUCCESS: Configuration reset. Run codesummary to set up again.'));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Edit configuration interactively
|
|
543
|
+
*/
|
|
544
|
+
async editConfig() {
|
|
545
|
+
let config = await this.configManager.loadConfig();
|
|
546
|
+
|
|
547
|
+
if (!config) {
|
|
548
|
+
console.log(chalk.yellow('WARNING: No configuration found. Running first-time setup...'));
|
|
549
|
+
config = await this.configManager.runFirstTimeSetup();
|
|
550
|
+
} else {
|
|
551
|
+
config = await this.configManager.editConfig(config);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Show version information
|
|
557
|
+
*/
|
|
558
|
+
showVersion() {
|
|
559
|
+
try {
|
|
560
|
+
const { version } = _require('../package.json');
|
|
561
|
+
console.log(`CodeSummary v${version}`);
|
|
562
|
+
} catch (error) {
|
|
563
|
+
console.log('CodeSummary version unknown');
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Show help information
|
|
569
|
+
*/
|
|
570
|
+
showHelp() {
|
|
571
|
+
console.log(chalk.cyan('\nCodeSummary - Generate PDF documentation from source code\n'));
|
|
572
|
+
|
|
573
|
+
console.log(chalk.white('Usage:'));
|
|
574
|
+
console.log(' codesummary [options] Scan current directory and generate PDF');
|
|
575
|
+
console.log(' codesummary config Edit configuration settings');
|
|
576
|
+
console.log();
|
|
577
|
+
|
|
578
|
+
console.log(chalk.white('Options:'));
|
|
579
|
+
console.log(' -o, --output <path> Override output directory');
|
|
580
|
+
console.log(' -f, --format <format> Output format: pdf (default), rag, llm, or both (pdf+rag)');
|
|
581
|
+
console.log(' --show-config Display current configuration');
|
|
582
|
+
console.log(' --reset-config Reset configuration to defaults');
|
|
583
|
+
console.log(' -h, --help Show this help message');
|
|
584
|
+
console.log(' -v, --version Show version information');
|
|
585
|
+
console.log();
|
|
586
|
+
|
|
587
|
+
console.log(chalk.white('Examples:'));
|
|
588
|
+
console.log(' codesummary Scan current project (PDF)');
|
|
589
|
+
console.log(' codesummary --format rag Generate RAG-optimised JSON');
|
|
590
|
+
console.log(' codesummary --format llm Generate LLM-optimised Markdown');
|
|
591
|
+
console.log(' codesummary --output ./docs Save output to ./docs folder');
|
|
592
|
+
console.log(' codesummary config Edit settings');
|
|
593
|
+
console.log(' codesummary --show-config View current settings');
|
|
594
|
+
console.log();
|
|
595
|
+
|
|
596
|
+
console.log(chalk.gray('For more information, visit: https://github.com/skamoll/CodeSummary'));
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
541
600
|
export default CLI;
|