auto-cr-cmd 2.0.27 → 2.0.29
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 +59 -4
- package/README.zh-CN.md +59 -4
- package/dist/index.js +211 -35
- package/dist/report/index.js +59 -35
- package/dist/types/report/index.d.ts +22 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -42,15 +42,70 @@ Common flags:
|
|
|
42
42
|
|
|
43
43
|
- `--language <zh|en>`: Switch CLI output language (defaults to auto-detection).
|
|
44
44
|
- `--rule-dir <directory>`: Load additional custom rules from a directory or package.
|
|
45
|
+
- `--output <text|json>`: Choose between human-friendly text logs or structured JSON results (defaults to `text`).
|
|
45
46
|
- `--help`: Display the full command reference.
|
|
46
47
|
|
|
47
48
|
Sample output:
|
|
48
49
|
|
|
49
50
|
```text
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
WARN [12:56:18] ⚠️ [Base Rules]: no-deep-relative-imports
|
|
52
|
+
|
|
53
|
+
File: /Volumes/Wei/Codes/github/auto-cr/examples/src/app/features/admin/pages/dashboard.ts:2
|
|
54
|
+
Description: Import path "../../../../shared/deep/utils" must not exceed max depth 2
|
|
55
|
+
Code: ../../../../shared/deep/utils
|
|
56
|
+
Suggestion: Use a path alias (for example: @shared/deep/utils). | Create an index file at a higher level to re-export the module and shorten the import.
|
|
57
|
+
|
|
58
|
+
WARN [12:56:18] ⚠️ [untagged]: no-index-import
|
|
59
|
+
|
|
60
|
+
File: /Volumes/Wei/Codes/github/auto-cr/examples/src/app/features/admin/pages/dashboard.ts:3
|
|
61
|
+
Description: Import ../../consts/index is not allowed. Import the concrete file instead.
|
|
62
|
+
|
|
63
|
+
✔ Code scan complete, scanned 3 files: 0 with errors, 1 with warnings, 0 with optimizing hints!
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
JSON output sample:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx auto-cr-cmd --output json -- ./src | jq
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"summary": {
|
|
75
|
+
"scannedFiles": 2,
|
|
76
|
+
"filesWithErrors": 1,
|
|
77
|
+
"filesWithWarnings": 0,
|
|
78
|
+
"filesWithOptimizing": 1,
|
|
79
|
+
"violationTotals": {
|
|
80
|
+
"total": 3,
|
|
81
|
+
"error": 2,
|
|
82
|
+
"warning": 0,
|
|
83
|
+
"optimizing": 1
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"files": [
|
|
87
|
+
{
|
|
88
|
+
"filePath": "/workspace/src/example.ts",
|
|
89
|
+
"severityCounts": {
|
|
90
|
+
"error": 2,
|
|
91
|
+
"warning": 0,
|
|
92
|
+
"optimizing": 1
|
|
93
|
+
},
|
|
94
|
+
"totalViolations": 3,
|
|
95
|
+
"errorViolations": 2,
|
|
96
|
+
"violations": [
|
|
97
|
+
{
|
|
98
|
+
"tag": "imports",
|
|
99
|
+
"ruleName": "no-deep-relative-imports",
|
|
100
|
+
"severity": "error",
|
|
101
|
+
"message": "Avoid deep relative imports from src/components/button",
|
|
102
|
+
"line": 13
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
"notifications": []
|
|
108
|
+
}
|
|
54
109
|
```
|
|
55
110
|
|
|
56
111
|
## Writing Custom Rules
|
package/README.zh-CN.md
CHANGED
|
@@ -42,15 +42,70 @@ npx auto-cr-cmd --language zh [需要扫描的代码目录]
|
|
|
42
42
|
|
|
43
43
|
- `--language <zh|en>`:切换 CLI 输出语言(默认为自动检测)。
|
|
44
44
|
- `--rule-dir <directory>`:加载额外的自定义规则目录或包。
|
|
45
|
+
- `--output <text|json>`:选择输出格式,`text` 为友好的终端日志,`json` 用于集成脚本(默认为 `text`)。
|
|
45
46
|
- `--help`:查看完整命令说明。
|
|
46
47
|
|
|
47
48
|
示例输出:
|
|
48
49
|
|
|
49
50
|
```text
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
WARN [12:52:48] ⚠️ [基础规则]:no-deep-relative-imports
|
|
52
|
+
|
|
53
|
+
文件位置: .../dashboard.ts:2
|
|
54
|
+
错误描述: 导入路径 "../../../../shared/deep/utils",不能超过最大层级2
|
|
55
|
+
错误代码: ../../../../shared/deep/utils
|
|
56
|
+
优化建议: 使用别名路径(如 @shared/deep/utils); 或在上层聚合导出,避免过深相对路径。
|
|
57
|
+
|
|
58
|
+
WARN [12:52:48] ⚠️ [未定义]:no-index-import
|
|
59
|
+
|
|
60
|
+
文件位置: .../dashboard.ts:3
|
|
61
|
+
错误描述: 禁止直接导入 ../../consts/index,请改用具体文件
|
|
62
|
+
|
|
63
|
+
✔ 代码扫描完成,本次共扫描3个文件,其中0个文件存在错误,1个文件存在警告,0个文件存在优化建议!
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
JSON 输出示例:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx auto-cr-cmd --output json -- ./src | jq
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"summary": {
|
|
75
|
+
"scannedFiles": 2,
|
|
76
|
+
"filesWithErrors": 1,
|
|
77
|
+
"filesWithWarnings": 0,
|
|
78
|
+
"filesWithOptimizing": 1,
|
|
79
|
+
"violationTotals": {
|
|
80
|
+
"total": 3,
|
|
81
|
+
"error": 2,
|
|
82
|
+
"warning": 0,
|
|
83
|
+
"optimizing": 1
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"files": [
|
|
87
|
+
{
|
|
88
|
+
"filePath": "/workspace/src/example.ts",
|
|
89
|
+
"severityCounts": {
|
|
90
|
+
"error": 2,
|
|
91
|
+
"warning": 0,
|
|
92
|
+
"optimizing": 1
|
|
93
|
+
},
|
|
94
|
+
"totalViolations": 3,
|
|
95
|
+
"errorViolations": 2,
|
|
96
|
+
"violations": [
|
|
97
|
+
{
|
|
98
|
+
"tag": "imports",
|
|
99
|
+
"ruleName": "no-deep-relative-imports",
|
|
100
|
+
"severity": "error",
|
|
101
|
+
"message": "避免从 src/components/button 进行深层相对导入",
|
|
102
|
+
"line": 13
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
"notifications": []
|
|
108
|
+
}
|
|
54
109
|
```
|
|
55
110
|
|
|
56
111
|
## 编写自定义规则
|
package/dist/index.js
CHANGED
|
@@ -59,6 +59,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
|
59
59
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
60
60
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
61
61
|
};
|
|
62
|
+
var _a;
|
|
62
63
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
63
64
|
var consola_1 = require("consola");
|
|
64
65
|
var fs_1 = __importDefault(require("fs"));
|
|
@@ -71,25 +72,77 @@ var i18n_1 = require("./i18n");
|
|
|
71
72
|
var file_1 = require("./utils/file");
|
|
72
73
|
var auto_cr_rules_1 = require("auto-cr-rules");
|
|
73
74
|
var loader_1 = require("./rules/loader");
|
|
75
|
+
consola_1.consola.options.formatOptions = __assign(__assign({}, consola_1.consola.options.formatOptions), { date: false });
|
|
76
|
+
var consolaLoggers = {
|
|
77
|
+
info: consola_1.consola.info.bind(consola_1.consola),
|
|
78
|
+
warn: consola_1.consola.warn.bind(consola_1.consola),
|
|
79
|
+
error: consola_1.consola.error.bind(consola_1.consola),
|
|
80
|
+
};
|
|
74
81
|
function run() {
|
|
75
|
-
return __awaiter(this, arguments, void 0, function (filePaths, ruleDir) {
|
|
76
|
-
var t, validPaths, allFiles, _i, validPaths_1, targetPath, stat, directoryFiles, scannableFiles, customRules, rules, filesWithErrors, filesWithWarnings, filesWithOptimizing, _a, scannableFiles_1, file, summary, error_1;
|
|
82
|
+
return __awaiter(this, arguments, void 0, function (filePaths, ruleDir, format) {
|
|
83
|
+
var t, notifications, log, validPaths, allFiles, _i, validPaths_1, targetPath, stat, directoryFiles, scannableFiles, customRules, rules, filesWithErrors, filesWithWarnings, filesWithOptimizing, totalViolations, totalErrorViolations, totalWarningViolations, totalOptimizingViolations, fileSummaries, _a, scannableFiles_1, file, summary, error_1;
|
|
77
84
|
if (filePaths === void 0) { filePaths = []; }
|
|
78
85
|
return __generator(this, function (_b) {
|
|
79
86
|
switch (_b.label) {
|
|
80
87
|
case 0:
|
|
81
88
|
t = (0, i18n_1.getTranslator)();
|
|
89
|
+
notifications = [];
|
|
90
|
+
log = function (level, message, detail) {
|
|
91
|
+
var detailText;
|
|
92
|
+
if (detail !== undefined) {
|
|
93
|
+
if (detail instanceof Error) {
|
|
94
|
+
detailText = detail.message;
|
|
95
|
+
}
|
|
96
|
+
else if (typeof detail === 'string') {
|
|
97
|
+
detailText = detail;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
try {
|
|
101
|
+
detailText = JSON.stringify(detail);
|
|
102
|
+
}
|
|
103
|
+
catch (_a) {
|
|
104
|
+
detailText = String(detail);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
notifications.push({ level: level, message: message, detail: detailText });
|
|
109
|
+
if (format === 'text') {
|
|
110
|
+
var logger = consolaLoggers[level];
|
|
111
|
+
if (detail === undefined) {
|
|
112
|
+
logger(message);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
logger(message, detail);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
82
119
|
_b.label = 1;
|
|
83
120
|
case 1:
|
|
84
121
|
_b.trys.push([1, 6, , 7]);
|
|
85
122
|
if (filePaths.length === 0) {
|
|
86
|
-
|
|
87
|
-
return [2 /*return*/, {
|
|
123
|
+
log('info', t.noPathsProvided());
|
|
124
|
+
return [2 /*return*/, {
|
|
125
|
+
scannedFiles: 0,
|
|
126
|
+
filesWithErrors: 0,
|
|
127
|
+
filesWithWarnings: 0,
|
|
128
|
+
filesWithOptimizing: 0,
|
|
129
|
+
violationTotals: { total: 0, error: 0, warning: 0, optimizing: 0 },
|
|
130
|
+
files: [],
|
|
131
|
+
notifications: notifications,
|
|
132
|
+
}];
|
|
88
133
|
}
|
|
89
134
|
validPaths = filePaths.filter(function (candidate) { return (0, file_1.checkPathExists)(candidate); });
|
|
90
135
|
if (validPaths.length === 0) {
|
|
91
|
-
|
|
92
|
-
return [2 /*return*/, {
|
|
136
|
+
log('error', t.allPathsMissing());
|
|
137
|
+
return [2 /*return*/, {
|
|
138
|
+
scannedFiles: 0,
|
|
139
|
+
filesWithErrors: 0,
|
|
140
|
+
filesWithWarnings: 0,
|
|
141
|
+
filesWithOptimizing: 0,
|
|
142
|
+
violationTotals: { total: 0, error: 0, warning: 0, optimizing: 0 },
|
|
143
|
+
files: [],
|
|
144
|
+
notifications: notifications,
|
|
145
|
+
}];
|
|
93
146
|
}
|
|
94
147
|
allFiles = [];
|
|
95
148
|
for (_i = 0, validPaths_1 = validPaths; _i < validPaths_1.length; _i++) {
|
|
@@ -104,30 +157,46 @@ function run() {
|
|
|
104
157
|
}
|
|
105
158
|
}
|
|
106
159
|
if (allFiles.length === 0) {
|
|
107
|
-
|
|
108
|
-
return [2 /*return*/, {
|
|
160
|
+
log('info', t.noFilesFound());
|
|
161
|
+
return [2 /*return*/, {
|
|
162
|
+
scannedFiles: 0,
|
|
163
|
+
filesWithErrors: 0,
|
|
164
|
+
filesWithWarnings: 0,
|
|
165
|
+
filesWithOptimizing: 0,
|
|
166
|
+
violationTotals: { total: 0, error: 0, warning: 0, optimizing: 0 },
|
|
167
|
+
files: [],
|
|
168
|
+
notifications: notifications,
|
|
169
|
+
}];
|
|
109
170
|
}
|
|
110
171
|
scannableFiles = allFiles.filter(function (candidate) { return !candidate.endsWith('.d.ts'); });
|
|
111
172
|
customRules = (0, loader_1.loadCustomRules)(ruleDir);
|
|
112
173
|
rules = __spreadArray(__spreadArray([], auto_cr_rules_1.builtinRules, true), customRules, true);
|
|
113
174
|
if (rules.length === 0) {
|
|
114
|
-
|
|
175
|
+
log('warn', t.noRulesLoaded());
|
|
115
176
|
return [2 /*return*/, {
|
|
116
177
|
scannedFiles: 0,
|
|
117
178
|
filesWithErrors: 0,
|
|
118
179
|
filesWithWarnings: 0,
|
|
119
180
|
filesWithOptimizing: 0,
|
|
181
|
+
violationTotals: { total: 0, error: 0, warning: 0, optimizing: 0 },
|
|
182
|
+
files: [],
|
|
183
|
+
notifications: notifications,
|
|
120
184
|
}];
|
|
121
185
|
}
|
|
122
186
|
filesWithErrors = 0;
|
|
123
187
|
filesWithWarnings = 0;
|
|
124
188
|
filesWithOptimizing = 0;
|
|
189
|
+
totalViolations = 0;
|
|
190
|
+
totalErrorViolations = 0;
|
|
191
|
+
totalWarningViolations = 0;
|
|
192
|
+
totalOptimizingViolations = 0;
|
|
193
|
+
fileSummaries = [];
|
|
125
194
|
_a = 0, scannableFiles_1 = scannableFiles;
|
|
126
195
|
_b.label = 2;
|
|
127
196
|
case 2:
|
|
128
197
|
if (!(_a < scannableFiles_1.length)) return [3 /*break*/, 5];
|
|
129
198
|
file = scannableFiles_1[_a];
|
|
130
|
-
return [4 /*yield*/, analyzeFile(file, rules)];
|
|
199
|
+
return [4 /*yield*/, analyzeFile(file, rules, format, log)];
|
|
131
200
|
case 3:
|
|
132
201
|
summary = _b.sent();
|
|
133
202
|
if (summary.severityCounts.error > 0) {
|
|
@@ -139,6 +208,17 @@ function run() {
|
|
|
139
208
|
if (summary.severityCounts.optimizing > 0) {
|
|
140
209
|
filesWithOptimizing += 1;
|
|
141
210
|
}
|
|
211
|
+
totalViolations += summary.totalViolations;
|
|
212
|
+
totalErrorViolations += summary.errorViolations;
|
|
213
|
+
totalWarningViolations += summary.severityCounts.warning;
|
|
214
|
+
totalOptimizingViolations += summary.severityCounts.optimizing;
|
|
215
|
+
fileSummaries.push({
|
|
216
|
+
filePath: file,
|
|
217
|
+
severityCounts: summary.severityCounts,
|
|
218
|
+
totalViolations: summary.totalViolations,
|
|
219
|
+
errorViolations: summary.errorViolations,
|
|
220
|
+
violations: summary.violations,
|
|
221
|
+
});
|
|
142
222
|
_b.label = 4;
|
|
143
223
|
case 4:
|
|
144
224
|
_a++;
|
|
@@ -148,6 +228,14 @@ function run() {
|
|
|
148
228
|
filesWithErrors: filesWithErrors,
|
|
149
229
|
filesWithWarnings: filesWithWarnings,
|
|
150
230
|
filesWithOptimizing: filesWithOptimizing,
|
|
231
|
+
violationTotals: {
|
|
232
|
+
total: totalViolations,
|
|
233
|
+
error: totalErrorViolations,
|
|
234
|
+
warning: totalWarningViolations,
|
|
235
|
+
optimizing: totalOptimizingViolations,
|
|
236
|
+
},
|
|
237
|
+
files: fileSummaries,
|
|
238
|
+
notifications: notifications,
|
|
151
239
|
}];
|
|
152
240
|
case 6:
|
|
153
241
|
error_1 = _b.sent();
|
|
@@ -157,27 +245,30 @@ function run() {
|
|
|
157
245
|
});
|
|
158
246
|
});
|
|
159
247
|
}
|
|
160
|
-
function analyzeFile(file, rules) {
|
|
248
|
+
function analyzeFile(file, rules, format, log) {
|
|
161
249
|
return __awaiter(this, void 0, void 0, function () {
|
|
162
250
|
var source, reporter, t, ast, parseOptions, language, baseContext, sharedHelpers, _loop_1, _i, rules_1, rule, summary;
|
|
163
251
|
return __generator(this, function (_a) {
|
|
164
252
|
switch (_a.label) {
|
|
165
253
|
case 0:
|
|
166
254
|
source = (0, file_1.readFile)(file);
|
|
167
|
-
reporter = (0, report_1.createReporter)(file, source);
|
|
255
|
+
reporter = (0, report_1.createReporter)(file, source, { format: format });
|
|
168
256
|
t = (0, i18n_1.getTranslator)();
|
|
169
257
|
try {
|
|
170
258
|
parseOptions = (0, config_1.loadParseOptions)(file);
|
|
171
259
|
ast = (0, wasm_1.parseSync)(source, parseOptions);
|
|
172
260
|
}
|
|
173
261
|
catch (error) {
|
|
174
|
-
|
|
262
|
+
log('error', t.parseFileFailed({ file: file }), error);
|
|
175
263
|
return [2 /*return*/, {
|
|
176
264
|
severityCounts: {
|
|
177
265
|
error: 1,
|
|
178
266
|
warning: 0,
|
|
179
267
|
optimizing: 0,
|
|
180
268
|
},
|
|
269
|
+
totalViolations: 1,
|
|
270
|
+
errorViolations: 1,
|
|
271
|
+
violations: [],
|
|
181
272
|
}];
|
|
182
273
|
}
|
|
183
274
|
language = (0, i18n_1.getLanguage)();
|
|
@@ -226,7 +317,7 @@ function analyzeFile(file, rules) {
|
|
|
226
317
|
return [3 /*break*/, 3];
|
|
227
318
|
case 2:
|
|
228
319
|
error_2 = _b.sent();
|
|
229
|
-
|
|
320
|
+
log('error', t.ruleExecutionFailed({ ruleName: rule.name, file: file }), error_2);
|
|
230
321
|
return [3 /*break*/, 3];
|
|
231
322
|
case 3: return [2 /*return*/];
|
|
232
323
|
}
|
|
@@ -248,6 +339,9 @@ function analyzeFile(file, rules) {
|
|
|
248
339
|
summary = reporter.flush();
|
|
249
340
|
return [2 /*return*/, {
|
|
250
341
|
severityCounts: summary.severityCounts,
|
|
342
|
+
totalViolations: summary.totalViolations,
|
|
343
|
+
errorViolations: summary.errorViolations,
|
|
344
|
+
violations: summary.violations,
|
|
251
345
|
}];
|
|
252
346
|
}
|
|
253
347
|
});
|
|
@@ -305,48 +399,130 @@ function normalizeViolationInput(input, spanArg) {
|
|
|
305
399
|
span: spanArg,
|
|
306
400
|
};
|
|
307
401
|
}
|
|
402
|
+
function parseOutputFormat(value) {
|
|
403
|
+
if (!value) {
|
|
404
|
+
return 'text';
|
|
405
|
+
}
|
|
406
|
+
var normalized = value.toLowerCase();
|
|
407
|
+
if (normalized === 'json' || normalized === 'text') {
|
|
408
|
+
return normalized;
|
|
409
|
+
}
|
|
410
|
+
throw new Error("Unsupported output format: ".concat(value, ". Use \"text\" or \"json\"."));
|
|
411
|
+
}
|
|
412
|
+
function severityToLabel(severity) {
|
|
413
|
+
switch (severity) {
|
|
414
|
+
case auto_cr_rules_1.RuleSeverity.Warning:
|
|
415
|
+
return 'warning';
|
|
416
|
+
case auto_cr_rules_1.RuleSeverity.Optimizing:
|
|
417
|
+
return 'optimizing';
|
|
418
|
+
case auto_cr_rules_1.RuleSeverity.Error:
|
|
419
|
+
default:
|
|
420
|
+
return 'error';
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function formatViolationForJson(violation) {
|
|
424
|
+
var suggestions = violation.suggestions
|
|
425
|
+
? violation.suggestions.map(function (suggestion) { return (__assign({}, suggestion)); })
|
|
426
|
+
: [];
|
|
427
|
+
var payload = {
|
|
428
|
+
tag: violation.tag,
|
|
429
|
+
ruleName: violation.ruleName,
|
|
430
|
+
severity: severityToLabel(violation.severity),
|
|
431
|
+
message: violation.message,
|
|
432
|
+
suggestions: suggestions,
|
|
433
|
+
};
|
|
434
|
+
if (typeof violation.line === 'number') {
|
|
435
|
+
payload.line = violation.line;
|
|
436
|
+
}
|
|
437
|
+
if (violation.code) {
|
|
438
|
+
payload.code = violation.code;
|
|
439
|
+
}
|
|
440
|
+
return payload;
|
|
441
|
+
}
|
|
442
|
+
function formatJsonOutput(result) {
|
|
443
|
+
return {
|
|
444
|
+
summary: {
|
|
445
|
+
scannedFiles: result.scannedFiles,
|
|
446
|
+
filesWithErrors: result.filesWithErrors,
|
|
447
|
+
filesWithWarnings: result.filesWithWarnings,
|
|
448
|
+
filesWithOptimizing: result.filesWithOptimizing,
|
|
449
|
+
violationTotals: result.violationTotals,
|
|
450
|
+
},
|
|
451
|
+
files: result.files.map(function (file) { return ({
|
|
452
|
+
filePath: file.filePath,
|
|
453
|
+
severityCounts: file.severityCounts,
|
|
454
|
+
totalViolations: file.totalViolations,
|
|
455
|
+
errorViolations: file.errorViolations,
|
|
456
|
+
violations: file.violations.map(formatViolationForJson),
|
|
457
|
+
}); }),
|
|
458
|
+
notifications: result.notifications,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
308
461
|
commander_1.program
|
|
309
462
|
.argument('[paths...]', '需要扫描的文件或目录路径列表 / Paths to scan')
|
|
310
463
|
.option('-r, --rule-dir <directory>', '自定义规则目录路径 / Custom rule directory')
|
|
311
464
|
.option('-l, --language <language>', '设置 CLI 语言 (zh/en) / Set CLI language (zh/en)')
|
|
465
|
+
.option('-o, --output <format>', '设置输出格式 (text/json) / Output format (text/json)', 'text')
|
|
312
466
|
.parse(process.argv);
|
|
313
467
|
var options = commander_1.program.opts();
|
|
314
468
|
var filePaths = commander_1.program.args.map(function (target) { return path_1.default.resolve(process.cwd(), target); });
|
|
469
|
+
(0, i18n_1.setLanguage)((_a = options.language) !== null && _a !== void 0 ? _a : process.env.LANG);
|
|
470
|
+
var outputFormat;
|
|
471
|
+
try {
|
|
472
|
+
outputFormat = parseOutputFormat(options.output);
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
var message = error instanceof Error ? error.message : String(error);
|
|
476
|
+
consola_1.consola.error(message);
|
|
477
|
+
process.exit(1);
|
|
478
|
+
}
|
|
479
|
+
;
|
|
315
480
|
(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
316
|
-
var
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
switch (_b.label) {
|
|
481
|
+
var result, t, payload, exitCode, language, resultMessage, exitCode, error_3, t, detail, payload;
|
|
482
|
+
return __generator(this, function (_a) {
|
|
483
|
+
switch (_a.label) {
|
|
320
484
|
case 0:
|
|
321
|
-
|
|
322
|
-
(
|
|
323
|
-
return [4 /*yield*/, run(filePaths, options.ruleDir)];
|
|
485
|
+
_a.trys.push([0, 2, , 3]);
|
|
486
|
+
return [4 /*yield*/, run(filePaths, options.ruleDir, outputFormat)];
|
|
324
487
|
case 1:
|
|
325
|
-
|
|
488
|
+
result = _a.sent();
|
|
326
489
|
t = (0, i18n_1.getTranslator)();
|
|
327
|
-
if (
|
|
490
|
+
if (outputFormat === 'json') {
|
|
491
|
+
payload = formatJsonOutput(result);
|
|
492
|
+
exitCode = result.filesWithErrors > 0 ? 1 : 0;
|
|
493
|
+
process.stdout.write("".concat(JSON.stringify(payload, null, 2), "\n"));
|
|
494
|
+
process.exit(exitCode);
|
|
495
|
+
}
|
|
496
|
+
if (result.scannedFiles > 0) {
|
|
328
497
|
consola_1.consola.log(' ');
|
|
329
498
|
language = (0, i18n_1.getLanguage)();
|
|
330
499
|
resultMessage = language.startsWith('zh')
|
|
331
|
-
? " ".concat(t.scanComplete(), "\uFF0C\u672C\u6B21\u5171\u626B\u63CF").concat(
|
|
332
|
-
: " ".concat(t.scanComplete(), ", scanned ").concat(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
else {
|
|
338
|
-
consola_1.consola.success(resultMessage);
|
|
339
|
-
process.exit(0);
|
|
340
|
-
}
|
|
500
|
+
? " ".concat(t.scanComplete(), "\uFF0C\u672C\u6B21\u5171\u626B\u63CF").concat(result.scannedFiles, "\u4E2A\u6587\u4EF6\uFF0C\u5176\u4E2D").concat(result.filesWithErrors, "\u4E2A\u6587\u4EF6\u5B58\u5728\u9519\u8BEF\uFF0C").concat(result.filesWithWarnings, "\u4E2A\u6587\u4EF6\u5B58\u5728\u8B66\u544A\uFF0C").concat(result.filesWithOptimizing, "\u4E2A\u6587\u4EF6\u5B58\u5728\u4F18\u5316\u5EFA\u8BAE\uFF01")
|
|
501
|
+
: " ".concat(t.scanComplete(), ", scanned ").concat(result.scannedFiles, " files: ").concat(result.filesWithErrors, " with errors, ").concat(result.filesWithWarnings, " with warnings, ").concat(result.filesWithOptimizing, " with optimizing hints!");
|
|
502
|
+
consola_1.consola.success(resultMessage);
|
|
503
|
+
exitCode = result.filesWithErrors > 0 ? 1 : 0;
|
|
504
|
+
process.exit(exitCode);
|
|
341
505
|
}
|
|
342
506
|
else {
|
|
343
507
|
process.exit(0);
|
|
344
508
|
}
|
|
345
509
|
return [3 /*break*/, 3];
|
|
346
510
|
case 2:
|
|
347
|
-
error_3 =
|
|
511
|
+
error_3 = _a.sent();
|
|
348
512
|
t = (0, i18n_1.getTranslator)();
|
|
349
|
-
|
|
513
|
+
detail = error_3 instanceof Error ? error_3.message : String(error_3);
|
|
514
|
+
if (outputFormat === 'json') {
|
|
515
|
+
payload = {
|
|
516
|
+
error: {
|
|
517
|
+
message: t.scanError(),
|
|
518
|
+
detail: detail,
|
|
519
|
+
},
|
|
520
|
+
};
|
|
521
|
+
process.stdout.write("".concat(JSON.stringify(payload, null, 2), "\n"));
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
consola_1.consola.error(t.scanError(), detail);
|
|
525
|
+
}
|
|
350
526
|
process.exit(1);
|
|
351
527
|
return [3 /*break*/, 3];
|
|
352
528
|
case 3: return [2 /*return*/];
|
package/dist/report/index.js
CHANGED
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
14
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
15
|
+
if (ar || !(i in from)) {
|
|
16
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
17
|
+
ar[i] = from[i];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
21
|
+
};
|
|
2
22
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
23
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
24
|
};
|
|
@@ -9,16 +29,20 @@ var auto_cr_rules_1 = require("auto-cr-rules");
|
|
|
9
29
|
var consola_1 = __importDefault(require("consola"));
|
|
10
30
|
var i18n_1 = require("../i18n");
|
|
11
31
|
var UNTAGGED_TAG = 'untagged';
|
|
32
|
+
var DEFAULT_FORMAT = 'text';
|
|
12
33
|
var severityLoggers = (_a = {},
|
|
13
34
|
_a[auto_cr_rules_1.RuleSeverity.Error] = consola_1.default.error,
|
|
14
35
|
_a[auto_cr_rules_1.RuleSeverity.Warning] = consola_1.default.warn,
|
|
15
36
|
_a[auto_cr_rules_1.RuleSeverity.Optimizing] = consola_1.default.info,
|
|
16
37
|
_a);
|
|
17
|
-
function createReporter(filePath, source) {
|
|
38
|
+
function createReporter(filePath, source, options) {
|
|
39
|
+
var _a;
|
|
40
|
+
if (options === void 0) { options = {}; }
|
|
18
41
|
var offsets = buildLineOffsets(source);
|
|
19
42
|
var t = (0, i18n_1.getTranslator)();
|
|
20
43
|
var language = (0, i18n_1.getLanguage)();
|
|
21
44
|
var records = [];
|
|
45
|
+
var format = (_a = options.format) !== null && _a !== void 0 ? _a : DEFAULT_FORMAT;
|
|
22
46
|
var totalViolations = 0;
|
|
23
47
|
var errorViolations = 0;
|
|
24
48
|
var severityCounts = {
|
|
@@ -114,6 +138,7 @@ function createReporter(filePath, source) {
|
|
|
114
138
|
return reporterWithRecord;
|
|
115
139
|
};
|
|
116
140
|
var flush = function () {
|
|
141
|
+
var violationSnapshot = records.map(function (record) { return (__assign(__assign({}, record), { suggestions: record.suggestions ? __spreadArray([], record.suggestions, true) : undefined })); });
|
|
117
142
|
var summary = {
|
|
118
143
|
totalViolations: totalViolations,
|
|
119
144
|
errorViolations: errorViolations,
|
|
@@ -122,42 +147,41 @@ function createReporter(filePath, source) {
|
|
|
122
147
|
warning: severityCounts.warning,
|
|
123
148
|
optimizing: severityCounts.optimizing,
|
|
124
149
|
},
|
|
150
|
+
violations: violationSnapshot,
|
|
125
151
|
};
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
152
|
+
if (violationSnapshot.length > 0 && format === 'text') {
|
|
153
|
+
var locale = language === 'zh' ? 'zh-CN' : 'en-US';
|
|
154
|
+
var formatter_1 = new Intl.DateTimeFormat(locale, {
|
|
155
|
+
hour: '2-digit',
|
|
156
|
+
minute: '2-digit',
|
|
157
|
+
second: '2-digit',
|
|
158
|
+
hour12: false,
|
|
159
|
+
});
|
|
160
|
+
var indent_1 = ' ';
|
|
161
|
+
var colon_1 = language === 'zh' ? ':' : ':';
|
|
162
|
+
var headerGap_1 = language === 'zh' ? '' : ' ';
|
|
163
|
+
violationSnapshot.forEach(function (violation) {
|
|
164
|
+
var timestamp = formatter_1.format(new Date());
|
|
165
|
+
var tagLabel = t.ruleTagLabel({ tag: violation.tag });
|
|
166
|
+
var severityIcon = t.reporterSeverityIcon({ severity: violation.severity });
|
|
167
|
+
var logger = getLoggerForSeverity(violation.severity);
|
|
168
|
+
var header = "[".concat(timestamp, "] ").concat(severityIcon, " [").concat(tagLabel, "]").concat(colon_1).concat(headerGap_1).concat(violation.ruleName);
|
|
169
|
+
logger(header);
|
|
170
|
+
var location = typeof violation.line === 'number' ? "".concat(filePath, ":").concat(violation.line) : filePath;
|
|
171
|
+
consola_1.default.log("".concat(indent_1).concat(t.reporterFileLabel(), ": ").concat(location));
|
|
172
|
+
consola_1.default.log("".concat(indent_1).concat(t.reporterDescriptionLabel(), ": ").concat(violation.message));
|
|
173
|
+
if (violation.code) {
|
|
174
|
+
consola_1.default.log("".concat(indent_1).concat(t.reporterCodeLabel(), ": ").concat(violation.code));
|
|
175
|
+
}
|
|
176
|
+
if (violation.suggestions && violation.suggestions.length > 0) {
|
|
177
|
+
var suggestionSeparator = language === 'zh' ? '; ' : ' | ';
|
|
178
|
+
var suggestionLine = violation.suggestions
|
|
179
|
+
.map(function (suggestion) { return t.reporterFormatSuggestion(suggestion); })
|
|
180
|
+
.join(suggestionSeparator);
|
|
181
|
+
consola_1.default.log("".concat(indent_1).concat(t.reporterSuggestionLabel(), ": ").concat(suggestionLine));
|
|
182
|
+
}
|
|
183
|
+
});
|
|
129
184
|
}
|
|
130
|
-
var locale = language === 'zh' ? 'zh-CN' : 'en-US';
|
|
131
|
-
var formatter = new Intl.DateTimeFormat(locale, {
|
|
132
|
-
hour: '2-digit',
|
|
133
|
-
minute: '2-digit',
|
|
134
|
-
second: '2-digit',
|
|
135
|
-
hour12: false,
|
|
136
|
-
});
|
|
137
|
-
var indent = ' ';
|
|
138
|
-
var colon = language === 'zh' ? ':' : ':';
|
|
139
|
-
var headerGap = language === 'zh' ? '' : ' ';
|
|
140
|
-
records.forEach(function (violation) {
|
|
141
|
-
var timestamp = formatter.format(new Date());
|
|
142
|
-
var tagLabel = t.ruleTagLabel({ tag: violation.tag });
|
|
143
|
-
var severityIcon = t.reporterSeverityIcon({ severity: violation.severity });
|
|
144
|
-
var logger = getLoggerForSeverity(violation.severity);
|
|
145
|
-
var header = "[".concat(timestamp, "] ").concat(severityIcon, " [").concat(tagLabel, "]").concat(colon).concat(headerGap).concat(violation.ruleName);
|
|
146
|
-
logger(header);
|
|
147
|
-
var location = typeof violation.line === 'number' ? "".concat(filePath, ":").concat(violation.line) : filePath;
|
|
148
|
-
consola_1.default.log("".concat(indent).concat(t.reporterFileLabel(), ": ").concat(location));
|
|
149
|
-
consola_1.default.log("".concat(indent).concat(t.reporterDescriptionLabel(), ": ").concat(violation.message));
|
|
150
|
-
if (violation.code) {
|
|
151
|
-
consola_1.default.log("".concat(indent).concat(t.reporterCodeLabel(), ": ").concat(violation.code));
|
|
152
|
-
}
|
|
153
|
-
if (violation.suggestions && violation.suggestions.length > 0) {
|
|
154
|
-
var suggestionSeparator = language === 'zh' ? '; ' : ' | ';
|
|
155
|
-
var suggestionLine = violation.suggestions
|
|
156
|
-
.map(function (suggestion) { return t.reporterFormatSuggestion(suggestion); })
|
|
157
|
-
.join(suggestionSeparator);
|
|
158
|
-
consola_1.default.log("".concat(indent).concat(t.reporterSuggestionLabel(), ": ").concat(suggestionLine));
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
185
|
records.length = 0;
|
|
162
186
|
resetCounters();
|
|
163
187
|
return summary;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { RuleSeverity } from 'auto-cr-rules';
|
|
1
2
|
import type { Rule, RuleReporter } from 'auto-cr-rules';
|
|
3
|
+
export type ReporterFormat = 'text' | 'json';
|
|
4
|
+
interface ReporterOptions {
|
|
5
|
+
format?: ReporterFormat;
|
|
6
|
+
}
|
|
2
7
|
export interface Reporter extends RuleReporter {
|
|
3
8
|
forRule(rule: Pick<Rule, 'name' | 'tag' | 'severity'>): RuleReporter;
|
|
4
9
|
flush(): ReporterSummary;
|
|
5
10
|
}
|
|
6
|
-
interface ReporterSummary {
|
|
11
|
+
export interface ReporterSummary {
|
|
7
12
|
totalViolations: number;
|
|
8
13
|
errorViolations: number;
|
|
9
14
|
severityCounts: {
|
|
@@ -11,6 +16,21 @@ interface ReporterSummary {
|
|
|
11
16
|
warning: number;
|
|
12
17
|
optimizing: number;
|
|
13
18
|
};
|
|
19
|
+
violations: ReadonlyArray<ViolationRecord>;
|
|
20
|
+
}
|
|
21
|
+
type Severity = RuleSeverity;
|
|
22
|
+
type SuggestionEntry = {
|
|
23
|
+
text: string;
|
|
24
|
+
link?: string;
|
|
25
|
+
};
|
|
26
|
+
export interface ViolationRecord {
|
|
27
|
+
tag: string;
|
|
28
|
+
ruleName: string;
|
|
29
|
+
severity: Severity;
|
|
30
|
+
message: string;
|
|
31
|
+
line?: number;
|
|
32
|
+
code?: string;
|
|
33
|
+
suggestions?: ReadonlyArray<SuggestionEntry>;
|
|
14
34
|
}
|
|
15
|
-
export declare function createReporter(filePath: string, source: string): Reporter;
|
|
35
|
+
export declare function createReporter(filePath: string, source: string, options?: ReporterOptions): Reporter;
|
|
16
36
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auto-cr-cmd",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.29",
|
|
4
4
|
"description": "Fast automated code review CLI powered by SWC-based static analysis",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@swc/core": "^1.13.20",
|
|
50
50
|
"@swc/wasm": "^1.13.20",
|
|
51
|
-
"auto-cr-rules": "^2.0.
|
|
51
|
+
"auto-cr-rules": "^2.0.29",
|
|
52
52
|
"commander": "^14.0.0",
|
|
53
53
|
"consola": "^3.4.2"
|
|
54
54
|
},
|