reposizer 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # reposizer
2
+
3
+ > Fast CLI to inspect GitHub repository sizes.
4
+
5
+ Check repo size before you clone. Analyze large codebases instantly.
6
+
7
+ ## Demo
8
+
9
+ ```bash
10
+ npx reposizer torvalds/linux
11
+ ```
12
+
13
+ ```text
14
+ Repository: torvalds/linux
15
+ Size: 4.82 GB
16
+ Stars: 190k
17
+ Language: C
18
+ ```
19
+
20
+ ## Features
21
+
22
+ - ⚡ Instant repository size lookup
23
+ - 🔐 Supports private repositories via `GITHUB_TOKEN`
24
+ - 📦 Works with `npx` (no global install required)
25
+ - 🧩 JSON output for scripts and CI tooling
26
+ - 🏢 Organization repository scanning
27
+ - 🔎 Current repository auto-detection (via git remote)
28
+ - 📚 Multi-repository lookup in one command
29
+ - 📊 Directory analysis (planned)
30
+
31
+ ## Installation
32
+
33
+ ### Option 1: no install (recommended)
34
+
35
+ ```bash
36
+ npx reposizer owner/repo
37
+ ```
38
+
39
+ ### Option 2: global install
40
+
41
+ ```bash
42
+ npm install -g reposizer
43
+ ```
44
+
45
+ ## Usage
46
+
47
+ ### Check repository size
48
+
49
+ ```bash
50
+ reposizer openai/gym
51
+ ```
52
+
53
+ ### Check multiple repositories
54
+
55
+ ```bash
56
+ reposizer openai/gym vercel/next.js torvalds/linux
57
+ ```
58
+
59
+ ### Detect current repository automatically
60
+
61
+ ```bash
62
+ reposizer
63
+ ```
64
+
65
+ ### Scan an organization
66
+
67
+ ```bash
68
+ reposizer org openai
69
+ reposizer org openai --limit 50 --json
70
+ ```
71
+
72
+ ### JSON output
73
+
74
+ ```bash
75
+ reposizer openai/gym --json
76
+ ```
77
+
78
+ ### Private repository access
79
+
80
+ ```bash
81
+ export GITHUB_TOKEN=your_token
82
+ reposizer your-org/private-repo
83
+ ```
84
+
85
+ ## Example JSON Output
86
+
87
+ ```json
88
+ {
89
+ "repository": "openai/gym",
90
+ "size_mb": 92.15,
91
+ "stars": 31000,
92
+ "language": "Python"
93
+ }
94
+ ```
95
+
96
+ ## Roadmap
97
+
98
+ - [x] Organization scanning
99
+ - [x] Current repository auto-detection
100
+ - [x] Multi-repository support
101
+ - [ ] Directory size analysis
102
+ - [ ] CI/CD threshold mode
103
+ - [ ] Repository growth tracking
104
+
105
+ ## Why
106
+
107
+ Cloning a massive repository blindly is painful. Reposizer helps you inspect before you pull.
108
+
109
+ ## Security Notes
110
+
111
+ - `GITHUB_TOKEN` is optional and used only for request authorization.
112
+ - Tokens are never printed to output or written to disk.
113
+
114
+ ## License
115
+
116
+ MIT - see [`LICENSE`](LICENSE).
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const repo_1 = require("../src/commands/repo");
6
+ const program = new commander_1.Command();
7
+ program
8
+ .name("reposizer")
9
+ .description("Fast CLI to inspect GitHub repository sizes");
10
+ program
11
+ .argument("[repositories...]", "One or more repositories in owner/repo format")
12
+ .option("--json", "Return machine-readable JSON output")
13
+ .action(async (repositories = [], options) => {
14
+ try {
15
+ await (0, repo_1.runRepositoriesCommand)(repositories, Boolean(options.json));
16
+ }
17
+ catch (error) {
18
+ const message = error instanceof Error ? error.message : "Unexpected error occurred";
19
+ console.error(`Error: ${message}`);
20
+ process.exitCode = 1;
21
+ }
22
+ })
23
+ .addHelpText("after", "\nExamples:\n reposizer openai/gym\n reposizer openai/gym vercel/next.js\n reposizer --json");
24
+ program
25
+ .command("org")
26
+ .description("Scan repositories in a GitHub organization")
27
+ .argument("<organization>", "GitHub organization name")
28
+ .option("--limit <number>", "Maximum repositories to scan (1-100)", "30")
29
+ .option("--json", "Return machine-readable JSON output")
30
+ .action(async (organization, options) => {
31
+ try {
32
+ const limit = Number.parseInt(options.limit, 10);
33
+ if (!Number.isFinite(limit) || limit < 1 || limit > 100) {
34
+ throw new Error("Invalid --limit value. Use a number between 1 and 100.");
35
+ }
36
+ await (0, repo_1.runOrganizationCommand)(organization, Boolean(options.json), limit);
37
+ }
38
+ catch (error) {
39
+ const message = error instanceof Error ? error.message : "Unexpected error occurred";
40
+ console.error(`Error: ${message}`);
41
+ process.exitCode = 1;
42
+ }
43
+ });
44
+ program.parseAsync(process.argv);
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runRepoCommand = runRepoCommand;
4
+ exports.runRepositoriesCommand = runRepositoriesCommand;
5
+ exports.runOrganizationCommand = runOrganizationCommand;
6
+ const github_1 = require("../services/github");
7
+ const git_1 = require("../utils/git");
8
+ const size_1 = require("../utils/size");
9
+ function parseRepository(input) {
10
+ const trimmed = input.trim();
11
+ const match = /^([^/\s]+)\/([^/\s]+)$/.exec(trimmed);
12
+ if (!match) {
13
+ throw new Error("Invalid repository format. Use owner/repo.");
14
+ }
15
+ return { owner: match[1], repo: match[2] };
16
+ }
17
+ async function runRepoCommand(repositoryInput, jsonOutput) {
18
+ const { owner, repo } = parseRepository(repositoryInput);
19
+ const metadata = await (0, github_1.fetchRepositoryMetadata)(owner, repo);
20
+ if (jsonOutput) {
21
+ const payload = {
22
+ repository: metadata.fullName,
23
+ size_mb: (0, size_1.kbToMbRounded)(metadata.sizeKb),
24
+ stars: metadata.stargazersCount,
25
+ language: metadata.language ?? "Unknown"
26
+ };
27
+ console.log(JSON.stringify(payload, null, 2));
28
+ return;
29
+ }
30
+ console.log(`Repository: ${metadata.fullName}`);
31
+ console.log(`Size: ${(0, size_1.formatSizeFromKb)(metadata.sizeKb)}`);
32
+ console.log(`Stars: ${(0, size_1.formatStars)(metadata.stargazersCount)}`);
33
+ console.log(`Language: ${metadata.language ?? "Unknown"}`);
34
+ }
35
+ function truncate(value, maxWidth) {
36
+ if (value.length <= maxWidth) {
37
+ return value;
38
+ }
39
+ if (maxWidth <= 1) {
40
+ return value.slice(0, maxWidth);
41
+ }
42
+ return `${value.slice(0, maxWidth - 1)}…`;
43
+ }
44
+ function renderOrgTable(rows) {
45
+ const headers = ["Repository", "Size", "Stars", "Language"];
46
+ const repositoryWidth = Math.min(48, Math.max(headers[0].length, ...rows.map((row) => truncate(row.repository, 48).length)));
47
+ const sizeWidth = Math.max(headers[1].length, ...rows.map((row) => (0, size_1.formatSizeFromKb)(row.size_mb * 1024).length));
48
+ const starsWidth = Math.max(headers[2].length, ...rows.map((row) => (0, size_1.formatStars)(row.stars).length));
49
+ const languageWidth = Math.max(headers[3].length, ...rows.map((row) => row.language.length));
50
+ const border = `+-${"-".repeat(repositoryWidth)}-+-${"-".repeat(sizeWidth)}-+-${"-".repeat(starsWidth)}-+-${"-".repeat(languageWidth)}-+`;
51
+ const headerRow = `| ${headers[0].padEnd(repositoryWidth)} | ${headers[1].padEnd(sizeWidth)} | ${headers[2].padEnd(starsWidth)} | ${headers[3].padEnd(languageWidth)} |`;
52
+ const bodyRows = rows.map((row) => {
53
+ const repository = truncate(row.repository, repositoryWidth).padEnd(repositoryWidth);
54
+ const size = (0, size_1.formatSizeFromKb)(row.size_mb * 1024).padEnd(sizeWidth);
55
+ const stars = (0, size_1.formatStars)(row.stars).padEnd(starsWidth);
56
+ const language = row.language.padEnd(languageWidth);
57
+ return `| ${repository} | ${size} | ${stars} | ${language} |`;
58
+ });
59
+ return [border, headerRow, border, ...bodyRows, border].join("\n");
60
+ }
61
+ function buildPayload(repositoryInput, metadata) {
62
+ return {
63
+ repository: metadata.fullName || repositoryInput,
64
+ size_mb: (0, size_1.kbToMbRounded)(metadata.sizeKb),
65
+ stars: metadata.stargazersCount,
66
+ language: metadata.language ?? "Unknown"
67
+ };
68
+ }
69
+ async function runRepositoriesCommand(repositoryInputs, jsonOutput) {
70
+ const effectiveInputs = repositoryInputs.length > 0
71
+ ? repositoryInputs
72
+ : [(0, git_1.detectCurrentRepositoryFromGitRemote)()];
73
+ const results = await Promise.all(effectiveInputs.map(async (input) => {
74
+ const { owner, repo } = parseRepository(input);
75
+ const metadata = await (0, github_1.fetchRepositoryMetadata)(owner, repo);
76
+ return buildPayload(input, metadata);
77
+ }));
78
+ if (jsonOutput) {
79
+ console.log(JSON.stringify(results.length === 1 ? results[0] : results, null, 2));
80
+ return;
81
+ }
82
+ for (const result of results) {
83
+ console.log(`Repository: ${result.repository}`);
84
+ console.log(`Size: ${(0, size_1.formatSizeFromKb)(result.size_mb * 1024)}`);
85
+ console.log(`Stars: ${(0, size_1.formatStars)(result.stars)}`);
86
+ console.log(`Language: ${result.language}`);
87
+ console.log("");
88
+ }
89
+ }
90
+ async function runOrganizationCommand(organization, jsonOutput, limit) {
91
+ const repositories = await (0, github_1.fetchOrganizationRepositories)(organization, limit);
92
+ const payload = repositories
93
+ .map((repo) => ({
94
+ repository: repo.fullName,
95
+ size_mb: (0, size_1.kbToMbRounded)(repo.sizeKb),
96
+ stars: repo.stargazersCount,
97
+ language: repo.language ?? "Unknown"
98
+ }))
99
+ .sort((a, b) => b.size_mb - a.size_mb);
100
+ if (jsonOutput) {
101
+ console.log(JSON.stringify(payload, null, 2));
102
+ return;
103
+ }
104
+ console.log(`Organization: ${organization}`);
105
+ console.log(`Repositories scanned: ${payload.length}`);
106
+ console.log("");
107
+ console.log(renderOrgTable(payload));
108
+ }
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchRepositoryMetadata = fetchRepositoryMetadata;
4
+ exports.fetchOrganizationRepositories = fetchOrganizationRepositories;
5
+ function createHeaders(token) {
6
+ const headers = {
7
+ Accept: "application/vnd.github+json",
8
+ "User-Agent": "reposizer-cli"
9
+ };
10
+ if (token) {
11
+ headers.Authorization = `Bearer ${token}`;
12
+ }
13
+ return headers;
14
+ }
15
+ function getErrorMessage(status) {
16
+ if (status === 404) {
17
+ return "Repository not found. Check the owner/repo value.";
18
+ }
19
+ if (status === 401 || status === 403) {
20
+ return "GitHub API access denied. Verify GITHUB_TOKEN or rate limits.";
21
+ }
22
+ return `GitHub API returned status ${status}.`;
23
+ }
24
+ async function fetchRepositoryMetadata(owner, repo, token = process.env.GITHUB_TOKEN) {
25
+ const endpoint = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`;
26
+ let response;
27
+ try {
28
+ response = await fetch(endpoint, {
29
+ method: "GET",
30
+ headers: createHeaders(token),
31
+ signal: AbortSignal.timeout(5000)
32
+ });
33
+ }
34
+ catch {
35
+ throw new Error("Unable to reach GitHub API. Check your network and try again.");
36
+ }
37
+ if (!response.ok) {
38
+ throw new Error(getErrorMessage(response.status));
39
+ }
40
+ const data = (await response.json());
41
+ return {
42
+ fullName: data.full_name,
43
+ sizeKb: data.size,
44
+ stargazersCount: data.stargazers_count,
45
+ language: data.language
46
+ };
47
+ }
48
+ async function fetchOrganizationRepositories(org, limit = 30, token = process.env.GITHUB_TOKEN) {
49
+ const safeLimit = Math.max(1, Math.min(limit, 100));
50
+ const endpoint = `https://api.github.com/orgs/${encodeURIComponent(org)}/repos?per_page=${safeLimit}&sort=updated&direction=desc`;
51
+ let response;
52
+ try {
53
+ response = await fetch(endpoint, {
54
+ method: "GET",
55
+ headers: createHeaders(token),
56
+ signal: AbortSignal.timeout(7000)
57
+ });
58
+ }
59
+ catch {
60
+ throw new Error("Unable to reach GitHub API. Check your network and try again.");
61
+ }
62
+ if (!response.ok) {
63
+ if (response.status === 404) {
64
+ throw new Error("Organization not found.");
65
+ }
66
+ throw new Error(getErrorMessage(response.status));
67
+ }
68
+ const data = (await response.json());
69
+ return data
70
+ .filter((repo) => !repo.archived && !repo.disabled)
71
+ .map((repo) => ({
72
+ fullName: repo.full_name,
73
+ sizeKb: repo.size,
74
+ stargazersCount: repo.stargazers_count,
75
+ language: repo.language
76
+ }));
77
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectCurrentRepositoryFromGitRemote = detectCurrentRepositoryFromGitRemote;
4
+ const node_child_process_1 = require("node:child_process");
5
+ function parseRemoteToOwnerRepo(remoteUrl) {
6
+ const normalized = remoteUrl.trim().replace(/\.git$/, "");
7
+ const sshMatch = /^git@github\.com:([^/]+)\/([^/]+)$/.exec(normalized);
8
+ if (sshMatch) {
9
+ return `${sshMatch[1]}/${sshMatch[2]}`;
10
+ }
11
+ const httpsMatch = /^https:\/\/github\.com\/([^/]+)\/([^/]+)$/.exec(normalized);
12
+ if (httpsMatch) {
13
+ return `${httpsMatch[1]}/${httpsMatch[2]}`;
14
+ }
15
+ return null;
16
+ }
17
+ function detectCurrentRepositoryFromGitRemote() {
18
+ let remoteUrl = "";
19
+ try {
20
+ remoteUrl = (0, node_child_process_1.execFileSync)("git", ["remote", "get-url", "origin"], {
21
+ encoding: "utf8",
22
+ timeout: 2000,
23
+ stdio: ["ignore", "pipe", "ignore"]
24
+ }).trim();
25
+ }
26
+ catch {
27
+ throw new Error("No repository argument provided and no git origin remote was detected.");
28
+ }
29
+ const ownerRepo = parseRemoteToOwnerRepo(remoteUrl);
30
+ if (!ownerRepo) {
31
+ throw new Error("Unable to parse git remote. Expected a GitHub origin remote URL.");
32
+ }
33
+ return ownerRepo;
34
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatSizeFromKb = formatSizeFromKb;
4
+ exports.kbToMbRounded = kbToMbRounded;
5
+ exports.formatStars = formatStars;
6
+ function formatSizeFromKb(sizeKb) {
7
+ const sizeMb = sizeKb / 1024;
8
+ if (sizeMb < 1024) {
9
+ return `${sizeMb.toFixed(2)} MB`;
10
+ }
11
+ const sizeGb = sizeMb / 1024;
12
+ return `${sizeGb.toFixed(2)} GB`;
13
+ }
14
+ function kbToMbRounded(sizeKb) {
15
+ return Number((sizeKb / 1024).toFixed(2));
16
+ }
17
+ function formatStars(value) {
18
+ if (value >= 1000) {
19
+ return `${Math.round((value / 1000) * 10) / 10}k`;
20
+ }
21
+ return String(value);
22
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "reposizer",
3
+ "version": "0.1.0",
4
+ "description": "Fast CLI to inspect GitHub repository sizes",
5
+ "license": "MIT",
6
+ "author": "Hanif",
7
+ "type": "commonjs",
8
+ "bin": {
9
+ "reposizer": "dist/bin/reposizer.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json",
21
+ "dev": "tsx bin/reposizer.ts",
22
+ "check": "tsc --noEmit -p tsconfig.json"
23
+ },
24
+ "keywords": [
25
+ "cli",
26
+ "github",
27
+ "repository",
28
+ "size",
29
+ "typescript"
30
+ ],
31
+ "dependencies": {
32
+ "commander": "^12.1.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^22.13.10",
36
+ "tsx": "^4.19.3",
37
+ "typescript": "^5.8.2"
38
+ },
39
+ "main": "index.js",
40
+ "directories": {
41
+ "doc": "docs"
42
+ },
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/Hanif-adedotun/reposizer.git"
46
+ },
47
+ "bugs": {
48
+ "url": "https://github.com/Hanif-adedotun/reposizer/issues"
49
+ },
50
+ "homepage": "https://github.com/Hanif-adedotun/reposizer#readme"
51
+ }