frontend-guardian-core 2.8.0 → 3.0.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.
@@ -0,0 +1,295 @@
1
+ "use strict";
2
+ /**
3
+ * AI Fix Suggester — LLM 驱动的 Issue 修复建议
4
+ *
5
+ * v3.0.0 功能:
6
+ * 1. 为无自动修复的 Issue 生成 AI 修复建议
7
+ * 2. 为低置信度修复提供替代方案
8
+ * 3. 支持 OpenAI / Claude API
9
+ * 4. 建议缓存避免重复调用
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.AIFixSuggester = void 0;
13
+ exports.detectAIConfig = detectAIConfig;
14
+ exports.generateAIFixSuggestions = generateAIFixSuggestions;
15
+ const node_fs_1 = require("node:fs");
16
+ const node_path_1 = require("node:path");
17
+ const node_crypto_1 = require("node:crypto");
18
+ /** 从环境变量检测 AI 配置 */
19
+ function detectAIConfig() {
20
+ const provider = process.env.FG_AI_PROVIDER || "auto";
21
+ const apiKey = process.env.FG_AI_API_KEY || process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY || "";
22
+ if (!apiKey) {
23
+ return null;
24
+ }
25
+ // 自动推断提供商
26
+ let detectedProvider = provider;
27
+ if (provider === "auto") {
28
+ if (process.env.ANTHROPIC_API_KEY) {
29
+ detectedProvider = "claude";
30
+ }
31
+ else if (process.env.OPENAI_API_KEY || process.env.FG_AI_API_KEY) {
32
+ detectedProvider = "openai";
33
+ }
34
+ }
35
+ // 默认模型
36
+ const defaultModel = detectedProvider === "claude"
37
+ ? "claude-3-5-sonnet-20241022"
38
+ : "gpt-4o-mini";
39
+ return {
40
+ provider: detectedProvider,
41
+ apiKey,
42
+ model: process.env.FG_AI_MODEL || defaultModel,
43
+ baseUrl: process.env.FG_AI_BASE_URL,
44
+ maxTokens: parseInt(process.env.FG_AI_MAX_TOKENS || "2048", 10),
45
+ temperature: parseFloat(process.env.FG_AI_TEMPERATURE || "0.2"),
46
+ cacheEnabled: process.env.FG_AI_CACHE !== "false",
47
+ cacheDir: process.env.FG_AI_CACHE_DIR,
48
+ };
49
+ }
50
+ /**
51
+ * AI 修复建议器
52
+ */
53
+ class AIFixSuggester {
54
+ config;
55
+ cacheDir;
56
+ constructor(config) {
57
+ this.config = config;
58
+ if (config.cacheEnabled !== false) {
59
+ this.cacheDir = config.cacheDir || (0, node_path_1.join)(process.cwd(), ".frontend-guardian", "ai-cache");
60
+ }
61
+ }
62
+ /**
63
+ * 为单个 Issue 生成 AI 修复建议
64
+ */
65
+ async suggestFix(issue, projectDir) {
66
+ const cacheKey = this.getCacheKey(issue);
67
+ // 尝试从缓存读取
68
+ if (this.cacheDir) {
69
+ const cached = this.readCache(cacheKey);
70
+ if (cached) {
71
+ return { ...cached, issue };
72
+ }
73
+ }
74
+ // 读取源代码
75
+ const source = this.readSource(issue.file, projectDir, issue.line);
76
+ if (!source) {
77
+ return null;
78
+ }
79
+ // 构建 prompt
80
+ const prompt = this.buildPrompt(issue, source);
81
+ try {
82
+ const response = await this.callLLM(prompt);
83
+ const suggestion = this.parseResponse(response, issue);
84
+ if (suggestion && this.cacheDir) {
85
+ this.writeCache(cacheKey, suggestion);
86
+ }
87
+ return suggestion;
88
+ }
89
+ catch (err) {
90
+ // 静默失败,返回 null
91
+ return null;
92
+ }
93
+ }
94
+ /**
95
+ * 批量生成 AI 修复建议
96
+ */
97
+ async suggestFixes(issues, projectDir) {
98
+ const results = [];
99
+ for (const issue of issues) {
100
+ const suggestion = await this.suggestFix(issue, projectDir);
101
+ if (suggestion) {
102
+ results.push(suggestion);
103
+ }
104
+ }
105
+ return results;
106
+ }
107
+ /** 构建给 LLM 的 prompt */
108
+ buildPrompt(issue, source) {
109
+ return `You are an expert frontend code reviewer. Given the following code issue, provide a precise fix.
110
+
111
+ Issue Details:
112
+ - Rule: ${issue.ruleId}
113
+ - Title: ${issue.title}
114
+ - Description: ${issue.description}
115
+ - File: ${issue.file}
116
+ - Line: ${issue.line}, Column: ${issue.column}
117
+ - Severity: ${issue.severity}
118
+
119
+ Source Code (around the issue):
120
+ \`\`\`
121
+ ${source}
122
+ \`\`\`
123
+
124
+ Please provide:
125
+ 1. The fixed code snippet (only the corrected version, no explanations)
126
+ 2. A brief explanation of the fix
127
+ 3. Your confidence in this fix: high / medium / low
128
+
129
+ Format your response as:
130
+ FIX:
131
+ <corrected code snippet>
132
+
133
+ EXPLANATION:
134
+ <brief explanation>
135
+
136
+ CONFIDENCE:
137
+ <high|medium|low>`;
138
+ }
139
+ /** 调用 LLM API */
140
+ async callLLM(prompt) {
141
+ if (this.config.provider === "claude") {
142
+ return this.callClaude(prompt);
143
+ }
144
+ return this.callOpenAI(prompt);
145
+ }
146
+ /** 调用 Claude API */
147
+ async callClaude(prompt) {
148
+ const url = this.config.baseUrl || "https://api.anthropic.com/v1/messages";
149
+ const response = await fetch(url, {
150
+ method: "POST",
151
+ headers: {
152
+ "Content-Type": "application/json",
153
+ "x-api-key": this.config.apiKey,
154
+ "anthropic-version": "2023-06-01",
155
+ },
156
+ body: JSON.stringify({
157
+ model: this.config.model,
158
+ max_tokens: this.config.maxTokens || 2048,
159
+ temperature: this.config.temperature || 0.2,
160
+ messages: [{ role: "user", content: prompt }],
161
+ }),
162
+ });
163
+ if (!response.ok) {
164
+ throw new Error(`Claude API error: ${response.status} ${response.statusText}`);
165
+ }
166
+ const data = await response.json();
167
+ return data.content[0]?.text || "";
168
+ }
169
+ /** 调用 OpenAI API */
170
+ async callOpenAI(prompt) {
171
+ const url = this.config.baseUrl || "https://api.openai.com/v1/chat/completions";
172
+ const response = await fetch(url, {
173
+ method: "POST",
174
+ headers: {
175
+ "Content-Type": "application/json",
176
+ Authorization: `Bearer ${this.config.apiKey}`,
177
+ },
178
+ body: JSON.stringify({
179
+ model: this.config.model,
180
+ max_tokens: this.config.maxTokens || 2048,
181
+ temperature: this.config.temperature || 0.2,
182
+ messages: [{ role: "user", content: prompt }],
183
+ }),
184
+ });
185
+ if (!response.ok) {
186
+ throw new Error(`OpenAI API error: ${response.status} ${response.statusText}`);
187
+ }
188
+ const data = await response.json();
189
+ return data.choices[0]?.message?.content || "";
190
+ }
191
+ /** 解析 LLM 响应 */
192
+ parseResponse(response, issue) {
193
+ const fixMatch = response.match(/FIX:\s*\n?([\s\S]*?)(?=\n?EXPLANATION:|\n?CONFIDENCE:|$)/);
194
+ const explanationMatch = response.match(/EXPLANATION:\s*\n?([\s\S]*?)(?=\n?CONFIDENCE:|$)/);
195
+ const confidenceMatch = response.match(/CONFIDENCE:\s*(high|medium|low)/i);
196
+ if (!fixMatch) {
197
+ return null;
198
+ }
199
+ const fixedCode = fixMatch[1].trim();
200
+ const explanation = explanationMatch ? explanationMatch[1].trim() : undefined;
201
+ const confidenceStr = confidenceMatch ? confidenceMatch[1].toLowerCase() : "medium";
202
+ const confidence = ["high", "medium", "low"].includes(confidenceStr) ? confidenceStr : "medium";
203
+ // 构建 Fix 对象
204
+ // 简单实现:替换整行代码
205
+ const fix = {
206
+ text: fixedCode,
207
+ start: { line: issue.line, column: issue.column },
208
+ end: { line: issue.endLine || issue.line, column: issue.endColumn || issue.column + 1 },
209
+ confidence,
210
+ description: explanation,
211
+ };
212
+ return {
213
+ issue,
214
+ fix,
215
+ confidence,
216
+ explanation,
217
+ model: this.config.model,
218
+ };
219
+ }
220
+ /** 读取源代码文件 */
221
+ readSource(filePath, projectDir, issueLine) {
222
+ const fullPath = (0, node_path_1.resolve)(projectDir, filePath);
223
+ if (!(0, node_fs_1.existsSync)(fullPath)) {
224
+ return null;
225
+ }
226
+ try {
227
+ const content = (0, node_fs_1.readFileSync)(fullPath, "utf-8");
228
+ const lines = content.split("\n");
229
+ // 提取 issue 周围的代码(前后 5 行)
230
+ const startLine = Math.max(0, issueLine - 6);
231
+ const endLine = Math.min(lines.length, issueLine + 5);
232
+ const contextLines = lines.slice(startLine, endLine);
233
+ // 添加行号
234
+ return contextLines.map((line, idx) => `${startLine + idx + 1}: ${line}`).join("\n");
235
+ }
236
+ catch {
237
+ return null;
238
+ }
239
+ }
240
+ /** 生成缓存 key */
241
+ getCacheKey(issue) {
242
+ const hash = (0, node_crypto_1.createHash)("sha256")
243
+ .update(`${issue.file}|${issue.ruleId}|${issue.line}|${issue.column}|${issue.title}`)
244
+ .digest("hex")
245
+ .slice(0, 16);
246
+ return hash;
247
+ }
248
+ /** 读取缓存 */
249
+ readCache(key) {
250
+ if (!this.cacheDir)
251
+ return null;
252
+ try {
253
+ const cachePath = (0, node_path_1.join)(this.cacheDir, `${key}.json`);
254
+ if (!(0, node_fs_1.existsSync)(cachePath))
255
+ return null;
256
+ const raw = (0, node_fs_1.readFileSync)(cachePath, "utf-8");
257
+ return JSON.parse(raw);
258
+ }
259
+ catch {
260
+ return null;
261
+ }
262
+ }
263
+ /** 写入缓存 */
264
+ writeCache(key, suggestion) {
265
+ if (!this.cacheDir)
266
+ return;
267
+ try {
268
+ if (!(0, node_fs_1.existsSync)(this.cacheDir)) {
269
+ (0, node_fs_1.mkdirSync)(this.cacheDir, { recursive: true });
270
+ }
271
+ const cachePath = (0, node_path_1.join)(this.cacheDir, `${key}.json`);
272
+ (0, node_fs_1.writeFileSync)(cachePath, JSON.stringify(suggestion, null, 2), "utf-8");
273
+ }
274
+ catch {
275
+ // 忽略缓存写入失败
276
+ }
277
+ }
278
+ }
279
+ exports.AIFixSuggester = AIFixSuggester;
280
+ /**
281
+ * 为 Issue 列表生成 AI 修复建议的便捷函数
282
+ */
283
+ async function generateAIFixSuggestions(issues, projectDir, config) {
284
+ const detected = detectAIConfig();
285
+ if (!detected) {
286
+ return [];
287
+ }
288
+ const mergedConfig = {
289
+ ...detected,
290
+ ...config,
291
+ };
292
+ const suggester = new AIFixSuggester(mergedConfig);
293
+ return suggester.suggestFixes(issues, projectDir);
294
+ }
295
+ //# sourceMappingURL=ai-fix-suggester.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-fix-suggester.js","sourceRoot":"","sources":["../../src/utils/ai-fix-suggester.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Monorepo 工作区检测与解析
3
+ *
4
+ * v2.9.0 功能:
5
+ * 1. 自动检测 monorepo 工具(pnpm / lerna / nx / yarn / npm workspaces)
6
+ * 2. 解析 workspace 配置,获取所有子包路径
7
+ * 3. 读取子包 package.json 元数据
8
+ */
9
+ /** 支持的 monorepo 工具 */
10
+ export type MonorepoTool = "pnpm-workspace" | "lerna" | "nx" | "yarn-workspaces" | "npm-workspaces" | "rush" | "none";
11
+ /** Workspace 子包信息 */
12
+ export interface WorkspacePackage {
13
+ /** 子包名称 */
14
+ name: string;
15
+ /** 子包目录(相对项目根目录) */
16
+ path: string;
17
+ /** 子包绝对路径 */
18
+ absolutePath: string;
19
+ /** package.json 中的版本 */
20
+ version?: string;
21
+ /** 依赖列表 */
22
+ dependencies?: string[];
23
+ /** devDependencies 列表 */
24
+ devDependencies?: string[];
25
+ /** peerDependencies 列表 */
26
+ peerDependencies?: string[];
27
+ /** 是否是 private 包 */
28
+ private?: boolean;
29
+ }
30
+ /** Monorepo 检测结果 */
31
+ export interface MonorepoInfo {
32
+ /** 是否是 monorepo */
33
+ isMonorepo: boolean;
34
+ /** 检测到的工具 */
35
+ tool: MonorepoTool;
36
+ /** 工具配置文件路径 */
37
+ configPath?: string;
38
+ /** 项目根目录 */
39
+ rootDir: string;
40
+ /** 所有子包 */
41
+ packages: WorkspacePackage[];
42
+ /** workspace 中包含的 glob 模式 */
43
+ patterns?: string[];
44
+ }
45
+ /** 检测项目是否为 monorepo */
46
+ export declare function detectMonorepo(projectDir: string): MonorepoInfo;
47
+ /** 检测跨包依赖问题 */
48
+ export interface CrossPackageIssue {
49
+ type: "circular-dependency" | "missing-dependency" | "unused-dependency" | "version-mismatch";
50
+ package: string;
51
+ relatedPackage?: string;
52
+ message: string;
53
+ severity: "critical" | "warning" | "suggestion";
54
+ }
55
+ /** 分析跨包依赖 */
56
+ export declare function analyzeCrossPackageDeps(packages: WorkspacePackage[]): CrossPackageIssue[];
57
+ //# sourceMappingURL=monorepo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monorepo.d.ts","sourceRoot":"","sources":["../../src/utils/monorepo.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,sBAAsB;AACtB,MAAM,MAAM,YAAY,GAAG,gBAAgB,GAAG,OAAO,GAAG,IAAI,GAAG,iBAAiB,GAAG,gBAAgB,GAAG,MAAM,GAAG,MAAM,CAAC;AAEtH,qBAAqB;AACrB,MAAM,WAAW,gBAAgB;IAC7B,WAAW;IACX,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW;IACX,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,yBAAyB;IACzB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,0BAA0B;IAC1B,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,oBAAoB;AACpB,MAAM,WAAW,YAAY;IACzB,mBAAmB;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa;IACb,IAAI,EAAE,YAAY,CAAC;IACnB,eAAe;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW;IACX,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,uBAAuB;AACvB,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,CAqC/D;AA0MD,eAAe;AACf,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,qBAAqB,GAAG,oBAAoB,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;IAC9F,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,UAAU,GAAG,SAAS,GAAG,YAAY,CAAC;CACnD;AAED,aAAa;AACb,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,iBAAiB,EAAE,CAsEzF"}
@@ -0,0 +1,298 @@
1
+ "use strict";
2
+ /**
3
+ * Monorepo 工作区检测与解析
4
+ *
5
+ * v2.9.0 功能:
6
+ * 1. 自动检测 monorepo 工具(pnpm / lerna / nx / yarn / npm workspaces)
7
+ * 2. 解析 workspace 配置,获取所有子包路径
8
+ * 3. 读取子包 package.json 元数据
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.detectMonorepo = detectMonorepo;
12
+ exports.analyzeCrossPackageDeps = analyzeCrossPackageDeps;
13
+ const node_fs_1 = require("node:fs");
14
+ const node_path_1 = require("node:path");
15
+ const globby_1 = require("globby");
16
+ /** 检测项目是否为 monorepo */
17
+ function detectMonorepo(projectDir) {
18
+ // 按优先级检测各种工具
19
+ const pnpmWorkspace = (0, node_path_1.resolve)(projectDir, "pnpm-workspace.yaml");
20
+ if ((0, node_fs_1.existsSync)(pnpmWorkspace)) {
21
+ return parsePnpmWorkspace(projectDir, pnpmWorkspace);
22
+ }
23
+ const lernaJson = (0, node_path_1.resolve)(projectDir, "lerna.json");
24
+ if ((0, node_fs_1.existsSync)(lernaJson)) {
25
+ return parseLernaWorkspace(projectDir, lernaJson);
26
+ }
27
+ const nxJson = (0, node_path_1.resolve)(projectDir, "nx.json");
28
+ if ((0, node_fs_1.existsSync)(nxJson)) {
29
+ return parseNxWorkspace(projectDir, nxJson);
30
+ }
31
+ const rushJson = (0, node_path_1.resolve)(projectDir, "rush.json");
32
+ if ((0, node_fs_1.existsSync)(rushJson)) {
33
+ return parseRushWorkspace(projectDir, rushJson);
34
+ }
35
+ const packageJsonPath = (0, node_path_1.resolve)(projectDir, "package.json");
36
+ if ((0, node_fs_1.existsSync)(packageJsonPath)) {
37
+ const pkg = readPackageJson(packageJsonPath);
38
+ // npm / yarn workspaces
39
+ if (pkg.workspaces) {
40
+ return parseNpmWorkspaces(projectDir, pkg);
41
+ }
42
+ }
43
+ return {
44
+ isMonorepo: false,
45
+ tool: "none",
46
+ rootDir: projectDir,
47
+ packages: [],
48
+ };
49
+ }
50
+ /** 解析 pnpm-workspace.yaml */
51
+ function parsePnpmWorkspace(rootDir, configPath) {
52
+ const content = (0, node_fs_1.readFileSync)(configPath, "utf-8");
53
+ const patterns = [];
54
+ // 简单 YAML 解析:提取 packages 字段
55
+ const packagesMatch = content.match(/packages:\s*\n((?:\s*-\s*[^\n]+\n?)*)/);
56
+ if (packagesMatch) {
57
+ const lines = packagesMatch[1].split("\n");
58
+ for (const line of lines) {
59
+ const match = line.match(/^\s*-\s*(.+)$/);
60
+ if (match) {
61
+ patterns.push(match[1].trim());
62
+ }
63
+ }
64
+ }
65
+ // 也支持单行格式 packages: ['apps/*', 'packages/*']
66
+ const inlineMatch = content.match(/packages:\s*\[([^\]]+)\]/);
67
+ if (inlineMatch) {
68
+ const items = inlineMatch[1].split(",").map((s) => s.trim().replace(/['"]/g, ""));
69
+ patterns.push(...items);
70
+ }
71
+ const packages = resolveWorkspacePackages(rootDir, patterns);
72
+ return {
73
+ isMonorepo: true,
74
+ tool: "pnpm-workspace",
75
+ configPath,
76
+ rootDir,
77
+ packages,
78
+ patterns,
79
+ };
80
+ }
81
+ /** 解析 lerna.json */
82
+ function parseLernaWorkspace(rootDir, configPath) {
83
+ try {
84
+ const config = JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf-8"));
85
+ const patterns = config.packages || ["packages/*"];
86
+ const packages = resolveWorkspacePackages(rootDir, patterns);
87
+ return {
88
+ isMonorepo: true,
89
+ tool: "lerna",
90
+ configPath,
91
+ rootDir,
92
+ packages,
93
+ patterns,
94
+ };
95
+ }
96
+ catch {
97
+ return { isMonorepo: false, tool: "none", rootDir, packages: [] };
98
+ }
99
+ }
100
+ /** 解析 nx.json */
101
+ function parseNxWorkspace(rootDir, configPath) {
102
+ try {
103
+ // nx 使用 workspace.json 或 angular.json 定义项目
104
+ const workspaceJsonPath = (0, node_path_1.resolve)(rootDir, "workspace.json");
105
+ const angularJsonPath = (0, node_path_1.resolve)(rootDir, "angular.json");
106
+ let projects = {};
107
+ if ((0, node_fs_1.existsSync)(workspaceJsonPath)) {
108
+ const ws = JSON.parse((0, node_fs_1.readFileSync)(workspaceJsonPath, "utf-8"));
109
+ projects = ws.projects || {};
110
+ }
111
+ else if ((0, node_fs_1.existsSync)(angularJsonPath)) {
112
+ const ng = JSON.parse((0, node_fs_1.readFileSync)(angularJsonPath, "utf-8"));
113
+ projects = ng.projects || {};
114
+ }
115
+ const packages = [];
116
+ for (const [name, projectPath] of Object.entries(projects)) {
117
+ const absPath = (0, node_path_1.resolve)(rootDir, projectPath);
118
+ const pkgJsonPath = (0, node_path_1.resolve)(absPath, "package.json");
119
+ if ((0, node_fs_1.existsSync)(pkgJsonPath)) {
120
+ const pkg = readPackageJson(pkgJsonPath);
121
+ packages.push(createWorkspacePackage(pkg, projectPath, absPath));
122
+ }
123
+ else {
124
+ packages.push({
125
+ name,
126
+ path: projectPath,
127
+ absolutePath: absPath,
128
+ });
129
+ }
130
+ }
131
+ return {
132
+ isMonorepo: true,
133
+ tool: "nx",
134
+ configPath,
135
+ rootDir,
136
+ packages,
137
+ };
138
+ }
139
+ catch {
140
+ return { isMonorepo: false, tool: "none", rootDir, packages: [] };
141
+ }
142
+ }
143
+ /** 解析 rush.json */
144
+ function parseRushWorkspace(rootDir, configPath) {
145
+ try {
146
+ const config = JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf-8"));
147
+ const projects = config.projects || [];
148
+ const patterns = projects.map((p) => p.projectFolder);
149
+ const packages = resolveWorkspacePackages(rootDir, patterns);
150
+ return {
151
+ isMonorepo: true,
152
+ tool: "rush",
153
+ configPath,
154
+ rootDir,
155
+ packages,
156
+ patterns,
157
+ };
158
+ }
159
+ catch {
160
+ return { isMonorepo: false, tool: "none", rootDir, packages: [] };
161
+ }
162
+ }
163
+ /** 解析 npm / yarn workspaces */
164
+ function parseNpmWorkspaces(rootDir, pkg) {
165
+ const workspaces = pkg.workspaces;
166
+ let patterns = [];
167
+ if (Array.isArray(workspaces)) {
168
+ patterns = workspaces;
169
+ }
170
+ else if (typeof workspaces === "object" && workspaces !== null) {
171
+ patterns = workspaces.packages || [];
172
+ }
173
+ const packages = resolveWorkspacePackages(rootDir, patterns);
174
+ const tool = pkg.packageManager?.toString().startsWith("pnpm") ? "pnpm-workspace" : "yarn-workspaces";
175
+ return {
176
+ isMonorepo: true,
177
+ tool: packages.length > 0 ? tool : "npm-workspaces",
178
+ rootDir,
179
+ packages,
180
+ patterns,
181
+ };
182
+ }
183
+ /** 根据 glob 模式解析 workspace 包 */
184
+ function resolveWorkspacePackages(rootDir, patterns) {
185
+ const packages = [];
186
+ const seen = new Set();
187
+ for (const pattern of patterns) {
188
+ // 将 glob 模式转换为实际路径
189
+ const globPattern = pattern.endsWith("/") ? `${pattern}package.json` : `${pattern}/package.json`;
190
+ try {
191
+ const pkgPaths = (0, globby_1.globbySync)(globPattern, { cwd: rootDir, onlyFiles: true });
192
+ for (const pkgPath of pkgPaths) {
193
+ const absPath = (0, node_path_1.resolve)(rootDir, (0, node_path_1.dirname)(pkgPath));
194
+ const relPath = (0, node_path_1.dirname)(pkgPath);
195
+ if (seen.has(absPath))
196
+ continue;
197
+ seen.add(absPath);
198
+ const pkg = readPackageJson((0, node_path_1.resolve)(rootDir, pkgPath));
199
+ packages.push(createWorkspacePackage(pkg, relPath, absPath));
200
+ }
201
+ }
202
+ catch {
203
+ // 忽略解析失败的包
204
+ }
205
+ }
206
+ return packages;
207
+ }
208
+ /** 读取 package.json */
209
+ function readPackageJson(path) {
210
+ try {
211
+ return JSON.parse((0, node_fs_1.readFileSync)(path, "utf-8"));
212
+ }
213
+ catch {
214
+ return {};
215
+ }
216
+ }
217
+ /** 创建 WorkspacePackage */
218
+ function createWorkspacePackage(pkg, relPath, absPath) {
219
+ const deps = pkg.dependencies ? Object.keys(pkg.dependencies) : [];
220
+ const devDeps = pkg.devDependencies ? Object.keys(pkg.devDependencies) : [];
221
+ const peerDeps = pkg.peerDependencies ? Object.keys(pkg.peerDependencies) : [];
222
+ return {
223
+ name: pkg.name || relPath,
224
+ path: relPath,
225
+ absolutePath: absPath,
226
+ version: pkg.version,
227
+ dependencies: deps,
228
+ devDependencies: devDeps,
229
+ peerDependencies: peerDeps,
230
+ private: pkg.private,
231
+ };
232
+ }
233
+ /** 分析跨包依赖 */
234
+ function analyzeCrossPackageDeps(packages) {
235
+ const issues = [];
236
+ const pkgMap = new Map(packages.map((p) => [p.name, p]));
237
+ const pkgNames = new Set(packages.map((p) => p.name));
238
+ for (const pkg of packages) {
239
+ const allDeps = new Set([...(pkg.dependencies || []), ...(pkg.devDependencies || []), ...(pkg.peerDependencies || [])]);
240
+ for (const dep of allDeps) {
241
+ // 检测缺失的内部依赖
242
+ if (dep.startsWith("@") || dep.includes("/")) {
243
+ const scopeName = dep.split("/")[0];
244
+ // 如果依赖的是 workspace 内的包但名称不匹配
245
+ if (!pkgNames.has(dep) && pkgNames.has(scopeName)) {
246
+ issues.push({
247
+ type: "missing-dependency",
248
+ package: pkg.name,
249
+ relatedPackage: dep,
250
+ message: `包 "${pkg.name}" 依赖 "${dep}",但该包不在 workspace 中`,
251
+ severity: "warning",
252
+ });
253
+ }
254
+ }
255
+ }
256
+ }
257
+ // 检测循环依赖
258
+ const visited = new Set();
259
+ const pathStack = [];
260
+ function findCycle(pkgName) {
261
+ if (pathStack.includes(pkgName)) {
262
+ const cycleStart = pathStack.indexOf(pkgName);
263
+ return pathStack.slice(cycleStart);
264
+ }
265
+ if (visited.has(pkgName))
266
+ return null;
267
+ visited.add(pkgName);
268
+ pathStack.push(pkgName);
269
+ const pkg = pkgMap.get(pkgName);
270
+ if (pkg) {
271
+ const allDeps = new Set([...(pkg.dependencies || []), ...(pkg.devDependencies || [])]);
272
+ for (const dep of allDeps) {
273
+ if (pkgNames.has(dep)) {
274
+ const cycle = findCycle(dep);
275
+ if (cycle)
276
+ return cycle;
277
+ }
278
+ }
279
+ }
280
+ pathStack.pop();
281
+ return null;
282
+ }
283
+ for (const pkg of packages) {
284
+ if (!visited.has(pkg.name)) {
285
+ const cycle = findCycle(pkg.name);
286
+ if (cycle && cycle.length > 1) {
287
+ issues.push({
288
+ type: "circular-dependency",
289
+ package: cycle[0],
290
+ message: `循环依赖 detected: ${cycle.join(" → ")} → ${cycle[0]}`,
291
+ severity: "critical",
292
+ });
293
+ }
294
+ }
295
+ }
296
+ return issues;
297
+ }
298
+ //# sourceMappingURL=monorepo.js.map