reposizer 0.2.1 → 0.2.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/README.md +19 -3
- package/dist/bin/reposizer.js +3 -2
- package/dist/src/commands/loc.js +180 -0
- package/dist/src/commands/repo.js +31 -1
- package/dist/src/utils/size.js +13 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,9 +24,10 @@ Language: C
|
|
|
24
24
|
- 📦 Works with `npx` (no global install required)
|
|
25
25
|
- 🧩 JSON output for scripts and CI tooling
|
|
26
26
|
- 🏢 Organization repository scanning
|
|
27
|
-
- 🔎 Current repository auto-detection (via git
|
|
27
|
+
- 🔎 Current repository auto-detection (via `.git/config`)
|
|
28
28
|
- 📚 Multi-repository lookup in one command
|
|
29
|
-
- 📊 Directory analysis (
|
|
29
|
+
- 📊 Directory analysis (no clone)
|
|
30
|
+
- 🧮 Approximate LOC analysis (no clone)
|
|
30
31
|
|
|
31
32
|
## Installation
|
|
32
33
|
|
|
@@ -54,6 +55,7 @@ reposizer openai/gym
|
|
|
54
55
|
|
|
55
56
|
```bash
|
|
56
57
|
reposizer openai/gym vercel/next.js torvalds/linux
|
|
58
|
+
reposizer openai/gym vercel/next.js --sort stars
|
|
57
59
|
```
|
|
58
60
|
|
|
59
61
|
### Detect current repository automatically
|
|
@@ -69,6 +71,19 @@ reposizer org openai
|
|
|
69
71
|
reposizer org openai --limit 50 --json
|
|
70
72
|
```
|
|
71
73
|
|
|
74
|
+
### Analyze top directories (no clone)
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
reposizer vercel/next.js --analyze
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Estimate lines of code (no clone)
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
reposizer vercel/next.js --loc
|
|
84
|
+
reposizer vercel/next.js --loc --json
|
|
85
|
+
```
|
|
86
|
+
|
|
72
87
|
### JSON output
|
|
73
88
|
|
|
74
89
|
```bash
|
|
@@ -98,7 +113,8 @@ reposizer your-org/private-repo
|
|
|
98
113
|
- [x] Organization scanning
|
|
99
114
|
- [x] Current repository auto-detection
|
|
100
115
|
- [x] Multi-repository support
|
|
101
|
-
- [
|
|
116
|
+
- [x] Directory size analysis
|
|
117
|
+
- [x] Approximate LOC analysis
|
|
102
118
|
- [ ] CI/CD threshold mode
|
|
103
119
|
- [ ] Repository growth tracking
|
|
104
120
|
|
package/dist/bin/reposizer.js
CHANGED
|
@@ -11,6 +11,7 @@ program
|
|
|
11
11
|
.argument("[repositories...]", "One or more repositories in owner/repo format")
|
|
12
12
|
.option("--json", "Return machine-readable JSON output")
|
|
13
13
|
.option("--analyze", "Approximate top-level directory sizes from Git tree metadata (no full clone)")
|
|
14
|
+
.option("--loc", "Approximate lines of code using Git tree metadata (no full clone)")
|
|
14
15
|
.option("--sort <field>", "When comparing multiple repositories: sort by size, stars, or name", "size")
|
|
15
16
|
.action(async (repositories = [], options) => {
|
|
16
17
|
try {
|
|
@@ -20,7 +21,7 @@ program
|
|
|
20
21
|
sortRaw !== "name") {
|
|
21
22
|
throw new Error('Invalid --sort. Use "size", "stars", or "name".');
|
|
22
23
|
}
|
|
23
|
-
await (0, repo_1.runRepositoriesCommand)(repositories, Boolean(options.json), Boolean(options.analyze), sortRaw);
|
|
24
|
+
await (0, repo_1.runRepositoriesCommand)(repositories, Boolean(options.json), Boolean(options.analyze), Boolean(options.loc), sortRaw);
|
|
24
25
|
}
|
|
25
26
|
catch (error) {
|
|
26
27
|
const message = error instanceof Error ? error.message : "Unexpected error occurred";
|
|
@@ -28,7 +29,7 @@ program
|
|
|
28
29
|
process.exitCode = 1;
|
|
29
30
|
}
|
|
30
31
|
})
|
|
31
|
-
.addHelpText("after", "\nExamples:\n reposizer openai/gym\n reposizer vercel/next.js facebook/react\n reposizer vercel/next.js facebook/react --sort stars\n reposizer vercel/next.js --analyze\n reposizer org vercel --limit 10\n reposizer --json");
|
|
32
|
+
.addHelpText("after", "\nExamples:\n reposizer openai/gym\n reposizer vercel/next.js facebook/react\n reposizer vercel/next.js facebook/react --sort stars\n reposizer vercel/next.js --analyze\n reposizer vercel/next.js --loc\n reposizer org vercel --limit 10\n reposizer --json");
|
|
32
33
|
program
|
|
33
34
|
.command("org")
|
|
34
35
|
.description("Scan repositories in a GitHub organization")
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getLocPayload = getLocPayload;
|
|
4
|
+
exports.printLocResult = printLocResult;
|
|
5
|
+
exports.runLocCommand = runLocCommand;
|
|
6
|
+
const tree_1 = require("../services/tree");
|
|
7
|
+
const IGNORED_SEGMENTS = new Set([
|
|
8
|
+
"node_modules",
|
|
9
|
+
"dist",
|
|
10
|
+
"build",
|
|
11
|
+
".next",
|
|
12
|
+
"coverage",
|
|
13
|
+
".git"
|
|
14
|
+
]);
|
|
15
|
+
const EXTENSION_TO_LANGUAGE = {
|
|
16
|
+
ts: "TypeScript",
|
|
17
|
+
tsx: "TypeScript",
|
|
18
|
+
js: "JavaScript",
|
|
19
|
+
jsx: "JavaScript",
|
|
20
|
+
mjs: "JavaScript",
|
|
21
|
+
cjs: "JavaScript",
|
|
22
|
+
py: "Python",
|
|
23
|
+
rb: "Ruby",
|
|
24
|
+
php: "PHP",
|
|
25
|
+
java: "Java",
|
|
26
|
+
kt: "Kotlin",
|
|
27
|
+
rs: "Rust",
|
|
28
|
+
go: "Go",
|
|
29
|
+
c: "C",
|
|
30
|
+
h: "C",
|
|
31
|
+
cc: "C++",
|
|
32
|
+
cpp: "C++",
|
|
33
|
+
cxx: "C++",
|
|
34
|
+
hpp: "C++",
|
|
35
|
+
cs: "C#",
|
|
36
|
+
swift: "Swift",
|
|
37
|
+
scala: "Scala",
|
|
38
|
+
r: "R",
|
|
39
|
+
sh: "Shell",
|
|
40
|
+
bash: "Shell",
|
|
41
|
+
zsh: "Shell",
|
|
42
|
+
ps1: "PowerShell",
|
|
43
|
+
sql: "SQL",
|
|
44
|
+
html: "HTML",
|
|
45
|
+
css: "CSS",
|
|
46
|
+
scss: "SCSS",
|
|
47
|
+
less: "Less",
|
|
48
|
+
vue: "Vue",
|
|
49
|
+
svelte: "Svelte",
|
|
50
|
+
json: "JSON",
|
|
51
|
+
yml: "YAML",
|
|
52
|
+
yaml: "YAML",
|
|
53
|
+
toml: "TOML",
|
|
54
|
+
md: "Markdown",
|
|
55
|
+
mdx: "Markdown",
|
|
56
|
+
txt: "Text"
|
|
57
|
+
};
|
|
58
|
+
const AVG_BYTES_PER_LINE = {
|
|
59
|
+
TypeScript: 33,
|
|
60
|
+
JavaScript: 34,
|
|
61
|
+
Python: 28,
|
|
62
|
+
Ruby: 30,
|
|
63
|
+
PHP: 32,
|
|
64
|
+
Java: 36,
|
|
65
|
+
Kotlin: 36,
|
|
66
|
+
Rust: 34,
|
|
67
|
+
Go: 31,
|
|
68
|
+
C: 30,
|
|
69
|
+
"C++": 33,
|
|
70
|
+
"C#": 35,
|
|
71
|
+
Swift: 35,
|
|
72
|
+
Scala: 37,
|
|
73
|
+
R: 29,
|
|
74
|
+
Shell: 27,
|
|
75
|
+
PowerShell: 32,
|
|
76
|
+
SQL: 36,
|
|
77
|
+
HTML: 40,
|
|
78
|
+
CSS: 34,
|
|
79
|
+
SCSS: 36,
|
|
80
|
+
Less: 35,
|
|
81
|
+
Vue: 38,
|
|
82
|
+
Svelte: 36,
|
|
83
|
+
JSON: 48,
|
|
84
|
+
YAML: 42,
|
|
85
|
+
TOML: 32,
|
|
86
|
+
Markdown: 52,
|
|
87
|
+
Text: 62,
|
|
88
|
+
Other: 38
|
|
89
|
+
};
|
|
90
|
+
function shouldIgnore(path) {
|
|
91
|
+
const segments = path.split("/");
|
|
92
|
+
return segments.some((segment) => IGNORED_SEGMENTS.has(segment));
|
|
93
|
+
}
|
|
94
|
+
function getLanguageFromPath(path) {
|
|
95
|
+
const fileName = path.split("/").pop() ?? path;
|
|
96
|
+
if (fileName.endsWith(".min.js") || fileName.endsWith(".min.css")) {
|
|
97
|
+
return "Other";
|
|
98
|
+
}
|
|
99
|
+
const ext = fileName.includes(".") ? fileName.split(".").pop() ?? "" : "";
|
|
100
|
+
if (!ext) {
|
|
101
|
+
return "Other";
|
|
102
|
+
}
|
|
103
|
+
return EXTENSION_TO_LANGUAGE[ext.toLowerCase()] ?? "Other";
|
|
104
|
+
}
|
|
105
|
+
function estimateLines(language, sizeBytes) {
|
|
106
|
+
const divisor = AVG_BYTES_PER_LINE[language] ?? AVG_BYTES_PER_LINE.Other;
|
|
107
|
+
return Math.max(1, Math.round(sizeBytes / divisor));
|
|
108
|
+
}
|
|
109
|
+
function toSortedEntries(record, limit) {
|
|
110
|
+
return Object.entries(record)
|
|
111
|
+
.sort((a, b) => b[1] - a[1])
|
|
112
|
+
.slice(0, limit)
|
|
113
|
+
.map(([key, value]) => ({ key, value }));
|
|
114
|
+
}
|
|
115
|
+
async function getLocPayload(owner, repo) {
|
|
116
|
+
const { tree, truncated } = await (0, tree_1.getRepoTree)(owner, repo);
|
|
117
|
+
const byLanguage = {};
|
|
118
|
+
const byDirectory = {};
|
|
119
|
+
let totalLines = 0;
|
|
120
|
+
for (const item of tree) {
|
|
121
|
+
if (item.type !== "blob") {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const path = item.path;
|
|
125
|
+
if (shouldIgnore(path)) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const sizeBytes = item.size ?? 0;
|
|
129
|
+
if (sizeBytes === 0) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const language = getLanguageFromPath(path);
|
|
133
|
+
const lines = estimateLines(language, sizeBytes);
|
|
134
|
+
totalLines += lines;
|
|
135
|
+
byLanguage[language] = (byLanguage[language] ?? 0) + lines;
|
|
136
|
+
const topDir = path.includes("/") ? path.split("/")[0] : "root";
|
|
137
|
+
byDirectory[topDir] = (byDirectory[topDir] ?? 0) + lines;
|
|
138
|
+
}
|
|
139
|
+
const topLanguages = toSortedEntries(byLanguage, 10).map(({ key, value }) => ({
|
|
140
|
+
language: key,
|
|
141
|
+
lines: value
|
|
142
|
+
}));
|
|
143
|
+
const topDirectories = toSortedEntries(byDirectory, 10).map(({ key, value }) => ({
|
|
144
|
+
directory: key,
|
|
145
|
+
lines: value
|
|
146
|
+
}));
|
|
147
|
+
return {
|
|
148
|
+
repository: `${owner}/${repo}`,
|
|
149
|
+
approx: true,
|
|
150
|
+
truncated,
|
|
151
|
+
total_lines: totalLines,
|
|
152
|
+
by_language: topLanguages,
|
|
153
|
+
by_directory: topDirectories,
|
|
154
|
+
method: "size-based-estimate"
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function printLocResult(payload) {
|
|
158
|
+
const formatLineCount = (value) => new Intl.NumberFormat("en-US").format(value);
|
|
159
|
+
if (payload.truncated) {
|
|
160
|
+
console.error("Warning: Git tree response was truncated by GitHub; LOC totals may be incomplete.");
|
|
161
|
+
}
|
|
162
|
+
console.log(`Repository: ${payload.repository}`);
|
|
163
|
+
console.log(`Total LOC (approx): ${formatLineCount(payload.total_lines)}`);
|
|
164
|
+
console.log("");
|
|
165
|
+
console.log("Top languages:");
|
|
166
|
+
console.log("--------------");
|
|
167
|
+
for (const row of payload.by_language) {
|
|
168
|
+
console.log(`${row.language.padEnd(15)} ${formatLineCount(row.lines)}`);
|
|
169
|
+
}
|
|
170
|
+
console.log("");
|
|
171
|
+
console.log("Top directories:");
|
|
172
|
+
console.log("----------------");
|
|
173
|
+
for (const row of payload.by_directory) {
|
|
174
|
+
console.log(`${row.directory.padEnd(15)} ${formatLineCount(row.lines)}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function runLocCommand(owner, repo) {
|
|
178
|
+
const payload = await getLocPayload(owner, repo);
|
|
179
|
+
printLocResult(payload);
|
|
180
|
+
}
|
|
@@ -5,6 +5,7 @@ exports.runRepositoriesCommand = runRepositoriesCommand;
|
|
|
5
5
|
exports.runOrganizationCommand = runOrganizationCommand;
|
|
6
6
|
const github_1 = require("../services/github");
|
|
7
7
|
const analyze_1 = require("./analyze");
|
|
8
|
+
const loc_1 = require("./loc");
|
|
8
9
|
const git_1 = require("../utils/git");
|
|
9
10
|
const size_1 = require("../utils/size");
|
|
10
11
|
function parseRepository(input, position) {
|
|
@@ -59,6 +60,7 @@ async function mapWithConcurrency(items, concurrency, mapper) {
|
|
|
59
60
|
}
|
|
60
61
|
const REPO_METADATA_CONCURRENCY = 4;
|
|
61
62
|
const ANALYZE_FETCH_CONCURRENCY = 2;
|
|
63
|
+
const LOC_FETCH_CONCURRENCY = 2;
|
|
62
64
|
function sortRepoRows(rows, sort) {
|
|
63
65
|
const copy = [...rows];
|
|
64
66
|
if (sort === "size") {
|
|
@@ -87,6 +89,8 @@ async function runRepoCommand(repositoryInput, jsonOutput) {
|
|
|
87
89
|
}
|
|
88
90
|
console.log(`Repository: ${metadata.fullName}`);
|
|
89
91
|
console.log(`Size: ${(0, size_1.formatSizeFromKb)(metadata.sizeKb)}`);
|
|
92
|
+
const loc = await (0, loc_1.getLocPayload)(owner, repo);
|
|
93
|
+
console.log(`Lines: ${(0, size_1.formatCompactCount)(loc.total_lines)}`);
|
|
90
94
|
console.log(`Stars: ${(0, size_1.formatStars)(metadata.stargazersCount)}`);
|
|
91
95
|
console.log(`Language: ${metadata.language ?? "Unknown"}`);
|
|
92
96
|
}
|
|
@@ -124,7 +128,10 @@ function buildPayload(repositoryInput, metadata) {
|
|
|
124
128
|
language: metadata.language ?? "Unknown"
|
|
125
129
|
};
|
|
126
130
|
}
|
|
127
|
-
async function runRepositoriesCommand(repositoryInputs, jsonOutput, analyze = false, sort = "size") {
|
|
131
|
+
async function runRepositoriesCommand(repositoryInputs, jsonOutput, analyze = false, loc = false, sort = "size") {
|
|
132
|
+
if (analyze && loc) {
|
|
133
|
+
throw new Error('Use either "--analyze" or "--loc", not both together.');
|
|
134
|
+
}
|
|
128
135
|
const effectiveInputs = repositoryInputs.length > 0
|
|
129
136
|
? repositoryInputs
|
|
130
137
|
: [(0, git_1.detectCurrentRepositoryFromGitRemote)()];
|
|
@@ -149,6 +156,26 @@ async function runRepositoriesCommand(repositoryInputs, jsonOutput, analyze = fa
|
|
|
149
156
|
}
|
|
150
157
|
return;
|
|
151
158
|
}
|
|
159
|
+
if (loc) {
|
|
160
|
+
if (jsonOutput) {
|
|
161
|
+
const payloads = await mapWithConcurrency(parsedArgs, LOC_FETCH_CONCURRENCY, async (item) => (0, loc_1.getLocPayload)(item.owner, item.repo));
|
|
162
|
+
console.log(JSON.stringify(payloads.length === 1 ? payloads[0] : payloads, null, 2));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (parsedArgs.length === 1) {
|
|
166
|
+
const only = parsedArgs[0];
|
|
167
|
+
await (0, loc_1.runLocCommand)(only.owner, only.repo);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const payloads = await mapWithConcurrency(parsedArgs, LOC_FETCH_CONCURRENCY, async (item) => (0, loc_1.getLocPayload)(item.owner, item.repo));
|
|
171
|
+
for (let i = 0; i < payloads.length; i++) {
|
|
172
|
+
(0, loc_1.printLocResult)(payloads[i]);
|
|
173
|
+
if (i < payloads.length - 1) {
|
|
174
|
+
console.log("");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
152
179
|
const results = await mapWithConcurrency(parsedArgs, REPO_METADATA_CONCURRENCY, async (item) => {
|
|
153
180
|
const metadata = await (0, github_1.fetchRepositoryMetadata)(item.owner, item.repo);
|
|
154
181
|
return buildPayload(item.input, metadata);
|
|
@@ -160,8 +187,11 @@ async function runRepositoriesCommand(repositoryInputs, jsonOutput, analyze = fa
|
|
|
160
187
|
}
|
|
161
188
|
if (sorted.length === 1) {
|
|
162
189
|
const result = sorted[0];
|
|
190
|
+
const only = parsedArgs[0];
|
|
191
|
+
const loc = await (0, loc_1.getLocPayload)(only.owner, only.repo);
|
|
163
192
|
console.log(`Repository: ${result.repository}`);
|
|
164
193
|
console.log(`Size: ${(0, size_1.formatSizeFromKb)(result.size_mb * 1024)}`);
|
|
194
|
+
console.log(`Lines: ${(0, size_1.formatCompactCount)(loc.total_lines)} `);
|
|
165
195
|
console.log(`Stars: ${(0, size_1.formatStars)(result.stars)}`);
|
|
166
196
|
console.log(`Language: ${result.language}`);
|
|
167
197
|
return;
|
package/dist/src/utils/size.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.formatBytes = formatBytes;
|
|
|
4
4
|
exports.formatSizeFromKb = formatSizeFromKb;
|
|
5
5
|
exports.kbToMbRounded = kbToMbRounded;
|
|
6
6
|
exports.formatStars = formatStars;
|
|
7
|
+
exports.formatCompactCount = formatCompactCount;
|
|
7
8
|
function formatBytes(bytes) {
|
|
8
9
|
const mb = bytes / (1024 * 1024);
|
|
9
10
|
const gb = mb / 1024;
|
|
@@ -35,3 +36,15 @@ function formatStars(value) {
|
|
|
35
36
|
}
|
|
36
37
|
return String(value);
|
|
37
38
|
}
|
|
39
|
+
function formatCompactCount(value) {
|
|
40
|
+
if (value >= 1_000_000_000) {
|
|
41
|
+
return `${(value / 1_000_000_000).toFixed(1)}B`;
|
|
42
|
+
}
|
|
43
|
+
if (value >= 1_000_000) {
|
|
44
|
+
return `${(value / 1_000_000).toFixed(1)}M`;
|
|
45
|
+
}
|
|
46
|
+
if (value >= 1_000) {
|
|
47
|
+
return `${(value / 1_000).toFixed(1)}K`;
|
|
48
|
+
}
|
|
49
|
+
return String(value);
|
|
50
|
+
}
|