tokenlean 0.1.0
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 +248 -0
- package/bin/tl-api.mjs +515 -0
- package/bin/tl-blame.mjs +345 -0
- package/bin/tl-complexity.mjs +514 -0
- package/bin/tl-component.mjs +274 -0
- package/bin/tl-config.mjs +135 -0
- package/bin/tl-context.mjs +156 -0
- package/bin/tl-coverage.mjs +456 -0
- package/bin/tl-deps.mjs +474 -0
- package/bin/tl-diff.mjs +183 -0
- package/bin/tl-entry.mjs +256 -0
- package/bin/tl-env.mjs +376 -0
- package/bin/tl-exports.mjs +583 -0
- package/bin/tl-flow.mjs +324 -0
- package/bin/tl-history.mjs +289 -0
- package/bin/tl-hotspots.mjs +321 -0
- package/bin/tl-impact.mjs +345 -0
- package/bin/tl-prompt.mjs +175 -0
- package/bin/tl-related.mjs +227 -0
- package/bin/tl-routes.mjs +627 -0
- package/bin/tl-search.mjs +123 -0
- package/bin/tl-structure.mjs +161 -0
- package/bin/tl-symbols.mjs +430 -0
- package/bin/tl-todo.mjs +341 -0
- package/bin/tl-types.mjs +441 -0
- package/bin/tl-unused.mjs +494 -0
- package/package.json +55 -0
- package/src/config.mjs +271 -0
- package/src/output.mjs +251 -0
- package/src/project.mjs +277 -0
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* tl-exports - Show public API surface of a module
|
|
5
|
+
*
|
|
6
|
+
* Lists what a file or package exports - functions, classes, types, constants.
|
|
7
|
+
* Perfect for understanding what a module provides without reading implementation.
|
|
8
|
+
*
|
|
9
|
+
* Usage: tl-exports <file-or-dir> [--types-only]
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Prompt info for tl-prompt
|
|
13
|
+
if (process.argv.includes('--prompt')) {
|
|
14
|
+
console.log(JSON.stringify({
|
|
15
|
+
name: 'tl-exports',
|
|
16
|
+
desc: 'Show public API surface of a module',
|
|
17
|
+
when: 'before-read',
|
|
18
|
+
example: 'tl-exports src/utils/'
|
|
19
|
+
}));
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
24
|
+
import { basename, extname, join, relative, dirname } from 'path';
|
|
25
|
+
import {
|
|
26
|
+
createOutput,
|
|
27
|
+
parseCommonArgs,
|
|
28
|
+
estimateTokens,
|
|
29
|
+
formatTokens,
|
|
30
|
+
COMMON_OPTIONS_HELP
|
|
31
|
+
} from '../src/output.mjs';
|
|
32
|
+
import { findProjectRoot, shouldSkip } from '../src/project.mjs';
|
|
33
|
+
|
|
34
|
+
const HELP = `
|
|
35
|
+
tl-exports - Show public API surface of a module
|
|
36
|
+
|
|
37
|
+
Usage: tl-exports <file-or-dir> [options]
|
|
38
|
+
|
|
39
|
+
Options:
|
|
40
|
+
--types-only, -t Show only type exports (interfaces, types, enums)
|
|
41
|
+
--values-only, -v Show only value exports (functions, classes, constants)
|
|
42
|
+
--with-signatures Include function signatures (more detail)
|
|
43
|
+
--tree Show as import tree (what to import from where)
|
|
44
|
+
${COMMON_OPTIONS_HELP}
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
tl-exports src/utils.ts # Exports from single file
|
|
48
|
+
tl-exports src/utils/ # Exports from directory (finds index)
|
|
49
|
+
tl-exports src/ --tree # Show as import tree
|
|
50
|
+
tl-exports src/api.ts -t # Types only
|
|
51
|
+
tl-exports src/ -j # JSON output
|
|
52
|
+
|
|
53
|
+
Supported: JavaScript, TypeScript, Python, Go
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
// ─────────────────────────────────────────────────────────────
|
|
57
|
+
// Language Detection
|
|
58
|
+
// ─────────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
const JS_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.jsx', '.ts', '.tsx', '.mts']);
|
|
61
|
+
const PY_EXTENSIONS = new Set(['.py']);
|
|
62
|
+
const GO_EXTENSIONS = new Set(['.go']);
|
|
63
|
+
|
|
64
|
+
function detectLanguage(filePath) {
|
|
65
|
+
const ext = extname(filePath).toLowerCase();
|
|
66
|
+
if (JS_EXTENSIONS.has(ext)) return 'js';
|
|
67
|
+
if (PY_EXTENSIONS.has(ext)) return 'python';
|
|
68
|
+
if (GO_EXTENSIONS.has(ext)) return 'go';
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isSourceFile(filePath) {
|
|
73
|
+
return detectLanguage(filePath) !== null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ─────────────────────────────────────────────────────────────
|
|
77
|
+
// JavaScript/TypeScript Export Extraction
|
|
78
|
+
// ─────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
function extractJsExports(content, withSignatures = false) {
|
|
81
|
+
const exports = {
|
|
82
|
+
types: [], // interfaces, type aliases, enums
|
|
83
|
+
functions: [], // functions, arrow functions
|
|
84
|
+
classes: [], // classes
|
|
85
|
+
constants: [], // const exports
|
|
86
|
+
reexports: [], // export { x } from './y' or export * from './z'
|
|
87
|
+
default: null // default export
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const lines = content.split('\n');
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < lines.length; i++) {
|
|
93
|
+
const line = lines[i];
|
|
94
|
+
const trimmed = line.trim();
|
|
95
|
+
|
|
96
|
+
// Skip non-export lines
|
|
97
|
+
if (!trimmed.startsWith('export ')) continue;
|
|
98
|
+
|
|
99
|
+
// Re-exports: export { x, y } from './module'
|
|
100
|
+
const reexportMatch = trimmed.match(/^export\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/);
|
|
101
|
+
if (reexportMatch) {
|
|
102
|
+
const names = reexportMatch[1].split(',').map(n => {
|
|
103
|
+
const parts = n.trim().split(/\s+as\s+/);
|
|
104
|
+
return parts.length > 1 ? `${parts[0].trim()} as ${parts[1].trim()}` : parts[0].trim();
|
|
105
|
+
});
|
|
106
|
+
exports.reexports.push({ names, from: reexportMatch[2] });
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Star re-export: export * from './module'
|
|
111
|
+
const starReexportMatch = trimmed.match(/^export\s+\*\s+(?:as\s+(\w+)\s+)?from\s+['"]([^'"]+)['"]/);
|
|
112
|
+
if (starReexportMatch) {
|
|
113
|
+
exports.reexports.push({
|
|
114
|
+
names: [starReexportMatch[1] ? `* as ${starReexportMatch[1]}` : '*'],
|
|
115
|
+
from: starReexportMatch[2]
|
|
116
|
+
});
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Default export
|
|
121
|
+
if (trimmed.startsWith('export default ')) {
|
|
122
|
+
const defaultMatch = trimmed.match(/^export\s+default\s+(?:(class|function|async\s+function)\s+)?(\w+)?/);
|
|
123
|
+
if (defaultMatch) {
|
|
124
|
+
exports.default = defaultMatch[2] || defaultMatch[1] || 'default';
|
|
125
|
+
}
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Type exports: interface, type, enum
|
|
130
|
+
const typeMatch = trimmed.match(/^export\s+(interface|type|enum|const\s+enum)\s+(\w+)/);
|
|
131
|
+
if (typeMatch) {
|
|
132
|
+
const kind = typeMatch[1].replace('const ', '');
|
|
133
|
+
const name = typeMatch[2];
|
|
134
|
+
|
|
135
|
+
if (withSignatures && kind === 'type') {
|
|
136
|
+
// Get full type definition for type aliases
|
|
137
|
+
const fullDef = extractTypeDefinition(lines, i);
|
|
138
|
+
exports.types.push({ name, kind, signature: fullDef });
|
|
139
|
+
} else {
|
|
140
|
+
exports.types.push({ name, kind });
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Function exports
|
|
146
|
+
const funcMatch = trimmed.match(/^export\s+(async\s+)?function\s+(\w+)\s*(<[^>]+>)?\s*\(([^)]*)\)(?:\s*:\s*([^{]+))?/);
|
|
147
|
+
if (funcMatch) {
|
|
148
|
+
const name = funcMatch[2];
|
|
149
|
+
const isAsync = !!funcMatch[1];
|
|
150
|
+
const generics = funcMatch[3] || '';
|
|
151
|
+
const params = funcMatch[4] || '';
|
|
152
|
+
const returnType = funcMatch[5]?.trim() || '';
|
|
153
|
+
|
|
154
|
+
if (withSignatures) {
|
|
155
|
+
let sig = `${isAsync ? 'async ' : ''}function ${name}${generics}(${params})`;
|
|
156
|
+
if (returnType) sig += `: ${returnType}`;
|
|
157
|
+
exports.functions.push({ name, signature: sig });
|
|
158
|
+
} else {
|
|
159
|
+
exports.functions.push({ name });
|
|
160
|
+
}
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Class exports
|
|
165
|
+
const classMatch = trimmed.match(/^export\s+(abstract\s+)?class\s+(\w+)(\s*<[^>]+>)?(\s+extends\s+\w+)?(\s+implements\s+[^{]+)?/);
|
|
166
|
+
if (classMatch) {
|
|
167
|
+
const name = classMatch[2];
|
|
168
|
+
const isAbstract = !!classMatch[1];
|
|
169
|
+
const generics = classMatch[3]?.trim() || '';
|
|
170
|
+
const extendsClause = classMatch[4]?.trim() || '';
|
|
171
|
+
const implementsClause = classMatch[5]?.trim() || '';
|
|
172
|
+
|
|
173
|
+
if (withSignatures) {
|
|
174
|
+
let sig = `${isAbstract ? 'abstract ' : ''}class ${name}${generics}`;
|
|
175
|
+
if (extendsClause) sig += ` ${extendsClause}`;
|
|
176
|
+
if (implementsClause) sig += ` ${implementsClause}`;
|
|
177
|
+
exports.classes.push({ name, signature: sig });
|
|
178
|
+
} else {
|
|
179
|
+
exports.classes.push({ name });
|
|
180
|
+
}
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Const exports (including arrow functions)
|
|
185
|
+
const constMatch = trimmed.match(/^export\s+const\s+(\w+)(?:\s*:\s*([^=]+))?\s*=/);
|
|
186
|
+
if (constMatch) {
|
|
187
|
+
const name = constMatch[1];
|
|
188
|
+
const typeAnnotation = constMatch[2]?.trim();
|
|
189
|
+
|
|
190
|
+
// Check if it's an arrow function
|
|
191
|
+
const isArrowFunc = trimmed.includes('=>') || (typeAnnotation && typeAnnotation.includes('=>'));
|
|
192
|
+
|
|
193
|
+
if (isArrowFunc) {
|
|
194
|
+
if (withSignatures && typeAnnotation) {
|
|
195
|
+
exports.functions.push({ name, signature: `const ${name}: ${typeAnnotation}` });
|
|
196
|
+
} else {
|
|
197
|
+
exports.functions.push({ name });
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
if (withSignatures && typeAnnotation) {
|
|
201
|
+
exports.constants.push({ name, type: typeAnnotation });
|
|
202
|
+
} else {
|
|
203
|
+
exports.constants.push({ name });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return exports;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function extractTypeDefinition(lines, startLine) {
|
|
214
|
+
const line = lines[startLine].trim();
|
|
215
|
+
// Simple one-line type
|
|
216
|
+
if (line.endsWith(';')) {
|
|
217
|
+
return line.replace(/^export\s+/, '');
|
|
218
|
+
}
|
|
219
|
+
// Multi-line - just return first line for brevity
|
|
220
|
+
return line.replace(/^export\s+/, '').replace(/\s*=\s*$/, ' = ...');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ─────────────────────────────────────────────────────────────
|
|
224
|
+
// Python Export Extraction
|
|
225
|
+
// ─────────────────────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
function extractPythonExports(content) {
|
|
228
|
+
const exports = {
|
|
229
|
+
functions: [],
|
|
230
|
+
classes: [],
|
|
231
|
+
constants: [],
|
|
232
|
+
all: null // __all__ list
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const lines = content.split('\n');
|
|
236
|
+
|
|
237
|
+
// Check for __all__
|
|
238
|
+
const allMatch = content.match(/__all__\s*=\s*\[([^\]]+)\]/);
|
|
239
|
+
if (allMatch) {
|
|
240
|
+
exports.all = allMatch[1]
|
|
241
|
+
.split(',')
|
|
242
|
+
.map(s => s.trim().replace(/['"]/g, ''))
|
|
243
|
+
.filter(Boolean);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
for (const line of lines) {
|
|
247
|
+
const trimmed = line.trim();
|
|
248
|
+
|
|
249
|
+
// Top-level functions (not indented)
|
|
250
|
+
if (!line.startsWith(' ') && !line.startsWith('\t')) {
|
|
251
|
+
const funcMatch = trimmed.match(/^(?:async\s+)?def\s+(\w+)/);
|
|
252
|
+
if (funcMatch && !funcMatch[1].startsWith('_')) {
|
|
253
|
+
exports.functions.push({ name: funcMatch[1] });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const classMatch = trimmed.match(/^class\s+(\w+)/);
|
|
257
|
+
if (classMatch && !classMatch[1].startsWith('_')) {
|
|
258
|
+
exports.classes.push({ name: classMatch[1] });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Constants (UPPER_CASE at module level)
|
|
262
|
+
const constMatch = trimmed.match(/^([A-Z][A-Z0-9_]*)\s*=/);
|
|
263
|
+
if (constMatch) {
|
|
264
|
+
exports.constants.push({ name: constMatch[1] });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return exports;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ─────────────────────────────────────────────────────────────
|
|
273
|
+
// Go Export Extraction
|
|
274
|
+
// ─────────────────────────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
function extractGoExports(content) {
|
|
277
|
+
const exports = {
|
|
278
|
+
functions: [],
|
|
279
|
+
types: [],
|
|
280
|
+
constants: [],
|
|
281
|
+
variables: []
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const lines = content.split('\n');
|
|
285
|
+
|
|
286
|
+
for (const line of lines) {
|
|
287
|
+
const trimmed = line.trim();
|
|
288
|
+
|
|
289
|
+
// Exported functions (start with uppercase)
|
|
290
|
+
const funcMatch = trimmed.match(/^func\s+(?:\([^)]+\)\s+)?([A-Z]\w*)\s*\(/);
|
|
291
|
+
if (funcMatch) {
|
|
292
|
+
exports.functions.push({ name: funcMatch[1] });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Exported types
|
|
296
|
+
const typeMatch = trimmed.match(/^type\s+([A-Z]\w*)\s+(struct|interface)/);
|
|
297
|
+
if (typeMatch) {
|
|
298
|
+
exports.types.push({ name: typeMatch[1], kind: typeMatch[2] });
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Exported constants
|
|
302
|
+
const constMatch = trimmed.match(/^const\s+([A-Z]\w*)/);
|
|
303
|
+
if (constMatch) {
|
|
304
|
+
exports.constants.push({ name: constMatch[1] });
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Exported variables
|
|
308
|
+
const varMatch = trimmed.match(/^var\s+([A-Z]\w*)/);
|
|
309
|
+
if (varMatch) {
|
|
310
|
+
exports.variables.push({ name: varMatch[1] });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return exports;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ─────────────────────────────────────────────────────────────
|
|
318
|
+
// File Discovery
|
|
319
|
+
// ─────────────────────────────────────────────────────────────
|
|
320
|
+
|
|
321
|
+
function findSourceFiles(dir, files = []) {
|
|
322
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
323
|
+
|
|
324
|
+
for (const entry of entries) {
|
|
325
|
+
const fullPath = join(dir, entry.name);
|
|
326
|
+
|
|
327
|
+
if (entry.isDirectory()) {
|
|
328
|
+
if (!shouldSkip(entry.name, true)) {
|
|
329
|
+
findSourceFiles(fullPath, files);
|
|
330
|
+
}
|
|
331
|
+
} else if (entry.isFile()) {
|
|
332
|
+
if (!shouldSkip(entry.name, false) && isSourceFile(fullPath)) {
|
|
333
|
+
files.push(fullPath);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return files;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function findIndexFile(dir) {
|
|
342
|
+
const indexNames = ['index.ts', 'index.tsx', 'index.js', 'index.mjs', 'mod.ts', '__init__.py'];
|
|
343
|
+
|
|
344
|
+
for (const name of indexNames) {
|
|
345
|
+
const indexPath = join(dir, name);
|
|
346
|
+
if (existsSync(indexPath)) {
|
|
347
|
+
return indexPath;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ─────────────────────────────────────────────────────────────
|
|
355
|
+
// Formatting
|
|
356
|
+
// ─────────────────────────────────────────────────────────────
|
|
357
|
+
|
|
358
|
+
function formatExports(exports, out, options = {}) {
|
|
359
|
+
const { typesOnly, valuesOnly, withSignatures, relPath } = options;
|
|
360
|
+
|
|
361
|
+
let count = 0;
|
|
362
|
+
|
|
363
|
+
// Default export
|
|
364
|
+
if (exports.default && !typesOnly) {
|
|
365
|
+
out.add(` default → ${exports.default}`);
|
|
366
|
+
count++;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Types
|
|
370
|
+
if (!valuesOnly && exports.types?.length > 0) {
|
|
371
|
+
for (const t of exports.types) {
|
|
372
|
+
if (withSignatures && t.signature) {
|
|
373
|
+
out.add(` ${t.kind} ${t.name}`);
|
|
374
|
+
out.add(` ${t.signature}`);
|
|
375
|
+
} else {
|
|
376
|
+
out.add(` ${t.kind} ${t.name}`);
|
|
377
|
+
}
|
|
378
|
+
count++;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Functions
|
|
383
|
+
if (!typesOnly && exports.functions?.length > 0) {
|
|
384
|
+
for (const f of exports.functions) {
|
|
385
|
+
if (withSignatures && f.signature) {
|
|
386
|
+
out.add(` fn ${f.name}`);
|
|
387
|
+
out.add(` ${f.signature}`);
|
|
388
|
+
} else {
|
|
389
|
+
out.add(` fn ${f.name}`);
|
|
390
|
+
}
|
|
391
|
+
count++;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Classes
|
|
396
|
+
if (!typesOnly && exports.classes?.length > 0) {
|
|
397
|
+
for (const c of exports.classes) {
|
|
398
|
+
if (withSignatures && c.signature) {
|
|
399
|
+
out.add(` class ${c.name}`);
|
|
400
|
+
out.add(` ${c.signature}`);
|
|
401
|
+
} else {
|
|
402
|
+
out.add(` class ${c.name}`);
|
|
403
|
+
}
|
|
404
|
+
count++;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Constants
|
|
409
|
+
if (!typesOnly && exports.constants?.length > 0) {
|
|
410
|
+
for (const c of exports.constants) {
|
|
411
|
+
if (withSignatures && c.type) {
|
|
412
|
+
out.add(` const ${c.name}: ${c.type}`);
|
|
413
|
+
} else {
|
|
414
|
+
out.add(` const ${c.name}`);
|
|
415
|
+
}
|
|
416
|
+
count++;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Variables (Go)
|
|
421
|
+
if (!typesOnly && exports.variables?.length > 0) {
|
|
422
|
+
for (const v of exports.variables) {
|
|
423
|
+
out.add(` var ${v.name}`);
|
|
424
|
+
count++;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Re-exports
|
|
429
|
+
if (exports.reexports?.length > 0) {
|
|
430
|
+
for (const r of exports.reexports) {
|
|
431
|
+
out.add(` ↳ { ${r.names.join(', ')} } from '${r.from}'`);
|
|
432
|
+
count += r.names.length;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return count;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function formatAsTree(fileExports, out, projectRoot) {
|
|
440
|
+
// Group by directory
|
|
441
|
+
const byDir = new Map();
|
|
442
|
+
|
|
443
|
+
for (const { file, exports } of fileExports) {
|
|
444
|
+
const dir = dirname(file);
|
|
445
|
+
if (!byDir.has(dir)) {
|
|
446
|
+
byDir.set(dir, []);
|
|
447
|
+
}
|
|
448
|
+
byDir.get(dir).push({ file: basename(file), exports });
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
for (const [dir, files] of byDir) {
|
|
452
|
+
out.add(`📁 ${dir || '.'}/`);
|
|
453
|
+
|
|
454
|
+
for (const { file, exports } of files) {
|
|
455
|
+
const allExports = [];
|
|
456
|
+
|
|
457
|
+
if (exports.default) allExports.push(`default`);
|
|
458
|
+
exports.types?.forEach(t => allExports.push(t.name));
|
|
459
|
+
exports.functions?.forEach(f => allExports.push(f.name));
|
|
460
|
+
exports.classes?.forEach(c => allExports.push(c.name));
|
|
461
|
+
exports.constants?.forEach(c => allExports.push(c.name));
|
|
462
|
+
|
|
463
|
+
if (allExports.length > 0) {
|
|
464
|
+
out.add(` ${file}: { ${allExports.join(', ')} }`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
out.blank();
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ─────────────────────────────────────────────────────────────
|
|
472
|
+
// Main
|
|
473
|
+
// ─────────────────────────────────────────────────────────────
|
|
474
|
+
|
|
475
|
+
const args = process.argv.slice(2);
|
|
476
|
+
const options = parseCommonArgs(args);
|
|
477
|
+
const typesOnly = options.remaining.includes('--types-only') || options.remaining.includes('-t');
|
|
478
|
+
const valuesOnly = options.remaining.includes('--values-only') || options.remaining.includes('-v');
|
|
479
|
+
const withSignatures = options.remaining.includes('--with-signatures');
|
|
480
|
+
const treeMode = options.remaining.includes('--tree');
|
|
481
|
+
const targetPath = options.remaining.find(a => !a.startsWith('-'));
|
|
482
|
+
|
|
483
|
+
if (options.help || !targetPath) {
|
|
484
|
+
console.log(HELP);
|
|
485
|
+
process.exit(options.help ? 0 : 1);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (!existsSync(targetPath)) {
|
|
489
|
+
console.error(`Path not found: ${targetPath}`);
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const projectRoot = findProjectRoot();
|
|
494
|
+
const out = createOutput(options);
|
|
495
|
+
|
|
496
|
+
const stat = statSync(targetPath);
|
|
497
|
+
let files = [];
|
|
498
|
+
|
|
499
|
+
if (stat.isDirectory()) {
|
|
500
|
+
// Check for index file first
|
|
501
|
+
const indexFile = findIndexFile(targetPath);
|
|
502
|
+
if (indexFile && !treeMode) {
|
|
503
|
+
files = [indexFile];
|
|
504
|
+
} else {
|
|
505
|
+
files = findSourceFiles(targetPath);
|
|
506
|
+
}
|
|
507
|
+
} else {
|
|
508
|
+
files = [targetPath];
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (files.length === 0) {
|
|
512
|
+
console.error('No source files found');
|
|
513
|
+
process.exit(1);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const allFileExports = [];
|
|
517
|
+
let totalExports = 0;
|
|
518
|
+
|
|
519
|
+
for (const filePath of files) {
|
|
520
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
521
|
+
const lang = detectLanguage(filePath);
|
|
522
|
+
const relPath = relative(projectRoot, filePath);
|
|
523
|
+
|
|
524
|
+
let exports;
|
|
525
|
+
switch (lang) {
|
|
526
|
+
case 'js':
|
|
527
|
+
exports = extractJsExports(content, withSignatures);
|
|
528
|
+
break;
|
|
529
|
+
case 'python':
|
|
530
|
+
exports = extractPythonExports(content);
|
|
531
|
+
break;
|
|
532
|
+
case 'go':
|
|
533
|
+
exports = extractGoExports(content);
|
|
534
|
+
break;
|
|
535
|
+
default:
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Count exports
|
|
540
|
+
let count = 0;
|
|
541
|
+
if (exports.default) count++;
|
|
542
|
+
count += exports.types?.length || 0;
|
|
543
|
+
count += exports.functions?.length || 0;
|
|
544
|
+
count += exports.classes?.length || 0;
|
|
545
|
+
count += exports.constants?.length || 0;
|
|
546
|
+
count += exports.variables?.length || 0;
|
|
547
|
+
exports.reexports?.forEach(r => count += r.names.length);
|
|
548
|
+
|
|
549
|
+
if (count === 0) continue;
|
|
550
|
+
|
|
551
|
+
totalExports += count;
|
|
552
|
+
allFileExports.push({ file: relPath, exports, count });
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Set JSON data
|
|
556
|
+
out.setData('files', allFileExports.map(({ file, exports }) => ({ file, exports })));
|
|
557
|
+
out.setData('totalExports', totalExports);
|
|
558
|
+
|
|
559
|
+
// Output
|
|
560
|
+
if (treeMode) {
|
|
561
|
+
out.header('Export Tree:');
|
|
562
|
+
out.blank();
|
|
563
|
+
formatAsTree(allFileExports, out, projectRoot);
|
|
564
|
+
} else {
|
|
565
|
+
for (const { file, exports, count } of allFileExports) {
|
|
566
|
+
if (allFileExports.length > 1) {
|
|
567
|
+
out.add(`📦 ${file} (${count} exports)`);
|
|
568
|
+
} else {
|
|
569
|
+
out.header(`📦 ${file} (${count} exports)`);
|
|
570
|
+
out.blank();
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
formatExports(exports, out, { typesOnly, valuesOnly, withSignatures, relPath: file });
|
|
574
|
+
out.blank();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Summary
|
|
579
|
+
if (!options.quiet && allFileExports.length > 0) {
|
|
580
|
+
out.add(`Total: ${totalExports} exports from ${allFileExports.length} file(s)`);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
out.print();
|