codeinf 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.
Files changed (4) hide show
  1. package/README.md +167 -0
  2. package/bin/cli.js +189 -0
  3. package/index.js +199 -0
  4. package/package.json +39 -0
package/README.md ADDED
@@ -0,0 +1,167 @@
1
+ # codeinf
2
+
3
+ A CLI tool and library to analyze code statistics like lines of code, file counts, and more with filtering capabilities.
4
+
5
+ ## Installation
6
+
7
+ ### Global Installation (CLI usage)
8
+ ```bash
9
+ npm install -g codeinf
10
+ ```
11
+
12
+ ### Local Installation (Programmatic usage)
13
+ ```bash
14
+ npm install codeinf
15
+ ```
16
+
17
+ ## CLI Usage
18
+
19
+ ### Basic Usage
20
+ ```bash
21
+ # Analyze current directory
22
+ codeinf
23
+
24
+ # Analyze specific directory
25
+ codeinf ./src
26
+
27
+ # Analyze with specific file extensions
28
+ codeinf -e js,ts,jsx,tsx
29
+ codeinf --extensions .js,.ts
30
+
31
+ # Output as JSON
32
+ codeinf --json
33
+ codeinf -f json
34
+
35
+ # Ignore specific directories
36
+ codeinf -i "test,docs,build"
37
+
38
+ # Show top 20 largest files
39
+ codeinf -t 20
40
+ ```
41
+
42
+ ### CLI Options
43
+
44
+ | Option | Description |
45
+ |--------|-------------|
46
+ | `-h, --help` | Show help message |
47
+ | `-v, --version` | Show version |
48
+ | `-e, --ext, --extensions` | Filter by file extensions (comma-separated) |
49
+ | `-i, --ignore` | Additional ignore patterns (comma-separated) |
50
+ | `-f, --format` | Output format: `table` or `json` |
51
+ | `--json` | Shortcut for JSON output |
52
+ | `-t, --top` | Number of largest files to show (default: 10) |
53
+
54
+ ## Programmatic API
55
+
56
+ ### Basic Usage
57
+
58
+ ```javascript
59
+ const { analyze, formatBytes } = require('codeinf');
60
+
61
+ // Analyze current directory
62
+ const stats = analyze('.');
63
+ console.log(stats);
64
+
65
+ // Analyze with options
66
+ const stats = analyze('./src', {
67
+ extensions: ['.js', '.ts'],
68
+ ignore: ['test', 'dist'],
69
+ recursive: true
70
+ });
71
+
72
+ console.log(`Total files: ${stats.summary.files}`);
73
+ console.log(`Total lines: ${stats.summary.totalLines}`);
74
+ console.log(`Code lines: ${stats.summary.codeLines}`);
75
+ ```
76
+
77
+ ### API Reference
78
+
79
+ #### `analyze(targetPath, options)`
80
+
81
+ Analyzes code statistics for a given path.
82
+
83
+ **Parameters:**
84
+ - `targetPath` (string): Path to analyze (default: '.')
85
+ - `options` (Object):
86
+ - `extensions` (string[]): Filter by file extensions (e.g., ['.js', '.ts'])
87
+ - `ignore` (string[]): Additional patterns to ignore
88
+ - `recursive` (boolean): Scan recursively (default: true)
89
+
90
+ **Returns:**
91
+ ```javascript
92
+ {
93
+ path: '/absolute/path',
94
+ summary: {
95
+ files: 42,
96
+ totalLines: 5000,
97
+ nonEmptyLines: 4500,
98
+ codeLines: 3800,
99
+ size: 1024000
100
+ },
101
+ byExtension: {
102
+ '.js': { files: 20, lines: 3000, size: 500000 },
103
+ '.ts': { files: 22, lines: 2000, size: 524000 }
104
+ },
105
+ largestFiles: [
106
+ { path: '...', totalLines: 500, nonEmptyLines: 450, codeLines: 400, size: 10000, extension: '.js' }
107
+ ],
108
+ allFiles: [...]
109
+ }
110
+ ```
111
+
112
+ #### `scanDirectory(dir, options)`
113
+
114
+ Scans a directory and returns file statistics.
115
+
116
+ ```javascript
117
+ const { scanDirectory } = require('codeinf');
118
+
119
+ const files = scanDirectory('./src', {
120
+ extensions: ['.js'],
121
+ ignore: ['node_modules'],
122
+ recursive: true
123
+ });
124
+
125
+ files.forEach(file => {
126
+ console.log(`${file.path}: ${file.totalLines} lines`);
127
+ });
128
+ ```
129
+
130
+ #### `getFileStats(filePath)`
131
+
132
+ Gets statistics for a single file.
133
+
134
+ ```javascript
135
+ const { getFileStats } = require('codeinf');
136
+
137
+ const stats = getFileStats('./src/index.js');
138
+ console.log(stats);
139
+ // { path, size, totalLines, nonEmptyLines, codeLines, extension }
140
+ ```
141
+
142
+ #### `formatBytes(bytes)`
143
+
144
+ Formats bytes to human-readable string.
145
+
146
+ ```javascript
147
+ const { formatBytes } = require('codeinf');
148
+
149
+ console.log(formatBytes(1024)); // "1 KB"
150
+ console.log(formatBytes(1048576)); // "1 MB"
151
+ ```
152
+
153
+ ## Default Ignore Patterns
154
+
155
+ The following directories are automatically ignored:
156
+ - `node_modules`
157
+ - `.git`, `.svn`, `.hg`
158
+ - `.DS_Store`
159
+ - `dist`, `build`
160
+ - `coverage`
161
+ - `.next`, `.nuxt`
162
+ - `.cache`
163
+ - `vendor`, `bin`, `obj`
164
+
165
+ ## License
166
+
167
+ MIT
package/bin/cli.js ADDED
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { analyze, formatBytes } = require('../index');
4
+ const path = require('path');
5
+
6
+ // Parse command line arguments
7
+ function parseArgs(args) {
8
+ const options = {
9
+ path: '.',
10
+ extensions: [],
11
+ ignore: [],
12
+ format: 'table',
13
+ top: 10,
14
+ help: false,
15
+ version: false
16
+ };
17
+
18
+ for (let i = 0; i < args.length; i++) {
19
+ const arg = args[i];
20
+
21
+ switch (arg) {
22
+ case '-h':
23
+ case '--help':
24
+ options.help = true;
25
+ break;
26
+ case '-v':
27
+ case '--version':
28
+ options.version = true;
29
+ break;
30
+ case '-e':
31
+ case '--ext':
32
+ case '--extensions':
33
+ i++;
34
+ if (args[i]) {
35
+ options.extensions = args[i].split(',').map(e => {
36
+ const ext = e.trim().toLowerCase();
37
+ return ext.startsWith('.') ? ext : '.' + ext;
38
+ });
39
+ }
40
+ break;
41
+ case '-i':
42
+ case '--ignore':
43
+ i++;
44
+ if (args[i]) {
45
+ options.ignore = args[i].split(',').map(s => s.trim());
46
+ }
47
+ break;
48
+ case '-f':
49
+ case '--format':
50
+ i++;
51
+ if (args[i]) {
52
+ options.format = args[i].toLowerCase();
53
+ }
54
+ break;
55
+ case '-t':
56
+ case '--top':
57
+ i++;
58
+ if (args[i]) {
59
+ options.top = parseInt(args[i], 10) || 10;
60
+ }
61
+ break;
62
+ case '--json':
63
+ options.format = 'json';
64
+ break;
65
+ default:
66
+ if (!arg.startsWith('-') && options.path === '.') {
67
+ options.path = arg;
68
+ }
69
+ }
70
+ }
71
+
72
+ return options;
73
+ }
74
+
75
+ // Show help
76
+ function showHelp() {
77
+ console.log(`
78
+ codeinf - Code statistics analyzer
79
+
80
+ Usage: codeinf [path] [options]
81
+
82
+ Options:
83
+ -h, --help Show this help message
84
+ -v, --version Show version
85
+ -e, --ext <extensions> Filter by file extensions (comma-separated)
86
+ -i, --ignore <patterns> Additional ignore patterns (comma-separated)
87
+ -f, --format <format> Output format: table, json (default: table)
88
+ --json Output as JSON
89
+ -t, --top <number> Number of largest files to show (default: 10)
90
+
91
+ Examples:
92
+ codeinf Analyze current directory
93
+ codeinf ./src Analyze src directory
94
+ codeinf -e js,ts Analyze only JS and TS files
95
+ codeinf -e .js --json Output JS stats as JSON
96
+ codeinf -i "test,dist" Ignore test and dist folders
97
+ codeinf -t 20 Show top 20 largest files
98
+ `);
99
+ }
100
+
101
+ // Show version
102
+ function showVersion() {
103
+ const pkg = require('../package.json');
104
+ console.log(pkg.version);
105
+ }
106
+
107
+ // Format output as table
108
+ function formatTable(data) {
109
+ const { summary, byExtension, largestFiles, path: targetPath } = data;
110
+
111
+ console.log(`\n 📊 Code Statistics for: ${targetPath}\n`);
112
+
113
+ // Summary
114
+ console.log(' Summary:');
115
+ console.log(` Files: ${summary.files.toLocaleString()}`);
116
+ console.log(` Total Lines: ${summary.totalLines.toLocaleString()}`);
117
+ console.log(` Code Lines: ${summary.codeLines.toLocaleString()}`);
118
+ console.log(` Non-Empty: ${summary.nonEmptyLines.toLocaleString()}`);
119
+ console.log(` Total Size: ${formatBytes(summary.size)}`);
120
+ console.log();
121
+
122
+ // By Extension
123
+ if (Object.keys(byExtension).length > 0) {
124
+ console.log(' By Extension:');
125
+ const sorted = Object.entries(byExtension)
126
+ .sort((a, b) => b[1].files - a[1].files);
127
+
128
+ console.log(` ${'Extension'.padEnd(12)} ${'Files'.padStart(8)} ${'Lines'.padStart(10)} ${'Size'.padStart(10)}`);
129
+ console.log(` ${'-'.repeat(42)}`);
130
+
131
+ for (const [ext, stats] of sorted) {
132
+ const extName = ext.padEnd(12);
133
+ const files = stats.files.toLocaleString().padStart(8);
134
+ const lines = stats.lines.toLocaleString().padStart(10);
135
+ const size = formatBytes(stats.size).padStart(10);
136
+ console.log(` ${extName} ${files} ${lines} ${size}`);
137
+ }
138
+ console.log();
139
+ }
140
+
141
+ // Largest Files
142
+ if (largestFiles.length > 0) {
143
+ console.log(` Largest Files (Top ${largestFiles.length}):`);
144
+ console.log(` ${'Lines'.padStart(8)} ${'Size'.padStart(10)} ${'Path'.padStart(4)}`);
145
+ console.log(` ${'-'.repeat(60)}`);
146
+
147
+ for (const file of largestFiles) {
148
+ const lines = file.totalLines.toLocaleString().padStart(8);
149
+ const size = formatBytes(file.size).padStart(10);
150
+ const relPath = path.relative(targetPath, file.path) || file.path;
151
+ console.log(` ${lines} ${size} ${relPath}`);
152
+ }
153
+ console.log();
154
+ }
155
+ }
156
+
157
+ // Main function
158
+ async function main() {
159
+ const args = process.argv.slice(2);
160
+ const options = parseArgs(args);
161
+
162
+ if (options.help) {
163
+ showHelp();
164
+ process.exit(0);
165
+ }
166
+
167
+ if (options.version) {
168
+ showVersion();
169
+ process.exit(0);
170
+ }
171
+
172
+ try {
173
+ const result = analyze(options.path, {
174
+ extensions: options.extensions,
175
+ ignore: options.ignore
176
+ });
177
+
178
+ if (options.format === 'json') {
179
+ console.log(JSON.stringify(result, null, 2));
180
+ } else {
181
+ formatTable(result);
182
+ }
183
+ } catch (err) {
184
+ console.error(`Error: ${err.message}`);
185
+ process.exit(1);
186
+ }
187
+ }
188
+
189
+ main();
package/index.js ADDED
@@ -0,0 +1,199 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Default ignore patterns (node_modules, .git, etc.)
6
+ */
7
+ const DEFAULT_IGNORES = [
8
+ 'node_modules',
9
+ '.git',
10
+ '.svn',
11
+ '.hg',
12
+ '.DS_Store',
13
+ 'dist',
14
+ 'build',
15
+ 'coverage',
16
+ '.next',
17
+ '.nuxt',
18
+ '.cache',
19
+ 'vendor',
20
+ 'bin',
21
+ 'obj'
22
+ ];
23
+
24
+ /**
25
+ * Check if a path should be ignored
26
+ * @param {string} filePath - Path to check
27
+ * @param {string[]} ignorePatterns - Patterns to ignore
28
+ * @returns {boolean}
29
+ */
30
+ function shouldIgnore(filePath, ignorePatterns = []) {
31
+ const allIgnores = [...DEFAULT_IGNORES, ...ignorePatterns];
32
+ const parts = filePath.split(path.sep);
33
+ return allIgnores.some(pattern =>
34
+ parts.some(part => part === pattern || part.match(new RegExp(pattern)))
35
+ );
36
+ }
37
+
38
+ /**
39
+ * Get file statistics
40
+ * @param {string} filePath - Path to file
41
+ * @returns {Object|null}
42
+ */
43
+ function getFileStats(filePath) {
44
+ try {
45
+ const content = fs.readFileSync(filePath, 'utf-8');
46
+ const lines = content.split('\n');
47
+ const nonEmptyLines = lines.filter(line => line.trim().length > 0);
48
+ const codeLines = lines.filter(line => {
49
+ const trimmed = line.trim();
50
+ return trimmed.length > 0 && !trimmed.startsWith('//') && !trimmed.startsWith('#');
51
+ });
52
+
53
+ return {
54
+ path: filePath,
55
+ size: fs.statSync(filePath).size,
56
+ totalLines: lines.length,
57
+ nonEmptyLines: nonEmptyLines.length,
58
+ codeLines: codeLines.length,
59
+ extension: path.extname(filePath).toLowerCase()
60
+ };
61
+ } catch (err) {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Recursively scan directory for files
68
+ * @param {string} dir - Directory to scan
69
+ * @param {Object} options - Options
70
+ * @returns {Object[]}
71
+ */
72
+ function scanDirectory(dir, options = {}) {
73
+ const {
74
+ extensions = [],
75
+ ignore = [],
76
+ recursive = true
77
+ } = options;
78
+
79
+ const results = [];
80
+
81
+ function scan(currentPath) {
82
+ if (shouldIgnore(currentPath, ignore)) {
83
+ return;
84
+ }
85
+
86
+ try {
87
+ const stats = fs.statSync(currentPath);
88
+
89
+ if (stats.isDirectory()) {
90
+ if (recursive) {
91
+ const items = fs.readdirSync(currentPath);
92
+ for (const item of items) {
93
+ scan(path.join(currentPath, item));
94
+ }
95
+ }
96
+ } else {
97
+ const ext = path.extname(currentPath).toLowerCase();
98
+ if (extensions.length === 0 || extensions.includes(ext)) {
99
+ const fileStats = getFileStats(currentPath);
100
+ if (fileStats) {
101
+ results.push(fileStats);
102
+ }
103
+ }
104
+ }
105
+ } catch (err) {
106
+ // Skip files we can't access
107
+ }
108
+ }
109
+
110
+ scan(dir);
111
+ return results;
112
+ }
113
+
114
+ /**
115
+ * Analyze code statistics
116
+ * @param {string} targetPath - Path to analyze
117
+ * @param {Object} options - Analysis options
118
+ * @returns {Object}
119
+ */
120
+ function analyze(targetPath = '.', options = {}) {
121
+ const resolvedPath = path.resolve(targetPath);
122
+
123
+ if (!fs.existsSync(resolvedPath)) {
124
+ throw new Error(`Path not found: ${targetPath}`);
125
+ }
126
+
127
+ const stats = fs.statSync(resolvedPath);
128
+ let files = [];
129
+
130
+ if (stats.isDirectory()) {
131
+ files = scanDirectory(resolvedPath, options);
132
+ } else {
133
+ const fileStats = getFileStats(resolvedPath);
134
+ if (fileStats) {
135
+ files = [fileStats];
136
+ }
137
+ }
138
+
139
+ // Calculate totals
140
+ const totalStats = files.reduce((acc, file) => ({
141
+ files: acc.files + 1,
142
+ totalLines: acc.totalLines + file.totalLines,
143
+ nonEmptyLines: acc.nonEmptyLines + file.nonEmptyLines,
144
+ codeLines: acc.codeLines + file.codeLines,
145
+ size: acc.size + file.size
146
+ }), {
147
+ files: 0,
148
+ totalLines: 0,
149
+ nonEmptyLines: 0,
150
+ codeLines: 0,
151
+ size: 0
152
+ });
153
+
154
+ // Group by extension
155
+ const byExtension = files.reduce((acc, file) => {
156
+ const ext = file.extension || '(no extension)';
157
+ if (!acc[ext]) {
158
+ acc[ext] = { files: 0, lines: 0, size: 0 };
159
+ }
160
+ acc[ext].files++;
161
+ acc[ext].lines += file.totalLines;
162
+ acc[ext].size += file.size;
163
+ return acc;
164
+ }, {});
165
+
166
+ // Sort files by lines (descending)
167
+ const largestFiles = [...files]
168
+ .sort((a, b) => b.totalLines - a.totalLines)
169
+ .slice(0, 10);
170
+
171
+ return {
172
+ path: resolvedPath,
173
+ summary: totalStats,
174
+ byExtension,
175
+ largestFiles,
176
+ allFiles: files
177
+ };
178
+ }
179
+
180
+ /**
181
+ * Format bytes to human readable
182
+ * @param {number} bytes
183
+ * @returns {string}
184
+ */
185
+ function formatBytes(bytes) {
186
+ if (bytes === 0) return '0 B';
187
+ const k = 1024;
188
+ const sizes = ['B', 'KB', 'MB', 'GB'];
189
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
190
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
191
+ }
192
+
193
+ module.exports = {
194
+ analyze,
195
+ scanDirectory,
196
+ getFileStats,
197
+ formatBytes,
198
+ DEFAULT_IGNORES
199
+ };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "codeinf",
3
+ "version": "1.0.0",
4
+ "description": "A CLI tool to analyze code statistics like lines of code, file counts, and more with filtering capabilities",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "codeinf": "./bin/cli.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "bin/",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "test": "node test.js"
16
+ },
17
+ "keywords": [
18
+ "code",
19
+ "statistics",
20
+ "lines-of-code",
21
+ "loc",
22
+ "cli",
23
+ "analyzer",
24
+ "code-metrics"
25
+ ],
26
+ "author": "",
27
+ "license": "MIT",
28
+ "engines": {
29
+ "node": ">=14.0.0"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/yourusername/codeinf.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/yourusername/codeinf/issues"
37
+ },
38
+ "homepage": "https://github.com/yourusername/codeinf#readme"
39
+ }