@lakindu_perera/toren 1.0.4 → 1.0.5

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/README.md CHANGED
@@ -67,6 +67,8 @@ toren --project-type
67
67
  toren --frameworks
68
68
  toren --entry-points
69
69
  toren --structure
70
+ toren --configs
71
+ toren --scripts
70
72
 
71
73
  # Lifecycle & Help Commands
72
74
  toren --help
@@ -84,6 +86,8 @@ toren --uninstall
84
86
  | `--frameworks` | Show detected frameworks only. |
85
87
  | `--entry-points` | Show detected entry points only. |
86
88
  | `--structure` | Show repository structure only. |
89
+ | `--configs` | Show detected project configuration files. |
90
+ | `--scripts` | Show available package scripts only. |
87
91
  | `--format <type>` | Output format: `console` (default), `json`, `markdown`, `html`. |
88
92
  | `--include-hidden` | Include hidden files and dot-directories in the scan. |
89
93
  | `--max-files <N>` | Override the default 50,000-file scan limit. |
@@ -93,7 +97,7 @@ toren --uninstall
93
97
  | `--uninstall` | Safely remove Toren from the global npm environment. |
94
98
 
95
99
  > **Note:** `--format md` is not a valid alias. Use `--format markdown` in full.
96
- > **Note:** Focused output flags (`--project-type`, `--frameworks`, `--entry-points`, `--structure`) are mutually exclusive.
100
+ > **Note:** Focused output flags (`--project-type`, `--frameworks`, `--entry-points`, `--structure`, `--configs`, `--scripts`) are mutually exclusive.
97
101
 
98
102
  ---
99
103
 
@@ -113,6 +117,19 @@ Frameworks:
113
117
  $ toren --entry-points
114
118
  Entry Points:
115
119
  - src/main.tsx
120
+
121
+ $ toren --configs
122
+ Configuration Files
123
+ ───────────────────
124
+
125
+ ✓ package.json
126
+ ✓ vite.config.ts
127
+
128
+ $ toren --scripts
129
+ Available Scripts
130
+
131
+ start node bin/toren.js
132
+ test node --test
116
133
  ```
117
134
 
118
135
  ### Console Output Example
@@ -121,7 +138,7 @@ The default `console` format renders a beautiful summary directly in your termin
121
138
  ```text
122
139
  Toren v1.0.4 — Codebase Onboarding Intelligence
123
140
 
124
- 🔍 Project Summary
141
+ Project Summary
125
142
  ────────────────────────────────────────────────────────────────────────────────
126
143
  Path: ./my-react-app
127
144
  Project type: React
@@ -129,11 +146,11 @@ The default `console` format renders a beautiful summary directly in your termin
129
146
  Total folders: 6
130
147
  Scan duration: 4 ms
131
148
 
132
- 🚪 Entry Points
149
+ Entry Points
133
150
  ────────────────────────────────────────────────────────────────────────────────
134
151
  → src/main.tsx
135
152
 
136
- 📁 Folder Structure (first 20 files)
153
+ Folder Structure (first 20 files)
137
154
  ────────────────────────────────────────────────────────────────────────────────
138
155
  my-react-app/
139
156
  ├── public/
@@ -151,7 +168,7 @@ my-react-app/
151
168
  … and 21 more file(s) not shown
152
169
 
153
170
  ────────────────────────────────────────────────────────────────────────────────
