tokenlean 0.1.0 → 0.3.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 +44 -1
- package/bin/tl-cache.mjs +168 -0
- package/bin/tl-component.mjs +109 -83
- package/bin/tl-context.mjs +147 -98
- package/bin/tl-diff.mjs +95 -62
- package/bin/tl-entry.mjs +7 -1
- package/bin/tl-impact.mjs +10 -2
- package/bin/tl-related.mjs +122 -75
- package/bin/tl-search.mjs +148 -27
- package/bin/tl-structure.mjs +152 -83
- package/bin/tl-todo.mjs +8 -1
- package/bin/tl-unused.mjs +29 -19
- package/package.json +2 -1
- package/src/cache.mjs +493 -0
- package/src/config.mjs +6 -0
package/bin/tl-related.mjs
CHANGED
|
@@ -23,41 +23,38 @@ if (process.argv.includes('--prompt')) {
|
|
|
23
23
|
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
24
24
|
import { join, dirname, basename, relative, extname } from 'path';
|
|
25
25
|
import { execSync } from 'child_process';
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
import {
|
|
27
|
+
createOutput,
|
|
28
|
+
parseCommonArgs,
|
|
29
|
+
estimateTokens,
|
|
30
|
+
formatTokens,
|
|
31
|
+
shellEscape,
|
|
32
|
+
COMMON_OPTIONS_HELP
|
|
33
|
+
} from '../src/output.mjs';
|
|
34
|
+
import { findProjectRoot } from '../src/project.mjs';
|
|
35
|
+
import { withCache } from '../src/cache.mjs';
|
|
36
|
+
|
|
37
|
+
const HELP = `
|
|
38
|
+
tl-related - Find related files (tests, types, usages)
|
|
39
|
+
|
|
40
|
+
Usage: tl-related <file> [options]
|
|
41
|
+
${COMMON_OPTIONS_HELP}
|
|
42
|
+
|
|
43
|
+
Examples:
|
|
44
|
+
tl-related src/Button.tsx # Find tests, types, importers
|
|
45
|
+
tl-related src/api.ts -j # JSON output
|
|
46
|
+
tl-related src/utils.ts -q # Quiet (file paths only)
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
// Check for ripgrep
|
|
50
|
+
try {
|
|
51
|
+
execSync('which rg', { stdio: 'ignore' });
|
|
52
|
+
} catch {
|
|
53
|
+
console.error('ripgrep (rg) not found. Install: brew install ripgrep');
|
|
30
54
|
process.exit(1);
|
|
31
55
|
}
|
|
32
56
|
|
|
33
|
-
|
|
34
|
-
'node_modules', '.git', 'android', 'ios', 'dist', 'build', '.expo', '.next'
|
|
35
|
-
]);
|
|
36
|
-
|
|
37
|
-
function findProjectRoot() {
|
|
38
|
-
let dir = process.cwd();
|
|
39
|
-
while (dir !== '/') {
|
|
40
|
-
if (existsSync(join(dir, 'package.json'))) return dir;
|
|
41
|
-
dir = dirname(dir);
|
|
42
|
-
}
|
|
43
|
-
return process.cwd();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function estimateTokens(filePath) {
|
|
47
|
-
try {
|
|
48
|
-
const content = readFileSync(filePath, 'utf-8');
|
|
49
|
-
return Math.ceil(content.length / 4);
|
|
50
|
-
} catch {
|
|
51
|
-
return 0;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function formatTokens(tokens) {
|
|
56
|
-
if (tokens >= 1000) return `${(tokens / 1000).toFixed(1)}k`;
|
|
57
|
-
return String(tokens);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function findTestFiles(filePath, projectRoot) {
|
|
57
|
+
function findTestFiles(filePath) {
|
|
61
58
|
const dir = dirname(filePath);
|
|
62
59
|
const name = basename(filePath, extname(filePath));
|
|
63
60
|
const tests = [];
|
|
@@ -66,10 +63,15 @@ function findTestFiles(filePath, projectRoot) {
|
|
|
66
63
|
const patterns = [
|
|
67
64
|
join(dir, `${name}.test.ts`),
|
|
68
65
|
join(dir, `${name}.test.tsx`),
|
|
66
|
+
join(dir, `${name}.test.js`),
|
|
67
|
+
join(dir, `${name}.test.jsx`),
|
|
69
68
|
join(dir, `${name}.spec.ts`),
|
|
70
69
|
join(dir, `${name}.spec.tsx`),
|
|
70
|
+
join(dir, `${name}.spec.js`),
|
|
71
|
+
join(dir, `${name}.spec.jsx`),
|
|
71
72
|
join(dir, '__tests__', `${name}.test.ts`),
|
|
72
73
|
join(dir, '__tests__', `${name}.test.tsx`),
|
|
74
|
+
join(dir, '__tests__', `${name}.test.js`),
|
|
73
75
|
join(dir, '__tests__', `${name}.spec.ts`),
|
|
74
76
|
join(dir, '__tests__', `${name}.spec.tsx`),
|
|
75
77
|
];
|
|
@@ -104,10 +106,12 @@ function findTypeFiles(filePath, projectRoot) {
|
|
|
104
106
|
// Check project-wide types directory
|
|
105
107
|
const globalTypes = join(projectRoot, 'src', 'types');
|
|
106
108
|
if (existsSync(globalTypes)) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
try {
|
|
110
|
+
const typeFiles = readdirSync(globalTypes).filter(f => f.endsWith('.ts'));
|
|
111
|
+
for (const tf of typeFiles.slice(0, 5)) {
|
|
112
|
+
types.push(join(globalTypes, tf));
|
|
113
|
+
}
|
|
114
|
+
} catch { /* permission error */ }
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
return types;
|
|
@@ -115,14 +119,18 @@ function findTypeFiles(filePath, projectRoot) {
|
|
|
115
119
|
|
|
116
120
|
function findImporters(filePath, projectRoot) {
|
|
117
121
|
const name = basename(filePath, extname(filePath));
|
|
118
|
-
|
|
119
122
|
const importers = new Set();
|
|
120
123
|
|
|
121
|
-
// Search for files that might import this module
|
|
124
|
+
// Search for files that might import this module (with caching)
|
|
122
125
|
try {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
const cacheKey = { op: 'rg-find-importers', module: name, glob: '*.{js,mjs,ts,tsx,jsx}' };
|
|
127
|
+
const result = withCache(
|
|
128
|
+
cacheKey,
|
|
129
|
+
() => execSync(
|
|
130
|
+
`rg -l -g "*.{js,mjs,ts,tsx,jsx}" -e "${shellEscape(name)}" "${shellEscape(projectRoot)}" 2>/dev/null || true`,
|
|
131
|
+
{ encoding: 'utf-8', maxBuffer: 5 * 1024 * 1024 }
|
|
132
|
+
),
|
|
133
|
+
{ projectRoot }
|
|
126
134
|
);
|
|
127
135
|
|
|
128
136
|
for (const line of result.trim().split('\n')) {
|
|
@@ -141,7 +149,7 @@ function findImporters(filePath, projectRoot) {
|
|
|
141
149
|
}
|
|
142
150
|
} catch { /* skip unreadable files */ }
|
|
143
151
|
}
|
|
144
|
-
} catch
|
|
152
|
+
} catch { /* rg not found or no matches */ }
|
|
145
153
|
|
|
146
154
|
return Array.from(importers);
|
|
147
155
|
}
|
|
@@ -153,43 +161,48 @@ function findSiblings(filePath) {
|
|
|
153
161
|
try {
|
|
154
162
|
const files = readdirSync(dir).filter(f => {
|
|
155
163
|
const fullPath = join(dir, f);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
164
|
+
try {
|
|
165
|
+
return statSync(fullPath).isFile() &&
|
|
166
|
+
f !== basename(filePath) &&
|
|
167
|
+
!f.includes('.test.') &&
|
|
168
|
+
!f.includes('.spec.') &&
|
|
169
|
+
(f.endsWith('.ts') || f.endsWith('.tsx') || f.endsWith('.js') || f.endsWith('.jsx'));
|
|
170
|
+
} catch {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
161
173
|
});
|
|
162
174
|
|
|
163
175
|
for (const f of files.slice(0, 5)) {
|
|
164
176
|
siblings.push(join(dir, f));
|
|
165
177
|
}
|
|
166
|
-
} catch
|
|
178
|
+
} catch { /* permission error */ }
|
|
167
179
|
|
|
168
180
|
return siblings;
|
|
169
181
|
}
|
|
170
182
|
|
|
171
|
-
function
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
183
|
+
function getFileInfo(filePath) {
|
|
184
|
+
try {
|
|
185
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
186
|
+
return {
|
|
187
|
+
tokens: estimateTokens(content),
|
|
188
|
+
lines: content.split('\n').length
|
|
189
|
+
};
|
|
190
|
+
} catch {
|
|
191
|
+
return { tokens: 0, lines: 0 };
|
|
179
192
|
}
|
|
180
193
|
}
|
|
181
194
|
|
|
182
195
|
// Main
|
|
183
196
|
const args = process.argv.slice(2);
|
|
184
|
-
const
|
|
197
|
+
const options = parseCommonArgs(args);
|
|
198
|
+
const targetFile = options.remaining.find(a => !a.startsWith('-'));
|
|
185
199
|
|
|
186
|
-
if (!targetFile) {
|
|
187
|
-
console.log(
|
|
188
|
-
|
|
189
|
-
process.exit(1);
|
|
200
|
+
if (options.help || !targetFile) {
|
|
201
|
+
console.log(HELP);
|
|
202
|
+
process.exit(options.help ? 0 : 1);
|
|
190
203
|
}
|
|
191
204
|
|
|
192
|
-
const fullPath = join(process.cwd(), targetFile);
|
|
205
|
+
const fullPath = targetFile.startsWith('/') ? targetFile : join(process.cwd(), targetFile);
|
|
193
206
|
if (!existsSync(fullPath)) {
|
|
194
207
|
console.error(`File not found: ${targetFile}`);
|
|
195
208
|
process.exit(1);
|
|
@@ -197,31 +210,65 @@ if (!existsSync(fullPath)) {
|
|
|
197
210
|
|
|
198
211
|
const projectRoot = findProjectRoot();
|
|
199
212
|
const relPath = relative(projectRoot, fullPath);
|
|
213
|
+
const out = createOutput(options);
|
|
200
214
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const tests = findTestFiles(fullPath, projectRoot);
|
|
215
|
+
const tests = findTestFiles(fullPath);
|
|
204
216
|
const types = findTypeFiles(fullPath, projectRoot);
|
|
205
217
|
const importers = findImporters(fullPath, projectRoot);
|
|
206
218
|
const siblings = findSiblings(fullPath);
|
|
207
219
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
220
|
+
// Collect file info for JSON
|
|
221
|
+
const testsInfo = tests.map(f => ({ path: relative(projectRoot, f), ...getFileInfo(f) }));
|
|
222
|
+
const typesInfo = types.map(f => ({ path: relative(projectRoot, f), ...getFileInfo(f) }));
|
|
223
|
+
const importersInfo = importers.slice(0, 10).map(f => ({ path: relative(projectRoot, f), ...getFileInfo(f) }));
|
|
224
|
+
const siblingsInfo = siblings.map(f => ({ path: relative(projectRoot, f), ...getFileInfo(f) }));
|
|
225
|
+
|
|
226
|
+
// Set JSON data
|
|
227
|
+
out.setData('file', relPath);
|
|
228
|
+
out.setData('tests', testsInfo);
|
|
229
|
+
out.setData('types', typesInfo);
|
|
230
|
+
out.setData('importers', importersInfo);
|
|
231
|
+
out.setData('siblings', siblingsInfo);
|
|
232
|
+
out.setData('totalImporters', importers.length);
|
|
233
|
+
|
|
234
|
+
// Header
|
|
235
|
+
out.header(`Related files for: ${relPath}`);
|
|
236
|
+
out.blank();
|
|
237
|
+
|
|
238
|
+
// Sections
|
|
239
|
+
function addSection(title, files) {
|
|
240
|
+
if (files.length === 0) return;
|
|
241
|
+
out.add(title);
|
|
242
|
+
for (const f of files) {
|
|
243
|
+
const rel = relative(projectRoot, f);
|
|
244
|
+
const info = getFileInfo(f);
|
|
245
|
+
out.add(` ${rel} (~${formatTokens(info.tokens)})`);
|
|
246
|
+
}
|
|
247
|
+
out.blank();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
addSection('Tests:', tests);
|
|
251
|
+
addSection('Types:', types);
|
|
252
|
+
addSection('Imported by:', importers.slice(0, 10));
|
|
253
|
+
addSection('Siblings:', siblings);
|
|
212
254
|
|
|
213
255
|
const totalFiles = tests.length + types.length + Math.min(importers.length, 10) + siblings.length;
|
|
214
256
|
if (totalFiles === 0) {
|
|
215
|
-
|
|
257
|
+
out.add(' No related files found.');
|
|
258
|
+
out.blank();
|
|
216
259
|
}
|
|
217
260
|
|
|
218
261
|
// Summary
|
|
219
|
-
const
|
|
220
|
-
|
|
262
|
+
const allFiles = [...tests, ...types, ...importers.slice(0, 10), ...siblings];
|
|
263
|
+
const totalTokens = allFiles.reduce((sum, f) => sum + getFileInfo(f).tokens, 0);
|
|
221
264
|
|
|
222
|
-
|
|
265
|
+
out.setData('totalFiles', totalFiles);
|
|
266
|
+
out.setData('totalTokens', totalTokens);
|
|
267
|
+
|
|
268
|
+
out.header(`Total: ${totalFiles} related files, ~${formatTokens(totalTokens)} tokens`);
|
|
223
269
|
|
|
224
270
|
if (importers.length > 10) {
|
|
225
|
-
|
|
271
|
+
out.header(`(${importers.length - 10} more importers not shown)`);
|
|
226
272
|
}
|
|
227
|
-
|
|
273
|
+
|
|
274
|
+
out.print();
|
package/bin/tl-search.mjs
CHANGED
|
@@ -22,7 +22,36 @@ if (process.argv.includes('--prompt')) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
import { spawn, execSync } from 'child_process';
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
createOutput,
|
|
27
|
+
parseCommonArgs,
|
|
28
|
+
COMMON_OPTIONS_HELP
|
|
29
|
+
} from '../src/output.mjs';
|
|
30
|
+
import { loadConfig, CONFIG_FILENAME } from '../src/config.mjs';
|
|
31
|
+
import { withCache } from '../src/cache.mjs';
|
|
32
|
+
|
|
33
|
+
const HELP = `
|
|
34
|
+
tl-search - Run pre-defined search patterns
|
|
35
|
+
|
|
36
|
+
Usage: tl-search <pattern-name> [options]
|
|
37
|
+
${COMMON_OPTIONS_HELP}
|
|
38
|
+
|
|
39
|
+
Configure patterns in ${CONFIG_FILENAME}:
|
|
40
|
+
{
|
|
41
|
+
"searchPatterns": {
|
|
42
|
+
"hooks": {
|
|
43
|
+
"description": "Find lifecycle hooks",
|
|
44
|
+
"pattern": "use(Effect|State|Callback)",
|
|
45
|
+
"glob": "**/*.{ts,tsx}"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Examples:
|
|
51
|
+
tl-search # List available patterns
|
|
52
|
+
tl-search hooks # Run the "hooks" pattern
|
|
53
|
+
tl-search todos -j # JSON output
|
|
54
|
+
`;
|
|
26
55
|
|
|
27
56
|
// Check for ripgrep
|
|
28
57
|
try {
|
|
@@ -32,13 +61,14 @@ try {
|
|
|
32
61
|
process.exit(1);
|
|
33
62
|
}
|
|
34
63
|
|
|
35
|
-
function
|
|
64
|
+
function showPatterns(patterns, out) {
|
|
36
65
|
const names = Object.keys(patterns);
|
|
37
66
|
|
|
38
67
|
if (names.length === 0) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
68
|
+
out.header('No search patterns defined.');
|
|
69
|
+
out.blank();
|
|
70
|
+
out.add(`Add patterns to ${CONFIG_FILENAME}:`);
|
|
71
|
+
out.add(`
|
|
42
72
|
{
|
|
43
73
|
"searchPatterns": {
|
|
44
74
|
"hooks": {
|
|
@@ -48,25 +78,37 @@ function showHelp(patterns) {
|
|
|
48
78
|
}
|
|
49
79
|
}
|
|
50
80
|
}`);
|
|
51
|
-
|
|
81
|
+
return;
|
|
52
82
|
}
|
|
53
83
|
|
|
54
|
-
|
|
84
|
+
out.header('Available search patterns:');
|
|
85
|
+
out.blank();
|
|
55
86
|
|
|
56
87
|
const maxLen = Math.max(...names.map(k => k.length));
|
|
57
88
|
|
|
89
|
+
// For JSON output, set data
|
|
90
|
+
out.setData('patterns', Object.entries(patterns).map(([name, config]) => ({
|
|
91
|
+
name,
|
|
92
|
+
description: config.description || '',
|
|
93
|
+
pattern: config.pattern,
|
|
94
|
+
glob: config.glob
|
|
95
|
+
})));
|
|
96
|
+
|
|
58
97
|
for (const [name, config] of Object.entries(patterns)) {
|
|
59
98
|
const paddedName = name.padEnd(maxLen);
|
|
60
|
-
|
|
99
|
+
out.add(` ${paddedName} ${config.description || '(no description)'}`);
|
|
61
100
|
}
|
|
62
101
|
|
|
63
|
-
|
|
64
|
-
|
|
102
|
+
out.blank();
|
|
103
|
+
out.add('Usage: tl-search <pattern-name>');
|
|
104
|
+
out.add('Example: tl-search hooks');
|
|
65
105
|
}
|
|
66
106
|
|
|
67
|
-
function runSearch(name, config, rootDir) {
|
|
68
|
-
|
|
69
|
-
|
|
107
|
+
function runSearch(name, config, rootDir, jsonMode) {
|
|
108
|
+
if (!jsonMode) {
|
|
109
|
+
console.log(`\nSearching: ${config.description || name}`);
|
|
110
|
+
console.log(`Pattern: ${config.pattern}\n`);
|
|
111
|
+
}
|
|
70
112
|
|
|
71
113
|
if (config.type === 'glob-only') {
|
|
72
114
|
const args = ['--files', '-g', config.pattern];
|
|
@@ -76,12 +118,39 @@ function runSearch(name, config, rootDir) {
|
|
|
76
118
|
}
|
|
77
119
|
}
|
|
78
120
|
args.push(rootDir);
|
|
79
|
-
|
|
80
|
-
|
|
121
|
+
|
|
122
|
+
if (jsonMode) {
|
|
123
|
+
// Capture output for JSON
|
|
124
|
+
try {
|
|
125
|
+
const result = execSync(`rg ${args.map(a => `"${a}"`).join(' ')}`, {
|
|
126
|
+
encoding: 'utf-8',
|
|
127
|
+
cwd: rootDir
|
|
128
|
+
});
|
|
129
|
+
const files = result.trim().split('\n').filter(Boolean);
|
|
130
|
+
console.log(JSON.stringify({
|
|
131
|
+
pattern: name,
|
|
132
|
+
description: config.description,
|
|
133
|
+
type: 'glob-only',
|
|
134
|
+
files,
|
|
135
|
+
count: files.length
|
|
136
|
+
}, null, 2));
|
|
137
|
+
} catch {
|
|
138
|
+
console.log(JSON.stringify({
|
|
139
|
+
pattern: name,
|
|
140
|
+
files: [],
|
|
141
|
+
count: 0
|
|
142
|
+
}, null, 2));
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
const proc = spawn('rg', args, { stdio: 'inherit' });
|
|
146
|
+
proc.on('close', code => process.exit(code === 1 ? 0 : code));
|
|
147
|
+
}
|
|
81
148
|
return;
|
|
82
149
|
}
|
|
83
150
|
|
|
84
|
-
const args =
|
|
151
|
+
const args = jsonMode
|
|
152
|
+
? ['-n', '--json', '-e', config.pattern]
|
|
153
|
+
: ['--color=always', '-n', '-e', config.pattern];
|
|
85
154
|
|
|
86
155
|
if (config.glob) {
|
|
87
156
|
args.push('-g', config.glob);
|
|
@@ -95,29 +164,81 @@ function runSearch(name, config, rootDir) {
|
|
|
95
164
|
|
|
96
165
|
args.push(rootDir);
|
|
97
166
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
167
|
+
if (jsonMode) {
|
|
168
|
+
try {
|
|
169
|
+
const cacheKey = { op: 'rg-search-pattern', name, pattern: config.pattern, glob: config.glob };
|
|
170
|
+
const result = withCache(
|
|
171
|
+
cacheKey,
|
|
172
|
+
() => execSync(`rg ${args.map(a => `"${a}"`).join(' ')} 2>/dev/null || true`, {
|
|
173
|
+
encoding: 'utf-8',
|
|
174
|
+
maxBuffer: 10 * 1024 * 1024
|
|
175
|
+
}),
|
|
176
|
+
{ projectRoot: rootDir }
|
|
177
|
+
);
|
|
178
|
+
const matches = result.trim().split('\n')
|
|
179
|
+
.filter(line => line.startsWith('{'))
|
|
180
|
+
.map(line => {
|
|
181
|
+
try { return JSON.parse(line); } catch { return null; }
|
|
182
|
+
})
|
|
183
|
+
.filter(Boolean)
|
|
184
|
+
.filter(m => m.type === 'match')
|
|
185
|
+
.map(m => ({
|
|
186
|
+
file: m.data.path.text,
|
|
187
|
+
line: m.data.line_number,
|
|
188
|
+
text: m.data.lines.text.trim()
|
|
189
|
+
}));
|
|
190
|
+
|
|
191
|
+
console.log(JSON.stringify({
|
|
192
|
+
pattern: name,
|
|
193
|
+
description: config.description,
|
|
194
|
+
searchPattern: config.pattern,
|
|
195
|
+
matches,
|
|
196
|
+
count: matches.length
|
|
197
|
+
}, null, 2));
|
|
198
|
+
} catch {
|
|
199
|
+
console.log(JSON.stringify({
|
|
200
|
+
pattern: name,
|
|
201
|
+
matches: [],
|
|
202
|
+
count: 0
|
|
203
|
+
}, null, 2));
|
|
102
204
|
}
|
|
103
|
-
|
|
104
|
-
|
|
205
|
+
} else {
|
|
206
|
+
const proc = spawn('rg', args, { stdio: 'inherit' });
|
|
207
|
+
proc.on('close', code => {
|
|
208
|
+
if (code === 1) {
|
|
209
|
+
console.log('No matches found.');
|
|
210
|
+
}
|
|
211
|
+
process.exit(code === 1 ? 0 : code);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
105
214
|
}
|
|
106
215
|
|
|
107
216
|
// Main
|
|
217
|
+
const args = process.argv.slice(2);
|
|
218
|
+
const options = parseCommonArgs(args);
|
|
219
|
+
|
|
220
|
+
if (options.help) {
|
|
221
|
+
console.log(HELP);
|
|
222
|
+
process.exit(0);
|
|
223
|
+
}
|
|
224
|
+
|
|
108
225
|
const { config, projectRoot } = loadConfig();
|
|
109
226
|
const patterns = config.searchPatterns || {};
|
|
110
|
-
const patternName =
|
|
227
|
+
const patternName = options.remaining.find(a => !a.startsWith('-'));
|
|
111
228
|
|
|
112
|
-
if (!patternName
|
|
113
|
-
|
|
229
|
+
if (!patternName) {
|
|
230
|
+
const out = createOutput(options);
|
|
231
|
+
showPatterns(patterns, out);
|
|
232
|
+
out.print();
|
|
114
233
|
process.exit(0);
|
|
115
234
|
}
|
|
116
235
|
|
|
117
236
|
if (!patterns[patternName]) {
|
|
118
237
|
console.error(`\nUnknown pattern: "${patternName}"`);
|
|
119
|
-
|
|
238
|
+
const out = createOutput({ ...options, json: false });
|
|
239
|
+
showPatterns(patterns, out);
|
|
240
|
+
out.print();
|
|
120
241
|
process.exit(1);
|
|
121
242
|
}
|
|
122
243
|
|
|
123
|
-
runSearch(patternName, patterns[patternName], projectRoot);
|
|
244
|
+
runSearch(patternName, patterns[patternName], projectRoot, options.json);
|