sloth-d2c-mcp 1.0.4-beta89 → 1.0.4-beta91

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/cli/run.js CHANGED
@@ -113,6 +113,21 @@ if (args[0] === 'config') {
113
113
 
114
114
  console.log(`[sloth] 已在后台启动,PID: ${child.pid}`);
115
115
  process.exit(0);
116
+ } else if (args[0] === 'add') {
117
+ // 设置环境变量标识这是 add 命令(安装插件)
118
+ process.env.SLOTH_COMMAND = 'add';
119
+ // 传递插件名称参数
120
+ process.env.SLOTH_ADD_ARGS = JSON.stringify(args.slice(1));
121
+ } else if (args[0] === 'remove') {
122
+ // 设置环境变量标识这是 remove 命令(卸载插件)
123
+ process.env.SLOTH_COMMAND = 'remove';
124
+ // 传递插件名称参数
125
+ process.env.SLOTH_REMOVE_ARGS = JSON.stringify(args.slice(1));
126
+ } else if (args[0] === 'plugins') {
127
+ // 设置环境变量标识这是 plugins 命令(列出插件)
128
+ process.env.SLOTH_COMMAND = 'plugins';
129
+ // 传递子命令参数
130
+ process.env.SLOTH_PLUGINS_ARGS = JSON.stringify(args.slice(1));
116
131
  } else if (args[0] === 'stop') {
117
132
  // stop 命令:停止监听 3100/3101 端口的 sloth 进程
118
133
  const { execSync } = await import('node:child_process');
@@ -164,7 +164,20 @@ export function buildComponentMappingPrompt(componentMappings, componentContexts
164
164
  }
165
165
  });
166
166
  prompt +=
167
- '**重要**:在最终写入代码时,请根据设计稿需求合理传递 props 参数。请按照项目路径规范合理引入组件。如果组件有继承关系,请注意父类的 props 和方法也可以使用。\n';
167
+ '**重要**:在最终写入代码时,请根据设计稿需求合理传递组件参数。请按照项目路径规范合理引入组件。如果组件有继承关系,请注意父类的参数和方法也可以使用。\n';
168
+ }
169
+ // 构建组件映射的设计稿代码(用于 AI 提取组件参数)
170
+ const mappingsWithCode = componentMappings.filter((m) => m.code);
171
+ if (mappingsWithCode.length > 0) {
172
+ prompt += '\n\n## 组件映射设计稿代码\n\n';
173
+ prompt += '以下是已映射组件对应的设计稿绝对定位代码,请根据这些代码提取信息,在使用组件时传入相应参数:\n\n';
174
+ mappingsWithCode.forEach((mapping) => {
175
+ prompt += `### ${mapping.groupName}\n\n`;
176
+ prompt += '```html\n';
177
+ prompt += mapping.code;
178
+ prompt += '\n```\n\n';
179
+ });
180
+ prompt += '**重要**:请从上述 HTML 代码中提取信息,作为对应组件的初始化参数。\n';
168
181
  }
169
182
  // 构建标记组件提示词
