@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.
@@ -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>Script</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(s.name)}</code></td>
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>`).join('')}
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">${body}</div>
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
- out.push(markdownTable([
186
- ['Project Type', projectType],
187
- ['Scan Path', relRoot],
188
- ['Total Files', String(flatFiles.length)],
189
- ['Total Folders', String(totalFolders)],
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
- out.push(`- ${s.name}: ${s.command}`);
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);
@@ -20,8 +20,12 @@
20
20
 
21
21
  import fs from 'node:fs';
22
22
  import path from 'node:path';
23
- import { detectConfigs } from '../detectors/config-detector.js';
24
- import { detectScripts } from '../detectors/script-detector.js';
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
- scripts = detectScripts(rootPath).scripts;
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
- scripts = detectScripts(path.dirname(rootPath)).scripts;
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,