@lakindu_perera/toren 1.0.7 → 1.0.8
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 +19 -0
- package/README.md +120 -31
- package/bin/toren.js +3 -0
- package/package.json +7 -3
- package/src/detectors/health-detector.js +72 -0
- package/src/detectors/important-files-detector.js +515 -0
- package/src/detectors/package-manager-detector.js +112 -0
- package/src/detectors/project-info-detector.js +89 -0
- package/src/detectors/script-detector.js +226 -10
- package/src/focused-output.js +78 -8
- package/src/renderers/console-renderer.js +53 -4
- package/src/renderers/html-renderer.js +128 -6
- package/src/renderers/json-renderer.js +15 -1
- package/src/renderers/markdown-renderer.js +70 -10
- package/src/scanner/scan.js +28 -6
|
@@ -804,6 +804,118 @@ function renderConfigurationFiles(configs) {
|
|
|
804
804
|
* @param {Array<{name: string, command: string}>} scripts
|
|
805
805
|
* @returns {string}
|
|
806
806
|
*/
|
|
807
|
+
function renderProjectInfo(projectInfo, packageManager) {
|
|
808
|
+
const rows = [];
|
|
809
|
+
if (projectInfo?.name) rows.push(['Name', projectInfo.name]);
|
|
810
|
+
if (packageManager) rows.push(['Package Manager', packageManager]);
|
|
811
|
+
if (projectInfo?.runtime) rows.push(['Runtime', projectInfo.runtime]);
|
|
812
|
+
if (projectInfo?.language) rows.push(['Language', projectInfo.language]);
|
|
813
|
+
if (projectInfo?.architecture) rows.push(['Architecture', projectInfo.architecture]);
|
|
814
|
+
if (projectInfo?.framework) rows.push(['Framework', projectInfo.framework]);
|
|
815
|
+
if (projectInfo?.entryPoint) rows.push(['Entry Point', projectInfo.entryPoint]);
|
|
816
|
+
if (projectInfo?.sourceDirectory) rows.push(['Source Directory', projectInfo.sourceDirectory]);
|
|
817
|
+
|
|
818
|
+
if (rows.length === 0) return '';
|
|
819
|
+
|
|
820
|
+
const rowsHTML = rows.map(([prop, value]) => `
|
|
821
|
+
<tr>
|
|
822
|
+
<td>${esc(prop)}</td>
|
|
823
|
+
<td class="val-plain">${esc(value)}</td>
|
|
824
|
+
</tr>`).join('');
|
|
825
|
+
|
|
826
|
+
return `
|
|
827
|
+
<section class="section">
|
|
828
|
+
<div class="section-header">
|
|
829
|
+
<div class="section-icon">${icon.info()}</div>
|
|
830
|
+
<h2 class="section-title">Project Info</h2>
|
|
831
|
+
</div>
|
|
832
|
+
<div class="section-body" style="padding:0">
|
|
833
|
+
<table class="data-table">
|
|
834
|
+
<thead>
|
|
835
|
+
<tr>
|
|
836
|
+
<th>Property</th>
|
|
837
|
+
<th style="text-align:right">Value</th>
|
|
838
|
+
</tr>
|
|
839
|
+
</thead>
|
|
840
|
+
<tbody>${rowsHTML}</tbody>
|
|
841
|
+
</table>
|
|
842
|
+
</div>
|
|
843
|
+
</section>`;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function renderImportantFiles(importantFiles) {
|
|
847
|
+
const count = importantFiles.length;
|
|
848
|
+
|
|
849
|
+
const body = count === 0
|
|
850
|
+
? `<p class="empty-msg" style="padding: 1.5rem">No important files detected.</p>`
|
|
851
|
+
: `<table class="data-table">
|
|
852
|
+
<thead>
|
|
853
|
+
<tr>
|
|
854
|
+
<th>Path</th>
|
|
855
|
+
<th>Reason</th>
|
|
856
|
+
</tr>
|
|
857
|
+
</thead>
|
|
858
|
+
<tbody>
|
|
859
|
+
${importantFiles.map(f => `
|
|
860
|
+
<tr>
|
|
861
|
+
<td><code>${esc(f.path)}</code></td>
|
|
862
|
+
<td class="val-plain">${esc(f.reason)}</td>
|
|
863
|
+
</tr>`).join('')}
|
|
864
|
+
</tbody>
|
|
865
|
+
</table>`;
|
|
866
|
+
|
|
867
|
+
const countBadge = count > 0
|
|
868
|
+
? `<span class="section-count">${count} found</span>`
|
|
869
|
+
: '';
|
|
870
|
+
|
|
871
|
+
return `
|
|
872
|
+
<section class="section">
|
|
873
|
+
<div class="section-header">
|
|
874
|
+
<div class="section-icon">${icon.file()}</div>
|
|
875
|
+
<h2 class="section-title">Important Files</h2>
|
|
876
|
+
${countBadge}
|
|
877
|
+
</div>
|
|
878
|
+
<div class="section-body" style="padding:0">
|
|
879
|
+
${body}
|
|
880
|
+
</div>
|
|
881
|
+
</section>`;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function renderProjectHealth(health) {
|
|
885
|
+
const count = health.length;
|
|
886
|
+
|
|
887
|
+
const body = count === 0
|
|
888
|
+
? `<p class="empty-msg" style="padding: 1.5rem">No project health observations available.</p>`
|
|
889
|
+
: `<ul class="entry-list">
|
|
890
|
+
${health.map(h => {
|
|
891
|
+
let emoji = 'ℹ';
|
|
892
|
+
if (h.status === 'pass') emoji = '✓';
|
|
893
|
+
else if (h.status === 'warning') emoji = '⚠';
|
|
894
|
+
return `
|
|
895
|
+
<li class="entry-item">
|
|
896
|
+
<span>${esc(emoji)}</span>
|
|
897
|
+
${esc(h.message)}
|
|
898
|
+
</li>`;
|
|
899
|
+
}).join('')}
|
|
900
|
+
</ul>`;
|
|
901
|
+
|
|
902
|
+
const countBadge = count > 0
|
|
903
|
+
? `<span class="section-count">${count} observations</span>`
|
|
904
|
+
: '';
|
|
905
|
+
|
|
906
|
+
return `
|
|
907
|
+
<section class="section">
|
|
908
|
+
<div class="section-header">
|
|
909
|
+
<div class="section-icon">${icon.info()}</div>
|
|
910
|
+
<h2 class="section-title">Project Health</h2>
|
|
911
|
+
${countBadge}
|
|
912
|
+
</div>
|
|
913
|
+
<div class="section-body">
|
|
914
|
+
${body}
|
|
915
|
+
</div>
|
|
916
|
+
</section>`;
|
|
917
|
+
}
|
|
918
|
+
|
|
807
919
|
function renderPackageScripts(scripts) {
|
|
808
920
|
const count = scripts.length;
|
|
809
921
|
|
|
@@ -812,16 +924,21 @@ function renderPackageScripts(scripts) {
|
|
|
812
924
|
: `<table class="data-table">
|
|
813
925
|
<thead>
|
|
814
926
|
<tr>
|
|
815
|
-
<th>
|
|
927
|
+
<th>Usage</th>
|
|
928
|
+
<th>Description</th>
|
|
816
929
|
<th>Command</th>
|
|
817
930
|
</tr>
|
|
818
931
|
</thead>
|
|
819
932
|
<tbody>
|
|
820
|
-
${scripts.map(s =>
|
|
933
|
+
${scripts.map(s => {
|
|
934
|
+
const usage = s.usage || `npm run ${s.name}`;
|
|
935
|
+
return `
|
|
821
936
|
<tr>
|
|
822
|
-
<td><code>${esc(
|
|
937
|
+
<td><code>${esc(usage)}</code></td>
|
|
938
|
+
<td class="val-plain">${esc(s.description || '')}</td>
|
|
823
939
|
<td class="val-plain"><code>${esc(s.command)}</code></td>
|
|
824
|
-
</tr
|
|
940
|
+
</tr>`;
|
|
941
|
+
}).join('')}
|
|
825
942
|
</tbody>
|
|
826
943
|
</table>`;
|
|
827
944
|
|
|
@@ -836,7 +953,9 @@ function renderPackageScripts(scripts) {
|
|
|
836
953
|
<h2 class="section-title">Package Scripts</h2>
|
|
837
954
|
${countBadge}
|
|
838
955
|
</div>
|
|
839
|
-
<div class="section-body" style="padding:0"
|
|
956
|
+
<div class="section-body" style="padding:0">
|
|
957
|
+
${body}
|
|
958
|
+
</div>
|
|
840
959
|
</section>`;
|
|
841
960
|
}
|
|
842
961
|
|
|
@@ -966,7 +1085,7 @@ function renderFooter() {
|
|
|
966
1085
|
* @param {{ cwd?: string }} [options]
|
|
967
1086
|
*/
|
|
968
1087
|
export function render(result, options = {}) {
|
|
969
|
-
const { rootPath, projectType, entryPoints, configs = [], scripts = [], flatFiles, tree } = result;
|
|
1088
|
+
const { rootPath, projectType, entryPoints, configs = [], scripts = [], flatFiles, tree, importantFiles = [], health = [], projectInfo, packageManager } = result;
|
|
970
1089
|
|
|
971
1090
|
const cwd = options.cwd ?? process.cwd();
|
|
972
1091
|
const relRoot = path.relative(cwd, rootPath) || '.';
|
|
@@ -990,6 +1109,9 @@ export function render(result, options = {}) {
|
|
|
990
1109
|
|
|
991
1110
|
<main>
|
|
992
1111
|
${renderSummaryCards(result)}
|
|
1112
|
+
${renderProjectInfo(projectInfo, packageManager)}
|
|
1113
|
+
${renderProjectHealth(health)}
|
|
1114
|
+
${renderImportantFiles(importantFiles)}
|
|
993
1115
|
${renderFrameworks(projectType)}
|
|
994
1116
|
${renderEntryPoints(entryPoints)}
|
|
995
1117
|
${renderConfigurationFiles(configs)}
|
|
@@ -88,6 +88,10 @@ export function render(result, options = {}) {
|
|
|
88
88
|
entryPoints = [],
|
|
89
89
|
configs = [],
|
|
90
90
|
scripts = [],
|
|
91
|
+
packageManager,
|
|
92
|
+
importantFiles = [],
|
|
93
|
+
projectInfo = null,
|
|
94
|
+
health = [],
|
|
91
95
|
tree,
|
|
92
96
|
flatFiles = [],
|
|
93
97
|
totalFolders = 0,
|
|
@@ -116,9 +120,16 @@ export function render(result, options = {}) {
|
|
|
116
120
|
// 2. Project identity — name is the human-readable basename; path is the
|
|
117
121
|
// relative path used for filesystem resolution.
|
|
118
122
|
project: {
|
|
119
|
-
name: rootName,
|
|
123
|
+
name: (projectInfo && projectInfo.name) ? projectInfo.name : rootName,
|
|
120
124
|
path: relRoot,
|
|
121
125
|
type: projectType,
|
|
126
|
+
packageManager: packageManager || null,
|
|
127
|
+
runtime: projectInfo ? projectInfo.runtime : null,
|
|
128
|
+
language: projectInfo ? projectInfo.language : null,
|
|
129
|
+
framework: projectInfo ? projectInfo.framework : null,
|
|
130
|
+
architecture: projectInfo ? projectInfo.architecture : null,
|
|
131
|
+
entryPoint: projectInfo ? projectInfo.entryPoint : null,
|
|
132
|
+
sourceDirectory: projectInfo ? projectInfo.sourceDirectory : null,
|
|
122
133
|
},
|
|
123
134
|
|
|
124
135
|
// 3. Detected frameworks — always an array.
|
|
@@ -128,6 +139,9 @@ export function render(result, options = {}) {
|
|
|
128
139
|
entryPoints: Array.isArray(entryPoints) ? entryPoints : [],
|
|
129
140
|
configs: Array.isArray(configs) ? configs : [],
|
|
130
141
|
scripts: Array.isArray(scripts) ? scripts : [],
|
|
142
|
+
|
|
143
|
+
importantFiles: Array.isArray(importantFiles) ? importantFiles : [],
|
|
144
|
+
health: Array.isArray(health) ? health : [],
|
|
131
145
|
|
|
132
146
|
// 7. Structured statistics — always a complete object with numeric values.
|
|
133
147
|
// durationMs is rounded to the nearest millisecond (integer).
|
|
@@ -177,17 +177,28 @@ function sectionTitle(out) {
|
|
|
177
177
|
* @param {string} relRoot
|
|
178
178
|
*/
|
|
179
179
|
function sectionSummary(out, result, relRoot) {
|
|
180
|
-
const { projectType, flatFiles, totalFolders } = result;
|
|
180
|
+
const { projectType, flatFiles, totalFolders, packageManager, projectInfo } = result;
|
|
181
181
|
|
|
182
182
|
out.push('');
|
|
183
183
|
out.push('## Project');
|
|
184
184
|
out.push('');
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
])
|
|
185
|
+
|
|
186
|
+
const rows = [];
|
|
187
|
+
if (projectInfo && projectInfo.name) rows.push(['Name', projectInfo.name]);
|
|
188
|
+
rows.push(['Project Type', projectType]);
|
|
189
|
+
if (packageManager) rows.push(['Package Manager', packageManager]);
|
|
190
|
+
if (projectInfo && projectInfo.runtime) rows.push(['Runtime', projectInfo.runtime]);
|
|
191
|
+
if (projectInfo && projectInfo.language) rows.push(['Language', projectInfo.language]);
|
|
192
|
+
if (projectInfo && projectInfo.architecture) rows.push(['Architecture', projectInfo.architecture]);
|
|
193
|
+
if (projectInfo && projectInfo.framework) rows.push(['Framework', projectInfo.framework]);
|
|
194
|
+
if (projectInfo && projectInfo.entryPoint) rows.push(['Entry Point', projectInfo.entryPoint]);
|
|
195
|
+
if (projectInfo && projectInfo.sourceDirectory) rows.push(['Source Directory', projectInfo.sourceDirectory]);
|
|
196
|
+
|
|
197
|
+
rows.push(['Scan Path', relRoot]);
|
|
198
|
+
rows.push(['Total Files', String(flatFiles.length)]);
|
|
199
|
+
rows.push(['Total Folders', String(totalFolders)]);
|
|
200
|
+
|
|
201
|
+
out.push(markdownTable(rows));
|
|
191
202
|
}
|
|
192
203
|
|
|
193
204
|
/**
|
|
@@ -230,7 +241,7 @@ function sectionConfigurationFiles(out, configs) {
|
|
|
230
241
|
|
|
231
242
|
/**
|
|
232
243
|
* @param {string[]} out
|
|
233
|
-
* @param {Array<{name: string, command: string}>} scripts
|
|
244
|
+
* @param {Array<{name: string, command: string, description: string, usage: string}>} scripts
|
|
234
245
|
*/
|
|
235
246
|
function sectionPackageScripts(out, scripts) {
|
|
236
247
|
out.push('');
|
|
@@ -241,7 +252,54 @@ function sectionPackageScripts(out, scripts) {
|
|
|
241
252
|
out.push('No package scripts detected.');
|
|
242
253
|
} else {
|
|
243
254
|
for (const s of scripts) {
|
|
244
|
-
|
|
255
|
+
const usage = s.usage || `npm run ${s.name}`;
|
|
256
|
+
out.push(`- **${usage}**`);
|
|
257
|
+
if (s.description) {
|
|
258
|
+
out.push(` ${s.description} `);
|
|
259
|
+
out.push(` \`${s.command}\``);
|
|
260
|
+
} else {
|
|
261
|
+
out.push(` \`${s.command}\``);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @param {string[]} out
|
|
269
|
+
* @param {Array<{path: string, reason: string}>} importantFiles
|
|
270
|
+
*/
|
|
271
|
+
function sectionImportantFiles(out, importantFiles) {
|
|
272
|
+
out.push('');
|
|
273
|
+
out.push('## Important Files');
|
|
274
|
+
out.push('');
|
|
275
|
+
|
|
276
|
+
if (importantFiles.length === 0) {
|
|
277
|
+
out.push('No important files detected.');
|
|
278
|
+
} else {
|
|
279
|
+
for (const f of importantFiles) {
|
|
280
|
+
out.push(`- **${f.path}** `);
|
|
281
|
+
out.push(` ${f.reason}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* @param {string[]} out
|
|
288
|
+
* @param {Array<{id: string, status: string, message: string}>} health
|
|
289
|
+
*/
|
|
290
|
+
function sectionProjectHealth(out, health) {
|
|
291
|
+
out.push('');
|
|
292
|
+
out.push('## Project Health');
|
|
293
|
+
out.push('');
|
|
294
|
+
|
|
295
|
+
if (health.length === 0) {
|
|
296
|
+
out.push('No project health observations available.');
|
|
297
|
+
} else {
|
|
298
|
+
for (const h of health) {
|
|
299
|
+
let icon = 'ℹ';
|
|
300
|
+
if (h.status === 'pass') icon = '✓';
|
|
301
|
+
else if (h.status === 'warning') icon = '⚠';
|
|
302
|
+
out.push(`- ${icon} ${h.message}`);
|
|
245
303
|
}
|
|
246
304
|
}
|
|
247
305
|
}
|
|
@@ -315,7 +373,7 @@ function sectionStatistics(out, result) {
|
|
|
315
373
|
* @param {{ cwd?: string }} [options]
|
|
316
374
|
*/
|
|
317
375
|
export function render(result, options = {}) {
|
|
318
|
-
const { rootPath, projectType, entryPoints, configs = [], scripts = [], flatFiles, tree } = result;
|
|
376
|
+
const { rootPath, projectType, entryPoints, configs = [], scripts = [], flatFiles, tree, importantFiles = [], health = [] } = result;
|
|
319
377
|
|
|
320
378
|
const cwd = options.cwd ?? process.cwd();
|
|
321
379
|
const relRoot = path.relative(cwd, rootPath) || '.';
|
|
@@ -326,6 +384,8 @@ export function render(result, options = {}) {
|
|
|
326
384
|
|
|
327
385
|
sectionTitle(out);
|
|
328
386
|
sectionSummary(out, result, relRoot);
|
|
387
|
+
sectionProjectHealth(out, health);
|
|
388
|
+
sectionImportantFiles(out, importantFiles);
|
|
329
389
|
sectionFrameworks(out, projectType);
|
|
330
390
|
sectionEntryPoints(out, entryPoints);
|
|
331
391
|
sectionConfigurationFiles(out, configs);
|
package/src/scanner/scan.js
CHANGED
|
@@ -20,8 +20,12 @@
|
|
|
20
20
|
|
|
21
21
|
import fs from 'node:fs';
|
|
22
22
|
import path from 'node:path';
|
|
23
|
-
import { detectConfigs }
|
|
24
|
-
import { detectScripts }
|
|
23
|
+
import { detectConfigs } from '../detectors/config-detector.js';
|
|
24
|
+
import { detectScripts } from '../detectors/script-detector.js';
|
|
25
|
+
import { detectPackageManager } from '../detectors/package-manager-detector.js';
|
|
26
|
+
import { detectImportantFiles } from '../detectors/important-files-detector.js';
|
|
27
|
+
import { detectProjectInfo } from '../detectors/project-info-detector.js';
|
|
28
|
+
import { detectHealth } from '../detectors/health-detector.js';
|
|
25
29
|
|
|
26
30
|
// ---------------------------------------------------------------------------
|
|
27
31
|
// Constants
|
|
@@ -99,7 +103,8 @@ const PROJECT_TYPE_MARKERS = [
|
|
|
99
103
|
* @property {string} projectType - Detected project type label
|
|
100
104
|
* @property {Array<string>} entryPoints - Relative paths of detected entry points
|
|
101
105
|
* @property {Array<string>} configs - Relative paths of detected config files
|
|
102
|
-
* @property {Array<{name: string, command: string}>} scripts - Parsed package scripts
|
|
106
|
+
* @property {Array<{name: string, command: string, category: string|null, description: string|null, usage: string}>} scripts - Parsed and enriched package scripts
|
|
107
|
+
* @property {string|null} packageManager - Detected package manager, or null if none/ambiguous
|
|
103
108
|
* @property {DirNode} tree - Full in-memory file tree
|
|
104
109
|
* @property {Array<string>} flatFiles - All relative file paths (flat list)
|
|
105
110
|
* @property {number} totalFolders - Total number of directories walked
|
|
@@ -475,12 +480,17 @@ export function scan(targetPath, options = {}) {
|
|
|
475
480
|
let configs = [];
|
|
476
481
|
/** @type {Array<{name: string, command: string}>} */
|
|
477
482
|
let scripts = [];
|
|
483
|
+
/** @type {string|null} */
|
|
484
|
+
let packageManager = null;
|
|
485
|
+
let importantFiles = [];
|
|
486
|
+
let projectInfo = null;
|
|
487
|
+
let health = [];
|
|
478
488
|
|
|
479
489
|
const startTime = performance.now();
|
|
480
490
|
let tree;
|
|
481
491
|
let projectType = 'Unknown';
|
|
482
492
|
let totalFolders = 0;
|
|
483
|
-
|
|
493
|
+
|
|
484
494
|
if (stat.isDirectory()) {
|
|
485
495
|
tree = walkDirectory(rootPath, rootPath, flatFiles, includeHidden, maxFiles);
|
|
486
496
|
projectType = detectProjectType(rootPath);
|
|
@@ -488,7 +498,11 @@ export function scan(targetPath, options = {}) {
|
|
|
488
498
|
totalFolders = countFolders(tree) - 1;
|
|
489
499
|
entryPoints = findEntryPoints(projectType, flatFiles, rootPath);
|
|
490
500
|
configs = detectConfigs(flatFiles).configs;
|
|
491
|
-
|
|
501
|
+
packageManager = detectPackageManager(flatFiles).packageManager;
|
|
502
|
+
scripts = detectScripts(rootPath, packageManager).scripts;
|
|
503
|
+
importantFiles = detectImportantFiles({ flatFiles, projectType, entryPoints, configs }).importantFiles;
|
|
504
|
+
projectInfo = detectProjectInfo({ rootPath, projectType, flatFiles, entryPoints, packageManager, scripts }).projectInfo;
|
|
505
|
+
health = detectHealth({ flatFiles, configs, importantFiles, scripts, projectType }).health;
|
|
492
506
|
} else if (stat.isFile()) {
|
|
493
507
|
const relFilePath = toPosix(path.basename(rootPath));
|
|
494
508
|
tree = {
|
|
@@ -506,7 +520,11 @@ export function scan(targetPath, options = {}) {
|
|
|
506
520
|
flatFiles.push(relFilePath);
|
|
507
521
|
entryPoints = [relFilePath]; // A single file is its own entry point
|
|
508
522
|
configs = detectConfigs(flatFiles).configs;
|
|
509
|
-
|
|
523
|
+
packageManager = detectPackageManager(flatFiles).packageManager;
|
|
524
|
+
scripts = detectScripts(path.dirname(rootPath), packageManager).scripts;
|
|
525
|
+
importantFiles = detectImportantFiles({ flatFiles, projectType, entryPoints, configs }).importantFiles;
|
|
526
|
+
projectInfo = detectProjectInfo({ rootPath, projectType, flatFiles, entryPoints, packageManager, scripts }).projectInfo;
|
|
527
|
+
health = detectHealth({ flatFiles, configs, importantFiles, scripts, projectType }).health;
|
|
510
528
|
} else {
|
|
511
529
|
const err = new Error(`Path is neither a file nor a directory: ${rootPath}`);
|
|
512
530
|
err.title = 'Unsupported path type';
|
|
@@ -524,6 +542,10 @@ export function scan(targetPath, options = {}) {
|
|
|
524
542
|
entryPoints,
|
|
525
543
|
configs,
|
|
526
544
|
scripts,
|
|
545
|
+
packageManager,
|
|
546
|
+
importantFiles,
|
|
547
|
+
projectInfo,
|
|
548
|
+
health,
|
|
527
549
|
tree,
|
|
528
550
|
flatFiles,
|
|
529
551
|
totalFolders,
|