170
183
  if (markedComponents.length > 0) {
@@ -40,21 +40,33 @@ export function buildNestingStructure(groupsData) {
40
40
  return { childrenMap, rootGroups, groupMap, allChildIndices };
41
41
  }
42
42
  /**
43
- * 处理组件映射(如果已映射则跳过采样)
43
+ * 处理组件映射(生成 HTML 代码供 AI 提取 props)
44
44
  * @param group 分组数据
45
+ * @param nodeList 节点列表
45
46
  * @param componentMappings 组件映射数组
47
+ * @param imageMap 图片映射
48
+ * @param convertConfig 转换配置
46
49
  * @returns 采样结果或 null
47
50
  */
48
- export function handleComponentMapping(group, componentMappings) {
51
+ export async function handleComponentMapping(group, nodeList, componentMappings, imageMap, convertConfig) {
49
52
  if (group.componentMapping) {
50
53
  const componentName = group.componentMapping.name || `Group${group.groupIndex + 1}`;
51
54
  group.name = componentName;
55
+ // 生成带绝对定位的 HTML 代码,供 AI 提取 props 信息
56
+ const resetNodeList = resetNodeListPosition(nodeList);
57
+ const code = await getCode({
58
+ d2cNodeList: resetNodeList,
59
+ config: convertConfig,
60
+ });
61
+ const replacedCode = replaceImageSrc(code, imageMap);
52
62
  componentMappings.push({
53
63
  groupIndex: group.groupIndex,
54
64
  groupName: componentName,
55
65
  component: group.componentMapping,
66
+ code: replacedCode,
56
67
  });
57
- return { skipped: true, groupIndex: group.groupIndex, componentName };
68
+ Logger.log(`组 ${group.groupIndex} 已映射组件 ${componentName},生成 HTML 代码长度: ${replacedCode.length}`);
69
+ return { skipped: true, groupIndex: group.groupIndex, componentName, code: replacedCode };
58
70
  }
59
71
  return null;
60
72
  }
@@ -138,7 +150,7 @@ export async function sampleSingleGroup(group, nodeList, config) {
138
150
  const { d2cNodeList, imageMap, convertConfig, frameworkPrompt, chunkPrompt, mcpServer, componentMappings, codeSnippets, componentContexts, isSupportSampling } = config;
139
151
  Logger.log(`开始采样组 ${group.groupIndex}, 元素数量: ${group.elements.length}, nodeList 长度: ${nodeList.length}`);
140
152
  // 检查组件映射
141
- const mappingResult = handleComponentMapping(group, componentMappings);
153
+ const mappingResult = await handleComponentMapping(group, nodeList, componentMappings, imageMap, convertConfig);
142
154
  if (mappingResult) {
143
155
  Logger.log(`组 ${group.groupIndex} 已映射组件,跳过采样: ${mappingResult.componentName}`);
144
156
  return mappingResult;
@@ -14,6 +14,8 @@ import { Logger } from './utils/logger.js';
14
14
  import { getAvailablePort, saveImageFile, replaceImageSrc, formatListRoots } from './utils/utils.js';
15
15
  import { processSampling, buildNestingStructure } from './core/sampling.js';
16
16
  import { buildComponentMappingPrompt, buildFullUpdatePromptForAI, buildPlaceholderPrompt } from './core/prompt-builder.js';
17
+ import { pluginManager } from './plugin/manager.js';
18
+ import { installPlugin, uninstallPlugin, listPlugins } from './plugin/loader.js';
17
19
  import { detectClientApiSupport, clearCapabilitiesCache } from './utils/client-capabilities.js';
18
20
  // @ts-ignore
19
21
  import * as flatted from 'flatted';
@@ -103,6 +105,9 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
103
105
  Logger.log('标准化根目录:', root);
104
106
  // 设置 fileManager 的工作目录根路径
105
107
  fileManager.setWorkspaceRoot(root);
108
+ // 设置插件管理器的工作区根目录并初始化
109
+ pluginManager.setWorkspaceRoot(root);
110
+ await pluginManager.initialize();
106
111
  }
107
112
  catch (error) {
108
113
  Logger.log('获取根目录时出错:', error);
@@ -194,6 +199,8 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
194
199
  console.log('d2cNodeList', d2cNodeList);
195
200
  console.log('imageMap', imageMap);
196
201
  console.log('absoluteHtml', absoluteHtml);
202
+ pluginManager.executeHook('onFetchImage', { imageMap });
203
+ console.log('pluginManager executeHook onFetchImage', imageMap);
197
204
  // 如果配置了figmaApiKey,则可以预览,传入absoluteHtml
198
205
  if (!hasLaunchWebview) {
199
206
  // await fileManager.saveFile(fileKey, nodeId, 'absolute.html', absoluteHtml)
@@ -288,7 +295,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
288
295
  const aggregationPrompt = savedPromptSetting?.aggregationOptimizePrompt || aggregationOptimizeCodePrompt;
289
296
  const finalPrompt = savedPromptSetting?.finalOptimizePrompt || finalOptimizeCodePrompt;
290
297
  const noSamplingPrompt = savedPromptSetting?.noSamplingAggregationPrompt || noSamplingAggregationPrompt;
291
- const codeSnippets = [];
298
+ let codeSnippets = [];
292
299
  let isSupportSampling = clientApiSupport.sampling;
293
300
  Logger.log(`客户端 API 支持检测: sampling=${isSupportSampling}, roots=${clientApiSupport.roots}`);
294
301
  // 收集已映射的组件信息,用于在最终写入时添加提示词
@@ -431,6 +438,14 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
431
438
  codeSnippets.unshift(placeholderPrompt + '\n' + replacedCode);
432
439
  }
433
440
  }
441
+ const onSamplingComplete = await pluginManager.executeHook('onSamplingComplete', {
442
+ codeSnippets,
443
+ imageMap
444
+ });
445
+ codeSnippets = onSamplingComplete?.codeSnippets || codeSnippets;
446
+ imageMap = onSamplingComplete?.imageMap || imageMap;
447
+ console.log('executeHook onSamplingComplete', imageMap);
448
+ console.log('executeHook onSamplingComplete', codeSnippets);
434
449
  // 保存图片文件
435
450
  saveImageFile({ imageMap, root });
436
451
  Logger.log('处理完成,生成的代码片段数量:', codeSnippets.length);
@@ -651,6 +666,9 @@ async function main() {
651
666
  console.log(' config 显示配置文件信息');
652
667
  console.log(' page 显示 interceptor-web 页面地址');
653
668
  console.log(' framework 添加或管理框架配置');
669
+ console.log(' add 安装 sloth 插件');
670
+ console.log(' remove 卸载 sloth 插件');
671
+ console.log(' plugins 列出已安装的插件');
654
672
  console.log(' clear 清理缓存文件');
655
673
  console.log(' version 显示版本信息');
656
674
  console.log('');
@@ -667,6 +685,9 @@ async function main() {
667
685
  console.log(' sloth page --list # 列出所有页面文件');
668
686
  console.log(' sloth framework react # 添加 react 框架');
669
687
  console.log(' sloth framework --list # 列出所有框架');
688
+ console.log(' sloth add sloth-plugin-cos # 安装插件');
689
+ console.log(' sloth remove sloth-plugin-cos # 卸载插件');
690
+ console.log(' sloth plugins # 列出已安装插件');
670
691
  console.log(' sloth clear # 清理缓存文件');
671
692
  console.log(' sloth version # 显示版本信息');
672
693
  console.log(' sloth --version # 显示版本信息');
@@ -1021,9 +1042,93 @@ async function main() {
1021
1042
  }
1022
1043
  process.exit(0);
1023
1044
  }
1045
+ // 检查是否是 add 命令(安装插件)
1046
+ if (process.env.SLOTH_COMMAND === 'add') {
1047
+ const addArgsStr = process.env.SLOTH_ADD_ARGS || '[]';
1048
+ const addArgs = JSON.parse(addArgsStr);
1049
+ // 处理帮助参数
1050
+ if (addArgs.includes('--help') || addArgs.includes('-h')) {
1051
+ console.log('使用方法: sloth add <插件包名>');
1052
+ console.log('');
1053
+ console.log('安装 sloth 插件');
1054
+ console.log('');
1055
+ console.log('示例:');
1056
+ console.log(' sloth add sloth-plugin-cos # 安装 COS 上传插件');
1057
+ console.log(' sloth add @my/sloth-plugin # 安装 scoped 插件');
1058
+ process.exit(0);
1059
+ }
1060
+ if (addArgs.length === 0) {
1061
+ console.log('错误: 请提供插件包名');
1062
+ console.log('使用 "sloth add --help" 查看帮助信息');
1063
+ process.exit(1);
1064
+ }
1065
+ const packageName = addArgs[0];
1066
+ const result = await installPlugin(packageName);
1067
+ process.exit(result.success ? 0 : 1);
1068
+ }
1069
+ // 检查是否是 remove 命令(卸载插件)
1070
+ if (process.env.SLOTH_COMMAND === 'remove') {
1071
+ const removeArgsStr = process.env.SLOTH_REMOVE_ARGS || '[]';
1072
+ const removeArgs = JSON.parse(removeArgsStr);
1073
+ // 处理帮助参数
1074
+ if (removeArgs.includes('--help') || removeArgs.includes('-h')) {
1075
+ console.log('使用方法: sloth remove <插件包名>');
1076
+ console.log('');
1077
+ console.log('卸载 sloth 插件');
1078
+ console.log('');
1079
+ console.log('示例:');
1080
+ console.log(' sloth remove sloth-plugin-cos # 卸载 COS 上传插件');
1081
+ process.exit(0);
1082
+ }
1083
+ if (removeArgs.length === 0) {
1084
+ console.log('错误: 请提供插件包名');
1085
+ console.log('使用 "sloth remove --help" 查看帮助信息');
1086
+ process.exit(1);
1087
+ }
1088
+ const packageName = removeArgs[0];
1089
+ const result = await uninstallPlugin(packageName);
1090
+ process.exit(result.success ? 0 : 1);
1091
+ }
1092
+ // 检查是否是 plugins 命令(列出插件)
1093
+ if (process.env.SLOTH_COMMAND === 'plugins') {
1094
+ const pluginsArgsStr = process.env.SLOTH_PLUGINS_ARGS || '[]';
1095
+ const pluginsArgs = JSON.parse(pluginsArgsStr);
1096
+ const isJsonOutput = pluginsArgs.includes('--json');
1097
+ // 处理帮助参数
1098
+ if (pluginsArgs.includes('--help') || pluginsArgs.includes('-h')) {
1099
+ console.log('使用方法: sloth plugins [选项]');
1100
+ console.log('');
1101
+ console.log('列出已安装的 sloth 插件');
1102
+ console.log('');
1103
+ console.log('选项:');
1104
+ console.log(' --help, -h 显示帮助信息');
1105
+ console.log(' --json 以 JSON 格式输出');
1106
+ process.exit(0);
1107
+ }
1108
+ const plugins = await listPlugins();
1109
+ if (isJsonOutput) {
1110
+ console.log(JSON.stringify({ plugins }, null, 2));
1111
+ }
1112
+ else {
1113
+ if (plugins.length === 0) {
1114
+ console.log('暂无已安装的插件');
1115
+ console.log('');
1116
+ console.log('使用 "sloth add <插件包名>" 安装插件');
1117
+ }
1118
+ else {
1119
+ console.log('已安装的插件:');
1120
+ plugins.forEach((plugin, index) => {
1121
+ console.log(` ${index + 1}. ${plugin}`);
1122
+ });
1123
+ }
1124
+ }
1125
+ process.exit(0);
1126
+ }
1024
1127
  try {
1025
1128
  // 先输出基本启动信息到控制台,确保即使 VSCode 日志失败也能看到启动过程
1026
1129
  console.log(`[${new Date().toISOString()}] Starting Figma transcoding interceptor MCP server...`);
1130
+ // 注意:插件管理器的初始化已移到 d2c_figma 工具中
1131
+ // 因为需要先获取工作区根目录才能加载工作区插件
1027
1132
  // Create config manager instance to pass to server
1028
1133
  await startListening(true);
1029
1134
  }
@@ -0,0 +1,4 @@
1
+ // 导出插件管理器
2
+ export { pluginManager } from './manager.js';
3
+ // 导出插件加载器函数
4
+ export { installPlugin, uninstallPlugin, listPlugins, listWorkspacePlugins, loadAllPlugins, loadWorkspacePlugins, loadPlugin } from './loader.js';
@@ -0,0 +1,320 @@
1
+ import { execSync } from 'child_process';
2
+ import { promises as fs } from 'fs';
3
+ import * as path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import envPaths from 'env-paths';
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ // 检测是否存在 pnpm
9
+ function hasPnpm() {
10
+ try {
11
+ execSync('pnpm --version', { stdio: 'ignore' });
12
+ return true;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ // 获取包管理器命令
19
+ function getPackageManager() {
20
+ return /* hasPnpm() ? 'pnpm' : */ 'npm';
21
+ }
22
+ // 使用 env-paths 获取配置目录(与 config-manager 保持一致)
23
+ const paths = envPaths('d2c-mcp');
24
+ // 获取全局 sloth-d2c-mcp 包的目录
25
+ function getGlobalPackageDir() {
26
+ // 在构建后的目录中: dist/build/plugin/loader.js
27
+ // 需要向上三级才能找到包根目录
28
+ return path.resolve(__dirname, '..', '..', '..');
29
+ }
30
+ // 获取全局插件配置文件路径(与 config.json 在同一目录)
31
+ function getGlobalPluginsConfigPath() {
32
+ return path.join(paths.config, 'plugins.json');
33
+ }
34
+ // 获取工作区插件目录路径
35
+ function getWorkspacePluginDir(workspaceRoot) {
36
+ return path.join(workspaceRoot, '.sloth', 'plugin');
37
+ }
38
+ // 获取工作区插件配置文件路径
39
+ function getWorkspacePluginsConfigPath(workspaceRoot) {
40
+ return path.join(getWorkspacePluginDir(workspaceRoot), 'plugins.json');
41
+ }
42
+ // 加载全局插件配置
43
+ async function loadGlobalPluginsConfig() {
44
+ const configPath = getGlobalPluginsConfigPath();
45
+ try {
46
+ const content = await fs.readFile(configPath, 'utf-8');
47
+ return JSON.parse(content);
48
+ }
49
+ catch {
50
+ return { plugins: [] };
51
+ }
52
+ }
53
+ // 保存全局插件配置
54
+ async function saveGlobalPluginsConfig(config) {
55
+ const configPath = getGlobalPluginsConfigPath();
56
+ // 确保目录存在
57
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
58
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
59
+ }
60
+ // 加载工作区插件配置
61
+ async function loadWorkspacePluginsConfig(workspaceRoot) {
62
+ const configPath = getWorkspacePluginsConfigPath(workspaceRoot);
63
+ try {
64
+ const content = await fs.readFile(configPath, 'utf-8');
65
+ return JSON.parse(content);
66
+ }
67
+ catch {
68
+ return { plugins: [] };
69
+ }
70
+ }
71
+ /**
72
+ * 解析插件配置项,统一返回 { name, config } 格式
73
+ */
74
+ function parsePluginConfigItem(item) {
75
+ if (typeof item === 'string') {
76
+ return { name: item };
77
+ }
78
+ return { name: item.name, config: item.config };
79
+ }
80
+ // 检查本地插件是否存在
81
+ async function checkLocalPlugin(workspaceRoot, pluginName) {
82
+ const pluginDir = getWorkspacePluginDir(workspaceRoot);
83
+ const localPluginPath = path.join(pluginDir, pluginName, 'index.cjs');
84
+ try {
85
+ await fs.access(localPluginPath);
86
+ return localPluginPath;
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ }
92
+ // 检查 npm 包是否已安装
93
+ async function isNpmPackageInstalled(packageName) {
94
+ try {
95
+ await import(packageName);
96
+ return true;
97
+ }
98
+ catch {
99
+ return false;
100
+ }
101
+ }
102
+ // 尝试安装 npm 包
103
+ async function tryInstallNpmPackage(packageName) {
104
+ const globalDir = getGlobalPackageDir();
105
+ const pm = getPackageManager();
106
+ try {
107
+ console.log(`[sloth] 尝试安装插件包: ${packageName}`);
108
+ execSync(`${pm} install ${packageName}`, {
109
+ cwd: globalDir,
110
+ stdio: 'pipe',
111
+ });
112
+ return true;
113
+ }
114
+ catch (error) {
115
+ console.error(`[sloth] 安装插件包 ${packageName} 失败:`, error);
116
+ return false;
117
+ }
118
+ }
119
+ /**
120
+ * 安装插件 npm 包到全局(sloth add 命令)
121
+ * 注意:这只是安装到全局,不会直接生效
122
+ * 需要在工作区的 .sloth/plugin/plugins.json 中声明才会生效
123
+ * @param packageName 插件包名
124
+ */
125
+ export async function installPlugin(packageName) {
126
+ const globalDir = getGlobalPackageDir();
127
+ console.log(`[sloth] 正在安装插件: ${packageName}`);
128
+ console.log(`[sloth] 安装目录: ${globalDir}`);
129
+ try {
130
+ const pm = getPackageManager();
131
+ // 在全局包目录下执行安装
132
+ execSync(`${pm} install ${packageName}`, {
133
+ cwd: globalDir,
134
+ stdio: 'inherit',
135
+ });
136
+ // 更新全局插件配置(记录已安装的插件)
137
+ const config = await loadGlobalPluginsConfig();
138
+ const existingNames = config.plugins.map(item => parsePluginConfigItem(item).name);
139
+ if (!existingNames.includes(packageName)) {
140
+ config.plugins.push(packageName);
141
+ await saveGlobalPluginsConfig(config);
142
+ }
143
+ console.log(`[sloth] 插件 ${packageName} 安装成功`);
144
+ console.log(`[sloth] 注意:需要在工作区的 .sloth/plugin/plugins.json 中声明才会生效`);
145
+ return {
146
+ success: true,
147
+ message: `插件 ${packageName} 安装成功。请在工作区的 .sloth/plugin/plugins.json 中添加该插件以启用。`
148
+ };
149
+ }
150
+ catch (error) {
151
+ const errorMessage = error instanceof Error ? error.message : String(error);
152
+ console.error(`[sloth] 插件安装失败: ${errorMessage}`);
153
+ return { success: false, message: `插件安装失败: ${errorMessage}` };
154
+ }
155
+ }
156
+ /**
157
+ * 卸载插件 npm 包
158
+ * @param packageName 插件包名
159
+ */
160
+ export async function uninstallPlugin(packageName) {
161
+ const globalDir = getGlobalPackageDir();
162
+ console.log(`[sloth] 正在卸载插件: ${packageName}`);
163
+ try {
164
+ const pm = getPackageManager();
165
+ // 在全局包目录下执行卸载
166
+ execSync(`${pm} uninstall ${packageName}`, {
167
+ cwd: globalDir,
168
+ stdio: 'inherit',
169
+ });
170
+ // 更新全局插件配置
171
+ const config = await loadGlobalPluginsConfig();
172
+ config.plugins = config.plugins.filter((p) => parsePluginConfigItem(p).name !== packageName);
173
+ await saveGlobalPluginsConfig(config);
174
+ console.log(`[sloth] 插件 ${packageName} 卸载成功`);
175
+ return { success: true, message: `插件 ${packageName} 卸载成功` };
176
+ }
177
+ catch (error) {
178
+ const errorMessage = error instanceof Error ? error.message : String(error);
179
+ console.error(`[sloth] 插件卸载失败: ${errorMessage}`);
180
+ return { success: false, message: `插件卸载失败: ${errorMessage}` };
181
+ }
182
+ }
183
+ /**
184
+ * 列出全局已安装的插件
185
+ */
186
+ export async function listPlugins() {
187
+ const config = await loadGlobalPluginsConfig();
188
+ return config.plugins.map(item => parsePluginConfigItem(item).name);
189
+ }
190
+ /**
191
+ * 列出工作区声明的插件
192
+ * @param workspaceRoot 工作区根目录
193
+ */
194
+ export async function listWorkspacePlugins(workspaceRoot) {
195
+ const config = await loadWorkspacePluginsConfig(workspaceRoot);
196
+ return config.plugins.map(item => parsePluginConfigItem(item));
197
+ }
198
+ /**
199
+ * 校验并加载工作区声明的所有插件
200
+ * 1. 先尝试查找 .sloth/plugin/ 文件夹下的本地插件
201
+ * 2. 再尝试通过 pnpm/npm 安装声明的 npm 包
202
+ * 3. 都失败则抛出错误
203
+ * @param workspaceRoot 工作区根目录
204
+ */
205
+ export async function loadWorkspacePlugins(workspaceRoot) {
206
+ const config = await loadWorkspacePluginsConfig(workspaceRoot);
207
+ const plugins = [];
208
+ const failedPlugins = [];
209
+ console.log(`[sloth] 开始加载工作区插件,工作区: ${workspaceRoot}`);
210
+ const pluginNames = config.plugins.map(item => parsePluginConfigItem(item).name);
211
+ console.log(`[sloth] 声明的插件: ${pluginNames.join(', ') || '无'}`);
212
+ for (const pluginItem of config.plugins) {
213
+ const { name: pluginName, config: pluginConfig } = parsePluginConfigItem(pluginItem);
214
+ let loaded = false;
215
+ console.log('pluginConfig', pluginConfig);
216
+ // 1. 先尝试查找本地插件
217
+ const localPluginPath = await checkLocalPlugin(workspaceRoot, pluginName);
218
+ if (localPluginPath) {
219
+ try {
220
+ console.log(`[sloth] 尝试加载本地插件: ${localPluginPath}`);
221
+ const pluginModule = await import(localPluginPath);
222
+ const plugin = {
223
+ name: pluginName,
224
+ ...(pluginModule.default || pluginModule),
225
+ _config: pluginConfig,
226
+ };
227
+ plugins.push(plugin);
228
+ console.log(`[sloth] 本地插件 ${pluginName} 加载成功`);
229
+ loaded = true;
230
+ }
231
+ catch (error) {
232
+ console.error(`[sloth] 本地插件 ${pluginName} 加载失败:`, error);
233
+ }
234
+ }
235
+ // 2. 如果本地插件不存在或加载失败,尝试加载 npm 包
236
+ if (!loaded) {
237
+ // 检查是否已安装
238
+ const isInstalled = await isNpmPackageInstalled(pluginName);
239
+ if (!isInstalled) {
240
+ // 尝试安装
241
+ console.log(`[sloth] 插件 ${pluginName} 未安装,尝试安装...`);
242
+ const installSuccess = await tryInstallNpmPackage(pluginName);
243
+ if (!installSuccess) {
244
+ failedPlugins.push(pluginName);
245
+ continue;
246
+ }
247
+ }
248
+ // 加载 npm 包
249
+ try {
250
+ const pluginModule = await import(pluginName);
251
+ console.log('pluginModule', pluginModule);
252
+ const plugin = {
253
+ name: pluginName,
254
+ ...(pluginModule.default || pluginModule),
255
+ _config: pluginConfig,
256
+ };
257
+ plugins.push(plugin);
258
+ console.log(`[sloth] npm 插件 ${pluginName} 加载成功`);
259
+ loaded = true;
260
+ }
261
+ catch (error) {
262
+ console.error(`[sloth] npm 插件 ${pluginName} 加载失败:`, error);
263
+ failedPlugins.push(pluginName);
264
+ }
265
+ }
266
+ }
267
+ // 如果有插件加载失败,打印错误
268
+ if (failedPlugins.length > 0) {
269
+ console.error(`以下插件加载失败: ${failedPlugins.join(', ')}。` +
270
+ `请确保插件存在于 .sloth/plugin/<pluginName>/index.js 或为有效的 npm 包。`);
271
+ }
272
+ return plugins;
273
+ }
274
+ /**
275
+ * 动态加载所有已安装的全局插件(旧方法,保留兼容)
276
+ * @deprecated 请使用 loadWorkspacePlugins 代替
277
+ */
278
+ export async function loadAllPlugins() {
279
+ const config = await loadGlobalPluginsConfig();
280
+ const plugins = [];
281
+ for (const pluginItem of config.plugins) {
282
+ const { name: packageName, config: pluginConfig } = parsePluginConfigItem(pluginItem);
283
+ try {
284
+ // 动态导入插件模块
285
+ const pluginModule = await import(packageName);
286
+ console.log('pluginModule', pluginModule);
287
+ console.log('pluginConfig', pluginConfig);
288
+ const plugin = {
289
+ name: packageName,
290
+ ...(pluginModule.default || pluginModule),
291
+ _config: pluginConfig,
292
+ };
293
+ plugins.push(plugin);
294
+ console.log(`[sloth] 插件 ${packageName} 加载成功`);
295
+ }
296
+ catch (error) {
297
+ console.error(`[sloth] 插件 ${packageName} 加载失败:`, error);
298
+ }
299
+ }
300
+ return plugins;
301
+ }
302
+ /**
303
+ * 动态加载单个插件
304
+ * @param packageName 插件包名
305
+ */
306
+ export async function loadPlugin(packageName) {
307
+ try {
308
+ const pluginModule = await import(packageName);
309
+ const plugin = {
310
+ name: packageName,
311
+ ...(pluginModule.default || pluginModule),
312
+ };
313
+ console.log(`[sloth] 插件 ${packageName} 加载成功`);
314
+ return plugin;
315
+ }
316
+ catch (error) {
317
+ console.error(`[sloth] 插件 ${packageName} 加载失败:`, error);
318
+ return null;
319
+ }
320
+ }