auto-cr-cmd 2.0.26 → 2.0.28

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 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
- ℹ️ Scanning directory: ./src
51
- ℹ️ Scanning file: ./src/main.ts
52
- ℹ️ [Base Rules]
53
- auto-cr scan complete
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
- ℹ️ 扫描目录: ./src
51
- ℹ️ 扫描文件: ./src/main.ts
52
- ℹ️ [基础规则]
53
- auto-cr 代码扫描完成
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,76 @@ 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
+ var consolaLoggers = {
76
+ info: consola_1.consola.info.bind(consola_1.consola),
77
+ warn: consola_1.consola.warn.bind(consola_1.consola),
78
+ error: consola_1.consola.error.bind(consola_1.consola),
79
+ };
74
80
  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;
81
+ return __awaiter(this, arguments, void 0, function (filePaths, ruleDir, format) {
82
+ 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
83
  if (filePaths === void 0) { filePaths = []; }
78
84
  return __generator(this, function (_b) {
79
85
  switch (_b.label) {
80
86
  case 0:
81
87
  t = (0, i18n_1.getTranslator)();
88
+ notifications = [];
89
+ log = function (level, message, detail) {
90
+ var detailText;
91
+ if (detail !== undefined) {
92
+ if (detail instanceof Error) {
93
+ detailText = detail.message;
94
+ }
95
+ else if (typeof detail === 'string') {
96
+ detailText = detail;
97
+ }
98
+ else {
99
+ try {
100
+ detailText = JSON.stringify(detail);
101
+ }
102
+ catch (_a) {
103
+ detailText = String(detail);
104
+ }
105
+ }
106
+ }
107
+ notifications.push({ level: level, message: message, detail: detailText });
108
+ if (format === 'text') {
109
+ var logger = consolaLoggers[level];
110
+ if (detail === undefined) {
111
+ logger(message);
112
+ }
113
+ else {
114
+ logger(message, detail);
115
+ }
116
+ }
117
+ };
82
118
  _b.label = 1;
83
119
  case 1:
84
120
  _b.trys.push([1, 6, , 7]);
85
121
  if (filePaths.length === 0) {
86
- consola_1.consola.info(t.noPathsProvided());
87
- return [2 /*return*/, { scannedFiles: 0, filesWithErrors: 0, filesWithWarnings: 0, filesWithOptimizing: 0 }];
122
+ log('info', t.noPathsProvided());
123
+ return [2 /*return*/, {
124
+ scannedFiles: 0,
125
+ filesWithErrors: 0,
126
+ filesWithWarnings: 0,
127
+ filesWithOptimizing: 0,
128
+ violationTotals: { total: 0, error: 0, warning: 0, optimizing: 0 },
129
+ files: [],
130
+ notifications: notifications,
131
+ }];
88
132
  }
89
133
  validPaths = filePaths.filter(function (candidate) { return (0, file_1.checkPathExists)(candidate); });
90
134
  if (validPaths.length === 0) {
91
- consola_1.consola.error(t.allPathsMissing());
92
- return [2 /*return*/, { scannedFiles: 0, filesWithErrors: 0, filesWithWarnings: 0, filesWithOptimizing: 0 }];
135
+ log('error', t.allPathsMissing());
136
+ return [2 /*return*/, {
137
+ scannedFiles: 0,
138
+ filesWithErrors: 0,
139
+ filesWithWarnings: 0,
140
+ filesWithOptimizing: 0,
141
+ violationTotals: { total: 0, error: 0, warning: 0, optimizing: 0 },
142
+ files: [],
143
+ notifications: notifications,
144
+ }];
93
145
  }
94
146
  allFiles = [];
95
147
  for (_i = 0, validPaths_1 = validPaths; _i < validPaths_1.length; _i++) {
@@ -104,30 +156,46 @@ function run() {
104
156
  }
105
157
  }
106
158
  if (allFiles.length === 0) {
107
- consola_1.consola.info(t.noFilesFound());
108
- return [2 /*return*/, { scannedFiles: 0, filesWithErrors: 0, filesWithWarnings: 0, filesWithOptimizing: 0 }];
159
+ log('info', t.noFilesFound());
160
+ return [2 /*return*/, {
161
+ scannedFiles: 0,
162
+ filesWithErrors: 0,
163
+ filesWithWarnings: 0,
164
+ filesWithOptimizing: 0,
165
+ violationTotals: { total: 0, error: 0, warning: 0, optimizing: 0 },
166
+ files: [],
167
+ notifications: notifications,
168
+ }];
109
169
  }
110
170
  scannableFiles = allFiles.filter(function (candidate) { return !candidate.endsWith('.d.ts'); });
111
171
  customRules = (0, loader_1.loadCustomRules)(ruleDir);
112
172
  rules = __spreadArray(__spreadArray([], auto_cr_rules_1.builtinRules, true), customRules, true);
113
173
  if (rules.length === 0) {
114
- consola_1.consola.warn(t.noRulesLoaded());
174
+ log('warn', t.noRulesLoaded());
115
175
  return [2 /*return*/, {
116
176
  scannedFiles: 0,
117
177
  filesWithErrors: 0,
118
178
  filesWithWarnings: 0,
119
179
  filesWithOptimizing: 0,
180
+ violationTotals: { total: 0, error: 0, warning: 0, optimizing: 0 },
181
+ files: [],
182
+ notifications: notifications,
120
183
  }];
121
184
  }
122
185
  filesWithErrors = 0;
123
186
  filesWithWarnings = 0;
124
187
  filesWithOptimizing = 0;
188
+ totalViolations = 0;
189
+ totalErrorViolations = 0;
190
+ totalWarningViolations = 0;
191
+ totalOptimizingViolations = 0;
192
+ fileSummaries = [];
125
193
  _a = 0, scannableFiles_1 = scannableFiles;
126
194
  _b.label = 2;
127
195
  case 2:
128
196
  if (!(_a < scannableFiles_1.length)) return [3 /*break*/, 5];
129
197
  file = scannableFiles_1[_a];
130
- return [4 /*yield*/, analyzeFile(file, rules)];
198
+ return [4 /*yield*/, analyzeFile(file, rules, format, log)];
131
199
  case 3:
132
200
  summary = _b.sent();
133
201
  if (summary.severityCounts.error > 0) {
@@ -139,6 +207,17 @@ function run() {
139
207
  if (summary.severityCounts.optimizing > 0) {
140
208
  filesWithOptimizing += 1;
141
209
  }
210
+ totalViolations += summary.totalViolations;
211
+ totalErrorViolations += summary.errorViolations;
212
+ totalWarningViolations += summary.severityCounts.warning;
213
+ totalOptimizingViolations += summary.severityCounts.optimizing;
214
+ fileSummaries.push({
215
+ filePath: file,
216
+ severityCounts: summary.severityCounts,
217
+ totalViolations: summary.totalViolations,
218
+ errorViolations: summary.errorViolations,
219
+ violations: summary.violations,
220
+ });
142
221
  _b.label = 4;
143
222
  case 4:
144
223
  _a++;
@@ -148,6 +227,14 @@ function run() {
148
227
  filesWithErrors: filesWithErrors,
149
228
  filesWithWarnings: filesWithWarnings,
150
229
  filesWithOptimizing: filesWithOptimizing,
230
+ violationTotals: {
231
+ total: totalViolations,
232
+ error: totalErrorViolations,
233
+ warning: totalWarningViolations,
234
+ optimizing: totalOptimizingViolations,
235
+ },
236
+ files: fileSummaries,
237
+ notifications: notifications,
151
238
  }];
152
239
  case 6:
153
240
  error_1 = _b.sent();
@@ -157,27 +244,30 @@ function run() {
157
244
  });
158
245
  });
159
246
  }
160
- function analyzeFile(file, rules) {
247
+ function analyzeFile(file, rules, format, log) {
161
248
  return __awaiter(this, void 0, void 0, function () {
162
249
  var source, reporter, t, ast, parseOptions, language, baseContext, sharedHelpers, _loop_1, _i, rules_1, rule, summary;
163
250
  return __generator(this, function (_a) {
164
251
  switch (_a.label) {
165
252
  case 0:
166
253
  source = (0, file_1.readFile)(file);
167
- reporter = (0, report_1.createReporter)(file, source);
254
+ reporter = (0, report_1.createReporter)(file, source, { format: format });
168
255
  t = (0, i18n_1.getTranslator)();
169
256
  try {
170
257
  parseOptions = (0, config_1.loadParseOptions)(file);
171
258
  ast = (0, wasm_1.parseSync)(source, parseOptions);
172
259
  }
173
260
  catch (error) {
174
- consola_1.consola.error(t.parseFileFailed({ file: file }), error instanceof Error ? error.message : error);
261
+ log('error', t.parseFileFailed({ file: file }), error);
175
262
  return [2 /*return*/, {
176
263
  severityCounts: {
177
264
  error: 1,
178
265
  warning: 0,
179
266
  optimizing: 0,
180
267
  },
268
+ totalViolations: 1,
269
+ errorViolations: 1,
270
+ violations: [],
181
271
  }];
182
272
  }
183
273
  language = (0, i18n_1.getLanguage)();
@@ -226,7 +316,7 @@ function analyzeFile(file, rules) {
226
316
  return [3 /*break*/, 3];
227
317
  case 2:
228
318
  error_2 = _b.sent();
229
- consola_1.consola.error(t.ruleExecutionFailed({ ruleName: rule.name, file: file }), error_2 instanceof Error ? error_2.message : error_2);
319
+ log('error', t.ruleExecutionFailed({ ruleName: rule.name, file: file }), error_2);
230
320
  return [3 /*break*/, 3];
231
321
  case 3: return [2 /*return*/];
232
322
  }
@@ -248,6 +338,9 @@ function analyzeFile(file, rules) {
248
338
  summary = reporter.flush();
249
339
  return [2 /*return*/, {
250
340
  severityCounts: summary.severityCounts,
341
+ totalViolations: summary.totalViolations,
342
+ errorViolations: summary.errorViolations,
343
+ violations: summary.violations,
251
344
  }];
252
345
  }
253
346
  });
@@ -305,48 +398,130 @@ function normalizeViolationInput(input, spanArg) {
305
398
  span: spanArg,
306
399
  };
307
400
  }
401
+ function parseOutputFormat(value) {
402
+ if (!value) {
403
+ return 'text';
404
+ }
405
+ var normalized = value.toLowerCase();
406
+ if (normalized === 'json' || normalized === 'text') {
407
+ return normalized;
408
+ }
409
+ throw new Error("Unsupported output format: ".concat(value, ". Use \"text\" or \"json\"."));
410
+ }
411
+ function severityToLabel(severity) {
412
+ switch (severity) {
413
+ case auto_cr_rules_1.RuleSeverity.Warning:
414
+ return 'warning';
415
+ case auto_cr_rules_1.RuleSeverity.Optimizing:
416
+ return 'optimizing';
417
+ case auto_cr_rules_1.RuleSeverity.Error:
418
+ default:
419
+ return 'error';
420
+ }
421
+ }
422
+ function formatViolationForJson(violation) {
423
+ var suggestions = violation.suggestions
424
+ ? violation.suggestions.map(function (suggestion) { return (__assign({}, suggestion)); })
425
+ : [];
426
+ var payload = {
427
+ tag: violation.tag,
428
+ ruleName: violation.ruleName,
429
+ severity: severityToLabel(violation.severity),
430
+ message: violation.message,
431
+ suggestions: suggestions,
432
+ };
433
+ if (typeof violation.line === 'number') {
434
+ payload.line = violation.line;
435
+ }
436
+ if (violation.code) {
437
+ payload.code = violation.code;
438
+ }
439
+ return payload;
440
+ }
441
+ function formatJsonOutput(result) {
442
+ return {
443
+ summary: {
444
+ scannedFiles: result.scannedFiles,
445
+ filesWithErrors: result.filesWithErrors,
446
+ filesWithWarnings: result.filesWithWarnings,
447
+ filesWithOptimizing: result.filesWithOptimizing,
448
+ violationTotals: result.violationTotals,
449
+ },
450
+ files: result.files.map(function (file) { return ({
451
+ filePath: file.filePath,
452
+ severityCounts: file.severityCounts,
453
+ totalViolations: file.totalViolations,
454
+ errorViolations: file.errorViolations,
455
+ violations: file.violations.map(formatViolationForJson),
456
+ }); }),
457
+ notifications: result.notifications,
458
+ };
459
+ }
308
460
  commander_1.program
309
461
  .argument('[paths...]', '需要扫描的文件或目录路径列表 / Paths to scan')
310
462
  .option('-r, --rule-dir <directory>', '自定义规则目录路径 / Custom rule directory')
311
463
  .option('-l, --language <language>', '设置 CLI 语言 (zh/en) / Set CLI language (zh/en)')
464
+ .option('-o, --output <format>', '设置输出格式 (text/json) / Output format (text/json)', 'text')
312
465
  .parse(process.argv);
313
466
  var options = commander_1.program.opts();
314
467
  var filePaths = commander_1.program.args.map(function (target) { return path_1.default.resolve(process.cwd(), target); });
468
+ (0, i18n_1.setLanguage)((_a = options.language) !== null && _a !== void 0 ? _a : process.env.LANG);
469
+ var outputFormat;
470
+ try {
471
+ outputFormat = parseOutputFormat(options.output);
472
+ }
473
+ catch (error) {
474
+ var message = error instanceof Error ? error.message : String(error);
475
+ consola_1.consola.error(message);
476
+ process.exit(1);
477
+ }
478
+ ;
315
479
  (function () { return __awaiter(void 0, void 0, void 0, function () {
316
- var summary, t, language, resultMessage, error_3, t;
317
- var _a;
318
- return __generator(this, function (_b) {
319
- switch (_b.label) {
480
+ var result, t, payload, exitCode, language, resultMessage, exitCode, error_3, t, detail, payload;
481
+ return __generator(this, function (_a) {
482
+ switch (_a.label) {
320
483
  case 0:
321
- _b.trys.push([0, 2, , 3]);
322
- (0, i18n_1.setLanguage)((_a = options.language) !== null && _a !== void 0 ? _a : process.env.LANG);
323
- return [4 /*yield*/, run(filePaths, options.ruleDir)];
484
+ _a.trys.push([0, 2, , 3]);
485
+ return [4 /*yield*/, run(filePaths, options.ruleDir, outputFormat)];
324
486
  case 1:
325
- summary = _b.sent();
487
+ result = _a.sent();
326
488
  t = (0, i18n_1.getTranslator)();
327
- if (summary.scannedFiles > 0) {
489
+ if (outputFormat === 'json') {
490
+ payload = formatJsonOutput(result);
491
+ exitCode = result.filesWithErrors > 0 ? 1 : 0;
492
+ process.stdout.write("".concat(JSON.stringify(payload, null, 2), "\n"));
493
+ process.exit(exitCode);
494
+ }
495
+ if (result.scannedFiles > 0) {
328
496
  consola_1.consola.log(' ');
329
497
  language = (0, i18n_1.getLanguage)();
330
498
  resultMessage = language.startsWith('zh')
331
- ? " ".concat(t.scanComplete(), "\uFF0C\u672C\u6B21\u5171\u626B\u63CF").concat(summary.scannedFiles, "\u4E2A\u6587\u4EF6\uFF0C\u5176\u4E2D").concat(summary.filesWithErrors, "\u4E2A\u6587\u4EF6\u5B58\u5728\u9519\u8BEF\uFF0C").concat(summary.filesWithWarnings, "\u4E2A\u6587\u4EF6\u5B58\u5728\u8B66\u544A\uFF0C").concat(summary.filesWithOptimizing, "\u4E2A\u6587\u4EF6\u5B58\u5728\u4F18\u5316\u5EFA\u8BAE\uFF01")
332
- : " ".concat(t.scanComplete(), ", scanned ").concat(summary.scannedFiles, " files: ").concat(summary.filesWithErrors, " with errors, ").concat(summary.filesWithWarnings, " with warnings, ").concat(summary.filesWithOptimizing, " with optimizing hints!");
333
- if (summary.filesWithErrors > 0) {
334
- consola_1.consola.success(resultMessage);
335
- process.exit(1);
336
- }
337
- else {
338
- consola_1.consola.success(resultMessage);
339
- process.exit(0);
340
- }
499
+ ? " ".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")
500
+ : " ".concat(t.scanComplete(), ", scanned ").concat(result.scannedFiles, " files: ").concat(result.filesWithErrors, " with errors, ").concat(result.filesWithWarnings, " with warnings, ").concat(result.filesWithOptimizing, " with optimizing hints!");
501
+ consola_1.consola.success(resultMessage);
502
+ exitCode = result.filesWithErrors > 0 ? 1 : 0;
503
+ process.exit(exitCode);
341
504
  }
342
505
  else {
343
506
  process.exit(0);
344
507
  }
345
508
  return [3 /*break*/, 3];
346
509
  case 2:
347
- error_3 = _b.sent();
510
+ error_3 = _a.sent();
348
511
  t = (0, i18n_1.getTranslator)();
349
- consola_1.consola.error(t.scanError(), error_3 instanceof Error ? error_3.message : error_3);
512
+ detail = error_3 instanceof Error ? error_3.message : String(error_3);
513
+ if (outputFormat === 'json') {
514
+ payload = {
515
+ error: {
516
+ message: t.scanError(),
517
+ detail: detail,
518
+ },
519
+ };
520
+ process.stdout.write("".concat(JSON.stringify(payload, null, 2), "\n"));
521
+ }
522
+ else {
523
+ consola_1.consola.error(t.scanError(), detail);
524
+ }
350
525
  process.exit(1);
351
526
  return [3 /*break*/, 3];
352
527
  case 3: return [2 /*return*/];
@@ -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 (records.length === 0) {
127
- resetCounters();
128
- return summary;
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.26",
3
+ "version": "2.0.28",
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.26",
51
+ "auto-cr-rules": "^2.0.28",
52
52
  "commander": "^14.0.0",
53
53
  "consola": "^3.4.2"
54
54
  },