@anirudw/repolens 0.1.0 → 0.1.3
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/dist/index.js +934 -0
- package/package.json +8 -4
- package/.github/workflows/publish.yml +0 -34
- package/bin/repolens.js +0 -6
- package/src/cli/commands.ts +0 -112
- package/src/cli/index.ts +0 -4
- package/src/graph/analyzer/pagerank.ts +0 -31
- package/src/graph/index.ts +0 -2
- package/src/graph/network.ts +0 -148
- package/src/parser/index.ts +0 -32
- package/src/parser/strategies/java.ts +0 -154
- package/src/parser/strategies/javascript.ts +0 -199
- package/src/parser/strategies/markdown.ts +0 -83
- package/src/parser/strategies/python.ts +0 -184
- package/src/parser/types.ts +0 -18
- package/src/renderers/json/exporter.ts +0 -56
- package/src/scanner/file-types.ts +0 -42
- package/src/scanner/ignore.ts +0 -58
- package/src/scanner/index.ts +0 -9
- package/src/scanner/walker.ts +0 -125
- package/src/utils/colors.ts +0 -13
- package/tests/fixtures/dummy-repo/README.md +0 -5
- package/tests/fixtures/dummy-repo/secret-keys.py~ +0 -0
- package/tests/fixtures/dummy-repo/src/App.java +0 -11
- package/tests/fixtures/dummy-repo/src/api.py +0 -9
- package/tests/fixtures/dummy-repo/src/components/App.tsx +0 -0
- package/tests/fixtures/dummy-repo/src/index.js +0 -7
- package/tests/parser.test.ts +0 -36
- package/tsconfig.json +0 -20
package/dist/index.js
ADDED
|
@@ -0,0 +1,934 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/commands.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
6
|
+
import { resolve as resolve3 } from "path";
|
|
7
|
+
|
|
8
|
+
// src/scanner/walker.ts
|
|
9
|
+
import { readdirSync, statSync } from "fs";
|
|
10
|
+
import { join, relative, isAbsolute, resolve } from "path";
|
|
11
|
+
|
|
12
|
+
// src/scanner/ignore.ts
|
|
13
|
+
import { readFileSync } from "fs";
|
|
14
|
+
import ignore from "ignore";
|
|
15
|
+
var DEFAULT_IGNORES = [
|
|
16
|
+
"node_modules",
|
|
17
|
+
"bower_components",
|
|
18
|
+
"dist",
|
|
19
|
+
"build",
|
|
20
|
+
"out",
|
|
21
|
+
"target",
|
|
22
|
+
".git",
|
|
23
|
+
".svn",
|
|
24
|
+
".hg",
|
|
25
|
+
".DS_Store",
|
|
26
|
+
"Thumbs.db",
|
|
27
|
+
"*.pyc",
|
|
28
|
+
"__pycache__",
|
|
29
|
+
".pytest_cache",
|
|
30
|
+
".mypy_cache",
|
|
31
|
+
".next",
|
|
32
|
+
".nuxt",
|
|
33
|
+
".cache",
|
|
34
|
+
".parcel-cache",
|
|
35
|
+
"coverage",
|
|
36
|
+
".nyc_output",
|
|
37
|
+
".turbo"
|
|
38
|
+
];
|
|
39
|
+
function createIgnoreRules(rootDir) {
|
|
40
|
+
const ig = ignore();
|
|
41
|
+
for (const pattern of DEFAULT_IGNORES) {
|
|
42
|
+
ig.add(pattern);
|
|
43
|
+
}
|
|
44
|
+
const gitignorePath = `${rootDir}/.gitignore`;
|
|
45
|
+
try {
|
|
46
|
+
const content = readFileSync(gitignorePath, "utf-8");
|
|
47
|
+
const rules = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
48
|
+
ig.add(rules);
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
return ig;
|
|
52
|
+
}
|
|
53
|
+
function shouldIgnore(ig, relativePath, isDirectory) {
|
|
54
|
+
const pathWithTrailingSlash = isDirectory ? `${relativePath}/` : relativePath;
|
|
55
|
+
return ig.test(pathWithTrailingSlash).ignored;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/scanner/file-types.ts
|
|
59
|
+
var LANGUAGE_EXTENSIONS = {
|
|
60
|
+
["javascript" /* JavaScript */]: [".js", ".jsx", ".mjs", ".cjs"],
|
|
61
|
+
["typescript" /* TypeScript */]: [".ts", ".tsx", ".mts", ".cts"],
|
|
62
|
+
["python" /* Python */]: [".py"],
|
|
63
|
+
["java" /* Java */]: [".java"],
|
|
64
|
+
["markdown" /* Markdown */]: [".md", ".markdown"],
|
|
65
|
+
["unknown" /* Unknown */]: []
|
|
66
|
+
};
|
|
67
|
+
var EXTENSION_TO_LANGUAGE = /* @__PURE__ */ new Map();
|
|
68
|
+
for (const [lang, extensions] of Object.entries(LANGUAGE_EXTENSIONS)) {
|
|
69
|
+
if (lang !== "unknown" /* Unknown */) {
|
|
70
|
+
for (const ext of extensions) {
|
|
71
|
+
EXTENSION_TO_LANGUAGE.set(ext, lang);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function getLanguageFromPath(filePath) {
|
|
76
|
+
const ext = getExtension(filePath);
|
|
77
|
+
return EXTENSION_TO_LANGUAGE.get(ext) ?? "unknown" /* Unknown */;
|
|
78
|
+
}
|
|
79
|
+
function getExtension(filePath) {
|
|
80
|
+
const lastDot = filePath.lastIndexOf(".");
|
|
81
|
+
if (lastDot === -1) return "";
|
|
82
|
+
return filePath.slice(lastDot);
|
|
83
|
+
}
|
|
84
|
+
function isTargetLanguage(filePath) {
|
|
85
|
+
return getLanguageFromPath(filePath) !== "unknown" /* Unknown */;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/scanner/walker.ts
|
|
89
|
+
function scanDirectory(options) {
|
|
90
|
+
const { rootDir, verbose = false } = options;
|
|
91
|
+
const absoluteRoot = isAbsolute(rootDir) ? rootDir : resolve(rootDir);
|
|
92
|
+
if (verbose) {
|
|
93
|
+
console.log(`Scanning directory: ${absoluteRoot}`);
|
|
94
|
+
}
|
|
95
|
+
const ig = createIgnoreRules(absoluteRoot);
|
|
96
|
+
const files = [];
|
|
97
|
+
let ignoredCount = 0;
|
|
98
|
+
function walk(dir, baseDir = dir) {
|
|
99
|
+
let entries;
|
|
100
|
+
try {
|
|
101
|
+
entries = readdirSync(dir);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
if (verbose) {
|
|
104
|
+
console.warn(`Warning: Cannot read directory ${dir}: ${err}`);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
const fullPath = join(dir, entry);
|
|
110
|
+
const relativePath = relative(baseDir, fullPath);
|
|
111
|
+
let isDir;
|
|
112
|
+
try {
|
|
113
|
+
isDir = statSync(fullPath).isDirectory();
|
|
114
|
+
} catch (err) {
|
|
115
|
+
if (verbose) {
|
|
116
|
+
console.warn(`Warning: Cannot stat ${fullPath}: ${err}`);
|
|
117
|
+
}
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (shouldIgnore(ig, relativePath, isDir)) {
|
|
121
|
+
if (!isDir) {
|
|
122
|
+
ignoredCount++;
|
|
123
|
+
}
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (isDir) {
|
|
127
|
+
walk(fullPath, baseDir);
|
|
128
|
+
} else {
|
|
129
|
+
if (isTargetLanguage(entry)) {
|
|
130
|
+
files.push({
|
|
131
|
+
path: entry,
|
|
132
|
+
absolutePath: fullPath,
|
|
133
|
+
relativePath: normalizePath(relativePath),
|
|
134
|
+
language: getLanguageFromFile(entry)
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
walk(absoluteRoot);
|
|
141
|
+
const filesByLanguage = countByLanguage(files);
|
|
142
|
+
if (verbose) {
|
|
143
|
+
console.log(`Found ${files.length} target files`);
|
|
144
|
+
for (const [lang, count] of Object.entries(filesByLanguage)) {
|
|
145
|
+
if (count > 0) {
|
|
146
|
+
console.log(` ${lang}: ${count}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
files,
|
|
152
|
+
totalFiles: files.length,
|
|
153
|
+
filesByLanguage,
|
|
154
|
+
ignoredCount
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function normalizePath(p) {
|
|
158
|
+
return p.replace(/\\/g, "/");
|
|
159
|
+
}
|
|
160
|
+
function getLanguageFromFile(filePath) {
|
|
161
|
+
return getLanguageFromPath(filePath);
|
|
162
|
+
}
|
|
163
|
+
function countByLanguage(files) {
|
|
164
|
+
const counts = {};
|
|
165
|
+
for (const file of files) {
|
|
166
|
+
counts[file.language] = (counts[file.language] ?? 0) + 1;
|
|
167
|
+
}
|
|
168
|
+
return counts;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/parser/strategies/markdown.ts
|
|
172
|
+
import { remark } from "remark";
|
|
173
|
+
import { visit } from "unist-util-visit";
|
|
174
|
+
var MarkdownParser = class {
|
|
175
|
+
processor = remark();
|
|
176
|
+
async parse(filePath, content) {
|
|
177
|
+
const tree = this.processor.parse(content);
|
|
178
|
+
const dependencies = [];
|
|
179
|
+
visit(tree, "link", (node) => {
|
|
180
|
+
if (node.url) {
|
|
181
|
+
dependencies.push({
|
|
182
|
+
rawSpecifier: node.url,
|
|
183
|
+
type: "markdown-link",
|
|
184
|
+
location: {
|
|
185
|
+
line: node.position?.start.line ?? 1,
|
|
186
|
+
column: node.position?.start.column ?? 1
|
|
187
|
+
},
|
|
188
|
+
resolvedPath: this.resolvePath(node.url)
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
const wikilinkRegex = /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g;
|
|
193
|
+
let match;
|
|
194
|
+
while ((match = wikilinkRegex.exec(content)) !== null) {
|
|
195
|
+
const lineInfo = this.getLineNumber(content, match.index);
|
|
196
|
+
dependencies.push({
|
|
197
|
+
rawSpecifier: match[1],
|
|
198
|
+
type: "wikilink",
|
|
199
|
+
location: {
|
|
200
|
+
line: lineInfo.line,
|
|
201
|
+
column: lineInfo.column
|
|
202
|
+
},
|
|
203
|
+
resolvedPath: this.resolvePath(match[1])
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
id: filePath,
|
|
208
|
+
relativePath: filePath.split("/").pop() ?? filePath,
|
|
209
|
+
language: "markdown",
|
|
210
|
+
dependencies,
|
|
211
|
+
metadata: {
|
|
212
|
+
sizeBytes: Buffer.byteLength(content, "utf-8"),
|
|
213
|
+
heuristics: {
|
|
214
|
+
hasWikilinks: dependencies.some((d) => d.type === "wikilink"),
|
|
215
|
+
hasExternalLinks: dependencies.some(
|
|
216
|
+
(d) => d.rawSpecifier.startsWith("http")
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
getLineNumber(content, index) {
|
|
223
|
+
const lines = content.substring(0, index).split("\n");
|
|
224
|
+
return {
|
|
225
|
+
line: lines.length,
|
|
226
|
+
column: (lines[lines.length - 1]?.length ?? 0) + 1
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
resolvePath(specifier) {
|
|
230
|
+
if (specifier.startsWith("http") || specifier.startsWith("//")) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
return specifier;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// src/parser/strategies/javascript.ts
|
|
238
|
+
var TreeSitterParser;
|
|
239
|
+
var JavaScript;
|
|
240
|
+
async function ensureLoaded() {
|
|
241
|
+
if (!TreeSitterParser) {
|
|
242
|
+
const [ts, js] = await Promise.all([
|
|
243
|
+
import("tree-sitter"),
|
|
244
|
+
import("tree-sitter-javascript")
|
|
245
|
+
]);
|
|
246
|
+
TreeSitterParser = ts.default;
|
|
247
|
+
JavaScript = js.default;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
var JavaScriptParser = class {
|
|
251
|
+
parser = null;
|
|
252
|
+
async ensureParser() {
|
|
253
|
+
if (!this.parser) {
|
|
254
|
+
await ensureLoaded();
|
|
255
|
+
this.parser = new TreeSitterParser();
|
|
256
|
+
this.parser.setLanguage(JavaScript);
|
|
257
|
+
}
|
|
258
|
+
return this.parser;
|
|
259
|
+
}
|
|
260
|
+
async parse(filePath, content) {
|
|
261
|
+
const parser = await this.ensureParser();
|
|
262
|
+
const tree = parser.parse(content);
|
|
263
|
+
const rootNode = tree.rootNode;
|
|
264
|
+
const dependencies = [];
|
|
265
|
+
const imports = /* @__PURE__ */ new Set();
|
|
266
|
+
const sourceModules = [];
|
|
267
|
+
this.extractImports(rootNode, dependencies, imports, sourceModules);
|
|
268
|
+
const heuristics = {};
|
|
269
|
+
const allImports = [...imports, ...sourceModules].map((s) => s.toLowerCase());
|
|
270
|
+
heuristics.isReact = allImports.some((i) => i === "react" || i === "@types/react");
|
|
271
|
+
heuristics.isReactNative = allImports.some((i) => i === "react-native");
|
|
272
|
+
heuristics.isNodejs = allImports.some((i) => i.startsWith("node:"));
|
|
273
|
+
heuristics.hasDefaultExport = this.hasDefaultExport(rootNode);
|
|
274
|
+
heuristics.hasNamedExports = this.hasNamedExports(rootNode);
|
|
275
|
+
return {
|
|
276
|
+
id: filePath,
|
|
277
|
+
relativePath: filePath.split("/").pop() ?? filePath,
|
|
278
|
+
language: "javascript",
|
|
279
|
+
dependencies,
|
|
280
|
+
metadata: {
|
|
281
|
+
sizeBytes: Buffer.byteLength(content, "utf-8"),
|
|
282
|
+
heuristics
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
extractImports(node, dependencies, imports, sourceModules) {
|
|
287
|
+
if (node.type === "import_statement") {
|
|
288
|
+
this.extractImportStatement(node, dependencies, imports, sourceModules);
|
|
289
|
+
} else if (node.type === "call_expression" && this.isRequireCall(node)) {
|
|
290
|
+
this.extractRequireCall(node, dependencies, sourceModules);
|
|
291
|
+
}
|
|
292
|
+
for (const child of node.children) {
|
|
293
|
+
this.extractImports(child, dependencies, imports, sourceModules);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
extractImportStatement(node, dependencies, imports, sourceModules) {
|
|
297
|
+
const sourceNode = node.childForFieldName("source");
|
|
298
|
+
if (sourceNode && sourceNode.type === "string") {
|
|
299
|
+
const rawSpecifier = sourceNode.text.slice(1, -1);
|
|
300
|
+
sourceModules.push(rawSpecifier);
|
|
301
|
+
dependencies.push({
|
|
302
|
+
rawSpecifier,
|
|
303
|
+
type: "import",
|
|
304
|
+
location: {
|
|
305
|
+
line: node.startPosition.row + 1,
|
|
306
|
+
column: node.startPosition.column + 1
|
|
307
|
+
},
|
|
308
|
+
resolvedPath: rawSpecifier
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
for (const child of node.children) {
|
|
312
|
+
if (child.type === "import_specifier") {
|
|
313
|
+
const nameNode = child.childForFieldName("name");
|
|
314
|
+
if (nameNode) {
|
|
315
|
+
imports.add(nameNode.text);
|
|
316
|
+
}
|
|
317
|
+
} else if (child.type === "import_default_specifier") {
|
|
318
|
+
const nameNode = child.childForFieldName("name");
|
|
319
|
+
if (nameNode) {
|
|
320
|
+
imports.add(nameNode.text);
|
|
321
|
+
}
|
|
322
|
+
} else if (child.type === "import_namespace_specifier") {
|
|
323
|
+
const nameNode = child.childForFieldName("name");
|
|
324
|
+
if (nameNode) {
|
|
325
|
+
imports.add(nameNode.text);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
extractRequireCall(node, dependencies, sourceModules) {
|
|
331
|
+
const args = node.childForFieldName("arguments");
|
|
332
|
+
if (args) {
|
|
333
|
+
for (const arg of args.children) {
|
|
334
|
+
if (arg.type === "string") {
|
|
335
|
+
const rawSpecifier = arg.text.slice(1, -1);
|
|
336
|
+
sourceModules.push(rawSpecifier);
|
|
337
|
+
dependencies.push({
|
|
338
|
+
rawSpecifier,
|
|
339
|
+
type: "require",
|
|
340
|
+
location: {
|
|
341
|
+
line: node.startPosition.row + 1,
|
|
342
|
+
column: node.startPosition.column + 1
|
|
343
|
+
},
|
|
344
|
+
resolvedPath: rawSpecifier
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
isRequireCall(node) {
|
|
351
|
+
const functionExpr = node.childForFieldName("function");
|
|
352
|
+
if (!functionExpr) return false;
|
|
353
|
+
if (functionExpr.type === "identifier" && functionExpr.text === "require") {
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
if (functionExpr.type === "member_expression") {
|
|
357
|
+
const object = functionExpr.childForFieldName("object");
|
|
358
|
+
const property = functionExpr.childForFieldName("property");
|
|
359
|
+
if (object?.text === "require" && property?.text === "resolve") {
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
hasDefaultExport(node) {
|
|
366
|
+
if (node.type === "export_statement") {
|
|
367
|
+
return node.children.some((c) => c.type === "default");
|
|
368
|
+
}
|
|
369
|
+
for (const child of node.children) {
|
|
370
|
+
if (this.hasDefaultExport(child)) return true;
|
|
371
|
+
}
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
hasNamedExports(node) {
|
|
375
|
+
if (node.type === "export_clause") return true;
|
|
376
|
+
if (node.type === "export_statement") {
|
|
377
|
+
const hasDefault = node.children.some((c) => c.type === "default");
|
|
378
|
+
if (hasDefault) return false;
|
|
379
|
+
return node.children.some(
|
|
380
|
+
(c) => c.type === "variable_declaration" || c.type === "function_declaration" || c.type === "class_declaration"
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
for (const child of node.children) {
|
|
384
|
+
if (this.hasNamedExports(child)) return true;
|
|
385
|
+
}
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// src/parser/strategies/python.ts
|
|
391
|
+
var TreeSitterParser2;
|
|
392
|
+
var Python;
|
|
393
|
+
async function ensureLoaded2() {
|
|
394
|
+
if (!TreeSitterParser2) {
|
|
395
|
+
const [ts, py] = await Promise.all([
|
|
396
|
+
import("tree-sitter"),
|
|
397
|
+
import("tree-sitter-python")
|
|
398
|
+
]);
|
|
399
|
+
TreeSitterParser2 = ts.default;
|
|
400
|
+
Python = py.default;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
var PythonParser = class {
|
|
404
|
+
parser = null;
|
|
405
|
+
async ensureParser() {
|
|
406
|
+
if (!this.parser) {
|
|
407
|
+
await ensureLoaded2();
|
|
408
|
+
this.parser = new TreeSitterParser2();
|
|
409
|
+
this.parser.setLanguage(Python);
|
|
410
|
+
}
|
|
411
|
+
return this.parser;
|
|
412
|
+
}
|
|
413
|
+
async parse(filePath, content) {
|
|
414
|
+
const parser = await this.ensureParser();
|
|
415
|
+
const tree = parser.parse(content);
|
|
416
|
+
const rootNode = tree.rootNode;
|
|
417
|
+
const dependencies = [];
|
|
418
|
+
const heuristics = {};
|
|
419
|
+
this.extractImports(rootNode, dependencies);
|
|
420
|
+
heuristics.hasEntrypoint = this.detectMainEntrypoint(rootNode);
|
|
421
|
+
return {
|
|
422
|
+
id: filePath,
|
|
423
|
+
relativePath: filePath.split("/").pop() ?? filePath,
|
|
424
|
+
language: "python",
|
|
425
|
+
dependencies,
|
|
426
|
+
metadata: {
|
|
427
|
+
sizeBytes: Buffer.byteLength(content, "utf-8"),
|
|
428
|
+
heuristics
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
extractImports(node, dependencies) {
|
|
433
|
+
if (node.type === "import_statement") {
|
|
434
|
+
for (const child of node.children) {
|
|
435
|
+
if (child.type === "dotted_name") {
|
|
436
|
+
dependencies.push({
|
|
437
|
+
rawSpecifier: child.text,
|
|
438
|
+
type: "import",
|
|
439
|
+
location: {
|
|
440
|
+
line: node.startPosition.row + 1,
|
|
441
|
+
column: node.startPosition.column + 1
|
|
442
|
+
},
|
|
443
|
+
resolvedPath: child.text
|
|
444
|
+
});
|
|
445
|
+
} else if (child.type === "aliased_name") {
|
|
446
|
+
const dottedName = child.childForFieldName("name");
|
|
447
|
+
if (dottedName) {
|
|
448
|
+
dependencies.push({
|
|
449
|
+
rawSpecifier: dottedName.text,
|
|
450
|
+
type: "import",
|
|
451
|
+
location: {
|
|
452
|
+
line: node.startPosition.row + 1,
|
|
453
|
+
column: node.startPosition.column + 1
|
|
454
|
+
},
|
|
455
|
+
resolvedPath: dottedName.text
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
} else if (node.type === "import_from_statement") {
|
|
461
|
+
let moduleName = "";
|
|
462
|
+
const importedNames = [];
|
|
463
|
+
let foundImportKeyword = false;
|
|
464
|
+
for (const child of node.children) {
|
|
465
|
+
if (child.type === "dotted_name") {
|
|
466
|
+
if (!foundImportKeyword) {
|
|
467
|
+
moduleName = child.text;
|
|
468
|
+
} else {
|
|
469
|
+
importedNames.push(child.text);
|
|
470
|
+
}
|
|
471
|
+
} else if (child.type === "identifier") {
|
|
472
|
+
if (foundImportKeyword) {
|
|
473
|
+
importedNames.push(child.text);
|
|
474
|
+
}
|
|
475
|
+
} else if (child.type === "import") {
|
|
476
|
+
foundImportKeyword = true;
|
|
477
|
+
} else if (child.type === "wildcard_import") {
|
|
478
|
+
importedNames.push("*");
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (importedNames.length === 0 && moduleName) {
|
|
482
|
+
dependencies.push({
|
|
483
|
+
rawSpecifier: moduleName,
|
|
484
|
+
type: "import",
|
|
485
|
+
location: {
|
|
486
|
+
line: node.startPosition.row + 1,
|
|
487
|
+
column: node.startPosition.column + 1
|
|
488
|
+
},
|
|
489
|
+
resolvedPath: moduleName
|
|
490
|
+
});
|
|
491
|
+
} else {
|
|
492
|
+
for (const name of importedNames) {
|
|
493
|
+
dependencies.push({
|
|
494
|
+
rawSpecifier: `${moduleName}.${name}`,
|
|
495
|
+
type: "import",
|
|
496
|
+
location: {
|
|
497
|
+
line: node.startPosition.row + 1,
|
|
498
|
+
column: node.startPosition.column + 1
|
|
499
|
+
},
|
|
500
|
+
resolvedPath: `${moduleName}.${name}`
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
for (const child of node.children) {
|
|
506
|
+
this.extractImports(child, dependencies);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
detectMainEntrypoint(node) {
|
|
510
|
+
if (node.type === "if_statement") {
|
|
511
|
+
const condition = node.childForFieldName("condition");
|
|
512
|
+
if (condition && this.isNameEqualsMainCheck(condition)) {
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
for (const child of node.children) {
|
|
517
|
+
if (this.detectMainEntrypoint(child)) {
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
isNameEqualsMainCheck(node) {
|
|
524
|
+
if (node.type === "comparison_operator") {
|
|
525
|
+
let hasName = false;
|
|
526
|
+
let hasMainString = false;
|
|
527
|
+
for (const child of node.children) {
|
|
528
|
+
if (child.type === "identifier" && child.text === "__name__") {
|
|
529
|
+
hasName = true;
|
|
530
|
+
} else if (child.type === "string") {
|
|
531
|
+
const innerText = child.text.replace(/['"]/g, "");
|
|
532
|
+
if (innerText === "__main__") {
|
|
533
|
+
hasMainString = true;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return hasName && hasMainString;
|
|
538
|
+
}
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
// src/parser/strategies/java.ts
|
|
544
|
+
var TreeSitterParser3;
|
|
545
|
+
var Java;
|
|
546
|
+
async function ensureLoaded3() {
|
|
547
|
+
if (!TreeSitterParser3) {
|
|
548
|
+
const [ts, j] = await Promise.all([
|
|
549
|
+
import("tree-sitter"),
|
|
550
|
+
import("tree-sitter-java")
|
|
551
|
+
]);
|
|
552
|
+
TreeSitterParser3 = ts.default;
|
|
553
|
+
Java = j.default;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
var JavaParser = class {
|
|
557
|
+
parser = null;
|
|
558
|
+
async ensureParser() {
|
|
559
|
+
if (!this.parser) {
|
|
560
|
+
await ensureLoaded3();
|
|
561
|
+
this.parser = new TreeSitterParser3();
|
|
562
|
+
this.parser.setLanguage(Java);
|
|
563
|
+
}
|
|
564
|
+
return this.parser;
|
|
565
|
+
}
|
|
566
|
+
async parse(filePath, content) {
|
|
567
|
+
const parser = await this.ensureParser();
|
|
568
|
+
const tree = parser.parse(content);
|
|
569
|
+
const rootNode = tree.rootNode;
|
|
570
|
+
const dependencies = [];
|
|
571
|
+
const heuristics = {};
|
|
572
|
+
this.extractImports(rootNode, dependencies);
|
|
573
|
+
heuristics.hasMainMethod = this.detectMainMethod(rootNode);
|
|
574
|
+
return {
|
|
575
|
+
id: filePath,
|
|
576
|
+
relativePath: filePath.split("/").pop() ?? filePath,
|
|
577
|
+
language: "java",
|
|
578
|
+
dependencies,
|
|
579
|
+
metadata: {
|
|
580
|
+
sizeBytes: Buffer.byteLength(content, "utf-8"),
|
|
581
|
+
heuristics
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
extractImports(node, dependencies) {
|
|
586
|
+
if (node.type === "import_declaration") {
|
|
587
|
+
const importText = this.getScopedIdentifierText(node);
|
|
588
|
+
if (importText) {
|
|
589
|
+
dependencies.push({
|
|
590
|
+
rawSpecifier: importText,
|
|
591
|
+
type: "import",
|
|
592
|
+
location: {
|
|
593
|
+
line: node.startPosition.row + 1,
|
|
594
|
+
column: node.startPosition.column + 1
|
|
595
|
+
},
|
|
596
|
+
resolvedPath: importText
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
for (const child of node.children) {
|
|
601
|
+
this.extractImports(child, dependencies);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
detectMainMethod(node) {
|
|
605
|
+
if (node.type === "method_declaration") {
|
|
606
|
+
const nameNode = this.getChildByType(node, "identifier");
|
|
607
|
+
const modifiersNode = this.getChildByType(node, "modifiers");
|
|
608
|
+
if (nameNode?.text === "main") {
|
|
609
|
+
if (modifiersNode) {
|
|
610
|
+
const modifierTexts = this.getModifierTexts(modifiersNode);
|
|
611
|
+
if (modifierTexts.includes("public") && modifierTexts.includes("static")) {
|
|
612
|
+
return true;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
for (const child of node.children) {
|
|
618
|
+
if (this.detectMainMethod(child)) {
|
|
619
|
+
return true;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
getChildByType(node, type) {
|
|
625
|
+
for (const child of node.children) {
|
|
626
|
+
if (child.type === type) return child;
|
|
627
|
+
}
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
getModifierTexts(node) {
|
|
631
|
+
const modifiers = [];
|
|
632
|
+
for (const child of node.children) {
|
|
633
|
+
if (child.type === "public" || child.type === "private" || child.type === "protected" || child.type === "static" || child.type === "final" || child.type === "abstract" || child.type === "synchronized" || child.type === "volatile" || child.type === "transient" || child.type === "native" || child.type === "strictfp") {
|
|
634
|
+
modifiers.push(child.text);
|
|
635
|
+
} else if (child.type === "annotation" || child.type === "identifier") {
|
|
636
|
+
modifiers.push(child.text);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return modifiers;
|
|
640
|
+
}
|
|
641
|
+
getScopedIdentifierText(node) {
|
|
642
|
+
for (const child of node.children) {
|
|
643
|
+
if (child.type === "scoped_identifier") {
|
|
644
|
+
const parts = [];
|
|
645
|
+
this.collectIdentifiers(child, parts);
|
|
646
|
+
return parts.join(".");
|
|
647
|
+
} else if (child.type === "identifier") {
|
|
648
|
+
return child.text;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return "";
|
|
652
|
+
}
|
|
653
|
+
collectIdentifiers(node, parts) {
|
|
654
|
+
for (const child of node.children) {
|
|
655
|
+
if (child.type === "identifier") {
|
|
656
|
+
parts.push(child.text);
|
|
657
|
+
} else if (child.type === "scoped_identifier") {
|
|
658
|
+
this.collectIdentifiers(child, parts);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
// src/parser/index.ts
|
|
665
|
+
var JAVASCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"]);
|
|
666
|
+
var MARKDOWN_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown"]);
|
|
667
|
+
var PYTHON_EXTENSIONS = /* @__PURE__ */ new Set([".py"]);
|
|
668
|
+
var JAVA_EXTENSIONS = /* @__PURE__ */ new Set([".java"]);
|
|
669
|
+
function createParser(filePath) {
|
|
670
|
+
const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
|
|
671
|
+
if (JAVASCRIPT_EXTENSIONS.has(ext)) {
|
|
672
|
+
return new JavaScriptParser();
|
|
673
|
+
}
|
|
674
|
+
if (MARKDOWN_EXTENSIONS.has(ext)) {
|
|
675
|
+
return new MarkdownParser();
|
|
676
|
+
}
|
|
677
|
+
if (PYTHON_EXTENSIONS.has(ext)) {
|
|
678
|
+
return new PythonParser();
|
|
679
|
+
}
|
|
680
|
+
if (JAVA_EXTENSIONS.has(ext)) {
|
|
681
|
+
return new JavaParser();
|
|
682
|
+
}
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// src/graph/network.ts
|
|
687
|
+
import { resolve as resolve2, dirname } from "path";
|
|
688
|
+
var Graph = class {
|
|
689
|
+
nodes = /* @__PURE__ */ new Map();
|
|
690
|
+
edges = [];
|
|
691
|
+
constructor(files) {
|
|
692
|
+
for (const file of files) {
|
|
693
|
+
this.nodes.set(file.id, {
|
|
694
|
+
id: file.id,
|
|
695
|
+
relativePath: file.relativePath,
|
|
696
|
+
language: file.language,
|
|
697
|
+
metadata: file.metadata,
|
|
698
|
+
inboundEdges: 0,
|
|
699
|
+
outboundEdges: 0
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
this.resolveEdges(files);
|
|
703
|
+
}
|
|
704
|
+
resolveEdges(files) {
|
|
705
|
+
for (const file of files) {
|
|
706
|
+
const dir = dirname(file.id);
|
|
707
|
+
for (const dep of file.dependencies) {
|
|
708
|
+
if (!dep.rawSpecifier) continue;
|
|
709
|
+
if (dep.rawSpecifier.startsWith("http") || dep.rawSpecifier.startsWith("//") || dep.rawSpecifier.startsWith("#") || !this.isLocalImport(dep.rawSpecifier)) {
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
const resolvedPath = this.resolveImportPath(
|
|
713
|
+
dir,
|
|
714
|
+
dep.rawSpecifier
|
|
715
|
+
);
|
|
716
|
+
if (resolvedPath && this.nodes.has(resolvedPath)) {
|
|
717
|
+
dep.resolvedPath = resolvedPath;
|
|
718
|
+
this.edges.push({
|
|
719
|
+
source: file.id,
|
|
720
|
+
target: resolvedPath,
|
|
721
|
+
type: dep.type
|
|
722
|
+
});
|
|
723
|
+
const targetNode = this.nodes.get(resolvedPath);
|
|
724
|
+
if (targetNode) {
|
|
725
|
+
targetNode.inboundEdges++;
|
|
726
|
+
}
|
|
727
|
+
const sourceNode = this.nodes.get(file.id);
|
|
728
|
+
if (sourceNode) {
|
|
729
|
+
sourceNode.outboundEdges++;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
resolveImportPath(fromDir, specifier) {
|
|
736
|
+
const candidatePaths = [
|
|
737
|
+
specifier,
|
|
738
|
+
`${specifier}.js`,
|
|
739
|
+
`${specifier}.ts`,
|
|
740
|
+
`${specifier}.jsx`,
|
|
741
|
+
`${specifier}.tsx`,
|
|
742
|
+
`${specifier}/index.js`,
|
|
743
|
+
`${specifier}/index.ts`,
|
|
744
|
+
`${specifier}/index.jsx`,
|
|
745
|
+
`${specifier}/index.tsx`
|
|
746
|
+
];
|
|
747
|
+
for (const candidate of candidatePaths) {
|
|
748
|
+
try {
|
|
749
|
+
const fullPath = resolve2(fromDir, candidate);
|
|
750
|
+
if (this.nodes.has(fullPath)) {
|
|
751
|
+
return fullPath;
|
|
752
|
+
}
|
|
753
|
+
} catch {
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
isLocalImport(specifier) {
|
|
760
|
+
return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/");
|
|
761
|
+
}
|
|
762
|
+
getNodes() {
|
|
763
|
+
return this.nodes;
|
|
764
|
+
}
|
|
765
|
+
getEdges() {
|
|
766
|
+
return this.edges;
|
|
767
|
+
}
|
|
768
|
+
getNode(id) {
|
|
769
|
+
return this.nodes.get(id);
|
|
770
|
+
}
|
|
771
|
+
getNeighbors(id) {
|
|
772
|
+
const inbound = [];
|
|
773
|
+
const outbound = [];
|
|
774
|
+
for (const edge of this.edges) {
|
|
775
|
+
if (edge.target === id) {
|
|
776
|
+
const node = this.nodes.get(edge.source);
|
|
777
|
+
if (node) inbound.push(node);
|
|
778
|
+
}
|
|
779
|
+
if (edge.source === id) {
|
|
780
|
+
const node = this.nodes.get(edge.target);
|
|
781
|
+
if (node) outbound.push(node);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
return { inbound, outbound };
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
// src/graph/analyzer/pagerank.ts
|
|
789
|
+
function analyzeGraph(graph) {
|
|
790
|
+
const nodes = graph.getNodes();
|
|
791
|
+
const ranked = [];
|
|
792
|
+
for (const [, node] of nodes) {
|
|
793
|
+
let centrality = node.inboundEdges;
|
|
794
|
+
if (node.metadata?.heuristics?.isReact) {
|
|
795
|
+
centrality += 5;
|
|
796
|
+
}
|
|
797
|
+
if (node.metadata?.heuristics?.hasMainMethod) {
|
|
798
|
+
centrality += 5;
|
|
799
|
+
}
|
|
800
|
+
ranked.push({
|
|
801
|
+
...node,
|
|
802
|
+
centrality
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
ranked.sort((a, b) => b.centrality - a.centrality);
|
|
806
|
+
return ranked;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// src/renderers/json/exporter.ts
|
|
810
|
+
import { writeFile } from "fs/promises";
|
|
811
|
+
async function exportGraphToJson(graph, parsedFiles, outputPath) {
|
|
812
|
+
const fileMap = /* @__PURE__ */ new Map();
|
|
813
|
+
for (const file of parsedFiles) {
|
|
814
|
+
fileMap.set(file.id, file);
|
|
815
|
+
}
|
|
816
|
+
const nodes = [];
|
|
817
|
+
for (const [nodeId] of graph.getNodes()) {
|
|
818
|
+
const parsedFile = fileMap.get(nodeId);
|
|
819
|
+
if (parsedFile) {
|
|
820
|
+
nodes.push({
|
|
821
|
+
id: parsedFile.id,
|
|
822
|
+
relativePath: parsedFile.relativePath,
|
|
823
|
+
language: parsedFile.language,
|
|
824
|
+
sizeBytes: parsedFile.metadata.sizeBytes,
|
|
825
|
+
heuristics: parsedFile.metadata.heuristics
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
const edges = graph.getEdges().map((edge) => ({
|
|
830
|
+
source: edge.source,
|
|
831
|
+
target: edge.target,
|
|
832
|
+
type: edge.type
|
|
833
|
+
}));
|
|
834
|
+
const output = { nodes, edges };
|
|
835
|
+
await writeFile(outputPath, JSON.stringify(output, null, 2));
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// src/utils/colors.ts
|
|
839
|
+
import picocolors from "picocolors";
|
|
840
|
+
var pc = {
|
|
841
|
+
bold: picocolors.bold,
|
|
842
|
+
dim: picocolors.dim,
|
|
843
|
+
cyan: picocolors.cyan,
|
|
844
|
+
yellow: picocolors.yellow,
|
|
845
|
+
green: picocolors.green,
|
|
846
|
+
red: picocolors.red,
|
|
847
|
+
blue: picocolors.blue,
|
|
848
|
+
magenta: picocolors.magenta,
|
|
849
|
+
white: picocolors.white
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
// src/cli/commands.ts
|
|
853
|
+
function createCommand() {
|
|
854
|
+
const program2 = new Command();
|
|
855
|
+
program2.name("repolens").description("Visualize repository dependency graphs").version("0.1.0").argument("[path]", "Directory to scan", process.cwd()).option("-v, --verbose", "Enable verbose output", false).option(
|
|
856
|
+
"-f, --format <format>",
|
|
857
|
+
"Output format (text, json)",
|
|
858
|
+
"text"
|
|
859
|
+
).option("-o, --output <file>", "Output file path").action(async (path, options) => {
|
|
860
|
+
const scanResult = scanDirectory({ rootDir: path, verbose: options.verbose });
|
|
861
|
+
if (options.verbose) {
|
|
862
|
+
console.log(pc.dim("\nParsing files..."));
|
|
863
|
+
}
|
|
864
|
+
const parsedFiles = [];
|
|
865
|
+
for (const file of scanResult.files) {
|
|
866
|
+
const parser = createParser(file.absolutePath);
|
|
867
|
+
if (parser) {
|
|
868
|
+
try {
|
|
869
|
+
const content = readFileSync2(file.absolutePath, "utf-8");
|
|
870
|
+
const parsed = await parser.parse(file.absolutePath, content);
|
|
871
|
+
parsedFiles.push(parsed);
|
|
872
|
+
} catch (err) {
|
|
873
|
+
if (options.verbose) {
|
|
874
|
+
console.warn(`Warning: Failed to parse ${file.relativePath}: ${err}`);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
const graph = new Graph(parsedFiles);
|
|
880
|
+
const rankedNodes = analyzeGraph(graph);
|
|
881
|
+
if (options.format === "json") {
|
|
882
|
+
const outputPath = options.output ?? resolve3(process.cwd(), "repolens-graph.json");
|
|
883
|
+
await exportGraphToJson(graph, parsedFiles, outputPath);
|
|
884
|
+
console.log(`Graph exported to ${outputPath}`);
|
|
885
|
+
} else {
|
|
886
|
+
printSummary(scanResult, rankedNodes, options.verbose);
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
return program2;
|
|
890
|
+
}
|
|
891
|
+
function printSummary(result, rankedNodes, verbose) {
|
|
892
|
+
console.log(pc.bold("\nRepository Scan Summary\n"));
|
|
893
|
+
console.log(`Total files found: ${pc.cyan(result.totalFiles.toString())}`);
|
|
894
|
+
console.log(`Files ignored: ${result.ignoredCount}
|
|
895
|
+
`);
|
|
896
|
+
console.log(pc.bold("Files by language:"));
|
|
897
|
+
const langColors = {
|
|
898
|
+
javascript: pc.yellow,
|
|
899
|
+
typescript: pc.blue,
|
|
900
|
+
python: pc.green,
|
|
901
|
+
java: pc.red,
|
|
902
|
+
markdown: pc.magenta
|
|
903
|
+
};
|
|
904
|
+
for (const [lang, count] of Object.entries(result.filesByLanguage)) {
|
|
905
|
+
if (count > 0) {
|
|
906
|
+
const color = langColors[lang] ?? pc.white;
|
|
907
|
+
console.log(` ${color(`\u2022 ${lang}`)}: ${pc.bold(count.toString())}`);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
const topHubs = rankedNodes.filter((n) => n.inboundEdges > 0).slice(0, 3);
|
|
911
|
+
if (topHubs.length > 0) {
|
|
912
|
+
console.log(pc.bold("\nTop Hubs (Most Connected):"));
|
|
913
|
+
for (const hub of topHubs) {
|
|
914
|
+
const badge = hub.metadata?.heuristics?.isReact ? " [React]" : "";
|
|
915
|
+
console.log(
|
|
916
|
+
` ${pc.cyan(hub.relativePath)}${badge}: ${pc.bold(hub.inboundEdges.toString())} inbound`
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
if (verbose && result.files.length > 0) {
|
|
921
|
+
console.log(pc.bold("\nScanned files:"));
|
|
922
|
+
for (const file of result.files.slice(0, 50)) {
|
|
923
|
+
console.log(` ${pc.dim(file.relativePath)}`);
|
|
924
|
+
}
|
|
925
|
+
if (result.files.length > 50) {
|
|
926
|
+
console.log(` ${pc.dim(`... and ${result.files.length - 50} more`)}`);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
console.log();
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// src/cli/index.ts
|
|
933
|
+
var program = createCommand();
|
|
934
|
+
program.parse(process.argv);
|