sloth-d2c-mcp 1.0.4-beta92 → 1.0.4-beta94

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
@@ -102,7 +102,7 @@ if (args[0] === 'config') {
102
102
  const scriptPath = fileURLToPath(import.meta.url);
103
103
 
104
104
  // 后台启动子进程
105
- const child = spawn(process.execPath, [scriptPath, '--stdio'], {
105
+ const child = spawn(process.execPath, [scriptPath, '--server'], {
106
106
  detached: true,
107
107
  stdio: 'ignore',
108
108
  env: { ...process.env, NODE_ENV: 'cli' }
@@ -198,6 +198,8 @@ if (args[0] === 'config') {
198
198
  console.log(`[sloth] 共停止 ${killedCount} 个进程`);
199
199
  }
200
200
  process.exit(0);
201
+ } else if (args[0] === '--server') {
202
+ process.env.SLOTH_COMMAND = 'server';
201
203
  } else {
202
204
  // 设置环境变量为CLI模式
203
205
  process.env.NODE_ENV = "cli";
@@ -14,8 +14,12 @@ export function buildComponentContext(group) {
14
14
  group.componentContext.forEach((comp) => {
15
15
  componentMap.set(comp.name, comp);
16
16
  });
17
- // 替换 @组件名 为组件详细信息
17
+ // 替换 @组件名 为组件详细信息(只替换组件,不替换 Markdown 文件)
18
18
  processedUserPrompt = processedUserPrompt.replace(mentionRegex, (match, display, id) => {
19
+ // 如果 id 包含路径分隔符,说明是文件,保持原样
20
+ if (id.includes('/') || id.includes('\\')) {
21
+ return match;
22
+ }
19
23
  const component = componentMap.get(id || display);
20
24
  if (component) {
21
25
  const libInfo = component.lib ? ` (${component.lib})` : '';
@@ -37,6 +41,51 @@ export function buildComponentContext(group) {
37
41
  }
38
42
  return { processedUserPrompt, componentContextPrompt };
39
43
  }
44
+ /**
45
+ * 处理 Markdown 文件上下文:解析 @文件名 并构建文档上下文提示词
46
+ * @param group 分组数据
47
+ * @returns 处理后的用户提示词和 Markdown 上下文提示词
48
+ */
49
+ export function buildMarkdownContext(group) {
50
+ let processedUserPrompt = group.userPrompt || '';
51
+ let markdownContextPrompt = '';
52
+ if (group.markdownContext && Array.isArray(group.markdownContext) && group.markdownContext.length > 0) {
53
+ // 解析 userPrompt 中的 @文件名(react-mentions 格式:@[文件名](文件路径))
54
+ const mentionRegex = /@\[([^\]]+)\]\(([^)]+)\)/g;
55
+ const markdownMap = new Map();
56
+ // 构建 Markdown 文件映射表
57
+ group.markdownContext.forEach((file) => {
58
+ markdownMap.set(file.path, file);
59
+ });
60
+ // 替换 @文件名 为文件详细信息(只替换文件,不替换组件)
61
+ processedUserPrompt = processedUserPrompt.replace(mentionRegex, (match, _display, id) => {
62
+ // 如果 id 不包含路径分隔符,说明是组件名,保持原样
63
+ if (!id.includes('/') && !id.includes('\\')) {
64
+ return match;
65
+ }
66
+ const markdownFile = markdownMap.get(id);
67
+ if (markdownFile) {
68
+ return `[文档: ${markdownFile.name}]`;
69
+ }
70
+ return match; // 如果找不到文件,保持原样
71
+ });
72
+ // 构建 Markdown 上下文说明
73
+ markdownContextPrompt = '\n\n## 参考文档\n\n以下是用户提供的参考文档,请在代码生成时参考这些文档中的规范、示例和最佳实践:\n\n';
74
+ group.markdownContext.forEach((file) => {
75
+ markdownContextPrompt += `### 📄 ${file.name}\n\n`;
76
+ if (file.content) {
77
+ markdownContextPrompt += '```markdown\n';
78
+ markdownContextPrompt += file.content;
79
+ markdownContextPrompt += '\n```\n\n';
80
+ }
81
+ else {
82
+ markdownContextPrompt += `**文件路径**: ${file.path}\n\n`;
83
+ }
84
+ });
85
+ markdownContextPrompt += '**重要**:请仔细阅读上述参考文档,遵循文档中的编码规范、API 使用方式和最佳实践,确保生成的代码符合项目要求。\n';
86
+ }
87
+ return { processedUserPrompt, markdownContextPrompt };
88
+ }
40
89
  /**
41
90
  * 构建用户提示词文本
42
91
  * @param userPrompt 用户提示词
@@ -2,7 +2,7 @@ import { getBoundingBox, getCode } from 'sloth-d2c-node/convert';
2
2
  import { Logger } from '../utils/logger.js';
3
3
  import { resetNodeListPosition, replaceImageSrc } from '../utils/utils.js';
4
4
  import { extractCodeAndComponents } from '../utils/extract.js';
5
- import { buildComponentContext, buildUserPromptText, buildPlaceholderPrompt } from './prompt-builder.js';
5
+ import { buildComponentContext, buildMarkdownContext, buildUserPromptText, buildPlaceholderPrompt } from './prompt-builder.js';
6
6
  /**
7
7
  * 构建嵌套结构映射
8
8
  * @param groupsData 分组数据数组
@@ -46,9 +46,10 @@ export function buildNestingStructure(groupsData) {
46
46
  * @param componentMappings 组件映射数组
47
47
  * @param imageMap 图片映射
48
48
  * @param convertConfig 转换配置
49
+ * @param isLocalMode 是否为 local 模式
49
50
  * @returns 采样结果或 null
50
51
  */
51
- export async function handleComponentMapping(group, nodeList, componentMappings, imageMap, convertConfig) {
52
+ export async function handleComponentMapping(group, nodeList, componentMappings, imageMap, convertConfig, isLocalMode) {
52
53
  if (group.componentMapping) {
53
54
  const componentName = group.componentMapping.name || `Group${group.groupIndex + 1}`;
54
55
  group.name = componentName;
@@ -57,6 +58,7 @@ export async function handleComponentMapping(group, nodeList, componentMappings,
57
58
  const code = await getCode({
58
59
  d2cNodeList: resetNodeList,
59
60
  config: convertConfig,
61
+ options: { isLocalMode },
60
62
  });
61
63
  const replacedCode = replaceImageSrc(code, imageMap);
62
64
  componentMappings.push({
@@ -147,10 +149,10 @@ export function injectChildPlaceholders(nodeList, childResults, nestingInfo, par
147
149
  * @returns 采样结果
148
150
  */
149
151
  export async function sampleSingleGroup(group, nodeList, config) {
150
- const { d2cNodeList, imageMap, convertConfig, frameworkPrompt, chunkPrompt, mcpServer, componentMappings, codeSnippets, componentContexts, isSupportSampling } = config;
152
+ const { d2cNodeList, imageMap, convertConfig, frameworkPrompt, chunkPrompt, mcpServer, componentMappings, codeSnippets, componentContexts, isSupportSampling, isLocalMode } = config;
151
153
  Logger.log(`开始采样组 ${group.groupIndex}, 元素数量: ${group.elements.length}, nodeList 长度: ${nodeList.length}`);
152
154
  // 检查组件映射
153
- const mappingResult = await handleComponentMapping(group, nodeList, componentMappings, imageMap, convertConfig);
155
+ const mappingResult = await handleComponentMapping(group, nodeList, componentMappings, imageMap, convertConfig, isLocalMode);
154
156
  if (mappingResult) {
155
157
  Logger.log(`组 ${group.groupIndex} 已映射组件,跳过采样: ${mappingResult.componentName}`);
156
158
  return mappingResult;
@@ -162,14 +164,16 @@ export async function sampleSingleGroup(group, nodeList, config) {
162
164
  const code = await getCode({
163
165
  d2cNodeList: resetNodeList,
164
166
  config: convertConfig,
167
+ options: { isLocalMode },
165
168
  });
166
169
  Logger.log(`组 ${group.groupIndex} 代码生成完成,代码长度: ${code.length}`);
167
170
  // 使用 imageMap 的 path 替换 code 中对应的 src
168
171
  const replacedCode = replaceImageSrc(code, imageMap);
169
172
  const componentName = `Group${group.groupIndex + 1}`;
170
173
  const codeWithCustomName = replacedCode.replace(/\bApp\b/g, componentName);
171
- // 处理组件上下文
172
- const { processedUserPrompt, componentContextPrompt } = buildComponentContext(group);
174
+ // 处理组件上下文和 Markdown 上下文
175
+ const { processedUserPrompt: promptAfterComponent, componentContextPrompt } = buildComponentContext(group);
176
+ const { processedUserPrompt, markdownContextPrompt } = buildMarkdownContext({ ...group, userPrompt: promptAfterComponent });
173
177
  const userPromptText = buildUserPromptText(processedUserPrompt);
174
178
  // 收集组件上下文信息
175
179
  if (group.componentContext && Array.isArray(group.componentContext) && group.componentContext.length > 0) {
@@ -186,8 +190,8 @@ export async function sampleSingleGroup(group, nodeList, config) {
186
190
  .map((node) => ({
187
191
  name: node.name || 'Unknown',
188
192
  }));
189
- const placeholderPrompt = buildPlaceholderPrompt(placeholders);
190
- Logger.log(`组 ${group.groupIndex} 开始调用 AI 采样,代码长度: ${codeWithCustomName.length}, 是否有用户提示: ${!!group.userPrompt}, 是否有组件上下文: ${!!(group.componentContext && group.componentContext.length > 0)}, 占位符数量: ${placeholders.length}`);
193
+ const placeholderPrompt = buildPlaceholderPrompt(placeholders, isSupportSampling);
194
+ Logger.log(`组 ${group.groupIndex} 开始调用 AI 采样,代码长度: ${codeWithCustomName.length}, 是否有用户提示: ${!!group.userPrompt}, 是否有组件上下文: ${!!(group.componentContext && group.componentContext.length > 0)}, 是否有 Markdown 上下文: ${!!(group.markdownContext && group.markdownContext.length > 0)}, 占位符数量: ${placeholders.length}`);
191
195
  // 执行采样
192
196
  try {
193
197
  if (!isSupportSampling)
@@ -202,6 +206,7 @@ export async function sampleSingleGroup(group, nodeList, config) {
202
206
  chunkPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || '') +
203
207
  userPromptText +
204
208
  componentContextPrompt +
209
+ markdownContextPrompt +
205
210
  placeholderPrompt,
206
211
  },
207
212
  },
@@ -7,7 +7,7 @@ import { ConfigManager, defaultConfigData, } from './config-manager/index.js';
7
7
  import { cleanup as cleanupTauri } from './interceptor/client.js';
8
8
  import { cleanup as cleanupVSCode, getUserInputFromVSCode, isVSCodeAvailable } from './interceptor/vscode.js';
9
9
  import { cleanup as cleanupWeb, getUserInput } from './interceptor/web.js';
10
- import { loadConfig, startHttpServer, stopHttpServer, generateComponentId, stopSocketServer } from './server.js';
10
+ import { loadConfig, startHttpServer, stopHttpServer, generateComponentId } from './server.js';
11
11
  import { FileManager } from './utils/file-manager.js';
12
12
  import { updateImageMapIfNeeded } from './utils/update.js';
13
13
  import { Logger } from './utils/logger.js';
@@ -24,6 +24,22 @@ import * as path from 'path';
24
24
  import { fileURLToPath } from 'url';
25
25
  import { extractCodeAndComponents } from './utils/extract.js';
26
26
  import { trackToolCall } from './utils/tj.js';
27
+ const spawnServerProcess = async () => {
28
+ // run 命令:后台运行 sloth --stdio
29
+ const { spawn } = await import('node:child_process');
30
+ const { fileURLToPath } = await import('node:url');
31
+ // 获取当前脚本路径(兼容 Node.js 18)
32
+ const scriptPath = fileURLToPath(import.meta.url);
33
+ // 后台启动子进程
34
+ const child = spawn(process.execPath, [scriptPath, '--server'], {
35
+ detached: true,
36
+ stdio: 'ignore',
37
+ env: { ...process.env, NODE_ENV: 'cli' }
38
+ });
39
+ // 解除父进程对子进程的引用,允许父进程退出
40
+ child.unref();
41
+ console.log(`[sloth] 已在后台启动,PID: ${child.pid}`);
42
+ };
27
43
  /**
28
44
  * 启动http & socket监听
29
45
  * @param init 是否是初始化,初始化时需要连接stdio transport
@@ -38,9 +54,13 @@ export async function startListening(init = false) {
38
54
  if (init) {
39
55
  const transport = new StdioServerTransport();
40
56
  await mcpServer.connect(transport);
57
+ // 单独启动httpServer进程,用于网页访问
58
+ await spawnServerProcess();
59
+ }
60
+ else {
61
+ // 启动 HTTP 服务器 - 仅启用web服务
62
+ await startHttpServer(port, mcpServer, configManager, false);
41
63
  }
42
- // 启动 HTTP 服务器 - 仅启用web服务
43
- await startHttpServer(port, mcpServer, configManager, false);
44
64
  }
45
65
  else {
46
66
  // 启动 HTTP 服务器 - 这是核心功能,不依赖 VSCode 日志
@@ -112,7 +132,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
112
132
  catch (error) {
113
133
  Logger.log('获取根目录时出错:', error);
114
134
  }
115
- await startListening();
135
+ // await startListening()
116
136
  let userConfirmedChanges;
117
137
  let userHtmlDiff; // 存储 HTML diff 文本
118
138
  // 没有配置figmaApiKey,非数据推送模式,无法预览,直接唤起配置页面
@@ -189,11 +209,18 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
189
209
  await fileManager.saveFile(fileKey, nodeId, 'imageMap.json', flatted.stringify(imageMap));
190
210
  // 保存figma绝对定位代码
191
211
  await fileManager.saveAbsoluteHtml(fileKey, nodeId, absoluteHtml);
212
+ await fileManager.saveAbsoluteHtml(fileKey, nodeId, absoluteHtml, true);
192
213
  }
193
214
  else {
194
- d2cNodeList = flatted.parse(await fileManager.loadFile(fileKey, nodeId, 'nodeList.json'));
195
- imageMap = flatted.parse(await fileManager.loadFile(fileKey, nodeId, 'imageMap.json'));
196
- absoluteHtml = await fileManager.loadAbsoluteHtml(fileKey, nodeId);
215
+ try {
216
+ d2cNodeList = flatted.parse(await fileManager.loadFile(fileKey, nodeId, 'nodeList.json'));
217
+ imageMap = flatted.parse(await fileManager.loadFile(fileKey, nodeId, 'imageMap.json'));
218
+ absoluteHtml = await fileManager.loadAbsoluteHtml(fileKey, nodeId);
219
+ }
220
+ catch (err) {
221
+ throw new Error('加载本地数据失败, 请检查文件路径:' + fileManager.getFilePathPublic(fileKey, nodeId, 'nodeList.json'));
222
+ }
223
+ await fileManager.saveAbsoluteHtml(fileKey, nodeId, absoluteHtml, true);
197
224
  console.log('cache load data');
198
225
  }
199
226
  console.log('d2cNodeList', d2cNodeList);
@@ -229,6 +256,9 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
229
256
  else {
230
257
  const configData = JSON.parse(configDataString);
231
258
  const { mcp, groupsData, confirmedChanges, htmlDiff, ...rest } = configData;
259
+ pluginManager.executeHook('afterSubmitConfigData', {
260
+ configData,
261
+ });
232
262
  userConfirmedChanges = confirmedChanges;
233
263
  userHtmlDiff = htmlDiff; // 保存 HTML diff
234
264
  // 保存旧配置用于比较
@@ -290,7 +320,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
290
320
  const savedPromptSetting = await fileManager.loadPromptSetting(fileKey, nodeId);
291
321
  const convertConfig = config.fileConfigs?.[fileKey] || defaultConfigData;
292
322
  // 获取提示词,优先使用用户保存的提示词,否则使用默认提示词
293
- const frameworkPrompt = savedPromptSetting?.enableFrameworkGuide ? savedPromptSetting?.frameworkGuidePrompt : '';
323
+ const frameworkPrompt = savedPromptSetting?.frameworkGuidePrompt || '';
294
324
  const chunkPrompt = savedPromptSetting?.chunkOptimizePrompt || chunkOptimizeCodePrompt;
295
325
  const aggregationPrompt = savedPromptSetting?.aggregationOptimizePrompt || aggregationOptimizeCodePrompt;
296
326
  const finalPrompt = savedPromptSetting?.finalOptimizePrompt || finalOptimizeCodePrompt;
@@ -314,6 +344,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
314
344
  codeSnippets,
315
345
  componentContexts,
316
346
  isSupportSampling,
347
+ isLocalMode: local,
317
348
  };
318
349
  // 深度遍历采样:针对每个分组生成 AI 相对布局树并转码
319
350
  if (groupsData && groupsData?.length > 0) {
@@ -400,6 +431,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
400
431
  const code = await getCode({
401
432
  d2cNodeList: ungroupedNodeList,
402
433
  config: convertConfig,
434
+ options: { isLocalMode: local },
403
435
  });
404
436
  // 使用 imageMap 的 path 替换 code 中对应的 src
405
437
  const replacedCode = replaceImageSrc(code, imageMap);
@@ -429,18 +461,18 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
429
461
  }, {
430
462
  timeout: 5 * 60 * 1000,
431
463
  });
432
- Logger.log('采样成功 (分组外元素)', text);
464
+ Logger.log('采样成功 (分组外元素)', text, '采样请求代码', replacedCode, '采样提示词', frameworkPrompt + aggregationPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || '') + placeholderPrompt);
433
465
  const { code: pageCode } = extractCodeAndComponents(text)[0];
434
466
  codeSnippets.unshift(pageCode);
435
467
  }
436
468
  catch (e) {
437
469
  Logger.log('调用分组外元素采样出错,降级到传统模式', e);
438
- codeSnippets.unshift(placeholderPrompt + '\n' + replacedCode);
470
+ codeSnippets.unshift(replacedCode);
439
471
  }
440
472
  }
441
473
  const onSamplingComplete = await pluginManager.executeHook('onSamplingComplete', {
442
474
  codeSnippets,
443
- imageMap
475
+ imageMap,
444
476
  });
445
477
  codeSnippets = onSamplingComplete?.codeSnippets || codeSnippets;
446
478
  imageMap = onSamplingComplete?.imageMap || imageMap;
@@ -469,17 +501,19 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
469
501
  Logger.log(`加载组件数据库,共 ${componentsDatabase.length} 个组件`);
470
502
  // 构建组件映射提示词(包含组件映射、组件上下文、标记组件和完整组件数据库)
471
503
  const componentMappingPrompt = buildComponentMappingPrompt(componentMappings, componentContexts, markedComponents, componentsDatabase);
504
+ const { prompt: pluginFinalPrompt = '' } = await pluginManager.executeHook('beforeFinalPrompt', { configData: config.fileConfigs?.[fileKey] || '' });
505
+ console.log('pluginFinalPrompt', pluginFinalPrompt);
472
506
  // 使用提示词(已包含默认值)
473
507
  return {
474
508
  content: [
475
509
  isSupportSampling
476
510
  ? {
477
511
  type: 'text',
478
- text: finalPrompt + componentMappingPrompt,
512
+ text: finalPrompt + componentMappingPrompt + pluginFinalPrompt,
479
513
  }
480
514
  : {
481
515
  type: 'text',
482
- text: noSamplingPrompt + componentMappingPrompt,
516
+ text: noSamplingPrompt + componentMappingPrompt + pluginFinalPrompt,
483
517
  },
484
518
  ...codeSnippets.map((code) => {
485
519
  return {
@@ -1124,6 +1158,9 @@ async function main() {
1124
1158
  }
1125
1159
  process.exit(0);
1126
1160
  }
1161
+ if (process.env.SLOTH_COMMAND === 'server') {
1162
+ await startListening();
1163
+ }
1127
1164
  try {
1128
1165
  // 先输出基本启动信息到控制台,确保即使 VSCode 日志失败也能看到启动过程
1129
1166
  console.log(`[${new Date().toISOString()}] Starting Figma transcoding interceptor MCP server...`);
@@ -1175,7 +1212,6 @@ process.on('SIGTERM', async () => {
1175
1212
  catch (error) {
1176
1213
  // 忽略日志错误,确保清理过程继续
1177
1214
  }
1178
- await stopSocketServer();
1179
1215
  cleanupTauri();
1180
1216
  cleanupWeb();
1181
1217
  cleanupVSCode();
@@ -1286,6 +1322,10 @@ else if (args[0] === 'clear') {
1286
1322
  // 设置环境变量标识这是 clear 命令
1287
1323
  process.env.SLOTH_COMMAND = 'clear';
1288
1324
  }
1325
+ else if (args[0] === '--server') {
1326
+ // 设置环境变量标识这是 clear 命令
1327
+ process.env.SLOTH_COMMAND = 'server';
1328
+ }
1289
1329
  else {
1290
1330
  // 设置环境变量为CLI模式
1291
1331
  process.env.NODE_ENV = 'cli';
@@ -3,6 +3,7 @@ import { promises as fs } from 'fs';
3
3
  import * as path from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  import envPaths from 'env-paths';
6
+ import { createRequire } from 'module';
6
7
  const __filename = fileURLToPath(import.meta.url);
7
8
  const __dirname = path.dirname(__filename);
8
9
  // 检测是否存在 pnpm
@@ -17,7 +18,7 @@ function hasPnpm() {
17
18
  }
18
19
  // 获取包管理器命令
19
20
  function getPackageManager() {
20
- return /* hasPnpm() ? 'pnpm' : */ 'npm';
21
+ return 'npm';
21
22
  }
22
23
  // 使用 env-paths 获取配置目录(与 config-manager 保持一致)
23
24
  const paths = envPaths('d2c-mcp');
@@ -25,7 +26,14 @@ const paths = envPaths('d2c-mcp');
25
26
  function getGlobalPackageDir() {
26
27
  // 在构建后的目录中: dist/build/plugin/loader.js
27
28
  // 需要向上三级才能找到包根目录
28
- return path.resolve(__dirname, '..', '..', '..');
29
+ // return path.resolve(__dirname, '..', '..', '..')
30
+ try {
31
+ const pm = getPackageManager();
32
+ return execSync(`${pm} root -g`, { encoding: 'utf-8' }).trim();
33
+ }
34
+ catch {
35
+ return '';
36
+ }
29
37
  }
30
38
  // 获取全局插件配置文件路径(与 config.json 在同一目录)
31
39
  function getGlobalPluginsConfigPath() {
@@ -68,6 +76,12 @@ async function loadWorkspacePluginsConfig(workspaceRoot) {
68
76
  return { plugins: [] };
69
77
  }
70
78
  }
79
+ export async function loadPluginFromGlobalDirAsync(packageName) {
80
+ const globalDir = getGlobalPackageDir();
81
+ const require = createRequire(path.join(globalDir));
82
+ const resolvedPath = require.resolve(packageName);
83
+ return await import(resolvedPath);
84
+ }
71
85
  /**
72
86
  * 解析插件配置项,统一返回 { name, config } 格式
73
87
  */
@@ -92,7 +106,7 @@ async function checkLocalPlugin(workspaceRoot, pluginName) {
92
106
  // 检查 npm 包是否已安装
93
107
  async function isNpmPackageInstalled(packageName) {
94
108
  try {
95
- await import(packageName);
109
+ await loadPluginFromGlobalDirAsync(packageName);
96
110
  return true;
97
111
  }
98
112
  catch {
@@ -125,11 +139,11 @@ async function tryInstallNpmPackage(packageName) {
125
139
  export async function installPlugin(packageName) {
126
140
  const globalDir = getGlobalPackageDir();
127
141
  console.log(`[sloth] 正在安装插件: ${packageName}`);
128
- console.log(`[sloth] 安装目录: ${globalDir}`);
142
+ // console.log(`[sloth] 安装目录: ${globalDir}`)
129
143
  try {
130
144
  const pm = getPackageManager();
131
145
  // 在全局包目录下执行安装
132
- execSync(`${pm} install ${packageName}`, {
146
+ execSync(`${pm} install -g ${packageName}`, {
133
147
  cwd: globalDir,
134
148
  stdio: 'inherit',
135
149
  });
@@ -239,15 +253,15 @@ export async function loadWorkspacePlugins(workspaceRoot) {
239
253
  if (!isInstalled) {
240
254
  // 尝试安装
241
255
  console.log(`[sloth] 插件 ${pluginName} 未安装,尝试安装...`);
242
- const installSuccess = await tryInstallNpmPackage(pluginName);
243
- if (!installSuccess) {
244
- failedPlugins.push(pluginName);
245
- continue;
246
- }
256
+ // const installSuccess = await tryInstallNpmPackage(pluginName)
257
+ // if (!installSuccess) {
258
+ // failedPlugins.push(pluginName)
259
+ // continue
260
+ // }
247
261
  }
248
262
  // 加载 npm 包
249
263
  try {
250
- const pluginModule = await import(pluginName);
264
+ const pluginModule = await loadPluginFromGlobalDirAsync(pluginName);
251
265
  console.log('pluginModule', pluginModule);
252
266
  const plugin = {
253
267
  name: pluginName,
@@ -100,6 +100,7 @@ class PluginManager {
100
100
  ...params,
101
101
  ...(lastReturnVal || {}),
102
102
  _config: context.config,
103
+ workspaceRoot: this.workspaceRoot,
103
104
  }, context);
104
105
  // 更新上一个插件信息
105
106
  lastPlugin = plugin.name;
@@ -107,6 +108,7 @@ class PluginManager {
107
108
  ...params,
108
109
  ...(lastReturnVal || {}),
109
110
  ...(result || {}),
111
+ workspaceRoot: this.workspaceRoot,
110
112
  };
111
113
  }
112
114
  catch (error) {