analyze-codebase 1.2.2 ā 1.3.2
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/.claude/settings.local.json +17 -0
- package/.vscode/settings.json +5 -0
- package/CHANGELOG.md +137 -0
- package/ENHANCEMENTS.md +257 -0
- package/FEATURES.md +225 -0
- package/dist/analyze/i18n/index.js +477 -0
- package/dist/analyze/index.js +187 -25
- package/dist/index.js +237 -16
- package/dist/utils/__tests__/config.test.js +94 -0
- package/dist/utils/__tests__/progress.test.js +37 -0
- package/dist/utils/cancellation.js +75 -0
- package/dist/utils/config.js +83 -0
- package/dist/utils/export.js +218 -0
- package/dist/utils/output.js +137 -41
- package/dist/utils/progress.js +51 -0
- package/dist/utils/spinner.js +61 -0
- package/dist/utils/watch.js +99 -0
- package/docs/agents/developer-agent.md +818 -0
- package/docs/agents/research-agent.md +244 -0
- package/package.json +44 -20
- package/readme.md +178 -83
- package/tsconfig.json +5 -2
- package/vitest.config.ts +12 -0
package/dist/analyze/index.js
CHANGED
|
@@ -1,15 +1,96 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
2
25
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
27
|
};
|
|
5
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
29
|
exports.analyzeCodebase = void 0;
|
|
7
30
|
const chalk_1 = __importDefault(require("chalk"));
|
|
31
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
32
|
+
const path = __importStar(require("path"));
|
|
8
33
|
const file_1 = require("../analyze/file");
|
|
9
|
-
const file_2 = require("../utils/file");
|
|
10
34
|
const output_1 = require("../utils/output");
|
|
35
|
+
const progress_1 = require("../utils/progress");
|
|
36
|
+
const spinner_1 = require("../utils/spinner");
|
|
37
|
+
const cancellation_1 = require("../utils/cancellation");
|
|
38
|
+
const collectFiles = async (directory, extensions, exclude) => {
|
|
39
|
+
const patterns = [];
|
|
40
|
+
const ignorePatterns = [
|
|
41
|
+
"**/node_modules/**",
|
|
42
|
+
"**/dist/**",
|
|
43
|
+
"**/build/**",
|
|
44
|
+
"**/coverage/**",
|
|
45
|
+
"**/.git/**",
|
|
46
|
+
"**/.next/**",
|
|
47
|
+
"**/public/**",
|
|
48
|
+
"**/test/**",
|
|
49
|
+
"**/tests/**",
|
|
50
|
+
"**/mocks/**",
|
|
51
|
+
];
|
|
52
|
+
if (exclude) {
|
|
53
|
+
ignorePatterns.push(...exclude.map((ex) => `**/${ex}/**`));
|
|
54
|
+
}
|
|
55
|
+
if (extensions && extensions.length > 0) {
|
|
56
|
+
patterns.push(...extensions.map((ext) => `**/*${ext}`), "**/*" // Also include files without extensions
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
patterns.push("**/*");
|
|
61
|
+
}
|
|
62
|
+
const files = await (0, fast_glob_1.default)(patterns, {
|
|
63
|
+
cwd: directory,
|
|
64
|
+
ignore: ignorePatterns,
|
|
65
|
+
absolute: true,
|
|
66
|
+
onlyFiles: true,
|
|
67
|
+
dot: false,
|
|
68
|
+
});
|
|
69
|
+
// Filter by extension if specified
|
|
70
|
+
if (extensions && extensions.length > 0) {
|
|
71
|
+
return files.filter((file) => {
|
|
72
|
+
const ext = path.extname(file);
|
|
73
|
+
return extensions.includes(ext);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return files;
|
|
77
|
+
};
|
|
78
|
+
const processFile = (filePath, output, fileNameCases) => {
|
|
79
|
+
const { fileNameCase } = (0, file_1.analyzeFile)(filePath, output);
|
|
80
|
+
if (fileNameCases[fileNameCase] !== undefined)
|
|
81
|
+
fileNameCases[fileNameCase] += 1;
|
|
82
|
+
else
|
|
83
|
+
fileNameCases[fileNameCase] = 1;
|
|
84
|
+
};
|
|
11
85
|
const analyzeCodebase = async (options) => {
|
|
12
86
|
const start = Date.now();
|
|
87
|
+
const cancellationToken = new cancellation_1.CancellationToken();
|
|
88
|
+
const spinner = (0, spinner_1.createSpinner)("š Discovering files...");
|
|
89
|
+
// Cleanup on cancellation
|
|
90
|
+
cancellationToken.onCancel(() => {
|
|
91
|
+
spinner.stop();
|
|
92
|
+
console.log(chalk_1.default.yellow("\n\nā ļø Cancellation requested. Cleaning up...\n"));
|
|
93
|
+
});
|
|
13
94
|
const fileNameCases = {};
|
|
14
95
|
const output = {
|
|
15
96
|
Physical: 0,
|
|
@@ -22,29 +103,110 @@ const analyzeCodebase = async (options) => {
|
|
|
22
103
|
Empty: 0,
|
|
23
104
|
ToDo: 0,
|
|
24
105
|
};
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
106
|
+
let progressBar = null;
|
|
107
|
+
try {
|
|
108
|
+
// Collect all files first
|
|
109
|
+
spinner.update("š Scanning directory structure...");
|
|
110
|
+
const files = await collectFiles(options.directory, options.extensions, options.exclude);
|
|
111
|
+
spinner.succeed(`ā
Found ${files.length} files to analyze`);
|
|
112
|
+
if (files.length === 0) {
|
|
113
|
+
await (0, output_1.logOutput)({
|
|
114
|
+
fileCount: 0,
|
|
115
|
+
fileNameCases: {},
|
|
116
|
+
options,
|
|
117
|
+
output,
|
|
118
|
+
duration: (Date.now() - start) / 1000,
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Create progress bar
|
|
123
|
+
const showProgress = options.showProgress !== false;
|
|
124
|
+
progressBar = showProgress
|
|
125
|
+
? new progress_1.ProgressBar({ total: files.length })
|
|
126
|
+
: null;
|
|
127
|
+
// Cleanup progress bar on cancellation
|
|
128
|
+
cancellationToken.onCancel(() => {
|
|
129
|
+
progressBar === null || progressBar === void 0 ? void 0 : progressBar.stop();
|
|
130
|
+
});
|
|
131
|
+
// Process files with optimized concurrency
|
|
132
|
+
// For file I/O operations, optimal concurrency is 20-30 to avoid I/O contention
|
|
133
|
+
// Too high concurrency can actually slow things down due to disk I/O bottlenecks
|
|
134
|
+
const fileCount = files.length;
|
|
135
|
+
// Optimal concurrency for file I/O: 20-30 is usually the sweet spot
|
|
136
|
+
const defaultConcurrency = fileCount > 500 ? 30 : fileCount > 100 ? 25 : 20;
|
|
137
|
+
const maxConcurrency = options.maxConcurrency || defaultConcurrency;
|
|
138
|
+
if (maxConcurrency > 1) {
|
|
139
|
+
// Parallel processing
|
|
140
|
+
const chunks = [];
|
|
141
|
+
for (let i = 0; i < files.length; i += maxConcurrency) {
|
|
142
|
+
chunks.push(files.slice(i, i + maxConcurrency));
|
|
143
|
+
}
|
|
144
|
+
for (const chunk of chunks) {
|
|
145
|
+
// Check for cancellation before processing each chunk
|
|
146
|
+
if (cancellationToken.isCancelled()) {
|
|
147
|
+
progressBar === null || progressBar === void 0 ? void 0 : progressBar.stop();
|
|
148
|
+
throw new Error("Operation cancelled by user");
|
|
149
|
+
}
|
|
150
|
+
// Use Promise.allSettled to handle cancellation better
|
|
151
|
+
const results = await Promise.allSettled(chunk.map(async (filePath) => {
|
|
152
|
+
if (cancellationToken.isCancelled()) {
|
|
153
|
+
throw new Error("Operation cancelled by user");
|
|
154
|
+
}
|
|
155
|
+
if (options.checkFileNames || options.checkFileContent) {
|
|
156
|
+
processFile(filePath, output, fileNameCases);
|
|
157
|
+
}
|
|
158
|
+
progressBar === null || progressBar === void 0 ? void 0 : progressBar.update(1);
|
|
159
|
+
}));
|
|
160
|
+
// Check if any promise was cancelled
|
|
161
|
+
const cancelled = results.some((result) => {
|
|
162
|
+
var _a;
|
|
163
|
+
return result.status === "rejected" &&
|
|
164
|
+
((_a = result.reason) === null || _a === void 0 ? void 0 : _a.message) === "Operation cancelled by user";
|
|
165
|
+
});
|
|
166
|
+
if (cancelled || cancellationToken.isCancelled()) {
|
|
167
|
+
progressBar === null || progressBar === void 0 ? void 0 : progressBar.stop();
|
|
168
|
+
throw new Error("Operation cancelled by user");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// Sequential processing
|
|
174
|
+
for (const filePath of files) {
|
|
175
|
+
if (cancellationToken.isCancelled()) {
|
|
176
|
+
progressBar === null || progressBar === void 0 ? void 0 : progressBar.stop();
|
|
177
|
+
throw new Error("Operation cancelled by user");
|
|
178
|
+
}
|
|
179
|
+
if (options.checkFileNames || options.checkFileContent) {
|
|
180
|
+
processFile(filePath, output, fileNameCases);
|
|
181
|
+
}
|
|
182
|
+
progressBar === null || progressBar === void 0 ? void 0 : progressBar.update(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (cancellationToken.isCancelled()) {
|
|
186
|
+
progressBar === null || progressBar === void 0 ? void 0 : progressBar.stop();
|
|
187
|
+
throw new Error("Operation cancelled by user");
|
|
188
|
+
}
|
|
189
|
+
progressBar === null || progressBar === void 0 ? void 0 : progressBar.complete();
|
|
190
|
+
const duration = (Date.now() - start) / 1000;
|
|
191
|
+
await (0, output_1.logOutput)({
|
|
192
|
+
fileCount,
|
|
193
|
+
fileNameCases,
|
|
194
|
+
options,
|
|
195
|
+
output,
|
|
196
|
+
duration,
|
|
197
|
+
exportFormat: options.exportFormat,
|
|
198
|
+
exportPath: options.exportPath,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
spinner.stop();
|
|
203
|
+
progressBar === null || progressBar === void 0 ? void 0 : progressBar.stop();
|
|
204
|
+
if ((error === null || error === void 0 ? void 0 : error.message) === "Operation cancelled by user" || cancellationToken.isCancelled()) {
|
|
205
|
+
console.log(chalk_1.default.yellow("\n\nā
Analysis cancelled by user\n"));
|
|
206
|
+
process.exit(130); // Standard exit code for SIGINT
|
|
207
|
+
}
|
|
208
|
+
spinner.fail(`ā Error during analysis: ${error}`);
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
49
211
|
};
|
|
50
212
|
exports.analyzeCodebase = analyzeCodebase;
|
package/dist/index.js
CHANGED
|
@@ -1,37 +1,258 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
20
|
+
if (mod && mod.__esModule) return mod;
|
|
21
|
+
var result = {};
|
|
22
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
23
|
+
__setModuleDefault(result, mod);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
3
26
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
27
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
28
|
};
|
|
6
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.mergeOptions = void 0;
|
|
7
31
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
32
|
const commander_1 = require("commander");
|
|
9
33
|
const runner_1 = __importDefault(require("./runner"));
|
|
34
|
+
const i18n_1 = require("./analyze/i18n");
|
|
35
|
+
const config_1 = require("./utils/config");
|
|
36
|
+
const boxen_1 = __importDefault(require("boxen"));
|
|
10
37
|
chalk_1.default.level = 3;
|
|
38
|
+
/**
|
|
39
|
+
* Calculates optimal concurrency based on system resources and project size
|
|
40
|
+
* For large projects, uses higher concurrency for better performance
|
|
41
|
+
*/
|
|
42
|
+
const calculateOptimalConcurrency = () => {
|
|
43
|
+
// Get CPU count (default to 4 if unavailable)
|
|
44
|
+
const cpuCount = require("os").cpus().length || 4;
|
|
45
|
+
// For large projects, use more aggressive concurrency
|
|
46
|
+
// Base: 2x CPU cores, but cap at reasonable limits
|
|
47
|
+
// This allows 50-100+ concurrent operations on modern machines
|
|
48
|
+
const baseConcurrency = Math.max(cpuCount * 2, 20);
|
|
49
|
+
// Cap at 200 to prevent overwhelming the system
|
|
50
|
+
return Math.min(baseConcurrency, 200);
|
|
51
|
+
};
|
|
52
|
+
// Merge config with CLI options (CLI options take precedence)
|
|
53
|
+
const mergeOptions = (config, cliOptions) => {
|
|
54
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
55
|
+
return {
|
|
56
|
+
directory: cliOptions.directory || "",
|
|
57
|
+
framework: cliOptions.framework || (config === null || config === void 0 ? void 0 : config.framework),
|
|
58
|
+
extensions: ((_a = cliOptions.extensions) === null || _a === void 0 ? void 0 : _a.length)
|
|
59
|
+
? cliOptions.extensions
|
|
60
|
+
: config === null || config === void 0 ? void 0 : config.extensions,
|
|
61
|
+
exclude: ((_b = cliOptions.exclude) === null || _b === void 0 ? void 0 : _b.length) ? cliOptions.exclude : config === null || config === void 0 ? void 0 : config.exclude,
|
|
62
|
+
checkFileNames: cliOptions.checkFileNames !== undefined
|
|
63
|
+
? cliOptions.checkFileNames
|
|
64
|
+
: (_c = config === null || config === void 0 ? void 0 : config.checkFileNames) !== null && _c !== void 0 ? _c : true,
|
|
65
|
+
checkFileContent: cliOptions.checkFileContent !== undefined
|
|
66
|
+
? cliOptions.checkFileContent
|
|
67
|
+
: (_d = config === null || config === void 0 ? void 0 : config.checkFileContent) !== null && _d !== void 0 ? _d : true,
|
|
68
|
+
writeJsonOutput: cliOptions.writeJsonOutput !== undefined
|
|
69
|
+
? cliOptions.writeJsonOutput
|
|
70
|
+
: (_e = config === null || config === void 0 ? void 0 : config.writeJsonOutput) !== null && _e !== void 0 ? _e : false,
|
|
71
|
+
showProgress: (_f = config === null || config === void 0 ? void 0 : config.showProgress) !== null && _f !== void 0 ? _f : true,
|
|
72
|
+
parallel: (_g = config === null || config === void 0 ? void 0 : config.parallel) !== null && _g !== void 0 ? _g : true,
|
|
73
|
+
maxConcurrency: (_h = config === null || config === void 0 ? void 0 : config.maxConcurrency) !== null && _h !== void 0 ? _h : calculateOptimalConcurrency(),
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
exports.mergeOptions = mergeOptions;
|
|
11
77
|
commander_1.program
|
|
12
|
-
.
|
|
13
|
-
.description("
|
|
78
|
+
.name("analyze-codebase")
|
|
79
|
+
.description("š A powerful CLI tool for analyzing codebase structure, naming conventions, and code quality")
|
|
80
|
+
.version("1.3.2")
|
|
81
|
+
.arguments("[directory]")
|
|
14
82
|
.option("-f, --framework <framework>", "Specify the framework")
|
|
15
83
|
.option("-e, --extensions <extensions...>", "Specify the extensions", (value, previous) => previous.concat(value), [])
|
|
16
84
|
.option("-exc, --exclude <exclude...>", "Specify the exclude", (value, previous) => previous.concat(value), [])
|
|
17
85
|
.option("--checkFileNames [checkFileNames]", "Check file names", parseBoolean)
|
|
18
|
-
.option("--checkFileContent [checkFileContent]", "Check
|
|
86
|
+
.option("--checkFileContent [checkFileContent]", "Check file content", parseBoolean)
|
|
19
87
|
.option("-w, --writeJsonOutput [writeJsonOutput]", "Write json output", parseBoolean)
|
|
20
|
-
.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
88
|
+
.option("--i18n-file <i18nFile>", "Check for unused translation keys in the specified i18n JSON file (e.g., messages/en.json)")
|
|
89
|
+
.option("--watch", "Watch mode: automatically re-analyze on file changes")
|
|
90
|
+
.option("--no-progress", "Disable progress bar")
|
|
91
|
+
.option("--max-concurrency <number>", "Max concurrent file processing (default: auto, up to 200 for large projects)", parseInt)
|
|
92
|
+
.option("--export <format>", "Export format: json, csv, or html", "json")
|
|
93
|
+
.option("--output <path>", "Output path for export")
|
|
94
|
+
.action(async (directory = ".", options) => {
|
|
95
|
+
try {
|
|
96
|
+
// Load config file
|
|
97
|
+
const config = await (0, config_1.loadConfig)(directory);
|
|
98
|
+
if (config) {
|
|
99
|
+
const configPath = await Promise.resolve().then(() => __importStar(require("./utils/config"))).then((m) => m.findConfigFile(directory));
|
|
100
|
+
if (configPath) {
|
|
101
|
+
(0, config_1.displayConfigInfo)(configPath);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// If i18n-file is specified, run i18n analysis instead
|
|
105
|
+
if (options.i18nFile) {
|
|
106
|
+
try {
|
|
107
|
+
await (0, i18n_1.analyzeAndRemoveUnusedKeys)({
|
|
108
|
+
directory,
|
|
109
|
+
i18nFile: options.i18nFile,
|
|
110
|
+
extensions: options.extensions,
|
|
111
|
+
exclude: options.exclude,
|
|
112
|
+
maxConcurrency: options.maxConcurrency,
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.error(chalk_1.default.red(`\nError during i18n analysis: ${error}\n`));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Watch mode
|
|
122
|
+
if (options.watch) {
|
|
123
|
+
const { startWatchMode } = await Promise.resolve().then(() => __importStar(require("./utils/watch")));
|
|
124
|
+
await startWatchMode(directory, options, config);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const mergedOptions = (0, exports.mergeOptions)(config, {
|
|
128
|
+
...options,
|
|
129
|
+
directory,
|
|
130
|
+
});
|
|
131
|
+
if (mergedOptions.checkFileNames === false &&
|
|
132
|
+
mergedOptions.checkFileContent === false) {
|
|
133
|
+
return console.error(chalk_1.default.red(`\nError: You must specify ${chalk_1.default.yellow("true")} at least one of the following options: ${chalk_1.default.yellow("--checkFileNames")}, ${chalk_1.default.yellow("--checkFileContent")}\n`));
|
|
134
|
+
}
|
|
135
|
+
// Auto-fix extensions without dot prefix (e.g., "ts" -> ".ts")
|
|
136
|
+
if (mergedOptions.extensions) {
|
|
137
|
+
mergedOptions.extensions = mergedOptions.extensions.map((ext) => ext.startsWith(".") ? ext : `.${ext}`);
|
|
138
|
+
}
|
|
139
|
+
// Override maxConcurrency if explicitly provided via CLI
|
|
140
|
+
const finalOptions = {
|
|
141
|
+
...mergedOptions,
|
|
142
|
+
exportFormat: options.export,
|
|
143
|
+
exportPath: options.output,
|
|
144
|
+
};
|
|
145
|
+
if (options.maxConcurrency !== undefined) {
|
|
146
|
+
finalOptions.maxConcurrency = options.maxConcurrency;
|
|
147
|
+
}
|
|
148
|
+
(0, runner_1.default)(finalOptions);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
console.error(chalk_1.default.red(`\nā Error: ${error}\n`));
|
|
152
|
+
process.exit(1);
|
|
25
153
|
}
|
|
26
|
-
|
|
27
|
-
|
|
154
|
+
});
|
|
155
|
+
// Dedicated i18n command - clearly visible in --help
|
|
156
|
+
commander_1.program
|
|
157
|
+
.command("i18n [directory]")
|
|
158
|
+
.description("š Find and remove unused i18n translation keys\n" +
|
|
159
|
+
" Example: analyze-codebase i18n ./src --file locales/en.json")
|
|
160
|
+
.option("--file <path>", "Path to i18n JSON file (e.g., locales/en.json, messages/tr.json)")
|
|
161
|
+
.option("-e, --extensions <ext...>", "File extensions to scan (default: ts tsx js jsx vue)", (value, previous) => previous.concat(value), [])
|
|
162
|
+
.option("-exc, --exclude <dirs...>", "Additional directories to exclude", (value, previous) => previous.concat(value), [])
|
|
163
|
+
.option("--max-concurrency <number>", "Max concurrent file processing (default: auto)", parseInt)
|
|
164
|
+
.action(async (directory = ".", options) => {
|
|
165
|
+
var _a, _b;
|
|
166
|
+
if (!options.file) {
|
|
167
|
+
console.error(chalk_1.default.red("\nā Missing required option: --file <path>\n\n" +
|
|
168
|
+
chalk_1.default.yellow("Usage:\n") +
|
|
169
|
+
` analyze-codebase i18n ${directory} --file locales/en.json\n\n` +
|
|
170
|
+
chalk_1.default.gray("Example:\n") +
|
|
171
|
+
` analyze-codebase i18n ./src --file public/locales/en.json\n` +
|
|
172
|
+
` analyze-codebase i18n . --file messages/en.json --extensions ts tsx vue\n`));
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
await (0, i18n_1.analyzeAndRemoveUnusedKeys)({
|
|
177
|
+
directory,
|
|
178
|
+
i18nFile: options.file,
|
|
179
|
+
extensions: ((_a = options.extensions) === null || _a === void 0 ? void 0 : _a.length) ? options.extensions : undefined,
|
|
180
|
+
exclude: ((_b = options.exclude) === null || _b === void 0 ? void 0 : _b.length) ? options.exclude : undefined,
|
|
181
|
+
maxConcurrency: options.maxConcurrency,
|
|
182
|
+
});
|
|
28
183
|
}
|
|
29
|
-
(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
184
|
+
catch (error) {
|
|
185
|
+
console.error(chalk_1.default.red(`\nError during i18n analysis: ${error}\n`));
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
// Add init command for creating config file
|
|
190
|
+
commander_1.program
|
|
191
|
+
.command("init")
|
|
192
|
+
.description("Initialize a config file (.analyze-codebase.json)")
|
|
193
|
+
.action(async () => {
|
|
194
|
+
const inquirer = (await Promise.resolve().then(() => __importStar(require("inquirer")))).default;
|
|
195
|
+
const { createConfigFile } = await Promise.resolve().then(() => __importStar(require("./utils/config")));
|
|
196
|
+
const answers = await inquirer.prompt([
|
|
197
|
+
{
|
|
198
|
+
type: "input",
|
|
199
|
+
name: "framework",
|
|
200
|
+
message: "Framework (optional):",
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
type: "input",
|
|
204
|
+
name: "extensions",
|
|
205
|
+
message: "File extensions (comma-separated, e.g., .ts,.tsx):",
|
|
206
|
+
default: ".ts,.tsx,.js,.jsx",
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
type: "input",
|
|
210
|
+
name: "exclude",
|
|
211
|
+
message: "Exclude directories (comma-separated):",
|
|
212
|
+
default: "node_modules,dist,build",
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
type: "confirm",
|
|
216
|
+
name: "checkFileNames",
|
|
217
|
+
message: "Check file names?",
|
|
218
|
+
default: true,
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
type: "confirm",
|
|
222
|
+
name: "checkFileContent",
|
|
223
|
+
message: "Check file content?",
|
|
224
|
+
default: true,
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
type: "confirm",
|
|
228
|
+
name: "writeJsonOutput",
|
|
229
|
+
message: "Write JSON output by default?",
|
|
230
|
+
default: false,
|
|
231
|
+
},
|
|
232
|
+
]);
|
|
233
|
+
const config = {
|
|
234
|
+
framework: answers.framework || undefined,
|
|
235
|
+
extensions: answers.extensions
|
|
236
|
+
.split(",")
|
|
237
|
+
.map((ext) => ext.trim())
|
|
238
|
+
.filter((ext) => ext),
|
|
239
|
+
exclude: answers.exclude
|
|
240
|
+
.split(",")
|
|
241
|
+
.map((ex) => ex.trim())
|
|
242
|
+
.filter((ex) => ex),
|
|
243
|
+
checkFileNames: answers.checkFileNames,
|
|
244
|
+
checkFileContent: answers.checkFileContent,
|
|
245
|
+
writeJsonOutput: answers.writeJsonOutput,
|
|
246
|
+
showProgress: true,
|
|
247
|
+
parallel: true,
|
|
248
|
+
// maxConcurrency will be auto-calculated based on project size
|
|
249
|
+
};
|
|
250
|
+
const configPath = await createConfigFile(".", config);
|
|
251
|
+
console.log((0, boxen_1.default)(chalk_1.default.green(`ā
Config file created: ${chalk_1.default.cyan(configPath)}`), {
|
|
252
|
+
padding: 1,
|
|
253
|
+
borderColor: "green",
|
|
254
|
+
borderStyle: "round",
|
|
255
|
+
}));
|
|
35
256
|
});
|
|
36
257
|
commander_1.program.parse(process.argv);
|
|
37
258
|
function parseBoolean(value) {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
const vitest_1 = require("vitest");
|
|
27
|
+
const fs = __importStar(require("fs/promises"));
|
|
28
|
+
const path = __importStar(require("path"));
|
|
29
|
+
const config_1 = require("../config");
|
|
30
|
+
(0, vitest_1.describe)("Config", () => {
|
|
31
|
+
const testDir = path.join(__dirname, "../../../test-temp");
|
|
32
|
+
const configPath = path.join(testDir, ".analyze-codebase.json");
|
|
33
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
34
|
+
// Create test directory
|
|
35
|
+
try {
|
|
36
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
catch (_a) {
|
|
39
|
+
// Directory might already exist
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
(0, vitest_1.afterEach)(async () => {
|
|
43
|
+
// Clean up test files
|
|
44
|
+
try {
|
|
45
|
+
await fs.unlink(configPath);
|
|
46
|
+
}
|
|
47
|
+
catch (_a) {
|
|
48
|
+
// File might not exist
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
await fs.rmdir(testDir);
|
|
52
|
+
}
|
|
53
|
+
catch (_b) {
|
|
54
|
+
// Directory might not be empty
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
(0, vitest_1.it)("should create a config file", async () => {
|
|
58
|
+
const config = {
|
|
59
|
+
extensions: [".ts", ".tsx"],
|
|
60
|
+
exclude: ["node_modules"],
|
|
61
|
+
checkFileNames: true,
|
|
62
|
+
checkFileContent: true,
|
|
63
|
+
};
|
|
64
|
+
const createdPath = await (0, config_1.createConfigFile)(testDir, config);
|
|
65
|
+
(0, vitest_1.expect)(createdPath).toBe(configPath);
|
|
66
|
+
const exists = await fs.access(configPath).then(() => true).catch(() => false);
|
|
67
|
+
(0, vitest_1.expect)(exists).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
(0, vitest_1.it)("should load a config file", async () => {
|
|
70
|
+
const config = {
|
|
71
|
+
extensions: [".ts", ".tsx"],
|
|
72
|
+
exclude: ["node_modules"],
|
|
73
|
+
checkFileNames: true,
|
|
74
|
+
checkFileContent: true,
|
|
75
|
+
};
|
|
76
|
+
await (0, config_1.createConfigFile)(testDir, config);
|
|
77
|
+
const loaded = await (0, config_1.loadConfig)(testDir);
|
|
78
|
+
(0, vitest_1.expect)(loaded).toBeTruthy();
|
|
79
|
+
(0, vitest_1.expect)(loaded === null || loaded === void 0 ? void 0 : loaded.extensions).toEqual(config.extensions);
|
|
80
|
+
(0, vitest_1.expect)(loaded === null || loaded === void 0 ? void 0 : loaded.exclude).toEqual(config.exclude);
|
|
81
|
+
});
|
|
82
|
+
(0, vitest_1.it)("should find config file in directory", async () => {
|
|
83
|
+
const config = {
|
|
84
|
+
extensions: [".ts"],
|
|
85
|
+
};
|
|
86
|
+
await (0, config_1.createConfigFile)(testDir, config);
|
|
87
|
+
const found = await (0, config_1.findConfigFile)(testDir);
|
|
88
|
+
(0, vitest_1.expect)(found).toBe(configPath);
|
|
89
|
+
});
|
|
90
|
+
(0, vitest_1.it)("should return null if config file doesn't exist", async () => {
|
|
91
|
+
const found = await (0, config_1.findConfigFile)(testDir);
|
|
92
|
+
(0, vitest_1.expect)(found).toBeNull();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const progress_1 = require("../progress");
|
|
5
|
+
(0, vitest_1.describe)("ProgressBar", () => {
|
|
6
|
+
let progressBar;
|
|
7
|
+
(0, vitest_1.beforeEach)(() => {
|
|
8
|
+
// Suppress console output during tests
|
|
9
|
+
vitest_1.vi.spyOn(console, "log").mockImplementation(() => { });
|
|
10
|
+
});
|
|
11
|
+
(0, vitest_1.afterEach)(() => {
|
|
12
|
+
if (progressBar) {
|
|
13
|
+
progressBar.stop();
|
|
14
|
+
}
|
|
15
|
+
vitest_1.vi.restoreAllMocks();
|
|
16
|
+
});
|
|
17
|
+
(0, vitest_1.it)("should create a progress bar with total", () => {
|
|
18
|
+
progressBar = new progress_1.ProgressBar({ total: 100 });
|
|
19
|
+
(0, vitest_1.expect)(progressBar).toBeInstanceOf(progress_1.ProgressBar);
|
|
20
|
+
progressBar.stop();
|
|
21
|
+
});
|
|
22
|
+
(0, vitest_1.it)("should update progress", () => {
|
|
23
|
+
progressBar = new progress_1.ProgressBar({ total: 100 });
|
|
24
|
+
progressBar.update(10);
|
|
25
|
+
progressBar.update(20);
|
|
26
|
+
progressBar.stop();
|
|
27
|
+
});
|
|
28
|
+
(0, vitest_1.it)("should set total", () => {
|
|
29
|
+
progressBar = new progress_1.ProgressBar({ total: 50 });
|
|
30
|
+
progressBar.setTotal(100);
|
|
31
|
+
progressBar.stop();
|
|
32
|
+
});
|
|
33
|
+
(0, vitest_1.it)("should complete progress bar", () => {
|
|
34
|
+
progressBar = new progress_1.ProgressBar({ total: 100 });
|
|
35
|
+
progressBar.complete();
|
|
36
|
+
});
|
|
37
|
+
});
|