@lowdep/codestat 1.0.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 Rushabh Shah
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,131 @@
1
+ # codestat
2
+
3
+ ![Zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen) ![Node](https://img.shields.io/badge/node-%3E%3D14-blue) ![License: MIT](https://img.shields.io/badge/license-MIT-green) ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey)
4
+
5
+
6
+ Lines of code by language — zero-dependency Node.js alternative to `cloc`, `tokei`, and `scc`.
7
+
8
+ Counts code lines, comment lines, and blank lines per language. Works on Windows, Mac, and Linux with a single `npx codestat`.
9
+
10
+ ---
11
+
12
+ ## Why?
13
+
14
+ - `cloc` requires Perl
15
+ - `tokei` requires Rust/cargo
16
+ - `scc` requires Go
17
+
18
+ `codestat` is a single Node.js file. If you have Node.js, you have `codestat`.
19
+
20
+ ---
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ npm install -g codestat
26
+ ```
27
+
28
+ Or without installing:
29
+
30
+ ```bash
31
+ npx codestat
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Usage
37
+
38
+ ```bash
39
+ codestat # Current directory
40
+ codestat ./src # Specific directory
41
+ codestat --json # JSON output
42
+ codestat --sort files # Sort by file count instead of code lines
43
+ codestat --sort name # Sort alphabetically
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Example Output
49
+
50
+ ```
51
+ codestat my-app
52
+
53
+ Language Files Code Comment Blank Total
54
+ ──────────────────────────────────────────────────────────────────────────
55
+ TypeScript 84 8,234 412 892 9,538 ████████████████
56
+ CSS 12 2,105 67 341 2,513 █████░░░░░░░░░░░
57
+ JavaScript 8 1,204 189 223 1,616 ███░░░░░░░░░░░░░
58
+ HTML 6 456 0 89 545 █░░░░░░░░░░░░░░░
59
+ YAML 4 112 34 28 174 ░░░░░░░░░░░░░░░░
60
+ Markdown 3 0 0 210 210 ░░░░░░░░░░░░░░░░
61
+ JSON 2 198 0 0 198 ░░░░░░░░░░░░░░░░
62
+ ──────────────────────────────────────────────────────────────────────────
63
+ TOTAL 119 12,309 702 1,783 14,794
64
+
65
+ 7 language(s) · skipped: node_modules, dist, build, .git
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Supported Languages (36+)
71
+
72
+ TypeScript, JavaScript, Python, Rust, Go, Java, C/C++, C#, PHP, Ruby, Swift, Kotlin, Shell, CSS, SCSS/Sass, Less, HTML, Vue, Svelte, JSON, YAML, TOML, Markdown, SQL, GraphQL, Dockerfile, Makefile, Terraform, Lua, Dart, R, Elixir, Erlang, Haskell, Scala, Text
73
+
74
+ ---
75
+
76
+ ## Skipped Automatically
77
+
78
+ `node_modules`, `.git`, `dist`, `build`, `out`, `.next`, `coverage`, `__pycache__`, `vendor`, `venv`, `target`
79
+
80
+ ---
81
+
82
+ ## JSON Output
83
+
84
+ ```bash
85
+ codestat --json
86
+ ```
87
+
88
+ ```json
89
+ {
90
+ "directory": "/path/to/project",
91
+ "summary": {
92
+ "files": 119,
93
+ "code": 12309,
94
+ "comments": 702,
95
+ "blank": 1783,
96
+ "total": 14794
97
+ },
98
+ "languages": [
99
+ {
100
+ "name": "TypeScript",
101
+ "files": 84,
102
+ "code": 8234,
103
+ "comments": 412,
104
+ "blank": 892,
105
+ "total": 9538
106
+ }
107
+ ]
108
+ }
109
+ ```
110
+
111
+ ---
112
+
113
+ ## License
114
+
115
+ MIT
116
+
117
+ ---
118
+
119
+ ## Keywords
120
+
121
+ `lines of code` · `loc counter` · `cloc alternative` · `tokei alternative` · `scc alternative` · `code statistics` · `count lines` · `sloc` · `zero dependencies` · `cross-platform`
122
+
123
+ ---
124
+
125
+ <div align="center">
126
+
127
+ **Built to solve, shared to help — Rushabh Shah 🛠️✨**
128
+
129
+ <sub>One of 40+ zero-dependency developer CLI tools — no <code>node_modules</code>, ever.</sub>
130
+
131
+ </div>
@@ -0,0 +1,256 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const VERSION = '1.0.0';
8
+
9
+ // ─── Language definitions ─────────────────────────────────────────────────────
10
+ // Each entry: extensions[], name, lineComment, blockCommentStart, blockCommentEnd
11
+ const LANGUAGES = [
12
+ { name: 'TypeScript', exts: ['.ts', '.tsx'], lc: '//', bs: '/*', be: '*/' },
13
+ { name: 'JavaScript', exts: ['.js', '.jsx', '.mjs', '.cjs'], lc: '//', bs: '/*', be: '*/' },
14
+ { name: 'Python', exts: ['.py', '.pyw'], lc: '#', bs: '"""', be: '"""' },
15
+ { name: 'Rust', exts: ['.rs'], lc: '//', bs: '/*', be: '*/' },
16
+ { name: 'Go', exts: ['.go'], lc: '//', bs: '/*', be: '*/' },
17
+ { name: 'Java', exts: ['.java'], lc: '//', bs: '/*', be: '*/' },
18
+ { name: 'C/C++', exts: ['.c', '.cpp', '.cc', '.cxx', '.h', '.hpp'], lc: '//', bs: '/*', be: '*/' },
19
+ { name: 'C#', exts: ['.cs'], lc: '//', bs: '/*', be: '*/' },
20
+ { name: 'PHP', exts: ['.php'], lc: '//', bs: '/*', be: '*/' },
21
+ { name: 'Ruby', exts: ['.rb'], lc: '#', bs: '=begin', be: '=end' },
22
+ { name: 'Swift', exts: ['.swift'], lc: '//', bs: '/*', be: '*/' },
23
+ { name: 'Kotlin', exts: ['.kt', '.kts'], lc: '//', bs: '/*', be: '*/' },
24
+ { name: 'Shell', exts: ['.sh', '.bash', '.zsh', '.fish'], lc: '#', bs: null, be: null },
25
+ { name: 'CSS', exts: ['.css'], lc: null, bs: '/*', be: '*/' },
26
+ { name: 'SCSS/Sass', exts: ['.scss', '.sass'], lc: '//', bs: '/*', be: '*/' },
27
+ { name: 'Less', exts: ['.less'], lc: '//', bs: '/*', be: '*/' },
28
+ { name: 'HTML', exts: ['.html', '.htm'], lc: null, bs: '<!--', be: '-->' },
29
+ { name: 'Vue', exts: ['.vue'], lc: '//', bs: '<!--', be: '-->' },
30
+ { name: 'Svelte', exts: ['.svelte'], lc: '//', bs: '<!--', be: '-->' },
31
+ { name: 'JSON', exts: ['.json'], lc: null, bs: null, be: null },
32
+ { name: 'YAML', exts: ['.yml', '.yaml'], lc: '#', bs: null, be: null },
33
+ { name: 'TOML', exts: ['.toml'], lc: '#', bs: null, be: null },
34
+ { name: 'Markdown', exts: ['.md', '.mdx'], lc: null, bs: null, be: null },
35
+ { name: 'SQL', exts: ['.sql'], lc: '--', bs: '/*', be: '*/' },
36
+ { name: 'GraphQL', exts: ['.graphql', '.gql'], lc: '#', bs: null, be: null },
37
+ { name: 'Dockerfile', exts: [], names: ['dockerfile'], lc: '#', bs: null, be: null },
38
+ { name: 'Makefile', exts: [], names: ['makefile', 'gnumakefile'], lc: '#', bs: null, be: null },
39
+ { name: 'Terraform', exts: ['.tf', '.tfvars'], lc: '#', bs: '/*', be: '*/' },
40
+ { name: 'Lua', exts: ['.lua'], lc: '--', bs: '--[[', be: ']]' },
41
+ { name: 'Dart', exts: ['.dart'], lc: '//', bs: '/*', be: '*/' },
42
+ { name: 'R', exts: ['.r', '.R'], lc: '#', bs: null, be: null },
43
+ { name: 'Elixir', exts: ['.ex', '.exs'], lc: '#', bs: null, be: null },
44
+ { name: 'Erlang', exts: ['.erl', '.hrl'], lc: '%', bs: null, be: null },
45
+ { name: 'Haskell', exts: ['.hs', '.lhs'], lc: '--', bs: '{-', be: '-}' },
46
+ { name: 'Scala', exts: ['.scala'], lc: '//', bs: '/*', be: '*/' },
47
+ { name: 'Text', exts: ['.txt'], lc: null, bs: null, be: null },
48
+ ];
49
+
50
+ const EXT_MAP = new Map();
51
+ const NAME_MAP = new Map();
52
+ for (const lang of LANGUAGES) {
53
+ for (const e of (lang.exts || [])) EXT_MAP.set(e, lang);
54
+ for (const n of (lang.names || [])) NAME_MAP.set(n, lang);
55
+ }
56
+
57
+ function detectLang(filePath) {
58
+ const ext = path.extname(filePath).toLowerCase();
59
+ if (ext && EXT_MAP.has(ext)) return EXT_MAP.get(ext);
60
+ const base = path.basename(filePath).toLowerCase();
61
+ if (NAME_MAP.has(base)) return NAME_MAP.get(base);
62
+ return null;
63
+ }
64
+
65
+ // ─── Line counter ─────────────────────────────────────────────────────────────
66
+ function countLines(filePath, lang) {
67
+ let content;
68
+ try { content = fs.readFileSync(filePath, 'utf8'); } catch { return null; }
69
+
70
+ const lines = content.split('\n');
71
+ let code = 0, comments = 0, blank = 0;
72
+ let inBlock = false;
73
+
74
+ for (const rawLine of lines) {
75
+ const line = rawLine.trim();
76
+
77
+ if (!line) { blank++; continue; }
78
+
79
+ // Block comment tracking
80
+ if (!inBlock && lang.bs && line.includes(lang.bs)) {
81
+ inBlock = true;
82
+ }
83
+ if (inBlock) {
84
+ comments++;
85
+ if (lang.be && line.includes(lang.be)) inBlock = false;
86
+ continue;
87
+ }
88
+
89
+ // Line comment
90
+ if (lang.lc && line.startsWith(lang.lc)) {
91
+ comments++;
92
+ continue;
93
+ }
94
+
95
+ code++;
96
+ }
97
+
98
+ return { code, comments, blank, total: lines.length };
99
+ }
100
+
101
+ // ─── Directory walker ─────────────────────────────────────────────────────────
102
+ const SKIP_DIRS = new Set([
103
+ 'node_modules', '.git', 'dist', 'build', 'out', '.next', '.nuxt',
104
+ '__pycache__', 'vendor', 'venv', '.venv', 'env', 'target',
105
+ '.turbo', 'coverage', '.cache', '.svelte-kit', 'public',
106
+ ]);
107
+
108
+ function walk(dir, results = new Map()) {
109
+ let entries;
110
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return results; }
111
+
112
+ for (const e of entries) {
113
+ if (SKIP_DIRS.has(e.name)) continue;
114
+ if (e.name.startsWith('.')) continue;
115
+
116
+ const full = path.join(dir, e.name);
117
+ if (e.isDirectory()) {
118
+ walk(full, results);
119
+ } else if (e.isFile()) {
120
+ const lang = detectLang(full);
121
+ if (!lang) continue;
122
+ const counts = countLines(full, lang);
123
+ if (!counts) continue;
124
+
125
+ const key = lang.name;
126
+ if (!results.has(key)) {
127
+ results.set(key, { name: key, files: 0, code: 0, comments: 0, blank: 0, total: 0 });
128
+ }
129
+ const r = results.get(key);
130
+ r.files++;
131
+ r.code += counts.code;
132
+ r.comments += counts.comments;
133
+ r.blank += counts.blank;
134
+ r.total += counts.total;
135
+ }
136
+ }
137
+ return results;
138
+ }
139
+
140
+ // ─── ANSI ─────────────────────────────────────────────────────────────────────
141
+ const isTTY = process.stdout.isTTY;
142
+ const c = (code, t) => isTTY ? `\x1b[${code}m${t}\x1b[0m` : t;
143
+ const bold = t => c('1', t);
144
+ const dim = t => c('2', t);
145
+ const cyan = t => c('36', t);
146
+ const green = t => c('32', t);
147
+ const yellow = t => c('33', t);
148
+
149
+ function bar(pct, width = 16) {
150
+ const filled = Math.round(pct * width);
151
+ const b = '█'.repeat(filled) + '░'.repeat(width - filled);
152
+ if (pct > 0.3) return green(b);
153
+ if (pct > 0.1) return yellow(b);
154
+ return dim(b);
155
+ }
156
+
157
+ // ─── CLI ──────────────────────────────────────────────────────────────────────
158
+ const args = process.argv.slice(2);
159
+ const flags = new Set(args.filter(a => a.startsWith('-')));
160
+ const positional = args.filter(a => !a.startsWith('-'));
161
+
162
+ if (flags.has('--version') || flags.has('-v')) {
163
+ console.log(`codestat v${VERSION}`); process.exit(0);
164
+ }
165
+ if (flags.has('--help') || flags.has('-h')) {
166
+ console.log(`
167
+ ${bold('codestat')} — Lines of code by language
168
+
169
+ ${bold('USAGE')}
170
+ codestat [directory]
171
+
172
+ ${bold('OPTIONS')}
173
+ --json Output as JSON
174
+ --sort code Sort by code lines (default)
175
+ --sort files Sort by file count
176
+ --sort name Sort alphabetically
177
+ --version Show version
178
+ --help Show this help
179
+
180
+ ${bold('EXAMPLES')}
181
+ codestat Current directory
182
+ codestat ./src Specific directory
183
+ codestat --json JSON output
184
+ codestat --sort files Sort by file count
185
+
186
+ ${bold('NOTE')}
187
+ Skips: node_modules, .git, dist, build, coverage, venv, target
188
+ `);
189
+ process.exit(0);
190
+ }
191
+
192
+ const targetDir = path.resolve(positional[0] || '.');
193
+ const asJson = flags.has('--json');
194
+ const sortKey = (() => {
195
+ const i = args.indexOf('--sort');
196
+ return i !== -1 ? (args[i + 1] || 'code') : 'code';
197
+ })();
198
+
199
+ if (!fs.existsSync(targetDir)) {
200
+ console.error(`codestat: directory not found: ${targetDir}`);
201
+ process.exit(1);
202
+ }
203
+
204
+ if (!asJson) process.stderr.write(dim('Counting lines…\r'));
205
+
206
+ const results = walk(targetDir);
207
+ const rows = [...results.values()];
208
+
209
+ // Sort
210
+ rows.sort((a, b) => {
211
+ if (sortKey === 'files') return b.files - a.files;
212
+ if (sortKey === 'name') return a.name.localeCompare(b.name);
213
+ return b.code - a.code;
214
+ });
215
+
216
+ const totCode = rows.reduce((s, r) => s + r.code, 0);
217
+ const totComments = rows.reduce((s, r) => s + r.comments, 0);
218
+ const totBlank = rows.reduce((s, r) => s + r.blank, 0);
219
+ const totTotal = rows.reduce((s, r) => s + r.total, 0);
220
+ const totFiles = rows.reduce((s, r) => s + r.files, 0);
221
+
222
+ if (asJson) {
223
+ console.log(JSON.stringify({
224
+ directory: targetDir,
225
+ summary: { files: totFiles, code: totCode, comments: totComments, blank: totBlank, total: totTotal },
226
+ languages: rows,
227
+ }, null, 2));
228
+ process.exit(0);
229
+ }
230
+
231
+ process.stderr.write(' \r');
232
+
233
+ const projectName = path.basename(targetDir);
234
+ console.log(`\n${bold('codestat')} ${cyan(projectName)} ${dim(targetDir)}\n`);
235
+
236
+ // Dynamic column widths
237
+ const nameW = Math.max(...rows.map(r => r.name.length), 12);
238
+
239
+ const hdr = (s, w) => bold(s.padStart(w));
240
+ console.log(
241
+ ` ${'Language'.padEnd(nameW)} ${hdr('Files', 6)} ${hdr('Code', 9)} ${hdr('Comment', 9)} ${hdr('Blank', 7)} ${hdr('Total', 9)} ${''}`,
242
+ );
243
+ console.log(dim(' ' + '─'.repeat(nameW + 60)));
244
+
245
+ for (const r of rows) {
246
+ const pct = totCode > 0 ? r.code / totCode : 0;
247
+ console.log(
248
+ ` ${r.name.padEnd(nameW)} ${dim(String(r.files).padStart(6))} ${String(r.code).padStart(9)} ${dim(String(r.comments).padStart(9))} ${dim(String(r.blank).padStart(7))} ${dim(String(r.total).padStart(9))} ${bar(pct)}`,
249
+ );
250
+ }
251
+
252
+ console.log(dim(' ' + '─'.repeat(nameW + 60)));
253
+ console.log(
254
+ ` ${bold('TOTAL'.padEnd(nameW))} ${dim(String(totFiles).padStart(6))} ${bold(String(totCode).padStart(9))} ${dim(String(totComments).padStart(9))} ${dim(String(totBlank).padStart(7))} ${dim(String(totTotal).padStart(9))}`,
255
+ );
256
+ console.log(dim(`\n ${rows.length} language(s) · skipped: node_modules, dist, build, .git\n`));
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@lowdep/codestat",
3
+ "version": "1.0.0",
4
+ "description": "Lines of code by language — zero-dep Node.js alternative to cloc/tokei, works on Windows",
5
+ "bin": {
6
+ "codestat": "bin/codestat.js"
7
+ },
8
+ "keywords": [
9
+ "lines-of-code",
10
+ "loc",
11
+ "cloc",
12
+ "statistics",
13
+ "cli",
14
+ "developer-tools",
15
+ "code-analysis",
16
+ "lines of code",
17
+ "loc counter",
18
+ "cloc alternative",
19
+ "tokei alternative",
20
+ "scc alternative",
21
+ "code statistics",
22
+ "count lines",
23
+ "sloc",
24
+ "zero dependencies",
25
+ "cross-platform"
26
+ ],
27
+ "author": "Rushabh Shah",
28
+ "license": "MIT",
29
+ "engines": {
30
+ "node": ">=14"
31
+ },
32
+ "files": [
33
+ "bin/"
34
+ ],
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/Rushabh5000/codestat.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/Rushabh5000/codestat/issues"
41
+ },
42
+ "homepage": "https://github.com/Rushabh5000/codestat#readme",
43
+ "publishConfig": {
44
+ "access": "public"
45
+ }
46
+ }