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 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,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
- consola_1.consola.info(t.noPathsProvided());
87
- return [2 /*return*/, { scannedFiles: 0, filesWithErrors: 0, filesWithWarnings: 0, filesWithOptimizing: 0 }];
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
- consola_1.consola.error(t.allPathsMissing());
92
- return [2 /*return*/, { scannedFiles: 0, filesWithErrors: 0, filesWithWarnings: 0, filesWithOptimizing: 0 }];
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
- consola_1.consola.info(t.noFilesFound());
108
- return [2 /*return*/, { scannedFiles: 0, filesWithErrors: 0, filesWithWarnings: 0, filesWithOptimizing: 0 }];
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
- consola_1.consola.warn(t.noRulesLoaded());
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
- consola_1.consola.error(t.parseFileFailed({ file: file }), error instanceof Error ? error.message : error);
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
- consola_1.consola.error(t.ruleExecutionFailed({ ruleName: rule.name, file: file }), error_2 instanceof Error ? error_2.message : error_2);
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 summary, t, language, resultMessage, error_3, t;
317
- var _a;
318
- return __generator(this, function (_b) {
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
- _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)];
485
+ _a.trys.push([0, 2, , 3]);
486
+ return [4 /*yield*/, run(filePaths, options.ruleDir, outputFormat)];
324
487
  case 1:
325
- summary = _b.sent();
488
+ result = _a.sent();
326
489
  t = (0, i18n_1.getTranslator)();
327
- if (summary.scannedFiles > 0) {
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(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
- }
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 = _b.sent();
511
+ error_3 = _a.sent();
348
512
  t = (0, i18n_1.getTranslator)();
349
- consola_1.consola.error(t.scanError(), error_3 instanceof Error ? error_3.message : error_3);
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*/];
@@ -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.27",
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.27",
51
+ "auto-cr-rules": "^2.0.29",
52
52
  "commander": "^14.0.0",
53
53
  "consola": "^3.4.2"
54
54
  },