154
- Scan complete.
171
+ Scan complete.
155
172
  ```
156
173
 
157
174
  ### JSON Output Example
@@ -236,12 +253,12 @@ Contributions to improve this codebase analyzer CLI are always welcome!
236
253
 
237
254
  ---
238
255
 
239
- ## 📄 License
256
+ ## License
240
257
 
241
258
  Distributed under the MIT License. See `LICENSE` for more information.
242
259
 
243
260
  ---
244
261
 
245
- ## 👨‍💻 Author
262
+ ## Author
246
263
 
247
264
  Built with by **[Lakindu Perera](https://github.com/lakindudev)**.
package/bin/toren.js CHANGED
@@ -52,6 +52,8 @@ function printHelp() {
52
52
  --frameworks Show detected frameworks only
53
53
  --entry-points Show detected entry points only
54
54
  --structure Show repository structure only
55
+ --configs Show detected project configuration files
56
+ --scripts Show available package scripts only
55
57
  --format <type> Output as console, json, markdown, or html
56
58
  --include-hidden Include hidden files and folders
57
59
  --max-files <n> Set scan file limit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lakindu_perera/toren",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "A powerful codebase scanner CLI tool for project analysis, framework detection, and understanding repository structure instantly.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  ],
15
15
  "scripts": {
16
16
  "start": "node bin/toren.js",
17
+ "test": "node --test",
17
18
  "lint": "node --check bin/toren.js src/scanner/scan.js src/lifecycle.js src/renderers/index.js src/renderers/console-renderer.js src/renderers/json-renderer.js src/renderers/markdown-renderer.js src/renderers/html-renderer.js"
18
19
  },
19
20
  "keywords": [
@@ -0,0 +1,81 @@
1
+ /**
2
+ * @fileoverview Configuration file detector
3
+ */
4
+
5
+ /**
6
+ * Detects common configuration files in the root of the repository.
7
+ *
8
+ * @param {string[]} flatFiles - Array of relative file paths from the scanner
9
+ * @returns {{ configs: string[] }}
10
+ */
11
+ export function detectConfigs(flatFiles) {
12
+ const configPatterns = [
13
+ // Package
14
+ /^package\.json$/,
15
+ /^package-lock\.json$/,
16
+ /^pnpm-lock\.yaml$/,
17
+ /^yarn\.lock$/,
18
+ /^bun\.lockb$/,
19
+
20
+ // TypeScript
21
+ /^tsconfig\.json$/,
22
+ /^jsconfig\.json$/,
23
+
24
+ // Frontend
25
+ /^vite\.config\.[a-zA-Z0-9]+$/,
26
+ /^next\.config\.[a-zA-Z0-9]+$/,
27
+ /^nuxt\.config\.[a-zA-Z0-9]+$/,
28
+ /^astro\.config\.[a-zA-Z0-9]+$/,
29
+ /^angular\.json$/,
30
+
31
+ // Build
32
+ /^webpack\.config\.[a-zA-Z0-9]+$/,
33
+ /^rollup\.config\.[a-zA-Z0-9]+$/,
34
+ /^turbo\.json$/,
35
+ /^nx\.json$/,
36
+
37
+ // Styling
38
+ /^tailwind\.config\.[a-zA-Z0-9]+$/,
39
+ /^postcss\.config\.[a-zA-Z0-9]+$/,
40
+
41
+ // Lint
42
+ /^eslint\.config\.[a-zA-Z0-9]+$/,
43
+ /^\.eslintrc.*$/,
44
+ /^prettier\.config\.[a-zA-Z0-9]+$/,
45
+ /^\.prettierrc.*$/,
46
+
47
+ // Backend
48
+ /^pom\.xml$/,
49
+ /^build\.gradle$/,
50
+ /^build\.gradle\.kts$/,
51
+
52
+ // Languages
53
+ /^requirements\.txt$/,
54
+ /^pyproject\.toml$/,
55
+ /^Cargo\.toml$/,
56
+ /^go\.mod$/,
57
+
58
+ // Deployment
59
+ /^Dockerfile$/,
60
+ /^docker-compose\.ya?ml$/
61
+ ];
62
+
63
+ const matchedSet = new Set();
64
+
65
+ for (const file of flatFiles) {
66
+ // Ensure we only match files at the root of the repository
67
+ if (file.includes('/')) continue;
68
+
69
+ for (const pattern of configPatterns) {
70
+ if (pattern.test(file)) {
71
+ matchedSet.add(file);
72
+ break; // Stop checking patterns once a match is found
73
+ }
74
+ }
75
+ }
76
+
77
+ // Convert Set to Array and sort alphabetically
78
+ const configs = Array.from(matchedSet).sort();
79
+
80
+ return { configs };
81
+ }
@@ -0,0 +1,32 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ /**
5
+ * Detects npm/package scripts from package.json in the project root.
6
+ *
7
+ * @param {string} rootPath - The absolute path of the scanned root
8
+ * @returns {{ scripts: Array<{name: string, command: string}> }}
9
+ */
10
+ export function detectScripts(rootPath) {
11
+ const scripts = [];
12
+ const pkgPath = path.join(rootPath, 'package.json');
13
+
14
+ try {
15
+ if (fs.existsSync(pkgPath)) {
16
+ const content = fs.readFileSync(pkgPath, 'utf8');
17
+ const pkg = JSON.parse(content);
18
+
19
+ if (pkg.scripts && typeof pkg.scripts === 'object') {
20
+ for (const [name, command] of Object.entries(pkg.scripts)) {
21
+ if (typeof command === 'string') {
22
+ scripts.push({ name, command });
23
+ }
24
+ }
25
+ }
26
+ }
27
+ } catch (error) {
28
+ // Return empty scripts on malformed or unreadable package.json
29
+ }
30
+
31
+ return { scripts };
32
+ }
@@ -8,7 +8,9 @@ export const FOCUSED_FLAGS = [
8
8
  '--project-type',
9
9
  '--frameworks',
10
10
  '--entry-points',
11
- '--structure'
11
+ '--structure',
12
+ '--configs',
13
+ '--scripts'
12
14
  ];
13
15
 
14
16
  /**
@@ -67,5 +69,28 @@ export function renderFocusedMode(mode, result) {
67
69
  case '--structure':
68
70
  renderStructure(result);
69
71
  break;
72
+
73
+ case '--configs':
74
+ console.log('Configuration Files');
75
+ console.log('───────────────────\n');
76
+ if (!result.configs || result.configs.length === 0) {
77
+ console.log('No configuration files detected.');
78
+ } else {
79
+ result.configs.forEach(c => console.log(`✓ ${c}`));
80
+ }
81
+ break;
82
+
83
+ case '--scripts':
84
+ console.log('Available Scripts\n');
85
+ if (!result.scripts || result.scripts.length === 0) {
86
+ console.log('No scripts found');
87
+ } else {
88
+ const maxNameLen = Math.max(...result.scripts.map(s => s.name.length));
89
+ result.scripts.forEach(s => {
90
+ const paddedName = s.name.padEnd(maxNameLen + 6, ' ');
91
+ console.log(`${paddedName}${s.command}`);
92
+ });
93
+ }
94
+ break;
70
95
  }
71
96
  }
@@ -173,6 +173,8 @@ export function render(result, options = {}) {
173
173
  rootPath,
174
174
  projectType,
175
175
  entryPoints,
176
+ configs = [],
177
+ scripts = [],
176
178
  tree,
177
179
  flatFiles,
178
180
  totalFolders,
@@ -204,6 +206,28 @@ export function render(result, options = {}) {
204
206
  }
205
207
  }
206
208
 
209
+ // ── Configuration Files ───────────────────────────────────────────────────
210
+ section('⚙️', 'Configuration Files');
211
+ if (configs.length === 0) {
212
+ console.log(paint(' No configuration files detected', C.dim));
213
+ } else {
214
+ for (const c of configs) {
215
+ console.log(` ${paint(c, C.white)}`);
216
+ }
217
+ }
218
+
219
+ // ── Package Scripts ───────────────────────────────────────────────────────
220
+ section('📜', 'Package Scripts');
221
+ if (scripts.length === 0) {
222
+ console.log(paint(' No package scripts detected', C.dim));
223
+ } else {
224
+ const maxNameLen = Math.max(...scripts.map(s => s.name.length));
225
+ for (const s of scripts) {
226
+ const paddedName = s.name.padEnd(maxNameLen, ' ');
227
+ console.log(` ${paint(paddedName, C.white)} ${paint(s.command, C.dim)}`);
228
+ }
229
+ }
230
+
207
231
  // ── Structure Preview ─────────────────────────────────────────────────────
208
232
  section('📁', `Folder Structure ${paint(`(first ${PREVIEW_LIMIT} files)`, C.dim)}`);
209
233
 
@@ -625,6 +625,7 @@ const icon = {
625
625
  tree: () => `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>`,
626
626
  bar: () => `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>`,
627
627
  info: () => `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>`,
628
+ terminal: () => `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>`,
628
629
  };
629
630
 
630
631
  // ---------------------------------------------------------------------------
@@ -748,6 +749,82 @@ function renderEntryPoints(entryPoints) {
748
749
  </div>`;
