frontend-guardian-core 3.6.0 → 3.7.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 CHANGED
@@ -62,6 +62,11 @@ import {
62
62
  saveComplianceReport,
63
63
  uploadToDashboardServer,
64
64
  detectDashboardConfig,
65
+ playwrightIntegration,
66
+ ProjectIndexer,
67
+ FileWatcher,
68
+ parseAllRoutes,
69
+ findRouteFiles,
65
70
  } from "../dist/index.js";
66
71
  import pc from "picocolors";
67
72
  import { runWatchMode } from "./watch-mode.js";
@@ -96,7 +101,7 @@ const MODULE_RULES = {
96
101
 
97
102
  function showHelp() {
98
103
  console.log(`
99
- Frontend Guardian Core v3.6.0
104
+ Frontend Guardian Core v3.7.0
100
105
 
101
106
  Usage:
102
107
  fg-core <project-dir> [options]
@@ -155,6 +160,10 @@ Options:
155
160
  --server <url> 扫描后上报到治理看板服务器
156
161
  --serve 扫描前启动本地看板服务(扫描完成后停止)
157
162
  --e2e-detect-gaps 检测 E2E 测试覆盖缺口(页面 + 接口)
163
+ --e2e-run 运行 Playwright E2E 测试并输出治理报告
164
+ --build-index 建立项目索引(预索引文件结构、符号、路由)
165
+ --watch-index 监听文件变更并自动同步索引
166
+ --index-status 查看项目索引状态
158
167
  --help, -h 显示帮助
159
168
 
160
169
  Examples:
@@ -230,6 +239,10 @@ async function main() {
230
239
  server: undefined,
231
240
  serve: false,
232
241
  e2eDetectGaps: false,
242
+ e2eRun: false,
243
+ buildIndex: false,
244
+ watchIndex: false,
245
+ indexStatus: false,
233
246
  };
234
247
 
235
248
  for (let i = 0; i < args.length; i++) {
@@ -400,6 +413,18 @@ async function main() {
400
413
  case "--e2e-detect-gaps":
401
414
  options.e2eDetectGaps = true;
402
415
  break;
416
+ case "--e2e-run":
417
+ options.e2eRun = true;
418
+ break;
419
+ case "--build-index":
420
+ options.buildIndex = true;
421
+ break;
422
+ case "--watch-index":
423
+ options.watchIndex = true;
424
+ break;
425
+ case "--index-status":
426
+ options.indexStatus = true;
427
+ break;
403
428
  case "--help":
404
429
  case "-h":
405
430
  showHelp();
@@ -418,6 +443,151 @@ async function main() {
418
443
  process.exit(gapResult.uncoveredPages.length + gapResult.uncoveredApis.length > 0 ? 1 : 0);
419
444
  }
420
445
 
446
+ // v3.6.1: 运行 Playwright E2E 测试
447
+ if (options.e2eRun) {
448
+ if (!playwrightIntegration.isAvailable(options.projectDir)) {
449
+ console.log(pc.yellow("⚠️ 未检测到 Playwright 配置(playwright.config.ts/js)或 playwright 包未安装"));
450
+ console.log(pc.gray(" 请先安装 Playwright: npm install -D @playwright/test"));
451
+ console.log(pc.gray(" 或初始化配置: npx playwright init"));
452
+ process.exit(1);
453
+ }
454
+
455
+ console.log(pc.cyan("🎭 正在运行 Playwright E2E 测试..."));
456
+ console.log(pc.gray(" 这可能需要几分钟(取决于测试数量和浏览器启动时间)"));
457
+ console.log("");
458
+
459
+ const start = Date.now();
460
+ const issues = playwrightIntegration.run(options.projectDir);
461
+ const duration = Date.now() - start;
462
+
463
+ if (options.json) {
464
+ console.log(JSON.stringify({
465
+ tool: "Playwright",
466
+ total: issues.length,
467
+ duration,
468
+ issues,
469
+ }, null, 2));
470
+ } else {
471
+ console.log(pc.cyan(`📊 Playwright 测试结果`));
472
+ console.log(pc.gray(` 耗时: ${duration}ms`));
473
+ if (issues.length === 0) {
474
+ console.log(pc.green(` ✅ 所有测试通过`));
475
+ } else {
476
+ console.log(pc.red(` ❌ ${issues.length} 个测试失败`));
477
+ for (const issue of issues) {
478
+ const severityColor = issue.severity === "critical" ? pc.red : pc.yellow;
479
+ console.log(severityColor(` [${issue.severity.toUpperCase()}] ${issue.title}`));
480
+ console.log(pc.gray(` ${issue.file}:${issue.line}`));
481
+ if (issue.description) {
482
+ const descLines = issue.description.split("\n").slice(0, 3);
483
+ for (const dl of descLines) {
484
+ console.log(pc.gray(` ${dl}`));
485
+ }
486
+ }
487
+ }
488
+ }
489
+ }
490
+
491
+ process.exit(issues.length > 0 ? 1 : 0);
492
+ }
493
+
494
+ // v3.7.0: 索引状态查看
495
+ if (options.indexStatus) {
496
+ const indexer = new ProjectIndexer(options.projectDir);
497
+ const stats = indexer.getStats();
498
+ const valid = indexer.isValid();
499
+
500
+ console.log(pc.cyan("📦 项目索引状态"));
501
+ console.log(pc.gray(` 索引有效: ${valid ? pc.green("是") : pc.yellow("否(请运行 --build-index)")}`));
502
+ console.log(pc.gray(` 索引文件数: ${stats.files}`));
503
+ console.log(pc.gray(` 路由数: ${stats.routes}`));
504
+ console.log(pc.gray(` 符号数: ${stats.symbols}`));
505
+
506
+ if (valid && stats.routes > 0) {
507
+ const routes = indexer.getRoutes();
508
+ console.log(pc.cyan("\n🗺️ 检测到的路由:"));
509
+ for (const route of routes.slice(0, 20)) {
510
+ console.log(pc.gray(` ${route.path} → ${route.file} (${route.framework})`));
511
+ }
512
+ if (routes.length > 20) {
513
+ console.log(pc.gray(` ... 还有 ${routes.length - 20} 条路由`));
514
+ }
515
+ }
516
+
517
+ process.exit(0);
518
+ }
519
+
520
+ // v3.7.0: 建立项目索引
521
+ if (options.buildIndex) {
522
+ console.log(pc.cyan("📦 正在建立项目索引..."));
523
+ const indexer = new ProjectIndexer(options.projectDir);
524
+
525
+ const { globbySync } = require("globby");
526
+ const patterns = ["**/*.{js,ts,jsx,tsx,vue}"];
527
+ const exclude = [
528
+ "**/node_modules/**",
529
+ "**/dist/**",
530
+ "**/build/**",
531
+ "**/.git/**",
532
+ "**/coverage/**",
533
+ ];
534
+ const files = globbySync(patterns, {
535
+ cwd: options.projectDir,
536
+ ignore: exclude,
537
+ absolute: true,
538
+ });
539
+
540
+ console.log(pc.gray(` 发现 ${files.length} 个源文件`));
541
+ await indexer.buildIndex(files);
542
+
543
+ const stats = indexer.getStats();
544
+ console.log(pc.green(` ✅ 索引建立完成`));
545
+ console.log(pc.gray(` 文件: ${stats.files} | 路由: ${stats.routes} | 符号: ${stats.symbols}`));
546
+
547
+ process.exit(0);
548
+ }
549
+
550
+ // v3.7.0: 监听文件变更并自动同步索引
551
+ if (options.watchIndex) {
552
+ console.log(pc.cyan("👁️ 启动文件监听(索引自动同步)..."));
553
+ console.log(pc.gray(" 按 Ctrl+C 停止"));
554
+
555
+ const watcher = new FileWatcher({
556
+ projectDir: options.projectDir,
557
+ onChange: (changed, deleted) => {
558
+ if (changed.length > 0) {
559
+ console.log(pc.cyan(`\n📝 变更: ${changed.map((f) => relative(options.projectDir, f)).join(", ")}`));
560
+ }
561
+ if (deleted.length > 0) {
562
+ console.log(pc.yellow(`\n🗑️ 删除: ${deleted.map((f) => relative(options.projectDir, f)).join(", ")}`));
563
+ }
564
+ },
565
+ onIndexUpdate: (stats) => {
566
+ console.log(pc.gray(` 索引已更新 | 文件: ${stats.files} | 路由: ${stats.routes} | 符号: ${stats.symbols}`));
567
+ },
568
+ onError: (err) => {
569
+ console.error(pc.red(` 监听错误: ${err.message}`));
570
+ },
571
+ });
572
+
573
+ await watcher.start();
574
+
575
+ // 保持进程运行
576
+ process.on("SIGINT", () => {
577
+ console.log(pc.gray("\n\n👋 停止监听..."));
578
+ watcher.stop();
579
+ process.exit(0);
580
+ });
581
+
582
+ process.on("SIGTERM", () => {
583
+ watcher.stop();
584
+ process.exit(0);
585
+ });
586
+
587
+ // 阻塞主线程
588
+ await new Promise(() => {});
589
+ }
590
+
421
591
  // Phase 6: 特殊命令处理
422
592
  if (options.installHooks) {
423
593
  const hookType = ["pre-commit", "pre-push", "commit-msg", "both", "all"].includes(options.installHooksType)
package/bin/fg-lsp.js CHANGED
@@ -26,7 +26,7 @@ for (let i = 0; i < args.length; i++) {
26
26
  } else if (arg === "--severity" || arg === "-s") {
27
27
  minSeverity = args[++i] ?? "suggestion";
28
28
  } else if (arg === "--help" || arg === "-h") {
29
- console.log(`Frontend Guardian LSP Server v3.6.0
29
+ console.log(`Frontend Guardian LSP Server v3.7.0
30
30
 
31
31
  Usage: fg-lsp [options]
32
32
 
package/bin/fg-server.js CHANGED
@@ -9,7 +9,7 @@ import pc from "picocolors";
9
9
 
10
10
  function showHelp() {
11
11
  console.log(`
12
- Frontend Guardian Dashboard Server v3.6.0
12
+ Frontend Guardian Dashboard Server v3.7.0
13
13
 
14
14
  Usage:
15
15
  fg-server [options]
@@ -0,0 +1,158 @@
1
+ /**
2
+ * ProjectIndexer — 项目级增量索引系统(v3.7.0)
3
+ *
4
+ * 设计目标:
5
+ * 1. 预索引项目文件结构(符号、import、路由),加速后续扫描
6
+ * 2. 基于文件哈希快速检测变更,只重新索引变更文件
7
+ * 3. 反向依赖图:快速定位"修改文件会影响谁"
8
+ * 4. 框架路由自动解析:React Router / Vue Router / Next.js / Nuxt / UniApp
9
+ * 5. 持久化到 `.frontend-guardian/index/index.json`,跨会话复用
10
+ *
11
+ * 与 SmartCache 的区别:
12
+ * - SmartCache 缓存扫描结果(Issue[]),按规则版本失效
13
+ * - ProjectIndexer 缓存文件结构(符号、import、路由),按文件内容哈希失效
14
+ */
15
+ /** 符号信息 */
16
+ export interface SymbolInfo {
17
+ name: string;
18
+ kind: "function" | "class" | "variable" | "hook" | "component";
19
+ line: number;
20
+ column: number;
21
+ }
22
+ /** 路由信息 */
23
+ export interface RouteInfo {
24
+ path: string;
25
+ component?: string;
26
+ file: string;
27
+ framework: string;
28
+ }
29
+ /** 单个文件的索引 */
30
+ export interface FileIndex {
31
+ /** 文件相对路径 */
32
+ path: string;
33
+ /** 文件内容 SHA-256 哈希(前 16 位) */
34
+ hash: string;
35
+ /** 最后索引时间 */
36
+ indexedAt: number;
37
+ /** 导入的文件列表(相对路径) */
38
+ imports: string[];
39
+ /** 导出的符号名列表 */
40
+ exports: string[];
41
+ /** 符号表 */
42
+ symbols: SymbolInfo[];
43
+ /** 路由信息(如果该文件定义了路由) */
44
+ route?: RouteInfo;
45
+ }
46
+ /** 项目级索引 */
47
+ export interface ProjectIndex {
48
+ /** 索引版本 */
49
+ version: string;
50
+ /** 项目目录 */
51
+ projectDir: string;
52
+ /** 创建时间 */
53
+ createdAt: number;
54
+ /** 更新时间 */
55
+ updatedAt: number;
56
+ /** 文件索引映射(相对路径 → FileIndex) */
57
+ files: Record<string, FileIndex>;
58
+ /** 反向依赖图:文件 → 导入它的文件列表(相对路径) */
59
+ reverseImports: Record<string, string[]>;
60
+ /** 路由表 */
61
+ routes: RouteInfo[];
62
+ }
63
+ export declare class ProjectIndexer {
64
+ private index;
65
+ private indexDir;
66
+ private indexFilePath;
67
+ private projectDir;
68
+ constructor(projectDir: string);
69
+ /** 索引是否有效(版本匹配且有数据) */
70
+ isValid(): boolean;
71
+ /** 获取索引统计 */
72
+ getStats(): {
73
+ files: number;
74
+ routes: number;
75
+ symbols: number;
76
+ };
77
+ /**
78
+ * 对比当前文件系统与索引,返回变更文件列表
79
+ * @param files 当前项目中的文件列表(绝对路径)
80
+ * @returns 变更/新增/删除的文件列表(绝对路径)
81
+ */
82
+ getChangedFiles(files: string[]): {
83
+ changed: string[];
84
+ deleted: string[];
85
+ };
86
+ /**
87
+ * 全量建立索引
88
+ * @param files 项目源文件列表(绝对路径)
89
+ */
90
+ buildIndex(files: string[]): Promise<void>;
91
+ /**
92
+ * 增量更新索引
93
+ * @param changedFiles 变更文件列表(绝对路径)
94
+ * @param deletedFiles 已删除文件列表(绝对路径)
95
+ */
96
+ updateIndex(changedFiles: string[], deletedFiles?: string[]): Promise<void>;
97
+ /**
98
+ * 获取导入某个文件的所有文件(上游依赖方)
99
+ * @param filePath 文件绝对路径
100
+ * @returns 导入该文件的文件列表(绝对路径)
101
+ */
102
+ getImporters(filePath: string): string[];
103
+ /**
104
+ * 获取文件的直接依赖(下游)
105
+ * @param filePath 文件绝对路径
106
+ * @returns 该文件导入的文件列表(绝对路径)
107
+ */
108
+ getDependencies(filePath: string): string[];
109
+ /**
110
+ * 获取文件的完整依赖链(递归上游)
111
+ * @param filePath 文件绝对路径
112
+ * @returns 所有导入该文件的文件(包括间接导入)
113
+ */
114
+ getTransitiveImporters(filePath: string): string[];
115
+ /** 获取项目路由表 */
116
+ getRoutes(): RouteInfo[];
117
+ /** 按路径查找路由 */
118
+ findRouteByPath(path: string): RouteInfo | undefined;
119
+ /** 按组件名查找路由 */
120
+ findRouteByComponent(component: string): RouteInfo | undefined;
121
+ /** 获取文件中的符号列表 */
122
+ getSymbols(filePath: string): SymbolInfo[];
123
+ /** 搜索项目中定义的符号 */
124
+ findSymbol(name: string): {
125
+ file: string;
126
+ symbol: SymbolInfo;
127
+ }[];
128
+ /** 获取文件的导出列表 */
129
+ getExports(filePath: string): string[];
130
+ /** 保存索引到磁盘 */
131
+ save(): void;
132
+ /** 清理索引(删除所有索引数据) */
133
+ clean(): void;
134
+ /** 索引单个文件 */
135
+ private _indexSingleFile;
136
+ /** 解析 import 路径为相对路径 */
137
+ private resolveImports;
138
+ /** 解析 import 源路径为绝对路径 */
139
+ private resolveImportPath;
140
+ /** 从 AST 提取符号信息 */
141
+ private extractSymbols;
142
+ /** 推断符号类型 */
143
+ private inferSymbolKind;
144
+ /** 检测文件是否包含路由定义 */
145
+ private detectRoute;
146
+ private parseNextJsRoute;
147
+ private parseNuxtRoute;
148
+ private parseUniAppRoutes;
149
+ private parseReactRouterConfig;
150
+ private parseVueRouterConfig;
151
+ /** 构建反向依赖图(文件 → 导入它的文件) */
152
+ private buildReverseImports;
153
+ /** 计算文件内容 SHA-256 哈希(取前 16 位) */
154
+ private computeHash;
155
+ /** 加载索引 */
156
+ private loadIndex;
157
+ }
158
+ //# sourceMappingURL=indexer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"indexer.d.ts","sourceRoot":"","sources":["../../src/engine/indexer.ts"],"names":[],"mappings":"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"}