UNPKG

code-insight-analyst

Version:
1,693 lines (1,677 loc) 140 kB
import { Command } from 'commander'; import * as path11 from 'path'; import path11__default from 'path'; import * as fs7 from 'fs'; import fs7__default from 'fs'; import * as os4 from 'os'; import os4__default from 'os'; import { promisify } from 'util'; import chalk6 from 'chalk'; import inquirer3 from 'inquirer'; import fs9 from 'fs-extra'; import * as globModule from 'glob'; import Table2 from 'cli-table3'; import { Worker } from 'worker_threads'; import crypto from 'crypto'; import ora from 'ora'; import { EventEmitter } from 'events'; import chokidar from 'chokidar'; import lodash from 'lodash'; import { exec, spawn } from 'child_process'; import { fileURLToPath } from 'url'; import { Listr } from 'listr2'; import { Project, ModuleResolutionKind, ModuleKind, ScriptTarget, SyntaxKind } from 'ts-morph'; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); var __commonJS = (cb, mod) => function __require2() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var mkdir2 = promisify(fs7.mkdir); var appendFile2 = promisify(fs7.appendFile); var ErrorHandler = class _ErrorHandler { /** * 创建错误处理器实例 * @param options 错误处理选项 */ constructor(options = {}) { this.options = { logToConsole: true, logToFile: true, logFilePath: path11.join( os4.homedir(), ".code-insight", "logs", "error.log" ), includeStack: true, minLevel: "warning" /* WARNING */, ...options }; this.ensureLogDirectory(); } /** * 获取单例实例 * @param options 错误处理选项 * @returns 错误处理器实例 */ static getInstance(options) { if (!_ErrorHandler.instance) { _ErrorHandler.instance = new _ErrorHandler(options); } return _ErrorHandler.instance; } /** * 确保日志目录存在 */ async ensureLogDirectory() { if (this.options.logToFile) { const logDir = path11.dirname(this.options.logFilePath || ""); try { await mkdir2(logDir, { recursive: true }); } catch (err) { console.error(`\u65E0\u6CD5\u521B\u5EFA\u65E5\u5FD7\u76EE\u5F55: ${logDir}`, err); } } } /** * 处理错误 * @param error 错误对象 * @param level 错误级别 * @param context 错误上下文信息 */ async handle(error, level = "error" /* ERROR */, context) { const errorLevelValues = { ["info" /* INFO */]: 0, ["warning" /* WARNING */]: 1, ["error" /* ERROR */]: 2, ["fatal" /* FATAL */]: 3 }; if (errorLevelValues[level] < errorLevelValues[this.options.minLevel || "warning" /* WARNING */]) { return; } const errorObj = typeof error === "string" ? new Error(error) : error; const timestamp = (/* @__PURE__ */ new Date()).toISOString(); const errorMessage = errorObj.message; const stack = this.options.includeStack ? errorObj.stack : void 0; const errorInfo = { timestamp, level, message: errorMessage, stack, context }; if (this.options.logToConsole) { this.logToConsole(errorInfo); } if (this.options.logToFile) { await this.logToFile(errorInfo); } if (level === "fatal" /* FATAL */) { console.error("\u9047\u5230\u81F4\u547D\u9519\u8BEF\uFF0C\u7A0B\u5E8F\u5C06\u9000\u51FA"); process.exit(1); } } /** * 控制台输出错误信息 * @param errorInfo 错误信息对象 */ logToConsole(errorInfo) { const { level, message, stack, context } = errorInfo; let logMethod; switch (level) { case "info" /* INFO */: logMethod = console.info; break; case "warning" /* WARNING */: logMethod = console.warn; break; case "error" /* ERROR */: case "fatal" /* FATAL */: logMethod = console.error; break; default: logMethod = console.log; } logMethod(`[${level.toUpperCase()}] ${message}`); if (context && Object.keys(context).length > 0) { logMethod("\u4E0A\u4E0B\u6587:", context); } if (stack && level !== "info" /* INFO */) { logMethod("\u5806\u6808:", stack); } } /** * 将错误信息写入日志文件 * @param errorInfo 错误信息对象 */ async logToFile(errorInfo) { try { const { timestamp, level, message, stack, context } = errorInfo; let logEntry = `[${timestamp}] [${level.toUpperCase()}] ${message} `; if (context && Object.keys(context).length > 0) { logEntry += `\u4E0A\u4E0B\u6587: ${JSON.stringify(context)} `; } if (stack) { logEntry += `\u5806\u6808: ${stack} `; } logEntry += "----------------------\n"; await appendFile2(this.options.logFilePath || "", logEntry); } catch (err) { console.error("\u65E0\u6CD5\u5199\u5165\u9519\u8BEF\u65E5\u5FD7\u6587\u4EF6:", err); } } /** * 记录信息级别日志 * @param message 日志消息 * @param context 上下文信息 */ async info(message, context) { await this.handle(message, "info" /* INFO */, context); } /** * 记录警告级别日志 * @param message 日志消息 * @param context 上下文信息 */ async warning(message, context) { await this.handle(message, "warning" /* WARNING */, context); } /** * 记录错误级别日志 * @param error 错误对象或消息 * @param context 上下文信息 */ async error(error, context) { await this.handle(error, "error" /* ERROR */, context); } /** * 记录致命级别日志 * @param error 错误对象或消息 * @param context 上下文信息 */ async fatal(error, context) { await this.handle(error, "fatal" /* FATAL */, context); } }; // src/cli/commands/analyze-command.ts var AnalyzeCommand = class { constructor() { this.errorHandler = ErrorHandler.getInstance(); } /** * 注册analyze命令 * @param program Commander程序实例 */ register(program) { program.command("analyze").description("\u5206\u6790\u4EE3\u7801\u5E93\u5E76\u751F\u6210\u6D1E\u5BDF\u62A5\u544A").option("-p, --path <path>", "\u8981\u5206\u6790\u7684\u4EE3\u7801\u8DEF\u5F84", process.cwd()).option( "-o, --output <output>", "\u8F93\u51FA\u62A5\u544A\u7684\u8DEF\u5F84", "./code-insight-report" ).option("--ignore <patterns...>", "\u8981\u5FFD\u7565\u7684\u6587\u4EF6\u6A21\u5F0F", [ "node_modules", "dist", ".git" ]).action(async (options) => { try { const targetPath = path11__default.resolve(options.path); const outputPath = path11__default.resolve(options.output); console.log(`\u5F00\u59CB\u5206\u6790\u4EE3\u7801: ${targetPath}`); console.log(`\u5206\u6790\u62A5\u544A\u5C06\u4FDD\u5B58\u5230: ${outputPath}`); console.log("\u5206\u6790\u5B8C\u6210\uFF01"); } catch (error) { this.errorHandler.error( error instanceof Error ? error : String(error) ); process.exit(1); } }); } }; var Logger = class { constructor() { this.verbose = false; } /** * 设置是否显示详细日志 */ setVerbose(verbose) { this.verbose = verbose; } /** * 记录普通信息 */ info(message, ...args) { console.info(chalk6.blue("\u2139"), message, ...args); } /** * 记录成功信息 */ success(message, ...args) { console.log(chalk6.green("\u2713"), message, ...args); } /** * 记录警告信息 */ warn(message, ...args) { console.warn(chalk6.yellow("\u26A0"), message, ...args); } /** * 记录错误信息 */ error(message, ...args) { console.error(chalk6.red("\u2717"), message, ...args); } /** * 记录调试信息(仅在详细模式下显示) */ debug(message, ...args) { if (this.verbose) { console.debug(chalk6.magenta("\u{1F50D}"), message, ...args); } } /** * 显示进度信息 */ progress(step, total, message) { const percentage = Math.round(step / total * 100); const progressBar = this.createProgressBar(percentage); console.log(`${progressBar} ${percentage}% | ${message}`); } /** * 创建进度条 */ createProgressBar(percentage) { const width = 20; const completed = Math.floor(width * percentage / 100); const remaining = width - completed; const filledBar = "\u2588".repeat(completed); const emptyBar = "\u2591".repeat(remaining); return chalk6.cyan(`[${filledBar}${emptyBar}]`); } }; // src/core/permission-manager.ts var PermissionManager = class { constructor() { this.configDir = path11__default.join(os4__default.homedir(), ".code-insight"); this.accessLogPath = path11__default.join(this.configDir, "access.log"); this.authorizedPaths = /* @__PURE__ */ new Set(); this.logger = new Logger(); this.init(); } /** * 初始化 */ init() { try { fs9.ensureDirSync(this.configDir); if (fs9.existsSync(this.accessLogPath)) { const content = fs9.readFileSync(this.accessLogPath, "utf-8"); content.split("\n").filter(Boolean).forEach((line) => { const [path18] = line.split(","); if (path18) { this.authorizedPaths.add(path18); } }); } } catch (error) { this.logger.error("\u521D\u59CB\u5316\u6743\u9650\u7BA1\u7406\u5668\u5931\u8D25:", error); } } /** * 请求访问权限 * 注意:这个方法本身不再实现交互逻辑,而是由专门的交互模块调用 * @param path 文件路径 * @returns 是否已获得授权 */ async requestAccess(path18) { if (this.isAuthorized(path18)) { return true; } return false; } /** * 检查是否已授权 * @param path 文件路径 */ isAuthorized(path18) { const resolvedPath = fs9.realpathSync(path18); if (this.authorizedPaths.has(resolvedPath)) { return true; } for (const authorizedPath of this.authorizedPaths) { if (resolvedPath.startsWith(authorizedPath)) { return true; } } return false; } /** * 记录访问 * @param path 文件路径 */ recordAccess(path18) { try { const resolvedPath = fs9.realpathSync(path18); this.authorizedPaths.add(resolvedPath); const timestamp = (/* @__PURE__ */ new Date()).toISOString(); const logEntry = `${resolvedPath},${timestamp} `; fs9.appendFileSync(this.accessLogPath, logEntry); } catch (error) { this.logger.error("\u8BB0\u5F55\u6743\u9650\u4FE1\u606F\u5931\u8D25:", error); } } /** * 清除授权记录 */ clearAuthorizations() { try { this.authorizedPaths.clear(); fs9.writeFileSync(this.accessLogPath, ""); } catch (error) { this.logger.error("\u6E05\u9664\u6743\u9650\u8BB0\u5F55\u5931\u8D25:", error); } } }; var ConfigManager = class { constructor() { this.configDir = path11__default.join(os4__default.homedir(), ".code-insight"); this.configPath = path11__default.join(this.configDir, "config.json"); this.logger = new Logger(); this.config = {}; this.init(); } /** * 初始化 */ init() { try { fs9.ensureDirSync(this.configDir); if (fs9.existsSync(this.configPath)) { this.config = fs9.readJsonSync(this.configPath); } else { this.config = { permissions: {}, lastUsedOptions: [], preferredMode: "single", analyzers: {}, plugins: {}, watchMode: { enabled: false, interval: 5e3, // 5秒 patterns: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"], exclude: [ "**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**" ], analyzers: [] } }; this.saveConfig(); } } catch (error) { this.logger.error("\u521D\u59CB\u5316\u914D\u7F6E\u7BA1\u7406\u5668\u5931\u8D25:", error); } } /** * 获取配置 */ getConfig() { return this.config; } /** * 获取特定配置项 */ get(key) { return this.config[key]; } /** * 设置特定配置项 */ set(key, value) { this.config[key] = value; this.saveConfig(); } /** * 设置上次使用的选项 */ setLastUsedOptions(options) { this.config.lastUsedOptions = options; this.saveConfig(); } /** * 设置首选模式 */ setPreferredMode(mode) { this.config.preferredMode = mode; this.saveConfig(); } /** * 保存配置 */ saveConfig() { try { fs9.writeJsonSync(this.configPath, this.config, { spaces: 2 }); } catch (error) { this.logger.error("\u4FDD\u5B58\u914D\u7F6E\u5931\u8D25:", error); } } /** * 重置配置 */ resetConfig() { this.config = { permissions: {}, lastUsedOptions: [], preferredMode: "single", analyzers: {}, plugins: {}, watchMode: { enabled: false, interval: 5e3, patterns: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"], exclude: [ "**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**" ], analyzers: [] } }; this.saveConfig(); } /** * 更新监测模式配置 * @param watchConfig 监测模式配置 */ updateWatchConfig(watchConfig = {}) { if (!this.config.watchMode) { this.config.watchMode = { enabled: false, interval: 5e3, patterns: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"], exclude: [ "**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**" ], analyzers: [] }; } this.config.watchMode = { enabled: typeof watchConfig.enabled === "boolean" ? watchConfig.enabled : this.config.watchMode.enabled, interval: typeof watchConfig.interval === "number" ? watchConfig.interval : this.config.watchMode.interval, patterns: watchConfig.patterns || this.config.watchMode.patterns, exclude: watchConfig.exclude || this.config.watchMode.exclude, analyzers: watchConfig.analyzers || this.config.watchMode.analyzers }; this.saveConfig(); } /** * 启用监测模式 */ enableWatchMode() { if (this.config.watchMode) { this.config.watchMode.enabled = true; this.saveConfig(); } } /** * 禁用监测模式 */ disableWatchMode() { if (this.config.watchMode) { this.config.watchMode.enabled = false; this.saveConfig(); } } /** * 获取插件配置 * @param pluginName 插件名称 */ getPluginConfig(pluginName) { const plugins = this.config.plugins || {}; return plugins[pluginName] || {}; } /** * 设置插件配置 * @param pluginName 插件名称 * @param config 插件配置 */ setPluginConfig(pluginName, config) { if (!this.config.plugins) { this.config.plugins = {}; } this.config.plugins[pluginName] = config; this.saveConfig(); } /** * 删除插件配置 * @param pluginName 插件名称 */ removePluginConfig(pluginName) { if (this.config.plugins && this.config.plugins[pluginName]) { delete this.config.plugins[pluginName]; this.saveConfig(); } } /** * 获取配置文件路径 */ getConfigPath() { return this.configPath; } /** * 获取配置目录 */ getConfigDir() { return this.configDir; } }; // src/cli/prompt/permission-prompt.ts var PermissionPrompt = class { /** * 构造函数 */ constructor() { this.permissionManager = new PermissionManager(); this.configManager = new ConfigManager(); } /** * 执行三步授权流程 * @param projectPath 项目路径 * @returns 是否获得授权 */ async requestPermission(projectPath) { try { if (this.permissionManager.isAuthorized(projectPath)) { console.log(chalk6.green(`\u2713 \u5DF2\u6709\u8BBF\u95EE\u6388\u6743: ${projectPath}`)); return true; } console.log(chalk6.blue(`\u9700\u8981\u8BBF\u95EE\u4EE5\u4E0B\u76EE\u5F55: ${projectPath}`)); if (!fs9.existsSync(projectPath)) { console.log(chalk6.red(`\u9519\u8BEF: \u76EE\u5F55\u4E0D\u5B58\u5728`)); return false; } const fileTypesInfo = this.collectFileTypeInfo(projectPath); const totalFiles = fileTypesInfo.reduce( (sum, info) => sum + info.count, 0 ); console.log(chalk6.blue("\u5C06\u5206\u6790\u4EE5\u4E0B\u7C7B\u578B\u7684\u6587\u4EF6:")); fileTypesInfo.forEach((info) => { console.log( chalk6.blue(` ${info.extension.toUpperCase()} \u6587\u4EF6: ${info.count} \u4E2A`) ); }); console.log(chalk6.blue(`\u5171\u8BA1 ${totalFiles} \u4E2A\u6587\u4EF6`)); console.log(chalk6.yellow("\u5206\u6790\u5C06\u5305\u62EC\u4EE5\u4E0B\u5185\u5BB9:")); console.log(chalk6.yellow(" - \u6587\u4EF6\u5185\u5BB9 (\u7528\u4E8E\u4EE3\u7801\u5206\u6790)")); console.log(chalk6.yellow(" - \u9879\u76EE\u7ED3\u6784 (\u7528\u4E8E\u4F9D\u8D56\u5206\u6790)")); console.log( chalk6.yellow(" - \u5F00\u53D1\u914D\u7F6E (package.json, tsconfig.json \u7B49)") ); const { permission } = await inquirer3.prompt([ { type: "confirm", name: "permission", message: "\u6388\u6743\u8BBF\u95EE?", default: false } ]); if (permission) { const { rememberPermission } = await inquirer3.prompt([ { type: "confirm", name: "rememberPermission", message: "\u8BB0\u4F4F\u6B64\u6388\u6743?", default: true } ]); this.permissionManager.recordAccess(projectPath); if (rememberPermission) { const permissions = this.configManager.get("permissions") || {}; permissions[projectPath] = true; this.configManager.set("permissions", permissions); } console.log(chalk6.green(`\u2713 \u5DF2\u83B7\u5F97\u6388\u6743`)); return true; } else { console.log(chalk6.red(`\u2717 \u6388\u6743\u88AB\u62D2\u7EDD`)); return false; } } catch (error) { console.error(chalk6.red(`\u8BF7\u6C42\u6388\u6743\u65F6\u51FA\u9519: ${error}`)); return false; } } /** * 收集项目中的文件类型信息 * @param projectPath 项目路径 * @returns 文件类型信息数组 */ collectFileTypeInfo(projectPath) { const extensions = [ "ts", "js", "tsx", "jsx", "json", "css", "scss", "less", "html", "vue", "md" ]; const fileTypesInfo = []; for (const ext of extensions) { const pattern = `**/*.${ext}`; const files = globModule.sync(pattern, { cwd: projectPath, ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"], absolute: true }); if (files.length > 0) { fileTypesInfo.push({ extension: ext, count: files.length }); } } return fileTypesInfo.sort((a, b) => b.count - a.count); } }; var madge = async (projectPath) => { console.info(`\u6A21\u62DF\u5206\u6790\u9879\u76EE\u4F9D\u8D56\u5173\u7CFB: ${projectPath}`); const mockDependencies = { "src/index.ts": [ "src/core/analyzers/dependency-analyzer.ts", "src/services/dependency-service.ts" ], "src/cli/index.ts": ["src/cli/app.ts"], "src/cli/app.ts": [ "src/cli/commands/dependency-command.ts", "src/cli/commands/watch-command.ts" ], "src/cli/commands/dependency-command.ts": [ "src/services/dependency-service.ts" ], "src/cli/commands/watch-command.ts": ["src/services/watch-service.ts"], "src/services/dependency-service.ts": [ "src/core/analyzers/dependency-analyzer.ts", "src/core/report/report-generator.ts" ], "src/services/watch-service.ts": [ "src/core/analyzers/dependency-analyzer.ts" ], "src/core/analyzers/dependency-analyzer.ts": [ "src/types/dependency-types.ts" ], "src/core/report/report-generator.ts": ["src/types/dependency-types.ts"], "src/types/dependency-types.ts": [] }; const mockCircular = [ [ "src/index.ts", "src/core/analyzers/dependency-analyzer.ts", "src/types/dependency-types.ts", "src/index.ts" ] ]; return { obj: () => mockDependencies, circular: () => mockCircular }; }; var DependencyAnalysisError = class extends Error { constructor(message, code) { super(`[${code}] ${message}`); this.code = code; this.name = "DependencyAnalysisError"; } }; var DependencyAnalyzer = class { /** * 创建依赖分析器实例 * @param basePath - 项目根路径 * @param fileExtensions - 要分析的文件扩展名 */ constructor(basePath, fileExtensions = ["js", "jsx", "ts", "tsx"]) { this.graph = null; this.basePath = path11__default.resolve(basePath); this.fileExtensions = fileExtensions; } /** * 构建项目的依赖关系图 * @returns 依赖图对象 */ async buildGraph() { if (!fs7__default.existsSync(this.basePath)) { throw new DependencyAnalysisError( `\u9879\u76EE\u8DEF\u5F84\u4E0D\u5B58\u5728: ${this.basePath}`, "ERR_PATH_NOT_FOUND" ); } try { const madgeResult = await madge(this.basePath); const dependencyMap = madgeResult.obj(); this.graph = { nodes: this.buildNodes(dependencyMap), edges: this.buildEdges(dependencyMap), circularDependencies: this.extractCircularDependencies(madgeResult) }; return this.graph; } catch (error) { throw new DependencyAnalysisError( `\u4F9D\u8D56\u5206\u6790\u5931\u8D25: ${error.message}`, "ERR_ANALYSIS_FAILED" ); } } /** * 执行完整的依赖分析 * @returns 完整的依赖分析结果 */ async analyze() { if (!this.graph) { await this.buildGraph(); } const graph = this.graph; const levels = await this.getDependencyLevels(); const counts = await this.getDependencyCounts(); const stats = this.computeStats(graph, levels, counts); return { graph, levels, counts, stats }; } /** * 查找项目中的循环依赖 * @returns 循环依赖列表 */ async getCircularDependencies() { if (!this.graph) { await this.buildGraph(); } return this.graph?.circularDependencies || []; } /** * 获取依赖层级分析 * @returns 每个文件的依赖层级信息 */ async getDependencyLevels() { if (!this.graph) { await this.buildGraph(); } const levels = /* @__PURE__ */ new Map(); const { nodes, edges } = this.graph; const calculateLevel = (nodeId, visited = /* @__PURE__ */ new Set()) => { if (visited.has(nodeId)) { return 0; } if (levels.has(nodeId)) { return levels.get(nodeId); } const dependencies = edges.filter((edge) => edge.target === nodeId).map((edge) => edge.source); if (dependencies.length === 0) { levels.set(nodeId, 0); return 0; } visited.add(nodeId); const maxLevel = Math.max( ...dependencies.map( (dep) => calculateLevel(dep, /* @__PURE__ */ new Set([...visited])) ) ); const level = maxLevel + 1; levels.set(nodeId, level); return level; }; nodes.forEach((node) => { calculateLevel(node.id); }); return levels; } /** * 计算每个文件的依赖数量 * @returns 每个文件的依赖数量统计 */ async getDependencyCounts() { if (!this.graph) { await this.buildGraph(); } const counts = /* @__PURE__ */ new Map(); const { nodes, edges } = this.graph; nodes.forEach((node) => { counts.set(node.id, { incoming: 0, outgoing: 0 }); }); edges.forEach((edge) => { const source = counts.get(edge.source); const target = counts.get(edge.target); if (source) { source.outgoing += 1; counts.set(edge.source, source); } if (target) { target.incoming += 1; counts.set(edge.target, target); } }); return counts; } /** * 计算各种统计信息 */ computeStats(graph, levels, counts) { const totalFiles = graph.nodes.length; const totalDependencies = graph.edges.length; const circularDependencyCount = graph.circularDependencies.length; let maxDependencyLevel = 0; levels.forEach((level) => { if (level > maxDependencyLevel) { maxDependencyLevel = level; } }); let mostDepended = { id: "", count: 0 }; let mostDependsOn = { id: "", count: 0 }; counts.forEach((count, id) => { if (count.incoming > mostDepended.count) { mostDepended = { id, count: count.incoming }; } if (count.outgoing > mostDependsOn.count) { mostDependsOn = { id, count: count.outgoing }; } }); return { totalFiles, totalDependencies, circularDependencyCount, maxDependencyLevel, mostDepended, mostDependsOn }; } /** * 将madge依赖对象转换为节点列表 */ buildNodes(dependencyMap) { return Object.keys(dependencyMap).map((filePath) => ({ id: filePath, path: path11__default.resolve(this.basePath, filePath), size: this.getFileSize(path11__default.resolve(this.basePath, filePath)) })); } /** * 将madge依赖对象转换为边列表 */ buildEdges(dependencyMap) { const edges = []; Object.entries(dependencyMap).forEach(([file, dependencies]) => { dependencies.forEach((dependency) => { edges.push({ source: dependency, target: file }); }); }); return edges; } /** * 从madge结果中提取循环依赖信息 */ extractCircularDependencies(madgeResult) { try { const circular = madgeResult.circular(); return circular.map((dependencyCycle, index) => ({ id: `cycle-${index}`, cycle: dependencyCycle, length: dependencyCycle.length })); } catch (error) { console.error("\u83B7\u53D6\u5FAA\u73AF\u4F9D\u8D56\u5931\u8D25:", error); return []; } } /** * 获取文件大小 */ getFileSize(filePath) { try { const stats = fs7__default.statSync(filePath); return stats.size; } catch { return 0; } } }; var ReportGenerator = class { /** * 生成报告 * @param data 分析结果数据 * @param options 报告选项 * @returns 报告文件路径或控制台输出的结果 */ async generate(data, options) { switch (options.type) { case "console" /* CONSOLE */: return this.generateConsoleReport(data, options); case "html" /* HTML */: return this.generateHtmlReport(data, options); case "json" /* JSON */: return this.generateJsonReport(data, options); case "markdown" /* MARKDOWN */: return this.generateMarkdownReport(data, options); default: throw new Error(`\u4E0D\u652F\u6301\u7684\u62A5\u544A\u7C7B\u578B: ${options.type}`); } } /** * 生成控制台报告 */ generateConsoleReport(data, options) { return this.formatDataForConsole(data, options); } /** * 生成HTML报告 */ generateHtmlReport(data, options) { const outputPath = options.outputPath || "./report.html"; const htmlContent = this.formatDataForHtml(data, options); fs7__default.writeFileSync(outputPath, htmlContent, "utf8"); return path11__default.resolve(outputPath); } /** * 生成JSON报告 */ generateJsonReport(data, options) { const outputPath = options.outputPath || "./report.json"; const jsonContent = JSON.stringify(data, this.jsonReplacer, 2); fs7__default.writeFileSync(outputPath, jsonContent, "utf8"); return path11__default.resolve(outputPath); } /** * 生成Markdown报告 */ generateMarkdownReport(data, options) { const outputPath = options.outputPath || "./report.md"; const mdContent = this.formatDataForMarkdown(data, options); fs7__default.writeFileSync(outputPath, mdContent, "utf8"); return path11__default.resolve(outputPath); } /** * 格式化数据用于控制台输出 */ formatDataForConsole(data, options) { if (!data) return "\u65E0\u6570\u636E"; const detailed = options.detailed || false; const indent = detailed ? 2 : 1; return JSON.stringify(data, null, indent); } /** * 格式化数据用于HTML输出 */ formatDataForHtml(data, options) { const title = options.projectName ? `${options.projectName} - \u5206\u6790\u62A5\u544A` : "\u4EE3\u7801\u5206\u6790\u62A5\u544A"; return ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>${title}</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 20px; color: #333; line-height: 1.5; } .container { max-width: 1200px; margin: 0 auto; } .header { margin-bottom: 30px; } .section { margin-bottom: 30px; } h1 { color: #2c3e50; } h2 { color: #3498db; border-bottom: 1px solid #eee; padding-bottom: 10px; } table { width: 100%; border-collapse: collapse; margin: 20px 0; } th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #ddd; } th { background-color: #f8f9fa; } tr:hover { background-color: #f8f9fa; } .summary { background-color: #f9f9f9; padding: 20px; border-radius: 5px; } .warning { color: #e74c3c; } .info { color: #3498db; } </style> </head> <body> <div class="container"> <div class="header"> <h1>${title}</h1> <p>\u751F\u6210\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toLocaleString()}</p> </div> <div class="content"> ${this.dataToHtml(data, options)} </div> </div> </body> </html>`; } /** * 格式化数据用于Markdown输出 */ formatDataForMarkdown(data, options) { const title = options.projectName ? `${options.projectName} - \u5206\u6790\u62A5\u544A` : "\u4EE3\u7801\u5206\u6790\u62A5\u544A"; return `# ${title} \u751F\u6210\u65F6\u95F4: ${(/* @__PURE__ */ new Date()).toLocaleString()} ${this.dataToMarkdown(data, options)} `; } /** * 将JSON数据转换为适合在控制台显示的表格格式 */ jsonToTable(data) { const table = new Table2({ head: ["\u5C5E\u6027", "\u503C"], style: { head: ["cyan"] } }); Object.entries(data).forEach(([key, value]) => { table.push([key, this.formatValue(value)]); }); return table.toString(); } /** * 将值格式化为适合显示的文本 */ formatValue(value) { if (value === null || value === void 0) { return "-"; } if (typeof value === "object") { if (Array.isArray(value)) { return value.length ? `[${value.length} \u9879]` : "[]"; } else { return "{...}"; } } return String(value); } /** * 将数据转换为HTML格式 */ // eslint-disable-next-line @typescript-eslint/no-unused-vars dataToHtml(data, _options) { if (!data) return "<p>\u65E0\u6570\u636E</p>"; if (typeof data === "object") { return `<pre>${JSON.stringify(data, null, 2)}</pre>`; } return `<p>${String(data)}</p>`; } /** * 将数据转换为Markdown格式 */ // eslint-disable-next-line @typescript-eslint/no-unused-vars dataToMarkdown(data, _options) { if (!data) return "\u65E0\u6570\u636E"; if (typeof data === "object") { return "```json\n" + JSON.stringify(data, null, 2) + "\n```"; } return String(data); } /** * JSON.stringify 的替换函数,用于处理Map等特殊对象 */ jsonReplacer(_key, value) { if (value instanceof Map) { return Object.fromEntries(value); } return value; } }; var DependencyReportGenerator = class extends ReportGenerator { /** * 格式化依赖分析结果用于控制台输出 * @override */ formatDataForConsole(data, options) { const dependencyData = data; if (!dependencyData) return "\u65E0\u4F9D\u8D56\u5206\u6790\u6570\u636E"; const { stats, graph } = dependencyData; let output = ""; output += ` ${chalk6.bold.blue("\u4F9D\u8D56\u5173\u7CFB\u5206\u6790\u62A5\u544A")} `; output += chalk6.yellow("\u{1F4CA} \u7EDF\u8BA1\u6982\u89C8:\n"); const statsTable = new Table2({ style: { head: [], border: [] } }); statsTable.push( { \u6587\u4EF6\u603B\u6570: stats.totalFiles }, { \u4F9D\u8D56\u603B\u6570: stats.totalDependencies }, { \u5FAA\u73AF\u4F9D\u8D56\u6570: stats.circularDependencyCount }, { \u6700\u5927\u4F9D\u8D56\u6DF1\u5EA6: stats.maxDependencyLevel }, { \u88AB\u4F9D\u8D56\u6700\u591A: `${stats.mostDepended.id} (${stats.mostDepended.count}\u6B21)` }, { \u4F9D\u8D56\u6700\u591A: `${stats.mostDependsOn.id} (${stats.mostDependsOn.count}\u6B21)` } ); output += statsTable.toString() + "\n\n"; if (graph.circularDependencies.length > 0) { output += chalk6.red("\u{1F504} \u68C0\u6D4B\u5230\u5FAA\u73AF\u4F9D\u8D56:\n"); const circularTable = new Table2({ head: ["\u5E8F\u53F7", "\u5FAA\u73AF\u8DEF\u5F84", "\u957F\u5EA6"], style: { head: ["red"] } }); graph.circularDependencies.slice(0, 5).forEach((dep, index) => { circularTable.push([index + 1, dep.cycle.join(" \u2192 "), dep.length]); }); if (graph.circularDependencies.length > 5) { output += circularTable.toString() + "\n"; output += chalk6.yellow( `...\u5171\u68C0\u6D4B\u5230 ${graph.circularDependencies.length} \u4E2A\u5FAA\u73AF\u4F9D\u8D56\u95EE\u9898 ` ); } else { output += circularTable.toString() + "\n\n"; } } else { output += chalk6.green("\u2713 \u672A\u68C0\u6D4B\u5230\u5FAA\u73AF\u4F9D\u8D56\n\n"); } if (options.detailed && dependencyData.counts) { output += chalk6.yellow("\u{1F4CB} \u4F9D\u8D56\u6700\u591A\u7684\u6587\u4EF6 (Top 10):\n"); const countsArray = Array.from(dependencyData.counts.entries()); const topOutgoing = countsArray.sort((a, b) => b[1].outgoing - a[1].outgoing).slice(0, 10); if (topOutgoing.length > 0) { const topTable = new Table2({ head: ["\u6587\u4EF6", "\u4F9D\u8D56\u6570\u91CF"], style: { head: ["cyan"] } }); topOutgoing.forEach(([file, counts]) => { topTable.push([file, counts.outgoing]); }); output += topTable.toString() + "\n\n"; } } return output; } /** * 格式化依赖数据用于HTML输出 * @override */ dataToHtml(data, options) { const dependencyData = data; if (!dependencyData) return "<p>\u65E0\u4F9D\u8D56\u5206\u6790\u6570\u636E</p>"; const { stats, graph } = dependencyData; let html = ""; html += ` <div class="section summary"> <h2>\u{1F4CA} \u7EDF\u8BA1\u6982\u89C8</h2> <table> <tr><td>\u6587\u4EF6\u603B\u6570</td><td>${stats.totalFiles}</td></tr> <tr><td>\u4F9D\u8D56\u603B\u6570</td><td>${stats.totalDependencies}</td></tr> <tr><td>\u5FAA\u73AF\u4F9D\u8D56\u6570</td><td>${stats.circularDependencyCount}</td></tr> <tr><td>\u6700\u5927\u4F9D\u8D56\u6DF1\u5EA6</td><td>${stats.maxDependencyLevel}</td></tr> <tr><td>\u88AB\u4F9D\u8D56\u6700\u591A</td><td>${stats.mostDepended.id} (${stats.mostDepended.count}\u6B21)</td></tr> <tr><td>\u4F9D\u8D56\u6700\u591A</td><td>${stats.mostDependsOn.id} (${stats.mostDependsOn.count}\u6B21)</td></tr> </table> </div>`; if (graph.circularDependencies.length > 0) { html += ` <div class="section"> <h2 class="warning">\u{1F504} \u68C0\u6D4B\u5230\u5FAA\u73AF\u4F9D\u8D56</h2> <table> <thead> <tr> <th>\u5E8F\u53F7</th> <th>\u5FAA\u73AF\u8DEF\u5F84</th> <th>\u957F\u5EA6</th> </tr> </thead> <tbody>`; const displayCircular = graph.circularDependencies.slice(0, 20); displayCircular.forEach((dep, index) => { html += ` <tr> <td>${index + 1}</td> <td>${dep.cycle.join(" \u2192 ")}</td> <td>${dep.length}</td> </tr>`; }); html += ` </tbody> </table> ${graph.circularDependencies.length > 20 ? `<p class="info">...\u5171\u68C0\u6D4B\u5230 ${graph.circularDependencies.length} \u4E2A\u5FAA\u73AF\u4F9D\u8D56\u95EE\u9898</p>` : ""} </div>`; } else { html += ` <div class="section"> <h2 class="info">\u2713 \u672A\u68C0\u6D4B\u5230\u5FAA\u73AF\u4F9D\u8D56</h2> </div>`; } if (options.detailed && dependencyData.counts) { html += ` <div class="section"> <h2>\u{1F4CB} \u4F9D\u8D56\u8BE6\u60C5</h2>`; const countsArray = Array.from(dependencyData.counts.entries()); const topOutgoing = countsArray.sort((a, b) => b[1].outgoing - a[1].outgoing).slice(0, 20); html += ` <h3>\u4F9D\u8D56\u6700\u591A\u7684\u6587\u4EF6 (Top 20)</h3> <table> <thead> <tr> <th>\u6587\u4EF6</th> <th>\u4F9D\u8D56\u6570\u91CF</th> </tr> </thead> <tbody>`; topOutgoing.forEach(([file, counts]) => { html += ` <tr> <td>${file}</td> <td>${counts.outgoing}</td> </tr>`; }); html += ` </tbody> </table> </div>`; } return html; } /** * 格式化依赖数据用于Markdown输出 * @override */ dataToMarkdown(data, options) { const dependencyData = data; if (!dependencyData) return "\u65E0\u4F9D\u8D56\u5206\u6790\u6570\u636E"; const { stats, graph } = dependencyData; let md = ""; md += `## \u{1F4CA} \u7EDF\u8BA1\u6982\u89C8 | \u6307\u6807 | \u503C | |------|-----| | \u6587\u4EF6\u603B\u6570 | ${stats.totalFiles} | | \u4F9D\u8D56\u603B\u6570 | ${stats.totalDependencies} | | \u5FAA\u73AF\u4F9D\u8D56\u6570 | ${stats.circularDependencyCount} | | \u6700\u5927\u4F9D\u8D56\u6DF1\u5EA6 | ${stats.maxDependencyLevel} | | \u88AB\u4F9D\u8D56\u6700\u591A | ${stats.mostDepended.id} (${stats.mostDepended.count}\u6B21) | | \u4F9D\u8D56\u6700\u591A | ${stats.mostDependsOn.id} (${stats.mostDependsOn.count}\u6B21) | `; if (graph.circularDependencies.length > 0) { md += `## \u{1F504} \u68C0\u6D4B\u5230\u5FAA\u73AF\u4F9D\u8D56 | \u5E8F\u53F7 | \u5FAA\u73AF\u8DEF\u5F84 | \u957F\u5EA6 | |------|---------|------| `; const displayCircular = graph.circularDependencies.slice(0, 10); displayCircular.forEach((dep, index) => { md += `| ${index + 1} | ${dep.cycle.join(" \u2192 ")} | ${dep.length} | `; }); if (graph.circularDependencies.length > 10) { md += ` _...\u5171\u68C0\u6D4B\u5230 ${graph.circularDependencies.length} \u4E2A\u5FAA\u73AF\u4F9D\u8D56\u95EE\u9898_ `; } } else { md += `## \u2713 \u672A\u68C0\u6D4B\u5230\u5FAA\u73AF\u4F9D\u8D56 `; } if (options.detailed && dependencyData.counts) { md += `## \u{1F4CB} \u4F9D\u8D56\u8BE6\u60C5 `; const countsArray = Array.from(dependencyData.counts.entries()); const topOutgoing = countsArray.sort((a, b) => b[1].outgoing - a[1].outgoing).slice(0, 10); md += `### \u4F9D\u8D56\u6700\u591A\u7684\u6587\u4EF6 (Top 10) `; md += `| \u6587\u4EF6 | \u4F9D\u8D56\u6570\u91CF | |------|--------| `; topOutgoing.forEach(([file, counts]) => { md += `| ${file} | ${counts.outgoing} | `; }); md += "\n"; } return md; } }; var PerformanceOptimizer = class { /** * 构造函数 * @param config 分析配置 */ constructor(config) { this.config = config; this.cacheDir = path11__default.join(os4__default.homedir(), ".code-insight", "cache"); this.maxWorkers = config.performance?.maxWorkers || Math.max(1, os4__default.cpus().length - 1); this.ensureCacheDir(); } /** * 使用缓存运行函数 * @param key 缓存键 * @param fn 要运行的函数 * @param options 缓存选项 * @returns 函数执行结果或缓存的结果 */ async withCache(key, fn, options) { if (!this.config.performance?.useCache) { return fn(); } const opts = { ttl: this.config.performance?.cacheTTL || 86400, cacheDir: this.cacheDir, checkSourceChange: true, ...options }; const cacheFilePath = this.getCacheFilePath(key); try { if (fs7__default.existsSync(cacheFilePath)) { const cacheContent = fs7__default.readFileSync(cacheFilePath, "utf8"); const cache = JSON.parse(cacheContent); const now = Date.now(); const cacheAge = now - cache.timestamp; if (cacheAge < opts.ttl * 1e3) { console.info(`\u4F7F\u7528\u7F13\u5B58: ${key}`); return cache.data; } } } catch (error) { console.warn(`\u7F13\u5B58\u8BFB\u53D6\u5931\u8D25: ${error.message}`); } const result = await fn(); try { const cache = { timestamp: Date.now(), data: result }; fs7__default.writeFileSync(cacheFilePath, JSON.stringify(cache), "utf8"); console.info(`\u5DF2\u7F13\u5B58\u7ED3\u679C: ${key}`); } catch (error) { console.warn(`\u7F13\u5B58\u5199\u5165\u5931\u8D25: ${error.message}`); } return result; } /** * 并行运行多个任务 * @param tasks 任务列表 * @returns 任务执行结果 */ async runInParallel(tasks, workerScript, timeoutMs = 6e4) { if (!this.config.performance?.useParallel || tasks.length <= 1) { const result = {}; for (const task of tasks) { result[task.id] = await this.runSingleTask( task.data, workerScript ); } return result; } const maxConcurrent = Math.min(tasks.length, this.maxWorkers); const results = {}; const workQueue = [...tasks]; const runningWorkers = []; const processTask = async () => { if (workQueue.length === 0) return; const task = workQueue.shift(); console.info(`\u5F00\u59CB\u5904\u7406\u4EFB\u52A1: ${task.id}`); try { const resultPromise = this.runSingleTask(task.data, workerScript); const timeoutPromise = new Promise((_, reject) => { setTimeout( () => reject(new Error(`\u4EFB\u52A1\u8D85\u65F6: ${task.id}`)), timeoutMs ); }); results[task.id] = await Promise.race([resultPromise, timeoutPromise]); console.info(`\u4EFB\u52A1\u5B8C\u6210: ${task.id}`); } catch (error) { console.error(`\u4EFB\u52A1\u5931\u8D25 ${task.id}: ${error.message}`); results[task.id] = { error: error.message }; } if (workQueue.length > 0) { runningWorkers.push(processTask()); } }; for (let i = 0; i < maxConcurrent; i++) { if (workQueue.length > 0) { runningWorkers.push(processTask()); } } await Promise.all(runningWorkers); return results; } /** * 增量分析,只处理有变化的文件 * @param files 文件列表 * @param processFn 处理函数 * @returns 处理结果 */ async incrementalProcess(files, processFn) { const results = {}; for (const file of files) { const cacheKey = `incremental:${path11__default.basename(file)}`; results[file] = await this.withCache(cacheKey, () => processFn(file), { checkSourceChange: true, ttl: this.config.performance?.cacheTTL || 86400 }); } return results; } /** * 在工作线程中运行单个任务 */ async runSingleTask(data, workerScript) { return new Promise((resolve2, reject) => { const worker = new Worker(workerScript, { workerData: data }); worker.on("message", (result) => { resolve2(result); }); worker.on("error", (err) => { reject(err); }); worker.on("exit", (code) => { if (code !== 0) { reject(new Error(`\u5DE5\u4F5C\u7EBF\u7A0B\u9000\u51FA\uFF0C\u9000\u51FA\u7801: ${code}`)); } }); }); } /** * 获取缓存文件路径 */ getCacheFilePath(key) { const safeKey = key.replace(/[^a-z0-9]/gi, "_").toLowerCase(); return path11__default.join(this.cacheDir, `${safeKey}.json`); } /** * 确保缓存目录存在 */ ensureCacheDir() { if (!fs7__default.existsSync(this.cacheDir)) { fs7__default.mkdirSync(this.cacheDir, { recursive: true }); } } /** * 获取文件的哈希值 */ getFileHash(filePath) { try { const content = fs7__default.readFileSync(filePath); return crypto.createHash("md5").update(content).digest("hex"); } catch (error) { return ""; } } /** * 清理过期缓存 * @param maxAgeDays 最大缓存时间(天) */ cleanCache(maxAgeDays = 30) { if (!fs7__default.existsSync(this.cacheDir)) { return; } const files = fs7__default.readdirSync(this.cacheDir); const now = Date.now(); const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1e3; let cleanedCount = 0; for (const file of files) { const filePath = path11__default.join(this.cacheDir, file); try { const stats = fs7__default.statSync(filePath); const fileAge = now - stats.mtimeMs; if (fileAge > maxAgeMs) { fs7__default.unlinkSync(filePath); cleanedCount++; } } catch (error) { console.warn(`\u6E05\u7406\u7F13\u5B58\u5931\u8D25: ${error.message}`); } } console.info(`\u6E05\u7406\u4E86 ${cleanedCount} \u4E2A\u8FC7\u671F\u7F13\u5B58\u6587\u4EF6`); } async computeHash(data) { return new Promise((resolve2, reject) => { try { const dataHash = crypto.createHash("md5").update(data).digest("hex"); resolve2(dataHash); } catch (error) { reject(error); } }); } }; // src/services/dependency-service.ts var DependencyService = class { /** * 构造函数 * @param projectPath 项目路径 * @param config 分析配置 */ constructor(projectPath, config) { /** * 依赖分析器 */ this.analyzer = null; this.projectPath = path11__default.resolve(projectPath); this.config = config; this.optimizer = new PerformanceOptimizer(config); } /** * 运行依赖分析 * @returns 分析报告路径或控制台输出 */ async analyze() { console.info(`\u5F00\u59CB\u5206\u6790\u9879\u76EE: ${this.projectPath}`); console.info("\u6B63\u5728\u68C0\u7D22\u9879\u76EE\u6587\u4EF6..."); try { const files = await this.getProjectFiles(); console.info(`\u627E\u5230 ${files.length} \u4E2A\u6587\u4EF6\u9700\u8981\u5206\u6790`); this.analyzer = new DependencyAnalyzer( this.projectPath, this.config.includeExtensions ); console.info("\u6B63\u5728\u5206\u6790\u4F9D\u8D56\u5173\u7CFB..."); const analysisResult = await this.optimizer.withCache( `dependency-analysis:${this.projectPath}`, async () => { console.info("\u5904\u7406\u4F9D\u8D56\u5173\u7CFB\u4E2D..."); const result = await this.analyzer.analyze(); console.info("\u4F9D\u8D56\u5206\u6790\u8BA1\u7B97\u5B8C\u6210"); return result; }, { ttl: this.config.performance?.cacheTTL || 86400 } ); console.info("\u5206\u6790\u7ED3\u679C\u5904\u7406\u4E2D..."); console.info(`\u83B7\u53D6\u5230\u5206\u6790\u7ED3\u679C\u5BF9\u8C61: ${analysisResult ? "yes" : "no"}`); if (analysisResult) { console.info(`\u5206\u6790\u7ED3\u679C\u7C7B\u578B: ${typeof analysisResult}`); console.info(`\u5206\u6790\u7ED3\u679C\u5C5E\u6027: ${Object.keys(analysisResult).join(", ")}`); } console.info("\u6B63\u5728\u751F\u6210\u62A5\u544A..."); const reportGenerator = new DependencyReportGenerator(); const outputFormat = this.config.outputFormat || "console"; const reportType = this.mapOutputFormatToReportType(outputFormat); const detailed = true; const reportPathOrContent = await reportGenerator.generate( analysisResult, { type: reportType, outputPath: `${this.config.outputPath || "./code-insight-report"}/dependency-analysis.${outputFormat}`, projectName: this.config.projectName || path11__default.basename(this.projectPath), detailed } ); if (reportType === "console" /* CONSOLE */) { console.log(reportPathOrContent); console.info(` \u5206\u6790\u5B8C\u6210`); } else { console.info(`\u5206\u6790\u5B8C\u6210\uFF0C\u62A5\u544A\u5DF2\u751F\u6210: ${reportPathOrContent}`); } return reportPathOrContent; } catch (error) { console.error("\u5206\u6790\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF:"); console.error(error instanceof Error ? error.message : String(error)); if (error instanceof Error && error.stack) { console.error("\u9519\u8BEF\u5806\u6808:"); console.error(error.stack); } throw error; } } /** * 获取项目文件列表 * @returns 文件路径数组 */ async getProjectFiles() { const includePatterns = (this.config.includeExtensions || ["ts", "tsx", "js", "jsx"]).map((ext) => `**/*.${ext}`); const excludePatterns = (this.config.exclude || ["node_modules", "dist", "build", ".git"]).map((pattern) => `**/${pattern}/**`); console.info(`\u5305\u542B\u6587\u4EF6\u6A21\u5F0F: ${includePatterns.join(", ")}`); console.info(`\u639