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
package/src/project.mjs
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared project utilities for tokenlean CLI tools
|
|
3
|
+
*
|
|
4
|
+
* Project detection, file categorization, and skip lists.
|
|
5
|
+
*
|
|
6
|
+
* Built-in defaults can be extended via .tokenleanrc.json:
|
|
7
|
+
* skipDirs: ["my-custom-dir"]
|
|
8
|
+
* skipExtensions: [".custom"]
|
|
9
|
+
* importantDirs: ["domain"]
|
|
10
|
+
* importantFiles: ["ARCHITECTURE.md"]
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync } from 'fs';
|
|
14
|
+
import { dirname, join, relative, extname } from 'path';
|
|
15
|
+
import { getConfig } from './config.mjs';
|
|
16
|
+
|
|
17
|
+
// ─────────────────────────────────────────────────────────────
|
|
18
|
+
// Built-in Defaults (users can extend via config)
|
|
19
|
+
// ─────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
const DEFAULT_SKIP_DIRS = [
|
|
22
|
+
'node_modules', '.git', 'android', 'ios', 'dist', 'build',
|
|
23
|
+
'.expo', '.next', 'coverage', '__pycache__', '.cache', '.turbo',
|
|
24
|
+
'.venv', 'venv', 'env', '.tox', '.mypy_cache', '.pytest_cache',
|
|
25
|
+
'vendor', 'target', 'out', '.gradle', '.idea', '.vscode'
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const DEFAULT_SKIP_EXTENSIONS = [
|
|
29
|
+
'.jpg', '.jpeg', '.png', '.gif', '.ico', '.svg', '.webp',
|
|
30
|
+
'.mp3', '.mp4', '.wav', '.webm', '.ogg',
|
|
31
|
+
'.woff', '.woff2', '.ttf', '.eot',
|
|
32
|
+
'.zip', '.tar', '.gz', '.rar',
|
|
33
|
+
'.pdf', '.doc', '.docx',
|
|
34
|
+
'.lock', '.log',
|
|
35
|
+
'.min.js', '.min.css',
|
|
36
|
+
'.map'
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const DEFAULT_IMPORTANT_FILES = [
|
|
40
|
+
'package.json', 'tsconfig.json', 'CLAUDE.md', 'README.md',
|
|
41
|
+
'app.json', '.env.example', 'index.ts', 'index.tsx', 'index.js',
|
|
42
|
+
'Cargo.toml', 'go.mod', 'pyproject.toml', 'requirements.txt',
|
|
43
|
+
'Makefile', 'Dockerfile', 'docker-compose.yml'
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const DEFAULT_IMPORTANT_DIRS = [
|
|
47
|
+
'src', 'app', 'components', 'lib', 'utils', 'hooks', 'store',
|
|
48
|
+
'api', 'services', 'types', '.claude', 'scripts', 'tests',
|
|
49
|
+
'test', '__tests__', 'spec', 'cmd', 'pkg', 'internal'
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
// ─────────────────────────────────────────────────────────────
|
|
53
|
+
// Combined Sets (defaults + user config extensions)
|
|
54
|
+
// ─────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
let _cachedSets = null;
|
|
57
|
+
|
|
58
|
+
function getCombinedSets() {
|
|
59
|
+
if (_cachedSets) return _cachedSets;
|
|
60
|
+
|
|
61
|
+
const config = getConfig();
|
|
62
|
+
|
|
63
|
+
_cachedSets = {
|
|
64
|
+
skipDirs: new Set([...DEFAULT_SKIP_DIRS, ...(config.skipDirs || [])]),
|
|
65
|
+
skipExtensions: new Set([...DEFAULT_SKIP_EXTENSIONS, ...(config.skipExtensions || [])]),
|
|
66
|
+
importantFiles: new Set([...DEFAULT_IMPORTANT_FILES, ...(config.importantFiles || [])]),
|
|
67
|
+
importantDirs: new Set([...DEFAULT_IMPORTANT_DIRS, ...(config.importantDirs || [])])
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return _cachedSets;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Export getters that return combined sets
|
|
74
|
+
export function getSkipDirs() { return getCombinedSets().skipDirs; }
|
|
75
|
+
export function getSkipExtensions() { return getCombinedSets().skipExtensions; }
|
|
76
|
+
export function getImportantFiles() { return getCombinedSets().importantFiles; }
|
|
77
|
+
export function getImportantDirs() { return getCombinedSets().importantDirs; }
|
|
78
|
+
|
|
79
|
+
// Legacy exports for backwards compatibility (return combined sets)
|
|
80
|
+
export const SKIP_DIRS = new Set(DEFAULT_SKIP_DIRS);
|
|
81
|
+
export const SKIP_EXTENSIONS = new Set(DEFAULT_SKIP_EXTENSIONS);
|
|
82
|
+
export const IMPORTANT_FILES = new Set(DEFAULT_IMPORTANT_FILES);
|
|
83
|
+
export const IMPORTANT_DIRS = new Set(DEFAULT_IMPORTANT_DIRS);
|
|
84
|
+
|
|
85
|
+
// Clear cache (useful when config changes)
|
|
86
|
+
export function clearProjectCache() { _cachedSets = null; }
|
|
87
|
+
|
|
88
|
+
// ─────────────────────────────────────────────────────────────
|
|
89
|
+
// Project Detection
|
|
90
|
+
// ─────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Find the project root by looking for package.json or .git
|
|
94
|
+
*/
|
|
95
|
+
export function findProjectRoot(startDir = process.cwd()) {
|
|
96
|
+
let dir = startDir;
|
|
97
|
+
while (dir !== '/') {
|
|
98
|
+
if (existsSync(join(dir, 'package.json')) || existsSync(join(dir, '.git'))) {
|
|
99
|
+
return dir;
|
|
100
|
+
}
|
|
101
|
+
dir = dirname(dir);
|
|
102
|
+
}
|
|
103
|
+
return startDir;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get project info from package.json if available
|
|
108
|
+
*/
|
|
109
|
+
export function getProjectInfo(projectRoot) {
|
|
110
|
+
const pkgPath = join(projectRoot, 'package.json');
|
|
111
|
+
if (existsSync(pkgPath)) {
|
|
112
|
+
try {
|
|
113
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
114
|
+
return {
|
|
115
|
+
name: pkg.name,
|
|
116
|
+
version: pkg.version,
|
|
117
|
+
type: 'node',
|
|
118
|
+
hasTypeScript: existsSync(join(projectRoot, 'tsconfig.json'))
|
|
119
|
+
};
|
|
120
|
+
} catch {
|
|
121
|
+
// Invalid JSON
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check for other project types
|
|
126
|
+
if (existsSync(join(projectRoot, 'Cargo.toml'))) {
|
|
127
|
+
return { type: 'rust' };
|
|
128
|
+
}
|
|
129
|
+
if (existsSync(join(projectRoot, 'go.mod'))) {
|
|
130
|
+
return { type: 'go' };
|
|
131
|
+
}
|
|
132
|
+
if (existsSync(join(projectRoot, 'pyproject.toml')) || existsSync(join(projectRoot, 'setup.py'))) {
|
|
133
|
+
return { type: 'python' };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { type: 'unknown' };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─────────────────────────────────────────────────────────────
|
|
140
|
+
// File Categorization
|
|
141
|
+
// ─────────────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Categorize a file path as source, test, story, or mock
|
|
145
|
+
*/
|
|
146
|
+
export function categorizeFile(filePath, projectRoot = '') {
|
|
147
|
+
const rel = projectRoot ? relative(projectRoot, filePath) : filePath;
|
|
148
|
+
const lower = rel.toLowerCase();
|
|
149
|
+
|
|
150
|
+
if (lower.includes('test') || lower.includes('spec') || lower.includes('__tests__')) {
|
|
151
|
+
return 'test';
|
|
152
|
+
}
|
|
153
|
+
if (lower.includes('stories') || lower.includes('storybook') || lower.endsWith('.stories.tsx') || lower.endsWith('.stories.jsx')) {
|
|
154
|
+
return 'story';
|
|
155
|
+
}
|
|
156
|
+
if (lower.includes('mock') || lower.includes('fixture') || lower.includes('__mocks__')) {
|
|
157
|
+
return 'mock';
|
|
158
|
+
}
|
|
159
|
+
if (lower.includes('e2e') || lower.includes('cypress') || lower.includes('playwright')) {
|
|
160
|
+
return 'e2e';
|
|
161
|
+
}
|
|
162
|
+
return 'source';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Check if a path should be skipped during traversal
|
|
167
|
+
*/
|
|
168
|
+
export function shouldSkip(name, isDir = false) {
|
|
169
|
+
const { skipDirs, skipExtensions, importantDirs } = getCombinedSets();
|
|
170
|
+
|
|
171
|
+
// Skip hidden files/dirs (except important ones)
|
|
172
|
+
if (name.startsWith('.') && !importantDirs.has(name)) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (isDir) {
|
|
177
|
+
return skipDirs.has(name);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Check extension
|
|
181
|
+
const ext = extname(name).toLowerCase();
|
|
182
|
+
if (skipExtensions.has(ext)) {
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Check for minified files
|
|
187
|
+
if (name.endsWith('.min.js') || name.endsWith('.min.css')) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Check if a file/dir is considered important
|
|
196
|
+
*/
|
|
197
|
+
export function isImportant(name, isDir = false) {
|
|
198
|
+
const { importantDirs, importantFiles } = getCombinedSets();
|
|
199
|
+
|
|
200
|
+
if (isDir) {
|
|
201
|
+
return importantDirs.has(name);
|
|
202
|
+
}
|
|
203
|
+
return importantFiles.has(name);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ─────────────────────────────────────────────────────────────
|
|
207
|
+
// Language Detection
|
|
208
|
+
// ─────────────────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
const LANG_MAP = {
|
|
211
|
+
// JavaScript/TypeScript
|
|
212
|
+
'.js': 'javascript',
|
|
213
|
+
'.mjs': 'javascript',
|
|
214
|
+
'.cjs': 'javascript',
|
|
215
|
+
'.jsx': 'javascript',
|
|
216
|
+
'.ts': 'typescript',
|
|
217
|
+
'.tsx': 'typescript',
|
|
218
|
+
'.mts': 'typescript',
|
|
219
|
+
|
|
220
|
+
// Other languages
|
|
221
|
+
'.py': 'python',
|
|
222
|
+
'.go': 'go',
|
|
223
|
+
'.rs': 'rust',
|
|
224
|
+
'.rb': 'ruby',
|
|
225
|
+
'.java': 'java',
|
|
226
|
+
'.kt': 'kotlin',
|
|
227
|
+
'.swift': 'swift',
|
|
228
|
+
'.c': 'c',
|
|
229
|
+
'.cpp': 'cpp',
|
|
230
|
+
'.h': 'c',
|
|
231
|
+
'.hpp': 'cpp',
|
|
232
|
+
'.cs': 'csharp',
|
|
233
|
+
'.php': 'php',
|
|
234
|
+
|
|
235
|
+
// Config/data
|
|
236
|
+
'.json': 'json',
|
|
237
|
+
'.yaml': 'yaml',
|
|
238
|
+
'.yml': 'yaml',
|
|
239
|
+
'.toml': 'toml',
|
|
240
|
+
'.xml': 'xml',
|
|
241
|
+
'.md': 'markdown',
|
|
242
|
+
'.mdx': 'markdown',
|
|
243
|
+
|
|
244
|
+
// Styles
|
|
245
|
+
'.css': 'css',
|
|
246
|
+
'.scss': 'scss',
|
|
247
|
+
'.sass': 'sass',
|
|
248
|
+
'.less': 'less',
|
|
249
|
+
|
|
250
|
+
// Shell
|
|
251
|
+
'.sh': 'shell',
|
|
252
|
+
'.bash': 'shell',
|
|
253
|
+
'.zsh': 'shell',
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Detect the language of a file by extension
|
|
258
|
+
*/
|
|
259
|
+
export function detectLanguage(filePath) {
|
|
260
|
+
const ext = extname(filePath).toLowerCase();
|
|
261
|
+
return LANG_MAP[ext] || null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Check if a file is a code file (not config, data, or assets)
|
|
266
|
+
*/
|
|
267
|
+
export function isCodeFile(filePath) {
|
|
268
|
+
const lang = detectLanguage(filePath);
|
|
269
|
+
if (!lang) return false;
|
|
270
|
+
|
|
271
|
+
const codeLanguages = new Set([
|
|
272
|
+
'javascript', 'typescript', 'python', 'go', 'rust', 'ruby',
|
|
273
|
+
'java', 'kotlin', 'swift', 'c', 'cpp', 'csharp', 'php'
|
|
274
|
+
]);
|
|
275
|
+
|
|
276
|
+
return codeLanguages.has(lang);
|
|
277
|
+
}
|