@becrafter/prompt-manager 0.1.14 → 0.1.15
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/app/desktop/assets/app.1.png +0 -0
- package/app/desktop/assets/app.png +0 -0
- package/app/desktop/assets/icons/icon.icns +0 -0
- package/app/desktop/assets/icons/icon.ico +0 -0
- package/app/desktop/assets/icons/icon.png +0 -0
- package/app/desktop/assets/icons/tray.png +0 -0
- package/app/desktop/assets/templates/about.html +147 -0
- package/app/desktop/assets/tray.1.png +0 -0
- package/app/desktop/assets/tray.png +0 -0
- package/app/desktop/docs/ASSETS_PLANNING.md +351 -0
- package/app/desktop/docs/REFACTORING_SUMMARY.md +205 -0
- package/app/desktop/main.js +340 -0
- package/app/desktop/package-lock.json +6912 -0
- package/app/desktop/package.json +119 -0
- package/app/desktop/preload.js +7 -0
- package/app/desktop/src/core/error-handler.js +108 -0
- package/app/desktop/src/core/event-emitter.js +84 -0
- package/app/desktop/src/core/logger.js +130 -0
- package/app/desktop/src/core/state-manager.js +125 -0
- package/app/desktop/src/services/module-loader.js +330 -0
- package/app/desktop/src/services/runtime-manager.js +398 -0
- package/app/desktop/src/services/service-manager.js +210 -0
- package/app/desktop/src/services/update-manager.js +267 -0
- package/app/desktop/src/ui/about-dialog-manager.js +208 -0
- package/app/desktop/src/ui/admin-window-manager.js +757 -0
- package/app/desktop/src/ui/splash-manager.js +253 -0
- package/app/desktop/src/ui/tray-manager.js +186 -0
- package/app/desktop/src/utils/icon-manager.js +133 -0
- package/app/desktop/src/utils/path-utils.js +58 -0
- package/app/desktop/src/utils/resource-paths.js +49 -0
- package/app/desktop/src/utils/resource-sync.js +260 -0
- package/app/desktop/src/utils/runtime-sync.js +241 -0
- package/app/desktop/src/utils/self-check.js +288 -0
- package/app/desktop/src/utils/template-renderer.js +284 -0
- package/app/desktop/src/utils/version-utils.js +59 -0
- package/env.example +1 -1
- package/package.json +12 -1
- package/packages/server/.eslintrc.js +70 -0
- package/packages/server/.husky/pre-commit +8 -0
- package/packages/server/.husky/pre-push +8 -0
- package/packages/server/.prettierrc +14 -0
- package/packages/server/dev-server.js +90 -0
- package/packages/server/jsdoc.conf.json +39 -0
- package/packages/server/playwright.config.js +62 -0
- package/packages/server/scripts/generate-docs.js +300 -0
- package/packages/server/server.js +1 -0
- package/packages/server/services/TerminalService.js +84 -11
- package/packages/server/tests/e2e/terminal-e2e.test.js +315 -0
- package/packages/server/tests/integration/terminal-websocket.test.js +372 -0
- package/packages/server/tests/integration/tools.test.js +264 -0
- package/packages/server/tests/setup.js +45 -0
- package/packages/server/tests/unit/TerminalService.test.js +410 -0
- package/packages/server/tests/unit/WebSocketService.test.js +403 -0
- package/packages/server/tests/unit/core.test.js +94 -0
- package/packages/server/typedoc.json +52 -0
- package/packages/server/utils/config.js +1 -1
- package/packages/server/utils/util.js +59 -5
- package/packages/server/vitest.config.js +74 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { pathToFileURL } = require('url');
|
|
4
|
+
const { spawn } = require('child_process');
|
|
5
|
+
const { constants } = require('fs');
|
|
6
|
+
|
|
7
|
+
class ModuleLoader {
|
|
8
|
+
constructor(logger, errorHandler) {
|
|
9
|
+
this.logger = logger;
|
|
10
|
+
this.errorHandler = errorHandler;
|
|
11
|
+
this.moduleCache = new Map();
|
|
12
|
+
this.loadingPromises = new Map();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async loadServerModule(serverRoot, forceReload = false) {
|
|
16
|
+
const cacheKey = serverRoot;
|
|
17
|
+
|
|
18
|
+
// 如果正在加载,返回现有promise
|
|
19
|
+
if (this.loadingPromises.has(cacheKey) && !forceReload) {
|
|
20
|
+
this.logger.debug('Returning existing module loading promise');
|
|
21
|
+
return this.loadingPromises.get(cacheKey);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 强制重新加载时清理缓存
|
|
25
|
+
if (forceReload) {
|
|
26
|
+
this.logger.debug('Force reloading server module');
|
|
27
|
+
this.moduleCache.delete(cacheKey);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 检查缓存
|
|
31
|
+
if (this.moduleCache.has(cacheKey)) {
|
|
32
|
+
this.logger.debug('Returning cached server module');
|
|
33
|
+
return this.moduleCache.get(cacheKey);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const loadingPromise = this._loadModuleInternal(serverRoot);
|
|
37
|
+
this.loadingPromises.set(cacheKey, loadingPromise);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const module = await loadingPromise;
|
|
41
|
+
this.moduleCache.set(cacheKey, module);
|
|
42
|
+
return module;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
this.logger.error('Failed to load server module', error);
|
|
45
|
+
throw error;
|
|
46
|
+
} finally {
|
|
47
|
+
this.loadingPromises.delete(cacheKey);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async _loadModuleInternal(serverRoot) {
|
|
52
|
+
this.logger.info('Loading server module', { serverRoot });
|
|
53
|
+
|
|
54
|
+
// 判断是否为打包环境
|
|
55
|
+
const isPackaged = this._isPackagedEnvironment();
|
|
56
|
+
this.logger.debug('Environment check', { isPackaged, serverRoot });
|
|
57
|
+
|
|
58
|
+
// 根据环境类型决定路径优先级
|
|
59
|
+
let pathsToCheck;
|
|
60
|
+
|
|
61
|
+
if (isPackaged) {
|
|
62
|
+
// 打包环境:优先从 ASAR 包中加载
|
|
63
|
+
pathsToCheck = [
|
|
64
|
+
// 1. 优先从 app.asar 包中的 node_modules 加载
|
|
65
|
+
path.join(process.resourcesPath, 'app.asar', 'node_modules', '@becrafter', 'prompt-manager-core', 'index.js'),
|
|
66
|
+
// 2. 尝试从 app.asar 直接加载
|
|
67
|
+
path.join(process.resourcesPath, 'app.asar', 'packages', 'server', 'index.js'),
|
|
68
|
+
// 3. 尝试从应用目录的 packages/server 加载
|
|
69
|
+
path.join(process.resourcesPath, 'packages', 'server', 'index.js'),
|
|
70
|
+
// 4. 备用:从 serverRoot 加载
|
|
71
|
+
path.join(serverRoot, 'packages', 'server', 'index.js'),
|
|
72
|
+
];
|
|
73
|
+
} else {
|
|
74
|
+
// 开发环境:优先从项目目录加载
|
|
75
|
+
pathsToCheck = [
|
|
76
|
+
// 1. 优先从 serverRoot 的 packages/server 加载(开发环境)
|
|
77
|
+
path.join(serverRoot, 'packages', 'server', 'index.js'),
|
|
78
|
+
// 2. 尝试从 serverRoot 的 node_modules 加载
|
|
79
|
+
path.join(serverRoot, 'node_modules', '@becrafter', 'prompt-manager-core', 'index.js'),
|
|
80
|
+
// 3. 尝试从当前目录的 node_modules 加载
|
|
81
|
+
path.join(__dirname, '..', '..', '..', 'node_modules', '@becrafter', 'prompt-manager-core', 'index.js'),
|
|
82
|
+
// 4. 最后尝试从 ASAR 路径(以防万一)
|
|
83
|
+
path.join(process.resourcesPath, 'app.asar', 'node_modules', '@becrafter', 'prompt-manager-core', 'index.js'),
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let lastError = null;
|
|
88
|
+
|
|
89
|
+
for (const libPath of pathsToCheck) {
|
|
90
|
+
try {
|
|
91
|
+
if (await this._pathExists(libPath)) {
|
|
92
|
+
this.logger.info('Found server module at path', { path: libPath });
|
|
93
|
+
|
|
94
|
+
const entryUrl = pathToFileURL(libPath);
|
|
95
|
+
// 添加版本参数以防止缓存
|
|
96
|
+
entryUrl.searchParams.set('v', Date.now().toString());
|
|
97
|
+
|
|
98
|
+
this.logger.debug('Attempting to import module', { url: entryUrl.href });
|
|
99
|
+
const module = await import(entryUrl.href);
|
|
100
|
+
this.logger.debug('Module imported successfully', { moduleKeys: Object.keys(module) });
|
|
101
|
+
this._validateServerModule(module);
|
|
102
|
+
this.logger.info('Server module loaded successfully');
|
|
103
|
+
return module;
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
lastError = error;
|
|
107
|
+
this.logger.error('Failed to load from path', {
|
|
108
|
+
path: libPath,
|
|
109
|
+
error: error.message,
|
|
110
|
+
stack: error.stack
|
|
111
|
+
});
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 所有路径都失败,抛出详细的错误信息
|
|
117
|
+
const errorMsg = `Could not find core library in any of the expected paths: ${pathsToCheck.join(', ')}`;
|
|
118
|
+
this.logger.error(errorMsg, { lastError: lastError?.message });
|
|
119
|
+
throw new Error(errorMsg);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 判断是否为打包环境
|
|
124
|
+
*/
|
|
125
|
+
_isPackagedEnvironment() {
|
|
126
|
+
// 优先检查开发环境标志
|
|
127
|
+
const isDevelopment = process.env.NODE_ENV === 'development' ||
|
|
128
|
+
process.env.ELECTRON_IS_DEV === '1' ||
|
|
129
|
+
__dirname.includes('node_modules') ||
|
|
130
|
+
process.defaultApp === true;
|
|
131
|
+
|
|
132
|
+
// 如果是开发环境,直接返回 false
|
|
133
|
+
if (isDevelopment) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 检查是否在 Electron 的打包环境中
|
|
138
|
+
const appPath = process.resourcesPath || '';
|
|
139
|
+
const isElectronPackaged = appPath.includes('app.asar') ||
|
|
140
|
+
!appPath.includes('Electron.app');
|
|
141
|
+
|
|
142
|
+
return isElectronPackaged;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async _pathExists(targetPath) {
|
|
146
|
+
try {
|
|
147
|
+
this.logger.debug('Checking if path exists', { path: targetPath });
|
|
148
|
+
|
|
149
|
+
// 对于 ASAR 文件,使用多种方式验证存在性
|
|
150
|
+
if (targetPath.includes('.asar')) {
|
|
151
|
+
try {
|
|
152
|
+
// 方法1:尝试使用 fs.stat(最可靠的方式)
|
|
153
|
+
const stats = await fs.promises.stat(targetPath);
|
|
154
|
+
if (stats.isFile() || stats.isDirectory()) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
} catch (statError) {
|
|
158
|
+
// 如果 stat 失败,尝试其他方法
|
|
159
|
+
this.logger.debug('ASAR stat failed, trying alternative methods', {
|
|
160
|
+
path: targetPath,
|
|
161
|
+
error: statError.message
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
// 方法2:尝试读取文件的前几个字节
|
|
167
|
+
const buffer = Buffer.alloc(1);
|
|
168
|
+
const fd = await fs.promises.open(targetPath, 'r');
|
|
169
|
+
await fd.read(buffer, 0, 1, 0);
|
|
170
|
+
await fd.close();
|
|
171
|
+
return true;
|
|
172
|
+
} catch (readError) {
|
|
173
|
+
// 如果读取失败,尝试最后的方法
|
|
174
|
+
this.logger.debug('ASAR read failed', {
|
|
175
|
+
path: targetPath,
|
|
176
|
+
error: readError.message
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
// 方法3:尝试使用 fs.access(标准方法)
|
|
182
|
+
await fs.promises.access(targetPath, constants.F_OK);
|
|
183
|
+
return true;
|
|
184
|
+
} catch (accessError) {
|
|
185
|
+
this.logger.debug('ASAR access failed', {
|
|
186
|
+
path: targetPath,
|
|
187
|
+
error: accessError.message
|
|
188
|
+
});
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 普通文件检查 - 使用 fs.stat 而不是 fs.access
|
|
194
|
+
const stats = await fs.promises.stat(targetPath);
|
|
195
|
+
this.logger.debug('File check successful', { path: targetPath, isFile: stats.isFile() });
|
|
196
|
+
return stats.isFile();
|
|
197
|
+
} catch (error) {
|
|
198
|
+
this.logger.debug('File access check failed', {
|
|
199
|
+
path: targetPath,
|
|
200
|
+
error: error.message
|
|
201
|
+
});
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async _validateServerEntry(serverEntry) {
|
|
207
|
+
try {
|
|
208
|
+
// 验证库的 package.json 存在
|
|
209
|
+
const packageJsonPath = path.join(serverEntry, 'package.json');
|
|
210
|
+
await fs.promises.access(packageJsonPath, fs.constants.F_OK);
|
|
211
|
+
this.logger.debug('Server module package.json exists', { serverEntry });
|
|
212
|
+
} catch (error) {
|
|
213
|
+
this.logger.error('Server module not found', error);
|
|
214
|
+
throw new Error(`Server module not found: ${serverEntry}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
_validateServerModule(module) {
|
|
219
|
+
if (!module || typeof module.startServer !== 'function') {
|
|
220
|
+
throw new Error('Invalid server module: missing startServer function');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this.logger.debug('Server module validation passed');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async installDependencies(targetDir) {
|
|
227
|
+
this.logger.info('Installing server dependencies', { targetDir });
|
|
228
|
+
|
|
229
|
+
// 验证 package.json 存在
|
|
230
|
+
await this._validatePackageJson(targetDir);
|
|
231
|
+
|
|
232
|
+
// 检查并添加缺失的依赖
|
|
233
|
+
await this._ensureRequiredDependencies(targetDir);
|
|
234
|
+
|
|
235
|
+
return new Promise((resolve, reject) => {
|
|
236
|
+
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
237
|
+
const args = ['install', '--omit=dev', '--no-audit', '--no-fund'];
|
|
238
|
+
|
|
239
|
+
this.logger.debug('Running npm install', { command: npmCommand, args });
|
|
240
|
+
|
|
241
|
+
const child = spawn(npmCommand, args, {
|
|
242
|
+
cwd: targetDir,
|
|
243
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
let stdout = '';
|
|
247
|
+
let stderr = '';
|
|
248
|
+
|
|
249
|
+
child.stdout.on('data', (data) => {
|
|
250
|
+
const output = data.toString().trim();
|
|
251
|
+
stdout += output;
|
|
252
|
+
this.logger.debug(`[npm stdout] ${output}`);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
child.stderr.on('data', (data) => {
|
|
256
|
+
const output = data.toString().trim();
|
|
257
|
+
stderr += output;
|
|
258
|
+
this.logger.warn(`[npm stderr] ${output}`);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
child.on('error', (error) => {
|
|
262
|
+
this.logger.error('npm process error', error);
|
|
263
|
+
reject(new Error(`Failed to start npm process: ${error.message}`));
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
child.on('close', (code) => {
|
|
267
|
+
this.logger.debug('npm process exited', { code });
|
|
268
|
+
|
|
269
|
+
if (code === 0) {
|
|
270
|
+
this.logger.info('Dependencies installed successfully');
|
|
271
|
+
resolve();
|
|
272
|
+
} else {
|
|
273
|
+
const errorMsg = `npm install failed with exit code ${code}`;
|
|
274
|
+
this.logger.error(errorMsg, new Error(stderr));
|
|
275
|
+
reject(new Error(errorMsg));
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async _validatePackageJson(targetDir) {
|
|
282
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
await fs.promises.access(pkgPath, fs.constants.F_OK);
|
|
286
|
+
this.logger.debug('package.json found in target directory');
|
|
287
|
+
} catch (error) {
|
|
288
|
+
this.logger.error('package.json not found in target directory', { pkgPath });
|
|
289
|
+
throw new Error(`package.json not found in ${targetDir}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async _ensureRequiredDependencies(targetDir) {
|
|
294
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const pkgContent = await fs.promises.readFile(pkgPath, 'utf8');
|
|
298
|
+
const pkg = JSON.parse(pkgContent);
|
|
299
|
+
|
|
300
|
+
pkg.dependencies = pkg.dependencies || {};
|
|
301
|
+
|
|
302
|
+
// 确保 @becrafter/prompt-manager-core 依赖存在
|
|
303
|
+
if (!pkg.dependencies['@becrafter/prompt-manager-core']) {
|
|
304
|
+
pkg.dependencies['@becrafter/prompt-manager-core'] = '^0.0.19';
|
|
305
|
+
await fs.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');
|
|
306
|
+
this.logger.info('Added @becrafter/prompt-manager-core to dependencies');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 检查并添加 @modelcontextprotocol/sdk
|
|
310
|
+
if (!pkg.dependencies['@modelcontextprotocol/sdk']) {
|
|
311
|
+
pkg.dependencies['@modelcontextprotocol/sdk'] = '^1.20.2';
|
|
312
|
+
await fs.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');
|
|
313
|
+
this.logger.info('Added @modelcontextprotocol/sdk to dependencies');
|
|
314
|
+
}
|
|
315
|
+
} catch (error) {
|
|
316
|
+
this.logger.error('Error checking/adding dependencies', error);
|
|
317
|
+
throw error;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
clearCache(serverRoot) {
|
|
322
|
+
const cacheKey = serverRoot;
|
|
323
|
+
if (this.moduleCache.has(cacheKey)) {
|
|
324
|
+
this.moduleCache.delete(cacheKey);
|
|
325
|
+
this.logger.debug('Cleared module cache', { serverRoot });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
module.exports = ModuleLoader;
|