frontend-guardian-core 3.7.6 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/fg-core.js +218 -150
- package/bin/fg-lsp.js +4 -3
- package/bin/fg-server.js +4 -3
- package/dist/engine/rule-engine.d.ts +6 -1
- package/dist/engine/rule-engine.d.ts.map +1 -1
- package/dist/engine/rule-engine.js +42 -33
- package/dist/engine/rule-engine.js.map +1 -1
- package/dist/index.d.ts +70 -66
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +146 -137
- package/dist/index.js.map +1 -1
- package/dist/mcp/mcp-server.d.ts +17 -0
- package/dist/mcp/mcp-server.d.ts.map +1 -0
- package/dist/mcp/mcp-server.js +31 -0
- package/dist/mcp/mcp-server.js.map +1 -0
- package/dist/mcp/tools.d.ts +14 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +686 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/types.d.ts +95 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +6 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/utils/e2e-gap-detector.d.ts +4 -0
- package/dist/utils/e2e-gap-detector.d.ts.map +1 -1
- package/dist/utils/e2e-gap-detector.js +23 -21
- package/dist/utils/e2e-gap-detector.js.map +1 -1
- package/dist/utils/test-recommender.d.ts +68 -0
- package/dist/utils/test-recommender.d.ts.map +1 -0
- package/dist/utils/test-recommender.js +334 -0
- package/dist/utils/test-recommender.js.map +1 -0
- package/package.json +5 -4
package/bin/fg-core.js
CHANGED
|
@@ -5,76 +5,76 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { writeFileSync } from "node:fs";
|
|
8
|
-
import { join } from "node:path";
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
9
|
+
import pc from "picocolors";
|
|
9
10
|
import {
|
|
10
|
-
|
|
11
|
-
i18nRules,
|
|
12
|
-
performanceRules,
|
|
11
|
+
AIFixSuggester,
|
|
13
12
|
a11yRules,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
BaselineManager,
|
|
14
|
+
buildNotificationPayload,
|
|
15
|
+
CodeownersParser,
|
|
16
|
+
compareHistoryReports,
|
|
17
|
+
complianceReportToMarkdown,
|
|
17
18
|
componentRules,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
detectE2EGaps,
|
|
23
|
-
formatE2EGapReport,
|
|
24
|
-
formatE2EGapJson,
|
|
25
|
-
installGitHooks,
|
|
26
|
-
uninstallGitHooks,
|
|
27
|
-
hasGitHook,
|
|
28
|
-
generateCIConfig,
|
|
19
|
+
createEngine,
|
|
20
|
+
createPublisher,
|
|
21
|
+
crossFileRules,
|
|
22
|
+
detectAIConfig,
|
|
29
23
|
detectCIProvider,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
initConfig,
|
|
24
|
+
detectDashboardConfig,
|
|
25
|
+
detectE2EGaps,
|
|
26
|
+
detectFixBotConfig,
|
|
27
|
+
detectMonorepo,
|
|
28
|
+
detectNotificationConfig,
|
|
36
29
|
detectProjectMeta,
|
|
37
|
-
generatePRComment,
|
|
38
30
|
detectPublisherConfig,
|
|
39
|
-
createPublisher,
|
|
40
|
-
uploadReport,
|
|
41
31
|
detectUploadConfig,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
scanWorkspace,
|
|
49
|
-
formatWorkspaceReport,
|
|
50
|
-
formatWorkspaceJson,
|
|
51
|
-
AIFixSuggester,
|
|
52
|
-
detectAIConfig,
|
|
53
|
-
compareHistoryReports,
|
|
32
|
+
e2eRules,
|
|
33
|
+
FileWatcher,
|
|
34
|
+
findRouteFiles,
|
|
35
|
+
formatAllAnnotations,
|
|
36
|
+
formatE2EGapJson,
|
|
37
|
+
formatE2EGapReport,
|
|
54
38
|
formatHistoryCompare,
|
|
55
39
|
formatHistoryCompareJson,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
40
|
+
formatPageHealthJson,
|
|
41
|
+
formatPageHealthReport,
|
|
42
|
+
formatWorkspaceJson,
|
|
43
|
+
formatWorkspaceReport,
|
|
44
|
+
generateCIConfig,
|
|
60
45
|
generateComplianceReport,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
46
|
+
generateDashboard,
|
|
47
|
+
generatePRComment,
|
|
48
|
+
generateSarif,
|
|
49
|
+
HistoryReport,
|
|
50
|
+
hasGitHook,
|
|
51
|
+
hooksRules,
|
|
52
|
+
i18nRules,
|
|
53
|
+
initConfig,
|
|
54
|
+
installGitHooks,
|
|
55
|
+
isGitHubActions,
|
|
56
|
+
isPlaywrightAvailable,
|
|
57
|
+
namingRules,
|
|
66
58
|
ProjectIndexer,
|
|
67
|
-
FileWatcher,
|
|
68
59
|
parseAllRoutes,
|
|
69
|
-
|
|
60
|
+
performanceRules,
|
|
61
|
+
platformRules,
|
|
62
|
+
playwrightIntegration,
|
|
63
|
+
runFixBot,
|
|
70
64
|
runPageHealthCheck,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
65
|
+
SmartCache,
|
|
66
|
+
saveComplianceReport,
|
|
67
|
+
scanWorkspace,
|
|
68
|
+
securityRules,
|
|
69
|
+
sendNotifications,
|
|
70
|
+
svelteRules,
|
|
75
71
|
toScanResult,
|
|
72
|
+
uninstallGitHooks,
|
|
73
|
+
uploadPageHealthResult,
|
|
74
|
+
uploadReport,
|
|
75
|
+
uploadToDashboardServer,
|
|
76
|
+
writeJobSummary,
|
|
76
77
|
} from "../dist/index.js";
|
|
77
|
-
import pc from "picocolors";
|
|
78
78
|
import { runWatchMode } from "./watch-mode.js";
|
|
79
79
|
|
|
80
80
|
const MODULES = [
|
|
@@ -107,7 +107,7 @@ const MODULE_RULES = {
|
|
|
107
107
|
|
|
108
108
|
function showHelp() {
|
|
109
109
|
console.log(`
|
|
110
|
-
Frontend Guardian Core v3.
|
|
110
|
+
Frontend Guardian Core v3.9.0
|
|
111
111
|
|
|
112
112
|
Usage:
|
|
113
113
|
fg-core <project-dir> [options]
|
|
@@ -179,6 +179,8 @@ Options:
|
|
|
179
179
|
--build-index 建立项目索引(预索引文件结构、符号、路由)
|
|
180
180
|
--watch-index 监听文件变更并自动同步索引
|
|
181
181
|
--index-status 查看项目索引状态
|
|
182
|
+
--recommend-tests 智能测试推荐:基于变更影响分析推荐需要运行的测试(可配合 --staged/--diff/--auto-scope/--files)
|
|
183
|
+
--mcp 启动 MCP Server(stdio,供 AI Agent 调用)
|
|
182
184
|
--help, -h 显示帮助
|
|
183
185
|
|
|
184
186
|
Examples:
|
|
@@ -267,6 +269,8 @@ async function main() {
|
|
|
267
269
|
buildIndex: false,
|
|
268
270
|
watchIndex: false,
|
|
269
271
|
indexStatus: false,
|
|
272
|
+
recommendTests: false,
|
|
273
|
+
mcp: false,
|
|
270
274
|
};
|
|
271
275
|
|
|
272
276
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -476,6 +480,12 @@ async function main() {
|
|
|
476
480
|
case "--index-status":
|
|
477
481
|
options.indexStatus = true;
|
|
478
482
|
break;
|
|
483
|
+
case "--recommend-tests":
|
|
484
|
+
options.recommendTests = true;
|
|
485
|
+
break;
|
|
486
|
+
case "--mcp":
|
|
487
|
+
options.mcp = true;
|
|
488
|
+
break;
|
|
479
489
|
case "--help":
|
|
480
490
|
case "-h":
|
|
481
491
|
showHelp();
|
|
@@ -483,6 +493,17 @@ async function main() {
|
|
|
483
493
|
}
|
|
484
494
|
}
|
|
485
495
|
|
|
496
|
+
// v3.8.0: 启动 MCP Server(AI Agent 集成)
|
|
497
|
+
if (options.mcp) {
|
|
498
|
+
const { runMCPServer } = await import("../dist/index.js");
|
|
499
|
+
runMCPServer({
|
|
500
|
+
projectDir: options.projectDir,
|
|
501
|
+
configFile: options.configFile,
|
|
502
|
+
minSeverity: options.minSeverity,
|
|
503
|
+
});
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
486
507
|
// v3.6.0: E2E 测试覆盖缺口检测
|
|
487
508
|
if (options.e2eDetectGaps) {
|
|
488
509
|
const gapResult = detectE2EGaps({ projectDir: options.projectDir });
|
|
@@ -512,12 +533,18 @@ async function main() {
|
|
|
512
533
|
const duration = Date.now() - start;
|
|
513
534
|
|
|
514
535
|
if (options.json) {
|
|
515
|
-
console.log(
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
536
|
+
console.log(
|
|
537
|
+
JSON.stringify(
|
|
538
|
+
{
|
|
539
|
+
tool: "Playwright",
|
|
540
|
+
total: issues.length,
|
|
541
|
+
duration,
|
|
542
|
+
issues,
|
|
543
|
+
},
|
|
544
|
+
null,
|
|
545
|
+
2
|
|
546
|
+
)
|
|
547
|
+
);
|
|
521
548
|
} else {
|
|
522
549
|
console.log(pc.cyan(`📊 Playwright 测试结果`));
|
|
523
550
|
console.log(pc.gray(` 耗时: ${duration}ms`));
|
|
@@ -553,7 +580,7 @@ async function main() {
|
|
|
553
580
|
|
|
554
581
|
if (!options.baseUrl && !options.serveCommand) {
|
|
555
582
|
console.log(pc.yellow("⚠️ 请指定 --base-url 或 --serve"));
|
|
556
|
-
console.log(pc.gray(
|
|
583
|
+
console.log(pc.gray(' 示例: fg-core . --page-health --serve "npm run dev" --port 5173'));
|
|
557
584
|
console.log(pc.gray(" 示例: fg-core . --page-health --base-url http://localhost:3000"));
|
|
558
585
|
process.exit(1);
|
|
559
586
|
}
|
|
@@ -587,7 +614,8 @@ async function main() {
|
|
|
587
614
|
if (result.issues.length > 0) {
|
|
588
615
|
console.log(pc.cyan(`📋 发现 ${result.issues.length} 个问题:`));
|
|
589
616
|
for (const issue of result.issues) {
|
|
590
|
-
const color =
|
|
617
|
+
const color =
|
|
618
|
+
issue.severity === "critical" ? pc.red : issue.severity === "warning" ? pc.yellow : pc.blue;
|
|
591
619
|
console.log(color(` [${issue.severity.toUpperCase()}] ${issue.title}`));
|
|
592
620
|
console.log(pc.gray(` ${issue.description.split("\n")[0]}`));
|
|
593
621
|
}
|
|
@@ -605,11 +633,7 @@ async function main() {
|
|
|
605
633
|
if (!options.json) {
|
|
606
634
|
console.log(pc.gray(`\n 正在上报到看板服务器: ${options.server}`));
|
|
607
635
|
}
|
|
608
|
-
const uploadResult = await uploadPageHealthResult(
|
|
609
|
-
result,
|
|
610
|
-
options.projectDir,
|
|
611
|
-
dashboardConfig
|
|
612
|
-
);
|
|
636
|
+
const uploadResult = await uploadPageHealthResult(result, options.projectDir, dashboardConfig);
|
|
613
637
|
if (!options.json) {
|
|
614
638
|
if (uploadResult.success) {
|
|
615
639
|
console.log(pc.green(` ✅ 已上报到看板服务器`));
|
|
@@ -653,6 +677,29 @@ async function main() {
|
|
|
653
677
|
process.exit(0);
|
|
654
678
|
}
|
|
655
679
|
|
|
680
|
+
// v3.9.0: 智能测试推荐
|
|
681
|
+
if (options.recommendTests) {
|
|
682
|
+
const { recommendTests, formatRecommendations, formatRecommendationsJson } = await import("../dist/index.js");
|
|
683
|
+
const changedFiles = options.files?.map((f) => resolve(options.projectDir, f));
|
|
684
|
+
|
|
685
|
+
const result = await recommendTests({
|
|
686
|
+
projectDir: options.projectDir,
|
|
687
|
+
changedFiles,
|
|
688
|
+
staged: options.staged,
|
|
689
|
+
diffRange: options.diffRange,
|
|
690
|
+
autoScope: options.autoScope,
|
|
691
|
+
minPriority: 1,
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
if (options.json) {
|
|
695
|
+
console.log(JSON.stringify(formatRecommendationsJson(result), null, 2));
|
|
696
|
+
} else {
|
|
697
|
+
console.log(formatRecommendations(result));
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
process.exit(result.uncoveredChanges.length > 0 ? 1 : 0);
|
|
701
|
+
}
|
|
702
|
+
|
|
656
703
|
// v3.7.0: 建立项目索引
|
|
657
704
|
if (options.buildIndex) {
|
|
658
705
|
console.log(pc.cyan("📦 正在建立项目索引..."));
|
|
@@ -660,13 +707,7 @@ async function main() {
|
|
|
660
707
|
|
|
661
708
|
const { globbySync } = require("globby");
|
|
662
709
|
const patterns = ["**/*.{js,ts,jsx,tsx,vue}"];
|
|
663
|
-
const exclude = [
|
|
664
|
-
"**/node_modules/**",
|
|
665
|
-
"**/dist/**",
|
|
666
|
-
"**/build/**",
|
|
667
|
-
"**/.git/**",
|
|
668
|
-
"**/coverage/**",
|
|
669
|
-
];
|
|
710
|
+
const exclude = ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**", "**/coverage/**"];
|
|
670
711
|
const files = globbySync(patterns, {
|
|
671
712
|
cwd: options.projectDir,
|
|
672
713
|
ignore: exclude,
|
|
@@ -692,14 +733,20 @@ async function main() {
|
|
|
692
733
|
projectDir: options.projectDir,
|
|
693
734
|
onChange: (changed, deleted) => {
|
|
694
735
|
if (changed.length > 0) {
|
|
695
|
-
console.log(
|
|
736
|
+
console.log(
|
|
737
|
+
pc.cyan(`\n📝 变更: ${changed.map((f) => relative(options.projectDir, f)).join(", ")}`)
|
|
738
|
+
);
|
|
696
739
|
}
|
|
697
740
|
if (deleted.length > 0) {
|
|
698
|
-
console.log(
|
|
741
|
+
console.log(
|
|
742
|
+
pc.yellow(`\n🗑️ 删除: ${deleted.map((f) => relative(options.projectDir, f)).join(", ")}`)
|
|
743
|
+
);
|
|
699
744
|
}
|
|
700
745
|
},
|
|
701
746
|
onIndexUpdate: (stats) => {
|
|
702
|
-
console.log(
|
|
747
|
+
console.log(
|
|
748
|
+
pc.gray(` 索引已更新 | 文件: ${stats.files} | 路由: ${stats.routes} | 符号: ${stats.symbols}`)
|
|
749
|
+
);
|
|
703
750
|
},
|
|
704
751
|
onError: (err) => {
|
|
705
752
|
console.error(pc.red(` 监听错误: ${err.message}`));
|
|
@@ -756,7 +803,9 @@ async function main() {
|
|
|
756
803
|
console.log(pc.gray(" 使用 --init-config --force 覆盖(或使用 --init-config 直接覆盖)"));
|
|
757
804
|
} else if (result.created) {
|
|
758
805
|
console.log(pc.green(` ✅ 已创建: ${result.path}`));
|
|
759
|
-
console.log(
|
|
806
|
+
console.log(
|
|
807
|
+
pc.gray(` 框架: ${meta.framework ?? "auto-detect"} | 组件库: ${meta.componentLib ?? "auto-detect"}`)
|
|
808
|
+
);
|
|
760
809
|
}
|
|
761
810
|
process.exit(0);
|
|
762
811
|
}
|
|
@@ -796,16 +845,28 @@ async function main() {
|
|
|
796
845
|
if (filtered.length === 0) {
|
|
797
846
|
console.log(pc.gray(" 暂无历史记录"));
|
|
798
847
|
} else {
|
|
799
|
-
console.log(
|
|
848
|
+
console.log(
|
|
849
|
+
` ${"时间".padEnd(20)} ${"模块".padEnd(15)} ${"C".padStart(4)} ${"W".padStart(4)} ${"S".padStart(4)}`
|
|
850
|
+
);
|
|
800
851
|
console.log(pc.gray(" " + "-".repeat(50)));
|
|
801
852
|
for (const r of filtered) {
|
|
802
|
-
const time = new Date(r.timestamp).toLocaleString("zh-CN", {
|
|
853
|
+
const time = new Date(r.timestamp).toLocaleString("zh-CN", {
|
|
854
|
+
month: "2-digit",
|
|
855
|
+
day: "2-digit",
|
|
856
|
+
hour: "2-digit",
|
|
857
|
+
minute: "2-digit",
|
|
858
|
+
});
|
|
803
859
|
const c = pc.red(String(r.counts.critical).padStart(4));
|
|
804
860
|
const w = pc.yellow(String(r.counts.warning).padStart(4));
|
|
805
861
|
const s = pc.blue(String(r.counts.suggestion).padStart(4));
|
|
806
862
|
console.log(` ${time.padEnd(20)} ${r.module.padEnd(15)} ${c} ${w} ${s}`);
|
|
807
863
|
}
|
|
808
|
-
console.log(
|
|
864
|
+
console.log(
|
|
865
|
+
pc.gray(
|
|
866
|
+
` 共 ${reports.length} 条记录` +
|
|
867
|
+
(options.historyModule ? `,已按模块 "${options.historyModule}" 过滤` : "")
|
|
868
|
+
)
|
|
869
|
+
);
|
|
809
870
|
}
|
|
810
871
|
process.exit(0);
|
|
811
872
|
}
|
|
@@ -840,9 +901,7 @@ async function main() {
|
|
|
840
901
|
console.log(pc.yellow("⚠️ 暂无历史扫描记录,无法生成看板。请先运行 --save-report 生成历史数据。"));
|
|
841
902
|
process.exit(1);
|
|
842
903
|
}
|
|
843
|
-
const fullReports = reportList
|
|
844
|
-
.map((r) => hr.loadReport(r.filename))
|
|
845
|
-
.filter(Boolean);
|
|
904
|
+
const fullReports = reportList.map((r) => hr.loadReport(r.filename)).filter(Boolean);
|
|
846
905
|
const outputPath = generateDashboard(fullReports, { projectDir: options.projectDir });
|
|
847
906
|
console.log(pc.cyan("📊 趋势看板已生成"));
|
|
848
907
|
console.log(pc.green(` ✅ ${outputPath}`));
|
|
@@ -855,7 +914,11 @@ async function main() {
|
|
|
855
914
|
if (options.monorepo) {
|
|
856
915
|
const mono = detectMonorepo(options.projectDir);
|
|
857
916
|
if (!mono.isMonorepo) {
|
|
858
|
-
console.log(
|
|
917
|
+
console.log(
|
|
918
|
+
pc.yellow(
|
|
919
|
+
`⚠️ 未检测到 monorepo workspace 配置(pnpm-workspace.yaml / lerna.json / nx.json / package.json workspaces)`
|
|
920
|
+
)
|
|
921
|
+
);
|
|
859
922
|
console.log(pc.gray(` 将在项目根目录执行常规扫描...`));
|
|
860
923
|
if (options.module === "all") {
|
|
861
924
|
await runAllModules(options);
|
|
@@ -906,13 +969,24 @@ async function main() {
|
|
|
906
969
|
if (!pr.success || pr.result.total === 0) continue;
|
|
907
970
|
|
|
908
971
|
console.log(pc.cyan(`\n📦 ${pr.package.name} (${pr.package.path})`));
|
|
909
|
-
const allIssues = [
|
|
972
|
+
const allIssues = [
|
|
973
|
+
...pr.result.issues.critical,
|
|
974
|
+
...pr.result.issues.warning,
|
|
975
|
+
...pr.result.issues.suggestion,
|
|
976
|
+
];
|
|
910
977
|
for (const issue of allIssues) {
|
|
911
|
-
printIssue(
|
|
978
|
+
printIssue(
|
|
979
|
+
issue,
|
|
980
|
+
issue.severity === "critical" ? pc.red : issue.severity === "warning" ? pc.yellow : pc.blue
|
|
981
|
+
);
|
|
912
982
|
}
|
|
913
983
|
}
|
|
914
984
|
|
|
915
|
-
console.log(
|
|
985
|
+
console.log(
|
|
986
|
+
pc.gray(
|
|
987
|
+
`\n⏱️ 总耗时: ${workspaceResult.summary.totalDuration}ms | 扫描 ${workspaceResult.summary.totalFilesScanned} 个文件`
|
|
988
|
+
)
|
|
989
|
+
);
|
|
916
990
|
|
|
917
991
|
if (workspaceResult.summary.issuesBySeverity.critical > 0) {
|
|
918
992
|
process.exit(1);
|
|
@@ -1118,9 +1192,7 @@ async function runAllModules(options, cacheInstance) {
|
|
|
1118
1192
|
} else {
|
|
1119
1193
|
if (!options.json) {
|
|
1120
1194
|
console.log(
|
|
1121
|
-
pc.yellow(
|
|
1122
|
-
" ⚠️ 未检测到 Fix Bot 配置。请设置 FG_FIX_BOT_PROVIDER 和 FG_FIX_BOT_TOKEN 环境变量。"
|
|
1123
|
-
)
|
|
1195
|
+
pc.yellow(" ⚠️ 未检测到 Fix Bot 配置。请设置 FG_FIX_BOT_PROVIDER 和 FG_FIX_BOT_TOKEN 环境变量。")
|
|
1124
1196
|
);
|
|
1125
1197
|
console.log("");
|
|
1126
1198
|
}
|
|
@@ -1208,14 +1280,9 @@ async function runAllModules(options, cacheInstance) {
|
|
|
1208
1280
|
// 构建发布器配置
|
|
1209
1281
|
let pubConfig = detectPublisherConfig();
|
|
1210
1282
|
if (!pubConfig && options.commentProvider) {
|
|
1211
|
-
const token =
|
|
1212
|
-
options.commentProvider === "github"
|
|
1213
|
-
? process.env.GITHUB_TOKEN
|
|
1214
|
-
: process.env.GITLAB_TOKEN;
|
|
1283
|
+
const token = options.commentProvider === "github" ? process.env.GITHUB_TOKEN : process.env.GITLAB_TOKEN;
|
|
1215
1284
|
const repo =
|
|
1216
|
-
options.commentProvider === "github"
|
|
1217
|
-
? process.env.GITHUB_REPOSITORY
|
|
1218
|
-
: process.env.CI_PROJECT_ID;
|
|
1285
|
+
options.commentProvider === "github" ? process.env.GITHUB_REPOSITORY : process.env.CI_PROJECT_ID;
|
|
1219
1286
|
if (token && repo && options.prNumber) {
|
|
1220
1287
|
pubConfig = {
|
|
1221
1288
|
provider: options.commentProvider,
|
|
@@ -1231,9 +1298,7 @@ async function runAllModules(options, cacheInstance) {
|
|
|
1231
1298
|
const publisher = createPublisher(pubConfig);
|
|
1232
1299
|
const result = await publisher.publish(commentBody);
|
|
1233
1300
|
if (result.success) {
|
|
1234
|
-
console.log(
|
|
1235
|
-
pc.cyan(`💬 PR/MR 评论已${result.action === "updated" ? "更新" : "发布"}`)
|
|
1236
|
-
);
|
|
1301
|
+
console.log(pc.cyan(`💬 PR/MR 评论已${result.action === "updated" ? "更新" : "发布"}`));
|
|
1237
1302
|
if (result.commentUrl) {
|
|
1238
1303
|
console.log(pc.gray(` ${result.commentUrl}`));
|
|
1239
1304
|
}
|
|
@@ -1254,7 +1319,7 @@ async function runAllModules(options, cacheInstance) {
|
|
|
1254
1319
|
}
|
|
1255
1320
|
|
|
1256
1321
|
// 生成并写入报告文件(--output)
|
|
1257
|
-
|
|
1322
|
+
const reportPath = options.output;
|
|
1258
1323
|
if (reportPath) {
|
|
1259
1324
|
const reportBody = generatePRComment(
|
|
1260
1325
|
allResults,
|
|
@@ -1283,25 +1348,27 @@ async function runAllModules(options, cacheInstance) {
|
|
|
1283
1348
|
if (options.upload) {
|
|
1284
1349
|
const uploadConfig = detectUploadConfig();
|
|
1285
1350
|
if (uploadConfig) {
|
|
1286
|
-
const targetPath =
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
?
|
|
1299
|
-
:
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1351
|
+
const targetPath =
|
|
1352
|
+
reportPath ||
|
|
1353
|
+
(() => {
|
|
1354
|
+
const tmpPath = join(process.cwd(), `fg-report-${Date.now()}.md`);
|
|
1355
|
+
const reportBody = generatePRComment(
|
|
1356
|
+
allResults,
|
|
1357
|
+
{
|
|
1358
|
+
timestamp: new Date().toISOString(),
|
|
1359
|
+
duration: totalDuration,
|
|
1360
|
+
filesScanned: totalFilesScanned,
|
|
1361
|
+
},
|
|
1362
|
+
{
|
|
1363
|
+
external: externalResults.length > 0 ? externalResults : undefined,
|
|
1364
|
+
fixResult: fixResult
|
|
1365
|
+
? { fixedCount: fixResult.fixedCount, filesModified: fixResult.filesModified }
|
|
1366
|
+
: null,
|
|
1367
|
+
}
|
|
1368
|
+
);
|
|
1369
|
+
writeFileSync(tmpPath, reportBody, "utf-8");
|
|
1370
|
+
return tmpPath;
|
|
1371
|
+
})();
|
|
1305
1372
|
const uploadResult = await uploadReport(targetPath, uploadConfig);
|
|
1306
1373
|
if (uploadResult.success) {
|
|
1307
1374
|
console.log(pc.cyan("📤 报告已上传"));
|
|
@@ -1412,7 +1479,12 @@ async function runAllModules(options, cacheInstance) {
|
|
|
1412
1479
|
if (suggestions.length > 0) {
|
|
1413
1480
|
console.log(pc.cyan(` ✅ 生成 ${suggestions.length} 个 AI 修复建议`));
|
|
1414
1481
|
for (const s of suggestions) {
|
|
1415
|
-
const confidenceIcon =
|
|
1482
|
+
const confidenceIcon =
|
|
1483
|
+
s.confidence === "high"
|
|
1484
|
+
? pc.green("●")
|
|
1485
|
+
: s.confidence === "medium"
|
|
1486
|
+
? pc.yellow("●")
|
|
1487
|
+
: pc.red("●");
|
|
1416
1488
|
console.log(pc.cyan(`\n 📄 ${s.issue.file}:${s.issue.line}`));
|
|
1417
1489
|
console.log(pc.yellow(` [${s.issue.ruleId}] ${s.issue.title}`));
|
|
1418
1490
|
console.log(pc.gray(` AI 置信度: ${confidenceIcon} ${s.confidence}`));
|
|
@@ -1470,18 +1542,18 @@ async function runAllModules(options, cacheInstance) {
|
|
|
1470
1542
|
console.log(pc.gray(` ${icon} ${nr.channel}: ${nr.success ? "已发送" : nr.error}`));
|
|
1471
1543
|
}
|
|
1472
1544
|
} else {
|
|
1473
|
-
console.log(
|
|
1545
|
+
console.log(
|
|
1546
|
+
pc.yellow(
|
|
1547
|
+
"⚠️ 未配置通知渠道。请设置 FG_NOTIFY_FEISHU / FG_NOTIFY_DINGTALK / FG_NOTIFY_WECOM / FG_NOTIFY_SLACK 环境变量"
|
|
1548
|
+
)
|
|
1549
|
+
);
|
|
1474
1550
|
}
|
|
1475
1551
|
}
|
|
1476
1552
|
|
|
1477
1553
|
// v3.5.0: 生成合规报告
|
|
1478
1554
|
if (options.compliance) {
|
|
1479
1555
|
const moduleResults = MODULES.map((m) => allResults[m]).filter(Boolean);
|
|
1480
|
-
const report = generateComplianceReport(
|
|
1481
|
-
moduleResults,
|
|
1482
|
-
options.projectDir,
|
|
1483
|
-
options.strategy
|
|
1484
|
-
);
|
|
1556
|
+
const report = generateComplianceReport(moduleResults, options.projectDir, options.strategy);
|
|
1485
1557
|
const markdown = complianceReportToMarkdown(report);
|
|
1486
1558
|
saveComplianceReport(report, options.compliance);
|
|
1487
1559
|
if (!options.json) {
|
|
@@ -1732,7 +1804,9 @@ async function runSingleModule(options, cacheInstance) {
|
|
|
1732
1804
|
issues.warning = issues.warning.filter((i) => newKeySet.has(`${i.file}|${i.ruleId}|${i.line}`));
|
|
1733
1805
|
issues.suggestion = issues.suggestion.filter((i) => newKeySet.has(`${i.file}|${i.ruleId}|${i.line}`));
|
|
1734
1806
|
result.total = issues.critical.length + issues.warning.length + issues.suggestion.length;
|
|
1735
|
-
result.filesWithIssues = new Set(
|
|
1807
|
+
result.filesWithIssues = new Set(
|
|
1808
|
+
[...issues.critical, ...issues.warning, ...issues.suggestion].map((i) => i.file)
|
|
1809
|
+
).size;
|
|
1736
1810
|
allIssues = baselineResult.newIssues;
|
|
1737
1811
|
}
|
|
1738
1812
|
|
|
@@ -1780,13 +1854,9 @@ async function runSingleModule(options, cacheInstance) {
|
|
|
1780
1854
|
let pubConfig = detectPublisherConfig();
|
|
1781
1855
|
if (!pubConfig && options.commentProvider) {
|
|
1782
1856
|
const token =
|
|
1783
|
-
options.commentProvider === "github"
|
|
1784
|
-
? process.env.GITHUB_TOKEN
|
|
1785
|
-
: process.env.GITLAB_TOKEN;
|
|
1857
|
+
options.commentProvider === "github" ? process.env.GITHUB_TOKEN : process.env.GITLAB_TOKEN;
|
|
1786
1858
|
const repo =
|
|
1787
|
-
options.commentProvider === "github"
|
|
1788
|
-
? process.env.GITHUB_REPOSITORY
|
|
1789
|
-
: process.env.CI_PROJECT_ID;
|
|
1859
|
+
options.commentProvider === "github" ? process.env.GITHUB_REPOSITORY : process.env.CI_PROJECT_ID;
|
|
1790
1860
|
if (token && repo && options.prNumber) {
|
|
1791
1861
|
pubConfig = {
|
|
1792
1862
|
provider: options.commentProvider,
|
|
@@ -1802,9 +1872,7 @@ async function runSingleModule(options, cacheInstance) {
|
|
|
1802
1872
|
const publisher = createPublisher(pubConfig);
|
|
1803
1873
|
const pubResult = await publisher.publish(commentBody);
|
|
1804
1874
|
if (pubResult.success) {
|
|
1805
|
-
console.log(
|
|
1806
|
-
pc.cyan(`💬 PR/MR 评论已${pubResult.action === "updated" ? "更新" : "发布"}`)
|
|
1807
|
-
);
|
|
1875
|
+
console.log(pc.cyan(`💬 PR/MR 评论已${pubResult.action === "updated" ? "更新" : "发布"}`));
|
|
1808
1876
|
if (pubResult.commentUrl) {
|
|
1809
1877
|
console.log(pc.gray(` ${pubResult.commentUrl}`));
|
|
1810
1878
|
}
|
|
@@ -1903,7 +1971,12 @@ async function runSingleModule(options, cacheInstance) {
|
|
|
1903
1971
|
if (suggestions.length > 0) {
|
|
1904
1972
|
console.log(pc.cyan(` ✅ 生成 ${suggestions.length} 个 AI 修复建议`));
|
|
1905
1973
|
for (const s of suggestions) {
|
|
1906
|
-
const confidenceIcon =
|
|
1974
|
+
const confidenceIcon =
|
|
1975
|
+
s.confidence === "high"
|
|
1976
|
+
? pc.green("●")
|
|
1977
|
+
: s.confidence === "medium"
|
|
1978
|
+
? pc.yellow("●")
|
|
1979
|
+
: pc.red("●");
|
|
1907
1980
|
console.log(pc.cyan(`\n 📄 ${s.issue.file}:${s.issue.line}`));
|
|
1908
1981
|
console.log(pc.yellow(` [${s.issue.ruleId}] ${s.issue.title}`));
|
|
1909
1982
|
console.log(pc.gray(` AI 置信度: ${confidenceIcon} ${s.confidence}`));
|
|
@@ -1990,9 +2063,7 @@ async function applyBaselineToResults(allResults, externalResults, baselinePath,
|
|
|
1990
2063
|
return baselineResult;
|
|
1991
2064
|
}
|
|
1992
2065
|
|
|
1993
|
-
const newKeySet = new Set(
|
|
1994
|
-
baselineResult.newIssues.map((i) => `${i.file}|${i.ruleId}|${i.line}`)
|
|
1995
|
-
);
|
|
2066
|
+
const newKeySet = new Set(baselineResult.newIssues.map((i) => `${i.file}|${i.ruleId}|${i.line}`));
|
|
1996
2067
|
|
|
1997
2068
|
function isNew(issue) {
|
|
1998
2069
|
return newKeySet.has(`${issue.file}|${issue.ruleId}|${issue.line}`);
|
|
@@ -2006,12 +2077,9 @@ async function applyBaselineToResults(allResults, externalResults, baselinePath,
|
|
|
2006
2077
|
r.issues.warning = r.issues.warning.filter(isNew);
|
|
2007
2078
|
r.issues.suggestion = r.issues.suggestion.filter(isNew);
|
|
2008
2079
|
r.total = r.issues.critical.length + r.issues.warning.length + r.issues.suggestion.length;
|
|
2009
|
-
r.filesWithIssues =
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
...r.issues.warning,
|
|
2013
|
-
...r.issues.suggestion,
|
|
2014
|
-
].map((i) => i.file)).size;
|
|
2080
|
+
r.filesWithIssues = new Set(
|
|
2081
|
+
[...r.issues.critical, ...r.issues.warning, ...r.issues.suggestion].map((i) => i.file)
|
|
2082
|
+
).size;
|
|
2015
2083
|
}
|
|
2016
2084
|
|
|
2017
2085
|
// 过滤外部工具 issues
|
package/bin/fg-lsp.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
4
|
* Frontend Guardian LSP Server
|
|
4
5
|
* Usage: fg-lsp --stdio [--project-dir <dir>] [--config <file>]
|
|
@@ -6,9 +7,9 @@
|
|
|
6
7
|
* v3.3.0: Language Server Protocol 实现,为 IDE 提供实时诊断和快速修复。
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
import { runLSPServer } from "../dist/ide/lsp-server.js";
|
|
10
|
-
import { resolve } from "node:path";
|
|
11
10
|
import { existsSync } from "node:fs";
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
import { runLSPServer } from "../dist/ide/lsp-server.js";
|
|
12
13
|
|
|
13
14
|
const args = process.argv.slice(2);
|
|
14
15
|
|
|
@@ -26,7 +27,7 @@ for (let i = 0; i < args.length; i++) {
|
|
|
26
27
|
} else if (arg === "--severity" || arg === "-s") {
|
|
27
28
|
minSeverity = args[++i] ?? "suggestion";
|
|
28
29
|
} else if (arg === "--help" || arg === "-h") {
|
|
29
|
-
console.log(`Frontend Guardian LSP Server v3.
|
|
30
|
+
console.log(`Frontend Guardian LSP Server v3.9.0
|
|
30
31
|
|
|
31
32
|
Usage: fg-lsp [options]
|
|
32
33
|
|
package/bin/fg-server.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
4
|
* Frontend Guardian Dashboard Server CLI
|
|
4
5
|
* Usage: fg-server [options]
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
import { DashboardServer } from "../dist/index.js";
|
|
8
8
|
import pc from "picocolors";
|
|
9
|
+
import { DashboardServer } from "../dist/index.js";
|
|
9
10
|
|
|
10
11
|
function showHelp() {
|
|
11
12
|
console.log(`
|
|
12
|
-
Frontend Guardian Dashboard Server v3.
|
|
13
|
+
Frontend Guardian Dashboard Server v3.9.0
|
|
13
14
|
|
|
14
15
|
Usage:
|
|
15
16
|
fg-server [options]
|
|
@@ -61,7 +62,7 @@ async function main() {
|
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
console.log(pc.cyan("Frontend Guardian Dashboard Server"));
|
|
64
|
-
console.log(pc.gray(` Version: 3.
|
|
65
|
+
console.log(pc.gray(` Version: 3.9.0`));
|
|
65
66
|
console.log("");
|
|
66
67
|
|
|
67
68
|
const server = new DashboardServer({
|