@lakindu_perera/toren 1.0.7 → 1.0.9
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 +26 -0
- package/README.md +120 -31
- package/dist/cli/toren.d.ts +24 -0
- package/dist/cli/toren.js +254 -0
- package/dist/cli/toren.js.map +1 -0
- package/dist/detectors/config-detector.d.ts +3 -0
- package/dist/detectors/config-detector.js +58 -0
- package/dist/detectors/config-detector.js.map +1 -0
- package/dist/detectors/health-detector.d.ts +12 -0
- package/dist/detectors/health-detector.js +53 -0
- package/dist/detectors/health-detector.js.map +1 -0
- package/dist/detectors/important-files-detector.d.ts +51 -0
- package/dist/detectors/important-files-detector.js +455 -0
- package/dist/detectors/important-files-detector.js.map +1 -0
- package/dist/detectors/package-manager-detector.d.ts +7 -0
- package/dist/detectors/package-manager-detector.js +35 -0
- package/dist/detectors/package-manager-detector.js.map +1 -0
- package/dist/detectors/project-info-detector.d.ts +13 -0
- package/dist/detectors/project-info-detector.js +97 -0
- package/dist/detectors/project-info-detector.js.map +1 -0
- package/dist/detectors/script-detector.d.ts +42 -0
- package/dist/detectors/script-detector.js +199 -0
- package/dist/detectors/script-detector.js.map +1 -0
- package/dist/focused-output.d.ts +27 -0
- package/dist/focused-output.js +201 -0
- package/dist/focused-output.js.map +1 -0
- package/dist/lifecycle.d.ts +2 -0
- package/dist/lifecycle.js +114 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/renderers/console-renderer.d.ts +19 -0
- package/dist/renderers/console-renderer.js +361 -0
- package/dist/renderers/console-renderer.js.map +1 -0
- package/dist/renderers/html-renderer.d.ts +14 -0
- package/{src → dist}/renderers/html-renderer.js +263 -201
- package/dist/renderers/html-renderer.js.map +1 -0
- package/dist/renderers/index.d.ts +6 -0
- package/dist/renderers/index.js +12 -0
- package/dist/renderers/index.js.map +1 -0
- package/dist/renderers/json-renderer.d.ts +10 -0
- package/dist/renderers/json-renderer.js +127 -0
- package/dist/renderers/json-renderer.js.map +1 -0
- package/dist/renderers/markdown-renderer.d.ts +13 -0
- package/dist/renderers/markdown-renderer.js +360 -0
- package/dist/renderers/markdown-renderer.js.map +1 -0
- package/dist/scanner/scan.d.ts +2 -0
- package/dist/scanner/scan.js +573 -0
- package/dist/scanner/scan.js.map +1 -0
- package/dist/types/index.d.ts +85 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +16 -7
- package/bin/toren.js +0 -308
- package/src/detectors/config-detector.js +0 -81
- package/src/detectors/script-detector.js +0 -32
- package/src/focused-output.js +0 -146
- package/src/lifecycle.js +0 -124
- package/src/renderers/console-renderer.js +0 -350
- package/src/renderers/index.js +0 -41
- package/src/renderers/json-renderer.js +0 -154
- package/src/renderers/markdown-renderer.js +0 -337
- package/src/scanner/scan.js +0 -532
package/src/lifecycle.js
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import { execSync } from 'node:child_process';
|
|
3
|
-
import readline from 'node:readline';
|
|
4
|
-
|
|
5
|
-
const C = {
|
|
6
|
-
reset: '\x1b[0m',
|
|
7
|
-
cyan: '\x1b[36m',
|
|
8
|
-
green: '\x1b[32m',
|
|
9
|
-
yellow: '\x1b[33m',
|
|
10
|
-
red: '\x1b[31m',
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
function paint(text, ...codes) {
|
|
14
|
-
return `${codes.join('')}${text}${C.reset}`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function getGlobalNpmRoot() {
|
|
18
|
-
try {
|
|
19
|
-
return execSync('npm root -g', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
20
|
-
} catch {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function getInstallStatus() {
|
|
26
|
-
const status = {
|
|
27
|
-
isGlobal: false,
|
|
28
|
-
method: 'unknown',
|
|
29
|
-
binaryPath: process.argv[1] || 'unknown',
|
|
30
|
-
isBroken: false,
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
if (!status.binaryPath || !fs.existsSync(status.binaryPath)) {
|
|
35
|
-
status.isBroken = true;
|
|
36
|
-
return status;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const realPath = fs.realpathSync(status.binaryPath);
|
|
40
|
-
const globalRoot = getGlobalNpmRoot();
|
|
41
|
-
|
|
42
|
-
if (globalRoot) {
|
|
43
|
-
if (realPath.includes(globalRoot)) {
|
|
44
|
-
status.isGlobal = true;
|
|
45
|
-
status.method = 'npm install -g';
|
|
46
|
-
} else if (status.binaryPath !== realPath) {
|
|
47
|
-
status.isGlobal = true;
|
|
48
|
-
status.method = 'npm link';
|
|
49
|
-
} else {
|
|
50
|
-
status.isGlobal = false;
|
|
51
|
-
status.method = 'local';
|
|
52
|
-
}
|
|
53
|
-
} else {
|
|
54
|
-
if (status.binaryPath !== realPath) {
|
|
55
|
-
status.isGlobal = true;
|
|
56
|
-
status.method = 'npm link';
|
|
57
|
-
} else {
|
|
58
|
-
status.isGlobal = false;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
} catch (err) {
|
|
62
|
-
status.isBroken = true;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return status;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function runDoctor(pkgVersion) {
|
|
69
|
-
const status = getInstallStatus();
|
|
70
|
-
|
|
71
|
-
if (status.isBroken) {
|
|
72
|
-
console.log(paint('⚠ Broken installation detected', C.red));
|
|
73
|
-
console.log(`→ Run: ${paint('npm uninstall -g toren', C.cyan)}`);
|
|
74
|
-
console.log('');
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (status.isGlobal) {
|
|
79
|
-
console.log(`${paint('✔', C.green)} Toren installed globally`);
|
|
80
|
-
} else {
|
|
81
|
-
console.log(`${paint('✔', C.green)} Toren installed locally`);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
console.log(`${paint('✔', C.green)} Binary path valid`);
|
|
85
|
-
console.log(`${paint('✔', C.green)} Version match confirmed`);
|
|
86
|
-
console.log('');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function runUninstall() {
|
|
90
|
-
const status = getInstallStatus();
|
|
91
|
-
|
|
92
|
-
if (status.isBroken) {
|
|
93
|
-
console.log(paint('⚠ Toren installation is corrupted. Please reinstall using npm install -g toren', C.yellow));
|
|
94
|
-
process.exit(0);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const rl = readline.createInterface({
|
|
98
|
-
input: process.stdin,
|
|
99
|
-
output: process.stdout
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
rl.question('Are you sure you want to remove Toren from global environment? (y/n) ', (answer) => {
|
|
103
|
-
rl.close();
|
|
104
|
-
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
105
|
-
process.exit(0);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
console.log('');
|
|
109
|
-
console.log('Uninstalling Toren...');
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
execSync('npm uninstall -g toren', { stdio: 'inherit' });
|
|
113
|
-
console.log('');
|
|
114
|
-
console.log(paint('✔ Successfully removed Toren from the global environment.', C.green));
|
|
115
|
-
} catch (err) {
|
|
116
|
-
console.log('');
|
|
117
|
-
console.log(paint('❌ Failed to uninstall Toren.', C.red));
|
|
118
|
-
console.log('Please try manually: npm uninstall -g toren');
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
console.log('');
|
|
122
|
-
process.exit(0);
|
|
123
|
-
});
|
|
124
|
-
}
|
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Toren — Console Renderer
|
|
3
|
-
*
|
|
4
|
-
* Consumes a {@link ScanResult} and produces styled terminal output.
|
|
5
|
-
*
|
|
6
|
-
* Design contract:
|
|
7
|
-
* - Presentation-only. Every value rendered comes from ScanResult.
|
|
8
|
-
* Presentation-level derivations (e.g. frameworks list from projectType) are
|
|
9
|
-
* permitted here; business logic is not.
|
|
10
|
-
* - No imports from the scanner or any domain module.
|
|
11
|
-
* - Stateless: render() may be called multiple times safely.
|
|
12
|
-
* - The shape expected here matches the ScanResult typedef in scan.js.
|
|
13
|
-
* When ScanResult grows new fields, add new render sections; never mutate data.
|
|
14
|
-
*
|
|
15
|
-
* Adding a new output format (JSON, Markdown, HTML …):
|
|
16
|
-
* - Create src/renderers/<format>-renderer.js
|
|
17
|
-
* - Export a render(result, options?) function with the same signature
|
|
18
|
-
* - Import and call it from bin/toren.js based on a --format flag
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import path from 'node:path';
|
|
22
|
-
import { createRequire } from 'node:module';
|
|
23
|
-
|
|
24
|
-
const require = createRequire(import.meta.url);
|
|
25
|
-
const pkg = require('../../package.json');
|
|
26
|
-
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
// ANSI palette — zero external dependencies
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
|
|
31
|
-
const C = {
|
|
32
|
-
reset: '\x1b[0m',
|
|
33
|
-
bold: '\x1b[1m',
|
|
34
|
-
dim: '\x1b[2m',
|
|
35
|
-
cyan: '\x1b[36m',
|
|
36
|
-
green: '\x1b[32m',
|
|
37
|
-
yellow: '\x1b[33m',
|
|
38
|
-
blue: '\x1b[34m',
|
|
39
|
-
magenta: '\x1b[35m',
|
|
40
|
-
red: '\x1b[31m',
|
|
41
|
-
white: '\x1b[97m',
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/** Maximum files shown in the structure preview. */
|
|
45
|
-
const PREVIEW_LIMIT = 20;
|
|
46
|
-
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
// Cross-platform capability detection
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
|
|
51
|
-
function shouldEnableColors() {
|
|
52
|
-
if ('FORCE_COLOR' in process.env) {
|
|
53
|
-
return process.env.FORCE_COLOR !== '0' && process.env.FORCE_COLOR !== 'false';
|
|
54
|
-
}
|
|
55
|
-
if ('NO_COLOR' in process.env) return false;
|
|
56
|
-
if (!process.stdout || !process.stdout.isTTY) return false;
|
|
57
|
-
if (process.env.TERM === 'dumb') return false;
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function isUnicodeSupported() {
|
|
62
|
-
if (process.platform !== 'win32') {
|
|
63
|
-
return process.env.TERM !== 'linux';
|
|
64
|
-
}
|
|
65
|
-
return Boolean(
|
|
66
|
-
process.env.CI ||
|
|
67
|
-
process.env.WT_SESSION ||
|
|
68
|
-
process.env.TERMINUS_SUBLIME ||
|
|
69
|
-
process.env.ConEmuTask === '{cmd::Cmder}' ||
|
|
70
|
-
process.env.TERM_PROGRAM === 'Terminus-Sublime' ||
|
|
71
|
-
process.env.TERM_PROGRAM === 'vscode' ||
|
|
72
|
-
process.env.TERM === 'xterm-256color' ||
|
|
73
|
-
process.env.TERM === 'alacritty' ||
|
|
74
|
-
process.env.TERMINAL_EMULATOR === 'JetBrains-JediTerm'
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const useColors = shouldEnableColors();
|
|
79
|
-
const useUnicode = isUnicodeSupported();
|
|
80
|
-
|
|
81
|
-
const CHARS = {
|
|
82
|
-
dash: useUnicode ? '─' : '-',
|
|
83
|
-
corner: useUnicode ? '└── ' : '\\-- ',
|
|
84
|
-
tee: useUnicode ? '├── ' : '+-- ',
|
|
85
|
-
pipe: useUnicode ? '│ ' : '| ',
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// ---------------------------------------------------------------------------
|
|
89
|
-
// Low-level paint / layout helpers (private to this module)
|
|
90
|
-
// ---------------------------------------------------------------------------
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Wrap `text` with one or more ANSI codes, resetting after.
|
|
94
|
-
* @param {string} text
|
|
95
|
-
* @param {...string} codes
|
|
96
|
-
* @returns {string}
|
|
97
|
-
*/
|
|
98
|
-
function paint(text, ...codes) {
|
|
99
|
-
if (!useColors) return text;
|
|
100
|
-
return `${codes.join('')}${text}${C.reset}`;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Print a titled section header followed by a matched-length divider.
|
|
105
|
-
* @param {string} title
|
|
106
|
-
*/
|
|
107
|
-
function section(title) {
|
|
108
|
-
const cleanTitle = title.replace(/\x1b\[[0-9;]*m/g, '');
|
|
109
|
-
console.log(paint(title, C.bold, C.white));
|
|
110
|
-
console.log(paint(CHARS.dash.repeat(cleanTitle.length), C.dim));
|
|
111
|
-
console.log('');
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Print a single labelled key-value row.
|
|
116
|
-
* @param {string} label - Left-hand label (dim)
|
|
117
|
-
* @param {string} value - Right-hand value
|
|
118
|
-
* @param {string} [valueColor] - Optional ANSI code(s) for the value
|
|
119
|
-
*/
|
|
120
|
-
function row(label, value, ...valueCodes) {
|
|
121
|
-
const coloured = valueCodes.length ? paint(value, ...valueCodes) : value;
|
|
122
|
-
console.log(`${paint(label, C.dim)} ${coloured}`);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Format a scan duration into a human-readable string.
|
|
127
|
-
* Mirrors the formatDuration helpers in markdown-renderer and html-renderer.
|
|
128
|
-
* @param {number} ms
|
|
129
|
-
* @returns {string}
|
|
130
|
-
*/
|
|
131
|
-
function formatDuration(ms) {
|
|
132
|
-
if (ms < 1) return '< 1 ms';
|
|
133
|
-
if (ms >= 1000) return `${(ms / 1000).toFixed(2)} s`;
|
|
134
|
-
return `${Math.round(ms)} ms`;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Derive a frameworks array from the projectType string.
|
|
139
|
-
* Returns [] when no specific framework is detected (projectType is falsy or 'Unknown').
|
|
140
|
-
* Mirrors the identical derivation in json-renderer.js — both must stay in sync.
|
|
141
|
-
*
|
|
142
|
-
* @param {string} projectType
|
|
143
|
-
* @returns {string[]}
|
|
144
|
-
*/
|
|
145
|
-
function deriveFrameworks(projectType) {
|
|
146
|
-
if (!projectType || projectType === 'Unknown') return [];
|
|
147
|
-
return [projectType];
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ---------------------------------------------------------------------------
|
|
151
|
-
// File-tree renderer (private)
|
|
152
|
-
// ---------------------------------------------------------------------------
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Recursively print a file tree with classic tree connectors.
|
|
156
|
-
* Stops after PREVIEW_LIMIT files have been printed.
|
|
157
|
-
*
|
|
158
|
-
* @param {import('../scanner/scan.js').DirNode | import('../scanner/scan.js').FileNode} node
|
|
159
|
-
* @param {string} prefix - Accumulated indentation
|
|
160
|
-
* @param {boolean} isLast - Whether this is the last sibling
|
|
161
|
-
* @param {{ count: number, maxReached: boolean }} counter - Shared mutable file counter
|
|
162
|
-
* @param {number} limit - Max files to render
|
|
163
|
-
* @param {number} depth - Current depth
|
|
164
|
-
* @param {number} maxDepth - Max recursion depth
|
|
165
|
-
*/
|
|
166
|
-
function renderTree(node, prefix, isLast, counter, limit = PREVIEW_LIMIT, depth = 0, maxDepth = 4) {
|
|
167
|
-
if (counter.maxReached) return;
|
|
168
|
-
if (depth >= maxDepth) return;
|
|
169
|
-
|
|
170
|
-
const connector = isLast ? CHARS.corner : CHARS.tee;
|
|
171
|
-
const extension = isLast ? ' ' : CHARS.pipe;
|
|
172
|
-
|
|
173
|
-
if (node.type === 'directory') {
|
|
174
|
-
console.log(`${prefix}${connector}${paint(`${node.name}${path.sep}`, C.bold, C.blue)}`);
|
|
175
|
-
const children = node.children ?? [];
|
|
176
|
-
|
|
177
|
-
if (depth === maxDepth - 1 && children.length > 0) {
|
|
178
|
-
console.log(`${prefix}${extension}${CHARS.corner}${paint('...', C.dim)}`);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
for (let i = 0; i < children.length; i++) {
|
|
183
|
-
if (counter.count >= limit) {
|
|
184
|
-
console.log(`${prefix}${extension}${CHARS.corner}${paint('...', C.dim)}`);
|
|
185
|
-
counter.maxReached = true;
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
// Check if this child will be the last one we render due to limits
|
|
189
|
-
let willBeLast = i === children.length - 1;
|
|
190
|
-
|
|
191
|
-
renderTree(children[i], prefix + extension, willBeLast, counter, limit, depth + 1, maxDepth);
|
|
192
|
-
if (counter.maxReached) break;
|
|
193
|
-
}
|
|
194
|
-
} else {
|
|
195
|
-
console.log(`${prefix}${connector}${paint(node.name, C.white)}`);
|
|
196
|
-
counter.count += 1;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
// ---------------------------------------------------------------------------
|
|
202
|
-
// Banner (private)
|
|
203
|
-
// ---------------------------------------------------------------------------
|
|
204
|
-
|
|
205
|
-
function printBanner() {
|
|
206
|
-
const name = paint('Toren', C.bold, C.cyan);
|
|
207
|
-
const version = paint(`v${pkg.version}`, C.dim);
|
|
208
|
-
const tagline = paint('Codebase Onboarding Intelligence', C.dim);
|
|
209
|
-
console.log(`${name} ${version} — ${tagline}`);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// ---------------------------------------------------------------------------
|
|
213
|
-
// Public API
|
|
214
|
-
// ---------------------------------------------------------------------------
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Render a ScanResult to the terminal.
|
|
218
|
-
*
|
|
219
|
-
* All sections read from the ScanResult. Presentation-level derivations
|
|
220
|
-
* (e.g. frameworks list) are computed here; no business logic is added.
|
|
221
|
-
*
|
|
222
|
-
* @param {import('../scanner/scan.js').ScanResult} result
|
|
223
|
-
* @param {{ cwd?: string }} [options]
|
|
224
|
-
*/
|
|
225
|
-
export function render(result, options = {}) {
|
|
226
|
-
const {
|
|
227
|
-
rootPath,
|
|
228
|
-
projectType,
|
|
229
|
-
entryPoints,
|
|
230
|
-
configs = [],
|
|
231
|
-
scripts = [],
|
|
232
|
-
tree,
|
|
233
|
-
flatFiles,
|
|
234
|
-
totalFolders,
|
|
235
|
-
scanDurationMs,
|
|
236
|
-
} = result;
|
|
237
|
-
|
|
238
|
-
const cwd = options.cwd ?? process.cwd();
|
|
239
|
-
const relRoot = path.relative(cwd, rootPath) || '.';
|
|
240
|
-
|
|
241
|
-
// ── Banner ────────────────────────────────────────────────────────────────
|
|
242
|
-
printBanner();
|
|
243
|
-
console.log('');
|
|
244
|
-
|
|
245
|
-
// ── Summary ───────────────────────────────────────────────────────────────
|
|
246
|
-
section('Project Summary');
|
|
247
|
-
row('Path: ', paint(relRoot, C.cyan));
|
|
248
|
-
row('Project type: ', paint(projectType, C.bold, C.green));
|
|
249
|
-
row('Total files: ', paint(String(flatFiles.length), C.yellow));
|
|
250
|
-
row('Total folders:', paint(String(totalFolders), C.yellow));
|
|
251
|
-
console.log('');
|
|
252
|
-
|
|
253
|
-
// ── Frameworks ────────────────────────────────────────────────────────────
|
|
254
|
-
section('Frameworks');
|
|
255
|
-
const frameworks = deriveFrameworks(projectType);
|
|
256
|
-
if (frameworks.length === 0) {
|
|
257
|
-
console.log(paint('No frameworks detected.', C.dim));
|
|
258
|
-
} else {
|
|
259
|
-
for (const fw of frameworks) {
|
|
260
|
-
console.log(paint(fw, C.white));
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
console.log('');
|
|
264
|
-
|
|
265
|
-
// ── Entry Points ──────────────────────────────────────────────────────────
|
|
266
|
-
section('Entry Points');
|
|
267
|
-
if (entryPoints.length === 0) {
|
|
268
|
-
console.log(paint('No entry points detected.', C.dim));
|
|
269
|
-
} else {
|
|
270
|
-
for (const ep of entryPoints) {
|
|
271
|
-
console.log(paint(ep, C.white));
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
console.log('');
|
|
275
|
-
|
|
276
|
-
// ── Configuration Files ───────────────────────────────────────────────────
|
|
277
|
-
section('Configuration Files');
|
|
278
|
-
if (configs.length === 0) {
|
|
279
|
-
console.log(paint('No configuration files detected.', C.dim));
|
|
280
|
-
} else {
|
|
281
|
-
for (const c of configs) {
|
|
282
|
-
console.log(paint(c, C.white));
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
console.log('');
|
|
286
|
-
|
|
287
|
-
// ── Package Scripts ───────────────────────────────────────────────────────
|
|
288
|
-
section('Package Scripts');
|
|
289
|
-
if (scripts.length === 0) {
|
|
290
|
-
console.log(paint('No package scripts detected.', C.dim));
|
|
291
|
-
} else {
|
|
292
|
-
const maxNameLen = Math.max(...scripts.map(s => s.name.length));
|
|
293
|
-
for (const s of scripts) {
|
|
294
|
-
const paddedName = s.name.padEnd(maxNameLen, ' ');
|
|
295
|
-
console.log(`${paint(paddedName, C.white)} ${paint(s.command, C.dim)}`);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
console.log('');
|
|
299
|
-
|
|
300
|
-
// ── Structure Preview ─────────────────────────────────────────────────────
|
|
301
|
-
section('Folder Structure');
|
|
302
|
-
|
|
303
|
-
console.log(paint(`${tree.name || '.'}${path.sep}`, C.bold, C.blue));
|
|
304
|
-
|
|
305
|
-
const counter = { count: 0, maxReached: false };
|
|
306
|
-
const children = tree.children ?? [];
|
|
307
|
-
for (let i = 0; i < children.length; i++) {
|
|
308
|
-
if (counter.count >= PREVIEW_LIMIT) {
|
|
309
|
-
console.log(`${CHARS.corner}${paint('...', C.dim)}`);
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
|
-
renderTree(children[i], '', i === children.length - 1, counter);
|
|
313
|
-
if (counter.maxReached) break;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (flatFiles.length > PREVIEW_LIMIT) {
|
|
317
|
-
const hidden = flatFiles.length - PREVIEW_LIMIT;
|
|
318
|
-
console.log(paint(`… ${hidden} more file(s) not shown`, C.dim));
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// ── Footer ────────────────────────────────────────────────────────────────
|
|
322
|
-
console.log('');
|
|
323
|
-
console.log(paint(`Scan completed in ${formatDuration(scanDurationMs)}`, C.green));
|
|
324
|
-
console.log('');
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Render only the project structure for the --structure flag.
|
|
329
|
-
*
|
|
330
|
-
* @param {import('../scanner/scan.js').ScanResult} result
|
|
331
|
-
*/
|
|
332
|
-
export function renderStructure(result) {
|
|
333
|
-
const { tree, flatFiles } = result;
|
|
334
|
-
|
|
335
|
-
section('Folder Structure');
|
|
336
|
-
console.log(paint(`${tree.name || '.'}${path.sep}`, C.bold, C.blue));
|
|
337
|
-
|
|
338
|
-
const counter = { count: 0, maxReached: false };
|
|
339
|
-
const children = tree.children ?? [];
|
|
340
|
-
const limit = flatFiles.length; // No preview limit for focused --structure
|
|
341
|
-
|
|
342
|
-
for (let i = 0; i < children.length; i++) {
|
|
343
|
-
if (counter.count >= limit) {
|
|
344
|
-
console.log(`${CHARS.corner}${paint('...', C.dim)}`);
|
|
345
|
-
break;
|
|
346
|
-
}
|
|
347
|
-
renderTree(children[i], '', i === children.length - 1, counter, limit, 0, Infinity);
|
|
348
|
-
if (counter.maxReached) break;
|
|
349
|
-
}
|
|
350
|
-
}
|
package/src/renderers/index.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Toren — Renderer Registry
|
|
3
|
-
*
|
|
4
|
-
* Maps format-name strings to their render functions.
|
|
5
|
-
*
|
|
6
|
-
* ┌─────────────────────────────────────────────────────────┐
|
|
7
|
-
* │ How to add a new output format: │
|
|
8
|
-
* │ │
|
|
9
|
-
* │ 1. Create src/renderers/<format>-renderer.js │
|
|
10
|
-
* │ and export: render(result, options?) => void │
|
|
11
|
-
* │ │
|
|
12
|
-
* │ 2. Import it here and add one line to RENDERERS. │
|
|
13
|
-
* │ │
|
|
14
|
-
* │ No changes to bin/toren.js or scan.js are needed. │
|
|
15
|
-
* └─────────────────────────────────────────────────────────┘
|
|
16
|
-
*
|
|
17
|
-
* @module renderers/index
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { render as renderConsole } from './console-renderer.js';
|
|
21
|
-
import { render as renderJson } from './json-renderer.js';
|
|
22
|
-
import { render as renderMarkdown } from './markdown-renderer.js';
|
|
23
|
-
import { render as renderHtml } from './html-renderer.js';
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Registry of all available output renderers.
|
|
27
|
-
*
|
|
28
|
-
* Keys — format names accepted by the `--format` flag.
|
|
29
|
-
* Values — render functions with the signature:
|
|
30
|
-
* render(result: ScanResult, options?: { cwd?: string }) => void
|
|
31
|
-
*
|
|
32
|
-
* @type {Record<string, function(import('../scanner/scan.js').ScanResult, object=): void>}
|
|
33
|
-
*/
|
|
34
|
-
const renderers = {
|
|
35
|
-
console: renderConsole,
|
|
36
|
-
json: renderJson,
|
|
37
|
-
markdown: renderMarkdown,
|
|
38
|
-
html: renderHtml,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export default renderers;
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Toren — JSON Renderer
|
|
3
|
-
*
|
|
4
|
-
* Consumes a ScanResult and produces a stable, consistently-ordered JSON
|
|
5
|
-
* object suitable for programmatic consumption.
|
|
6
|
-
*
|
|
7
|
-
* Design contract (mirrors all other renderers):
|
|
8
|
-
* - Accepts a ScanResult and an optional options object.
|
|
9
|
-
* - Never scans files or modifies the data it receives.
|
|
10
|
-
* - All arrays are always present — never undefined or missing.
|
|
11
|
-
* - Property order is fixed and documented below.
|
|
12
|
-
* - Backward compatible: no existing field is removed or renamed.
|
|
13
|
-
*
|
|
14
|
-
* Output property order:
|
|
15
|
-
* 1. meta — schema version + generator provenance
|
|
16
|
-
* 2. project — name (basename), path (relative), detected type
|
|
17
|
-
* 3. frameworks — derived array of detected frameworks ([] when none)
|
|
18
|
-
* 4. entryPoints — array of detected entry-point paths
|
|
19
|
-
* 5. configs — array of detected configuration file paths
|
|
20
|
-
* 6. scripts — array of { name, command } objects
|
|
21
|
-
* 7. statistics — file/folder counts and scan duration (structured)
|
|
22
|
-
* 8. structure — recursive file-tree array
|
|
23
|
-
* 9. summary — retained for backward compatibility (same data as statistics)
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import path from 'node:path';
|
|
27
|
-
import { createRequire } from 'node:module';
|
|
28
|
-
|
|
29
|
-
const require = createRequire(import.meta.url);
|
|
30
|
-
const pkg = require('../../package.json');
|
|
31
|
-
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
// Tree mapper
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Recursively map an internal DirNode / FileNode to a clean JSON shape.
|
|
38
|
-
* Directory nodes always include a `children` array (never undefined).
|
|
39
|
-
*
|
|
40
|
-
* @param {import('../scanner/scan.js').DirNode | import('../scanner/scan.js').FileNode} node
|
|
41
|
-
* @returns {{ type: 'folder'|'file', name: string, children?: object[] }}
|
|
42
|
-
*/
|
|
43
|
-
function mapTree(node) {
|
|
44
|
-
if (node.type === 'directory') {
|
|
45
|
-
return {
|
|
46
|
-
type: 'folder',
|
|
47
|
-
name: node.name,
|
|
48
|
-
children: (node.children || []).map(mapTree),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
return {
|
|
52
|
-
type: 'file',
|
|
53
|
-
name: node.name,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
|
-
// Helpers
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Derive a `frameworks` array from the scanner's `projectType` string.
|
|
63
|
-
* Returns a single-element array when a framework is detected, empty otherwise.
|
|
64
|
-
* This is a pure presentation decision — no business logic is added.
|
|
65
|
-
*
|
|
66
|
-
* @param {string} projectType
|
|
67
|
-
* @returns {string[]}
|
|
68
|
-
*/
|
|
69
|
-
function deriveFrameworks(projectType) {
|
|
70
|
-
if (!projectType || projectType === 'Unknown') return [];
|
|
71
|
-
return [projectType];
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
// Public API
|
|
76
|
-
// ---------------------------------------------------------------------------
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Render a ScanResult as a stable JSON document to stdout.
|
|
80
|
-
*
|
|
81
|
-
* @param {import('../scanner/scan.js').ScanResult} result
|
|
82
|
-
* @param {{ cwd?: string }} [options]
|
|
83
|
-
*/
|
|
84
|
-
export function render(result, options = {}) {
|
|
85
|
-
const {
|
|
86
|
-
rootPath,
|
|
87
|
-
projectType,
|
|
88
|
-
entryPoints = [],
|
|
89
|
-
configs = [],
|
|
90
|
-
scripts = [],
|
|
91
|
-
tree,
|
|
92
|
-
flatFiles = [],
|
|
93
|
-
totalFolders = 0,
|
|
94
|
-
scanDurationMs,
|
|
95
|
-
} = result;
|
|
96
|
-
|
|
97
|
-
const cwd = options.cwd ?? process.cwd();
|
|
98
|
-
const relRoot = path.relative(cwd, rootPath) || '.';
|
|
99
|
-
const rootName = path.basename(rootPath) || relRoot;
|
|
100
|
-
|
|
101
|
-
// Shared statistics values — computed once, used in both `statistics` and
|
|
102
|
-
// the backward-compatible `summary` block.
|
|
103
|
-
// Guard against NaN/undefined: JSON.stringify(NaN) produces null, breaking
|
|
104
|
-
// the schema guarantee that durationMs is always a number.
|
|
105
|
-
const totalFiles = flatFiles.length;
|
|
106
|
-
const durationMs = Number.isFinite(scanDurationMs) ? Math.round(scanDurationMs) : 0;
|
|
107
|
-
|
|
108
|
-
const output = {
|
|
109
|
-
// 1. Provenance — lets consumers detect schema changes.
|
|
110
|
-
meta: {
|
|
111
|
-
generatedBy: 'Toren',
|
|
112
|
-
version: pkg.version,
|
|
113
|
-
schema: 1,
|
|
114
|
-
},
|
|
115
|
-
|
|
116
|
-
// 2. Project identity — name is the human-readable basename; path is the
|
|
117
|
-
// relative path used for filesystem resolution.
|
|
118
|
-
project: {
|
|
119
|
-
name: rootName,
|
|
120
|
-
path: relRoot,
|
|
121
|
-
type: projectType,
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
// 3. Detected frameworks — always an array.
|
|
125
|
-
frameworks: deriveFrameworks(projectType),
|
|
126
|
-
|
|
127
|
-
// 4–6. Discovery results — all arrays, always present, never null.
|
|
128
|
-
entryPoints: Array.isArray(entryPoints) ? entryPoints : [],
|
|
129
|
-
configs: Array.isArray(configs) ? configs : [],
|
|
130
|
-
scripts: Array.isArray(scripts) ? scripts : [],
|
|
131
|
-
|
|
132
|
-
// 7. Structured statistics — always a complete object with numeric values.
|
|
133
|
-
// durationMs is rounded to the nearest millisecond (integer).
|
|
134
|
-
statistics: {
|
|
135
|
-
files: totalFiles,
|
|
136
|
-
folders: totalFolders,
|
|
137
|
-
durationMs,
|
|
138
|
-
},
|
|
139
|
-
|
|
140
|
-
// 8. File-tree — array of root-level nodes; always present.
|
|
141
|
-
// Empty array when the scanned directory is empty.
|
|
142
|
-
structure: (tree?.children || []).map(mapTree),
|
|
143
|
-
|
|
144
|
-
// 9. Backward-compatible summary block — preserved for existing consumers.
|
|
145
|
-
// Contains the same data under the original field names.
|
|
146
|
-
summary: {
|
|
147
|
-
totalFiles,
|
|
148
|
-
totalFolders,
|
|
149
|
-
scanDurationMs: durationMs,
|
|
150
|
-
},
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
console.log(JSON.stringify(output, null, 2));
|
|
154
|
-
}
|