749
750
  }
750
751
 
752
+ /**
753
+ * Render the configuration files section.
754
+ *
755
+ * @param {string[]} configs
756
+ * @returns {string}
757
+ */
758
+ function renderConfigurationFiles(configs) {
759
+ const count = configs.length;
760
+
761
+ const body = count === 0
762
+ ? `<p class="empty-msg">No configuration files detected.</p>`
763
+ : `<ul class="entry-list">
764
+ ${configs.map(c => `
765
+ <li class="entry-item">
766
+ <span class="entry-dot"></span>
767
+ ${esc(c)}
768
+ </li>`).join('')}
769
+ </ul>`;
770
+
771
+ const countBadge = count > 0
772
+ ? `<span class="section-count">${count} found</span>`
773
+ : '';
774
+
775
+ return `
776
+ <div class="section">
777
+ <div class="section-header">
778
+ <div class="section-icon">${icon.file()}</div>
779
+ <span class="section-title">Configuration Files</span>
780
+ ${countBadge}
781
+ </div>
782
+ <div class="section-body">${body}</div>
783
+ </div>`;
784
+ }
785
+
786
+ /**
787
+ * Render the package scripts section.
788
+ *
789
+ * @param {Array<{name: string, command: string}>} scripts
790
+ * @returns {string}
791
+ */
792
+ function renderPackageScripts(scripts) {
793
+ const count = scripts.length;
794
+
795
+ const body = count === 0
796
+ ? `<p class="empty-msg" style="padding: 1.5rem">No package scripts detected.</p>`
797
+ : `<table class="data-table">
798
+ <thead>
799
+ <tr>
800
+ <th>Script</th>
801
+ <th>Command</th>
802
+ </tr>
803
+ </thead>
804
+ <tbody>
805
+ ${scripts.map(s => `
806
+ <tr>
807
+ <td><code>${esc(s.name)}</code></td>
808
+ <td class="val-plain"><code>${esc(s.command)}</code></td>
809
+ </tr>`).join('')}
810
+ </tbody>
811
+ </table>`;
812
+
813
+ const countBadge = count > 0
814
+ ? `<span class="section-count">${count} found</span>`
815
+ : '';
816
+
817
+ return `
818
+ <div class="section">
819
+ <div class="section-header">
820
+ <div class="section-icon">${icon.terminal()}</div>
821
+ <span class="section-title">Package Scripts</span>
822
+ ${countBadge}
823
+ </div>
824
+ <div class="section-body" style="padding:0">${body}</div>
825
+ </div>`;
826
+ }
827
+
751
828
  /**
752
829
  * Render the folder structure section with a dark <pre><code> tree.
753
830
  *
@@ -873,7 +950,7 @@ function renderFooter() {
873
950
  * @param {{ cwd?: string }} [options]
874
951
  */
