@anirudw/repolens 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/.github/workflows/publish.yml +34 -0
- package/README.md +42 -0
- package/bin/repolens.js +6 -0
- package/package.json +42 -0
- package/src/cli/commands.ts +112 -0
- package/src/cli/index.ts +4 -0
- package/src/graph/analyzer/pagerank.ts +31 -0
- package/src/graph/index.ts +2 -0
- package/src/graph/network.ts +148 -0
- package/src/parser/index.ts +32 -0
- package/src/parser/strategies/java.ts +154 -0
- package/src/parser/strategies/javascript.ts +199 -0
- package/src/parser/strategies/markdown.ts +83 -0
- package/src/parser/strategies/python.ts +184 -0
- package/src/parser/types.ts +18 -0
- package/src/renderers/json/exporter.ts +56 -0
- package/src/scanner/file-types.ts +42 -0
- package/src/scanner/ignore.ts +58 -0
- package/src/scanner/index.ts +9 -0
- package/src/scanner/walker.ts +125 -0
- package/src/utils/colors.ts +13 -0
- package/tests/fixtures/dummy-repo/README.md +5 -0
- package/tests/fixtures/dummy-repo/secret-keys.py~ +0 -0
- package/tests/fixtures/dummy-repo/src/App.java +11 -0
- package/tests/fixtures/dummy-repo/src/api.py +9 -0
- package/tests/fixtures/dummy-repo/src/components/App.tsx +0 -0
- package/tests/fixtures/dummy-repo/src/index.js +7 -0
- package/tests/parser.test.ts +36 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join, relative, isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { createIgnoreRules, shouldIgnore } from "./ignore.js";
|
|
4
|
+
import {
|
|
5
|
+
isTargetLanguage,
|
|
6
|
+
SupportedLanguage,
|
|
7
|
+
getLanguageFromPath,
|
|
8
|
+
} from "./file-types.js";
|
|
9
|
+
|
|
10
|
+
export interface ScannedFile {
|
|
11
|
+
path: string;
|
|
12
|
+
absolutePath: string;
|
|
13
|
+
relativePath: string;
|
|
14
|
+
language: SupportedLanguage;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ScanResult {
|
|
18
|
+
files: ScannedFile[];
|
|
19
|
+
totalFiles: number;
|
|
20
|
+
filesByLanguage: Record<SupportedLanguage, number>;
|
|
21
|
+
ignoredCount: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ScanOptions {
|
|
25
|
+
rootDir: string;
|
|
26
|
+
verbose?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function scanDirectory(options: ScanOptions): ScanResult {
|
|
30
|
+
const { rootDir, verbose = false } = options;
|
|
31
|
+
const absoluteRoot = isAbsolute(rootDir) ? rootDir : resolve(rootDir);
|
|
32
|
+
|
|
33
|
+
if (verbose) {
|
|
34
|
+
console.log(`Scanning directory: ${absoluteRoot}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const ig = createIgnoreRules(absoluteRoot);
|
|
38
|
+
const files: ScannedFile[] = [];
|
|
39
|
+
let ignoredCount = 0;
|
|
40
|
+
|
|
41
|
+
function walk(dir: string, baseDir: string = dir): void {
|
|
42
|
+
let entries: string[];
|
|
43
|
+
try {
|
|
44
|
+
entries = readdirSync(dir);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (verbose) {
|
|
47
|
+
console.warn(`Warning: Cannot read directory ${dir}: ${err}`);
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
const fullPath = join(dir, entry);
|
|
54
|
+
const relativePath = relative(baseDir, fullPath);
|
|
55
|
+
|
|
56
|
+
let isDir: boolean;
|
|
57
|
+
try {
|
|
58
|
+
isDir = statSync(fullPath).isDirectory();
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (verbose) {
|
|
61
|
+
console.warn(`Warning: Cannot stat ${fullPath}: ${err}`);
|
|
62
|
+
}
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (shouldIgnore(ig, relativePath, isDir)) {
|
|
67
|
+
if (!isDir) {
|
|
68
|
+
ignoredCount++;
|
|
69
|
+
}
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (isDir) {
|
|
74
|
+
walk(fullPath, baseDir);
|
|
75
|
+
} else {
|
|
76
|
+
if (isTargetLanguage(entry)) {
|
|
77
|
+
files.push({
|
|
78
|
+
path: entry,
|
|
79
|
+
absolutePath: fullPath,
|
|
80
|
+
relativePath: normalizePath(relativePath),
|
|
81
|
+
language: getLanguageFromFile(entry),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
walk(absoluteRoot);
|
|
89
|
+
|
|
90
|
+
const filesByLanguage = countByLanguage(files);
|
|
91
|
+
|
|
92
|
+
if (verbose) {
|
|
93
|
+
console.log(`Found ${files.length} target files`);
|
|
94
|
+
for (const [lang, count] of Object.entries(filesByLanguage)) {
|
|
95
|
+
if (count > 0) {
|
|
96
|
+
console.log(` ${lang}: ${count}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
files,
|
|
103
|
+
totalFiles: files.length,
|
|
104
|
+
filesByLanguage,
|
|
105
|
+
ignoredCount,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function normalizePath(p: string): string {
|
|
110
|
+
return p.replace(/\\/g, "/");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getLanguageFromFile(filePath: string): SupportedLanguage {
|
|
114
|
+
return getLanguageFromPath(filePath);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function countByLanguage(
|
|
118
|
+
files: ScannedFile[]
|
|
119
|
+
): Record<SupportedLanguage, number> {
|
|
120
|
+
const counts: Partial<Record<SupportedLanguage, number>> = {};
|
|
121
|
+
for (const file of files) {
|
|
122
|
+
counts[file.language] = (counts[file.language] ?? 0) + 1;
|
|
123
|
+
}
|
|
124
|
+
return counts as Record<SupportedLanguage, number>;
|
|
125
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import picocolors from "picocolors";
|
|
2
|
+
|
|
3
|
+
export const pc = {
|
|
4
|
+
bold: picocolors.bold,
|
|
5
|
+
dim: picocolors.dim,
|
|
6
|
+
cyan: picocolors.cyan,
|
|
7
|
+
yellow: picocolors.yellow,
|
|
8
|
+
green: picocolors.green,
|
|
9
|
+
red: picocolors.red,
|
|
10
|
+
blue: picocolors.blue,
|
|
11
|
+
magenta: picocolors.magenta,
|
|
12
|
+
white: picocolors.white,
|
|
13
|
+
};
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { createParser } from '../src/parser/index';
|
|
4
|
+
|
|
5
|
+
describe('AST Language Parsers', () => {
|
|
6
|
+
it('should parse JavaScript imports and React heuristics', async () => {
|
|
7
|
+
const parser = createParser('.js');
|
|
8
|
+
expect(parser).not.toBeNull();
|
|
9
|
+
|
|
10
|
+
const content = readFileSync('tests/fixtures/dummy-repo/src/index.js', 'utf-8');
|
|
11
|
+
const result = await parser!.parse('tests/fixtures/dummy-repo/src/index.js', content);
|
|
12
|
+
|
|
13
|
+
expect(result.dependencies.length).toBe(3);
|
|
14
|
+
expect(result.dependencies.map(d => d.rawSpecifier)).toContain('react');
|
|
15
|
+
expect(result.metadata.heuristics.isReact).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should parse Python imports and Entry Point heuristics', async () => {
|
|
19
|
+
const parser = createParser('.py');
|
|
20
|
+
const content = readFileSync('tests/fixtures/dummy-repo/src/api.py', 'utf-8');
|
|
21
|
+
const result = await parser!.parse('tests/fixtures/dummy-repo/src/api.py', content);
|
|
22
|
+
|
|
23
|
+
expect(result.dependencies.length).toBe(3);
|
|
24
|
+
expect(result.dependencies.map(d => d.rawSpecifier)).toContain('os');
|
|
25
|
+
expect(result.metadata.heuristics.hasEntrypoint).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should parse Markdown links and wikilinks', async () => {
|
|
29
|
+
const parser = createParser('.md');
|
|
30
|
+
const content = readFileSync('tests/fixtures/dummy-repo/README.md', 'utf-8');
|
|
31
|
+
const result = await parser!.parse('tests/fixtures/dummy-repo/README.md', content);
|
|
32
|
+
|
|
33
|
+
expect(result.dependencies.length).toBe(2);
|
|
34
|
+
expect(result.dependencies.map(d => d.type)).toContain('wikilink');
|
|
35
|
+
});
|
|
36
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationMap": true,
|
|
16
|
+
"sourceMap": true
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "dist", "tests"]
|
|
20
|
+
}
|