875
952
  export function render(result, options = {}) {
876
- const { rootPath, projectType, entryPoints, flatFiles } = result;
953
+ const { rootPath, projectType, entryPoints, configs = [], scripts = [], flatFiles } = result;
877
954
 
878
955
  const cwd = options.cwd ?? process.cwd();
879
956
  const relRoot = path.relative(cwd, rootPath) || '.';
@@ -898,6 +975,8 @@ export function render(result, options = {}) {
898
975
  <main>
899
976
  ${renderSummaryCards(result)}
900
977
  ${renderEntryPoints(entryPoints)}
978
+ ${renderConfigurationFiles(configs)}
979
+ ${renderPackageScripts(scripts)}
901
980
  ${renderFolderStructure(flatFiles, rootName)}
902
981
  ${renderStats(result)}
903
982
  ${renderScanInfo()}
@@ -26,6 +26,8 @@ export function render(result, options = {}) {
26
26
  rootPath,
27
27
  projectType,
28
28
  entryPoints,
29
+ configs = [],
30
+ scripts = [],
29
31
  tree,
30
32
  flatFiles,
31
33
  totalFolders,
@@ -47,6 +49,8 @@ export function render(result, options = {}) {
47
49
  scanDurationMs: Math.round(scanDurationMs),
48
50
  },
49
51
  entryPoints,
52
+ configs,
53
+ scripts,
50
54
  structure: (tree.children || []).map(mapTree)
51
55
  };
52
56
 
@@ -218,6 +218,50 @@ function sectionSummary(out, result, relRoot) {
218
218
  out.push('---');
219
219
  }
220
220
 
221
+ /**
222
+ * @param {string[]} out
223
+ * @param {string[]} configs
224
+ */
225
+ function sectionConfigurationFiles(out, configs) {
226
+ out.push('');
227
+ out.push('## Configuration Files');
228
+ out.push('');
229
+
230
+ if (configs.length === 0) {
231
+ out.push('No configuration files detected.');
232
+ } else {
233
+ for (const c of configs) {
234
+ out.push(`- ${c}`);
235
+ }
236
+ }
237
+
238
+ out.push('');
239
+ out.push('---');
240
+ }
241
+
242
+ /**
243
+ * @param {string[]} out
244
+ * @param {Array<{name: string, command: string}>} scripts
245
+ */
246
+ function sectionPackageScripts(out, scripts) {
247
+ out.push('');
248
+ out.push('## Package Scripts');
249
+ out.push('');
250
+
251
+ if (scripts.length === 0) {
252
+ out.push('No package scripts detected.');
253
+ } else {
254
+ out.push('| Script | Command |');
255
+ out.push('|--------|---------|');
256
+ for (const s of scripts) {
257
+ out.push(`| \`${s.name}\` | \`${s.command}\` |`);
258
+ }
259
+ }
260
+
261
+ out.push('');
262
+ out.push('---');
263
+ }
264
+
221
265
  /**
222
266
  * @param {string[]} out
223
267
  * @param {string[]} entryPoints
@@ -308,7 +352,7 @@ function sectionScanInfo(out) {
308
352
  * @param {{ cwd?: string }} [options]
309
353
  */
310
354
  export function render(result, options = {}) {
311
- const { rootPath, entryPoints, flatFiles } = result;
355
+ const { rootPath, entryPoints, configs = [], scripts = [], flatFiles } = result;
312
356
 
313
357
  const cwd = options.cwd ?? process.cwd();
314
358
  const relRoot = path.relative(cwd, rootPath) || '.';
@@ -320,6 +364,8 @@ export function render(result, options = {}) {
320
364
  sectionTitle(out);
321
365
  sectionSummary(out, result, relRoot);
322
366
  sectionEntryPoints(out, entryPoints);
367
+ sectionConfigurationFiles(out, configs);
368
+ sectionPackageScripts(out, scripts);
323
369
  sectionFolderStructure(out, flatFiles, rootName);
324
370
  sectionStatistics(out, result);
325
371
  sectionScanInfo(out);
@@ -20,6 +20,8 @@
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
25
 
24
26
  // ---------------------------------------------------------------------------
25
27
  // Constants
@@ -96,6 +98,8 @@ const PROJECT_TYPE_MARKERS = [
96
98
  * @property {string} rootPath - Absolute path that was scanned
97
99
  * @property {string} projectType - Detected project type label
98
100
  * @property {Array<string>} entryPoints - Relative paths of detected entry points
101
+ * @property {Array<string>} configs - Relative paths of detected config files
102
+ * @property {Array<{name: string, command: string}>} scripts - Parsed package scripts
99
103
  * @property {DirNode} tree - Full in-memory file tree
100
104
  * @property {Array<string>} flatFiles - All relative file paths (flat list)
101
105
  * @property {number} totalFolders - Total number of directories walked
@@ -453,6 +457,10 @@ export function scan(targetPath, options = {}) {
453
457
  const flatFiles = [];
454
458
  /** @type {Array<string>} */
455
459
  let entryPoints = [];
460
+ /** @type {Array<string>} */
461
+ let configs = [];
462
+ /** @type {Array<{name: string, command: string}>} */
463
+ let scripts = [];
456
464
 
457
465
  const startTime = performance.now();
458
466
  let tree;
@@ -465,6 +473,8 @@ export function scan(targetPath, options = {}) {
465
473
  // Count all directory nodes in the tree (excluding root itself).
466
474
  totalFolders = countFolders(tree) - 1;
467
475
  entryPoints = findEntryPoints(projectType, flatFiles, rootPath);
476
+ configs = detectConfigs(flatFiles).configs;
477
+ scripts = detectScripts(rootPath).scripts;
468
478
  } else if (stat.isFile()) {
469
479
  const relFilePath = toPosix(path.basename(rootPath));
470
480
  tree = {
@@ -481,6 +491,8 @@ export function scan(targetPath, options = {}) {
481
491
  };
482
492
  flatFiles.push(relFilePath);
483
493
  entryPoints = [relFilePath]; // A single file is its own entry point
494
+ configs = detectConfigs(flatFiles).configs;
495
+ scripts = detectScripts(path.dirname(rootPath)).scripts;
484
496
  } else {
485
497
  throw new Error(`Path is neither a file nor a directory: ${rootPath}`);
486
498
  }
@@ -491,6 +503,8 @@ export function scan(targetPath, options = {}) {
491
503
  rootPath,
492
504
  projectType,
493
505
  entryPoints,
506
+ configs,
507
+ scripts,
494
508
  tree,
495
509
  flatFiles,
496
510
  totalFolders,