@becrafter/prompt-manager 0.1.17 → 0.1.20
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/env.example +1 -1
- package/package.json +19 -7
- package/packages/server/server.js +2 -1
- package/packages/server/services/TerminalService.js +247 -13
- package/packages/server/toolm/tool-sync.service.js +8 -0
- package/packages/server/utils/config.js +1 -1
- package/packages/web/css/{main.3b61356b384d2f11f47f.css → main.196f434e6a88cd448158.css} +10 -0
- package/packages/web/index.html +1 -1
- package/packages/web/{main.77c2c4b553ca3fac223b.js → main.b427a9e6f77a32a2f87f.js} +2 -2
- 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 +0 -147
- package/app/desktop/assets/tray.1.png +0 -0
- package/app/desktop/assets/tray.png +0 -0
- package/app/desktop/docs/ASSETS_PLANNING.md +0 -351
- package/app/desktop/docs/REFACTORING_SUMMARY.md +0 -205
- package/app/desktop/main.js +0 -340
- package/app/desktop/package-lock.json +0 -6912
- package/app/desktop/package.json +0 -119
- package/app/desktop/preload.js +0 -7
- package/app/desktop/src/core/error-handler.js +0 -108
- package/app/desktop/src/core/event-emitter.js +0 -84
- package/app/desktop/src/core/logger.js +0 -130
- package/app/desktop/src/core/state-manager.js +0 -125
- package/app/desktop/src/services/module-loader.js +0 -330
- package/app/desktop/src/services/runtime-manager.js +0 -398
- package/app/desktop/src/services/service-manager.js +0 -210
- package/app/desktop/src/services/update-manager.js +0 -267
- package/app/desktop/src/ui/about-dialog-manager.js +0 -208
- package/app/desktop/src/ui/admin-window-manager.js +0 -757
- package/app/desktop/src/ui/splash-manager.js +0 -253
- package/app/desktop/src/ui/tray-manager.js +0 -186
- package/app/desktop/src/utils/icon-manager.js +0 -133
- package/app/desktop/src/utils/path-utils.js +0 -58
- package/app/desktop/src/utils/resource-paths.js +0 -49
- package/app/desktop/src/utils/resource-sync.js +0 -260
- package/app/desktop/src/utils/runtime-sync.js +0 -241
- package/app/desktop/src/utils/self-check.js +0 -288
- package/app/desktop/src/utils/template-renderer.js +0 -284
- package/app/desktop/src/utils/version-utils.js +0 -59
- package/packages/server/.eslintrc.js +0 -70
- package/packages/server/.husky/pre-commit +0 -8
- package/packages/server/.husky/pre-push +0 -8
- package/packages/server/.prettierrc +0 -14
- package/packages/server/dev-server.js +0 -90
- package/packages/server/jsdoc.conf.json +0 -39
- package/packages/server/package.json +0 -85
- package/packages/server/playwright.config.js +0 -62
- package/packages/server/scripts/generate-docs.js +0 -300
- package/packages/server/tests/e2e/terminal-e2e.test.js +0 -315
- package/packages/server/tests/integration/terminal-websocket.test.js +0 -372
- package/packages/server/tests/integration/tools.test.js +0 -264
- package/packages/server/tests/setup.js +0 -45
- package/packages/server/tests/unit/TerminalService.test.js +0 -410
- package/packages/server/tests/unit/WebSocketService.test.js +0 -403
- package/packages/server/tests/unit/core.test.js +0 -94
- package/packages/server/typedoc.json +0 -52
- package/packages/server/vitest.config.js +0 -74
- /package/packages/web/{main.77c2c4b553ca3fac223b.js.LICENSE.txt → main.b427a9e6f77a32a2f87f.js.LICENSE.txt} +0 -0
|
@@ -1,398 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const { app } = require('electron');
|
|
4
|
-
|
|
5
|
-
class RuntimeManager {
|
|
6
|
-
constructor(logger, errorHandler) {
|
|
7
|
-
this.logger = logger;
|
|
8
|
-
this.errorHandler = errorHandler;
|
|
9
|
-
this.runtimeRoot = null;
|
|
10
|
-
// 更准确地判断是否为打包应用
|
|
11
|
-
// 在开发模式下,即使 app.isPackaged 为 true,我们的项目目录结构也不同于打包应用
|
|
12
|
-
this.isPackaged = this._checkIfActuallyPackaged();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
_checkIfActuallyPackaged() {
|
|
16
|
-
// 简化环境检测逻辑
|
|
17
|
-
const appPath = app.getAppPath();
|
|
18
|
-
const isDev = appPath.includes('node_modules/electron') ||
|
|
19
|
-
process.env.NODE_ENV === 'development' ||
|
|
20
|
-
process.defaultApp;
|
|
21
|
-
|
|
22
|
-
return !isDev && app.isPackaged;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async ensureRuntimeEnvironment() {
|
|
26
|
-
if (this.runtimeRoot) {
|
|
27
|
-
this.logger.debug('Using cached runtime root', { path: this.runtimeRoot });
|
|
28
|
-
return this.runtimeRoot;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
if (this.isPackaged) {
|
|
33
|
-
this.runtimeRoot = await this._setupPackagedEnvironment();
|
|
34
|
-
} else {
|
|
35
|
-
this.runtimeRoot = await this._setupDevelopmentEnvironment();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
this.logger.info('Runtime environment ensured', {
|
|
39
|
-
root: this.runtimeRoot,
|
|
40
|
-
isPackaged: this.isPackaged
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
return this.runtimeRoot;
|
|
44
|
-
} catch (error) {
|
|
45
|
-
this.logger.error('Failed to ensure runtime environment', error);
|
|
46
|
-
throw error;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async _setupDevelopmentEnvironment() {
|
|
51
|
-
this.logger.info('Setting up development environment');
|
|
52
|
-
|
|
53
|
-
// Go from app/desktop/src/services -> project root
|
|
54
|
-
const devRoot = path.resolve(__dirname, '..', '..', '..', '..');
|
|
55
|
-
|
|
56
|
-
// 验证开发环境
|
|
57
|
-
await this._validateDevelopmentEnvironment(devRoot);
|
|
58
|
-
|
|
59
|
-
this.runtimeRoot = devRoot;
|
|
60
|
-
return devRoot;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async _setupPackagedEnvironment() {
|
|
64
|
-
this.logger.info('Setting up packaged environment');
|
|
65
|
-
|
|
66
|
-
const runtimeRoot = path.join(app.getPath('userData'), 'prompt-manager');
|
|
67
|
-
|
|
68
|
-
this.logger.debug('Environment paths', {
|
|
69
|
-
runtimeRoot,
|
|
70
|
-
resourcesPath: process.resourcesPath,
|
|
71
|
-
appPath: app.getAppPath()
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// 确保 runtimeRoot 存在
|
|
75
|
-
await fs.promises.mkdir(runtimeRoot, { recursive: true });
|
|
76
|
-
|
|
77
|
-
// 创建系统工具目录结构
|
|
78
|
-
const toolsDir = path.join(runtimeRoot, 'tools');
|
|
79
|
-
await fs.promises.mkdir(toolsDir, { recursive: true });
|
|
80
|
-
|
|
81
|
-
// 检查并复制系统工具(如果存在)
|
|
82
|
-
const appPath = app.getAppPath();
|
|
83
|
-
const sourceToolsPath = path.join(appPath, 'runtime', 'toolbox');
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
// 检查源工具目录是否存在
|
|
87
|
-
const sourceExists = await this._pathExists(sourceToolsPath);
|
|
88
|
-
if (sourceExists) {
|
|
89
|
-
// 复制工具目录
|
|
90
|
-
await fs.promises.cp(sourceToolsPath, toolsDir, { recursive: true });
|
|
91
|
-
this.logger.info('Copied system tools to runtime directory');
|
|
92
|
-
} else {
|
|
93
|
-
this.logger.warn('System tools not found in packaged app, creating minimal directory structure');
|
|
94
|
-
|
|
95
|
-
// 创建基本的工具目录结构
|
|
96
|
-
const toolDirs = ['chrome-devtools', 'file-reader', 'filesystem', 'ollama-remote', 'pdf-reader', 'playwright', 'todolist'];
|
|
97
|
-
for (const dir of toolDirs) {
|
|
98
|
-
const dirPath = path.join(toolsDir, dir);
|
|
99
|
-
await fs.promises.mkdir(dirPath, { recursive: true });
|
|
100
|
-
|
|
101
|
-
// 在每个目录中创建一个空的 package.json 文件
|
|
102
|
-
const packageJsonPath = path.join(dirPath, 'package.json');
|
|
103
|
-
const emptyPackageJson = {
|
|
104
|
-
name: `prompt-manager-${dir}`,
|
|
105
|
-
version: "1.0.0",
|
|
106
|
-
description: `Placeholder for ${dir} tool`
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
await fs.promises.writeFile(
|
|
110
|
-
packageJsonPath,
|
|
111
|
-
JSON.stringify(emptyPackageJson, null, 2),
|
|
112
|
-
'utf8'
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
this.logger.info('Created minimal system tools directory structure');
|
|
117
|
-
}
|
|
118
|
-
} catch (error) {
|
|
119
|
-
this.logger.warn('Failed to setup system tools directory', { error: error.message });
|
|
120
|
-
// 即使工具目录设置失败,也不影响主程序运行
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// 复制必要的配置文件到运行时目录
|
|
124
|
-
const packageJsonPath = path.join(appPath, 'package.json');
|
|
125
|
-
const targetPackageJsonPath = path.join(runtimeRoot, 'package.json');
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
await fs.promises.copyFile(packageJsonPath, targetPackageJsonPath);
|
|
129
|
-
this.logger.debug('Copied package.json to runtime directory');
|
|
130
|
-
} catch (error) {
|
|
131
|
-
this.logger.warn('Failed to copy package.json, using fallback', { error: error.message });
|
|
132
|
-
|
|
133
|
-
// 创建一个基本的 package.json
|
|
134
|
-
const basicPackageJson = {
|
|
135
|
-
name: 'prompt-manager-runtime',
|
|
136
|
-
version: '1.0.0',
|
|
137
|
-
dependencies: {
|
|
138
|
-
'@becrafter/prompt-manager-core': '^0.0.19',
|
|
139
|
-
'@modelcontextprotocol/sdk': '^1.20.2'
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
await fs.promises.writeFile(
|
|
144
|
-
targetPackageJsonPath,
|
|
145
|
-
JSON.stringify(basicPackageJson, null, 2),
|
|
146
|
-
'utf8'
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
this.runtimeRoot = runtimeRoot;
|
|
151
|
-
return runtimeRoot;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
async _validateDevelopmentEnvironment(devRoot) {
|
|
155
|
-
const requiredPaths = [
|
|
156
|
-
path.join(devRoot, 'package.json')
|
|
157
|
-
];
|
|
158
|
-
|
|
159
|
-
for (const requiredPath of requiredPaths) {
|
|
160
|
-
try {
|
|
161
|
-
await fs.promises.access(requiredPath, fs.constants.F_OK);
|
|
162
|
-
} catch (error) {
|
|
163
|
-
throw new Error(`Development environment validation failed: ${requiredPath} not found`);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// 检查 @becrafter/prompt-manager-core 在 node_modules 中是否存在
|
|
168
|
-
const coreLibPath = path.join(devRoot, 'node_modules', '@becrafter', 'prompt-manager-core', 'index.js');
|
|
169
|
-
try {
|
|
170
|
-
await fs.promises.access(coreLibPath, fs.constants.F_OK);
|
|
171
|
-
} catch (error) {
|
|
172
|
-
this.logger.debug('Core library not found in node_modules, checking for local packages/server');
|
|
173
|
-
// 如果 node_modules 中不存在,检查 local packages/server 目录
|
|
174
|
-
const localServerPath = path.join(devRoot, 'packages', 'server', 'server.js');
|
|
175
|
-
try {
|
|
176
|
-
await fs.promises.access(localServerPath, fs.constants.F_OK);
|
|
177
|
-
} catch (localError) {
|
|
178
|
-
throw new Error(`Development environment validation failed: neither core library nor local server found (${coreLibPath}, ${localServerPath})`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
this.logger.debug('Development environment validation passed');
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
async _validatePackagedResources(packagedRoot) {
|
|
186
|
-
this.logger.debug('Validating packaged resources', { packagedRoot });
|
|
187
|
-
|
|
188
|
-
// 检查 app.asar 文件是否存在
|
|
189
|
-
if (packagedRoot.endsWith('.asar')) {
|
|
190
|
-
// 在 Electron 中,当代码在 ASAR 包内运行时,对 ASAR 文件的访问方式不同
|
|
191
|
-
// 我们需要检查文件是否存在,但不能依赖 stats.isFile()
|
|
192
|
-
try {
|
|
193
|
-
// 首先尝试使用 fs.access 来检查文件是否存在和可访问
|
|
194
|
-
await fs.promises.access(packagedRoot, fs.constants.F_OK);
|
|
195
|
-
this.logger.debug('Packaged ASAR is accessible via fs.access', { path: packagedRoot });
|
|
196
|
-
return; // Found ASAR file, validation passed
|
|
197
|
-
} catch (accessError) {
|
|
198
|
-
// 如果 fs.access 失败,尝试使用 fs.stat 并检查错误类型
|
|
199
|
-
try {
|
|
200
|
-
const stats = await fs.promises.stat(packagedRoot);
|
|
201
|
-
// 在 ASAR 环境中,即使文件存在,stats.isFile() 也可能返回 false
|
|
202
|
-
// 我们只检查是否存在错误,而不检查文件类型
|
|
203
|
-
this.logger.debug('Packaged ASAR exists via fs.stat', {
|
|
204
|
-
path: packagedRoot,
|
|
205
|
-
isFile: stats.isFile(),
|
|
206
|
-
isDirectory: stats.isDirectory()
|
|
207
|
-
});
|
|
208
|
-
return; // Found ASAR file, validation passed
|
|
209
|
-
} catch (statError) {
|
|
210
|
-
// 如果两种方法都失败,记录详细信息然后抛出错误
|
|
211
|
-
this.logger.error('Failed to validate packaged resources', {
|
|
212
|
-
path: packagedRoot,
|
|
213
|
-
accessError: accessError.message,
|
|
214
|
-
accessCode: accessError.code,
|
|
215
|
-
statError: statError.message,
|
|
216
|
-
statCode: statError.code,
|
|
217
|
-
processResourcesPath: process.resourcesPath,
|
|
218
|
-
appPath: app.getAppPath()
|
|
219
|
-
});
|
|
220
|
-
throw new Error(`Packaged resources not found: ${packagedRoot} (access: ${accessError.message}, stat: ${statError.message})`);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
} else {
|
|
224
|
-
try {
|
|
225
|
-
const stats = await fs.promises.stat(packagedRoot);
|
|
226
|
-
if (stats.isDirectory() || stats.isFile()) {
|
|
227
|
-
this.logger.debug('Packaged root exists', { path: packagedRoot });
|
|
228
|
-
}
|
|
229
|
-
} catch (error) {
|
|
230
|
-
this.logger.error('Packaged root not found or not accessible', { packagedRoot, error: error.message });
|
|
231
|
-
throw new Error(`Packaged resources not found: ${packagedRoot}`);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// 移除了无用的 _generateVolumePaths 方法
|
|
237
|
-
|
|
238
|
-
async _setupRuntimeDirectory(packagedRoot, runtimeRoot) {
|
|
239
|
-
try {
|
|
240
|
-
await fs.promises.access(runtimeRoot, fs.constants.F_OK);
|
|
241
|
-
this.logger.debug('Runtime root already exists');
|
|
242
|
-
} catch (error) {
|
|
243
|
-
this.logger.info('Creating runtime directory', { path: runtimeRoot });
|
|
244
|
-
await fs.promises.mkdir(runtimeRoot, { recursive: true });
|
|
245
|
-
|
|
246
|
-
this.logger.info('Copying packaged resources to runtime directory');
|
|
247
|
-
|
|
248
|
-
// 检查是否是 ASAR 文件
|
|
249
|
-
if (packagedRoot.endsWith('.asar')) {
|
|
250
|
-
// 对于 ASAR 文件,我们不需要解压整个包,只需要复制它
|
|
251
|
-
// 但我们需要确保文件存在且可访问
|
|
252
|
-
try {
|
|
253
|
-
await fs.promises.access(packagedRoot, fs.constants.F_OK);
|
|
254
|
-
this.logger.debug('ASAR file exists, copying to runtime directory');
|
|
255
|
-
|
|
256
|
-
// 复制 ASAR 文件到运行时目录
|
|
257
|
-
const targetAsarPath = path.join(runtimeRoot, 'app.asar');
|
|
258
|
-
await fs.promises.copyFile(packagedRoot, targetAsarPath);
|
|
259
|
-
this.logger.debug('Copied ASAR file to runtime directory', { source: packagedRoot, target: targetAsarPath });
|
|
260
|
-
} catch (copyError) {
|
|
261
|
-
this.logger.error('Failed to copy ASAR file to runtime directory', {
|
|
262
|
-
source: packagedRoot,
|
|
263
|
-
error: copyError.message
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
// 如果复制失败,记录错误但继续执行
|
|
267
|
-
// 应用程序可能仍然可以运行,因为它可以直接从原始位置访问 ASAR 文件
|
|
268
|
-
this.logger.warn('Continuing without copying ASAR file to runtime directory');
|
|
269
|
-
}
|
|
270
|
-
} else {
|
|
271
|
-
// 如果是普通目录,直接复制
|
|
272
|
-
await fs.promises.cp(packagedRoot, runtimeRoot, { recursive: true });
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
async _installDependencies(runtimeRoot) {
|
|
278
|
-
this.logger.info('Installing dependencies in runtime directory');
|
|
279
|
-
|
|
280
|
-
const ModuleLoader = require('./module-loader');
|
|
281
|
-
const moduleLoader = new ModuleLoader(this.logger, this.errorHandler);
|
|
282
|
-
|
|
283
|
-
try {
|
|
284
|
-
await moduleLoader.installDependencies(runtimeRoot);
|
|
285
|
-
this.logger.info('Dependencies installed successfully');
|
|
286
|
-
} catch (error) {
|
|
287
|
-
this.logger.error('Failed to install dependencies', error);
|
|
288
|
-
throw error;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
getServerRoot() {
|
|
293
|
-
if (!this.runtimeRoot) {
|
|
294
|
-
throw new Error('Runtime environment not initialized');
|
|
295
|
-
}
|
|
296
|
-
return this.runtimeRoot;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
getPackageInfo() {
|
|
300
|
-
// 优先从 app.asar 压缩包中读取 package.json
|
|
301
|
-
const asarPackagePath = path.join(process.resourcesPath, 'app.asar', 'package.json');
|
|
302
|
-
|
|
303
|
-
// 尝试从 app.asar 中读取
|
|
304
|
-
try {
|
|
305
|
-
const packageContent = fs.readFileSync(asarPackagePath, 'utf8');
|
|
306
|
-
this.logger.debug('Read package.json from app.asar', { path: asarPackagePath });
|
|
307
|
-
return JSON.parse(packageContent);
|
|
308
|
-
} catch (asarError) {
|
|
309
|
-
// 如果从 app.asar 读取失败,尝试从 app.getAppPath() 读取(开发环境或备用方案)
|
|
310
|
-
try {
|
|
311
|
-
const appPathPackagePath = path.join(app.getAppPath(), 'package.json');
|
|
312
|
-
const packageContent = fs.readFileSync(appPathPackagePath, 'utf8');
|
|
313
|
-
this.logger.debug('Read package.json from app path', { path: appPathPackagePath });
|
|
314
|
-
return JSON.parse(packageContent);
|
|
315
|
-
} catch (appPathError) {
|
|
316
|
-
// 最后尝试从 runtimeRoot 读取(向后兼容)
|
|
317
|
-
if (this.runtimeRoot) {
|
|
318
|
-
try {
|
|
319
|
-
const runtimePackagePath = path.join(this.runtimeRoot, 'package.json');
|
|
320
|
-
const packageContent = fs.readFileSync(runtimePackagePath, 'utf8');
|
|
321
|
-
this.logger.debug('Read package.json from runtime root', { path: runtimePackagePath });
|
|
322
|
-
return JSON.parse(packageContent);
|
|
323
|
-
} catch (runtimeError) {
|
|
324
|
-
this.logger.error('Failed to read package info from all locations', {
|
|
325
|
-
asarError: asarError.message,
|
|
326
|
-
appPathError: appPathError.message,
|
|
327
|
-
runtimeError: runtimeError.message
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
} else {
|
|
331
|
-
this.logger.error('Failed to read package info from app.asar and app path', {
|
|
332
|
-
asarError: asarError.message,
|
|
333
|
-
appPathError: appPathError.message
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// 所有尝试都失败,返回默认值
|
|
340
|
-
return { version: 'unknown' };
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* 检查文件或目录是否存在
|
|
345
|
-
* @param {string} targetPath - 目标路径
|
|
346
|
-
* @returns {Promise<boolean>} - 是否存在
|
|
347
|
-
*/
|
|
348
|
-
async _pathExists(targetPath) {
|
|
349
|
-
const fs = require('fs/promises');
|
|
350
|
-
const { constants } = require('fs');
|
|
351
|
-
|
|
352
|
-
try {
|
|
353
|
-
// 对于 ASAR 文件,使用多种方式验证存在性
|
|
354
|
-
if (targetPath.includes('.asar')) {
|
|
355
|
-
try {
|
|
356
|
-
// 方法1:尝试使用 fs.stat
|
|
357
|
-
const stats = await fs.stat(targetPath);
|
|
358
|
-
return stats.isFile() || stats.isDirectory();
|
|
359
|
-
} catch (statError) {
|
|
360
|
-
// 如果 stat 失败,尝试其他方法
|
|
361
|
-
this.logger.debug('ASAR stat failed', {
|
|
362
|
-
path: targetPath,
|
|
363
|
-
error: statError.message
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
try {
|
|
368
|
-
// 方法2:尝试使用 fs.access
|
|
369
|
-
await fs.access(targetPath, constants.F_OK);
|
|
370
|
-
return true;
|
|
371
|
-
} catch (accessError) {
|
|
372
|
-
this.logger.debug('ASAR access failed', {
|
|
373
|
-
path: targetPath,
|
|
374
|
-
error: accessError.message
|
|
375
|
-
});
|
|
376
|
-
return false;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// 普通文件检查
|
|
381
|
-
await fs.access(targetPath, constants.F_OK);
|
|
382
|
-
return true;
|
|
383
|
-
} catch (error) {
|
|
384
|
-
this.logger.debug('File access check failed', {
|
|
385
|
-
path: targetPath,
|
|
386
|
-
error: error.message
|
|
387
|
-
});
|
|
388
|
-
return false;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
async cleanup() {
|
|
393
|
-
this.logger.info('Cleaning up runtime environment');
|
|
394
|
-
this.runtimeRoot = null;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
module.exports = RuntimeManager;
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
const { dialog, shell } = require('electron');
|
|
2
|
-
const EventEmitter = require('../core/event-emitter');
|
|
3
|
-
|
|
4
|
-
class ServiceManager extends EventEmitter {
|
|
5
|
-
constructor(logger, errorHandler, moduleLoader) {
|
|
6
|
-
super();
|
|
7
|
-
this.logger = logger;
|
|
8
|
-
this.errorHandler = errorHandler;
|
|
9
|
-
this.moduleLoader = moduleLoader;
|
|
10
|
-
this.currentModule = null;
|
|
11
|
-
this.serverState = null;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async startService(serverRoot, stateManager) {
|
|
15
|
-
this.logger.info('Starting service', { serverRoot });
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
// 验证服务状态
|
|
19
|
-
if (!stateManager.canStartService()) {
|
|
20
|
-
this.logger.warn('Service cannot be started in current state', {
|
|
21
|
-
currentState: stateManager.get('service')
|
|
22
|
-
});
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// 更新状态
|
|
27
|
-
stateManager.set('service', 'starting');
|
|
28
|
-
this.emitStatusUpdate(stateManager);
|
|
29
|
-
|
|
30
|
-
// 加载服务器模块
|
|
31
|
-
const module = await this.moduleLoader.loadServerModule(serverRoot, true);
|
|
32
|
-
|
|
33
|
-
// 停止现有服务实例
|
|
34
|
-
await this.stopExistingService(module);
|
|
35
|
-
|
|
36
|
-
// 启动新服务
|
|
37
|
-
await module.startServer();
|
|
38
|
-
|
|
39
|
-
// 增强的服务器验证机制
|
|
40
|
-
let isRunning = false;
|
|
41
|
-
let retryCount = 0;
|
|
42
|
-
const maxRetries = 5;
|
|
43
|
-
const retryDelay = 1000; // 1秒
|
|
44
|
-
|
|
45
|
-
this.logger.info('Verifying server startup...');
|
|
46
|
-
|
|
47
|
-
while (!isRunning && retryCount < maxRetries) {
|
|
48
|
-
try {
|
|
49
|
-
this.logger.debug(`Server verification attempt ${retryCount + 1}/${maxRetries}`);
|
|
50
|
-
isRunning = await module.isServerRunning();
|
|
51
|
-
|
|
52
|
-
if (!isRunning) {
|
|
53
|
-
retryCount++;
|
|
54
|
-
if (retryCount < maxRetries) {
|
|
55
|
-
this.logger.debug(`Server not running, waiting ${retryDelay}ms before next attempt...`);
|
|
56
|
-
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
} catch (error) {
|
|
60
|
-
retryCount++;
|
|
61
|
-
this.logger.debug(`Server check failed (attempt ${retryCount}/${maxRetries}):`, {
|
|
62
|
-
error: error.message
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
if (retryCount < maxRetries) {
|
|
66
|
-
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (!isRunning) {
|
|
72
|
-
const errorMsg = `服务器启动失败:经过 ${maxRetries} 次重试后仍无法验证服务状态`;
|
|
73
|
-
this.logger.error(errorMsg);
|
|
74
|
-
throw new Error(errorMsg);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
this.logger.info('Server verification successful');
|
|
78
|
-
|
|
79
|
-
// 获取服务器状态
|
|
80
|
-
this.serverState = module.getServerState();
|
|
81
|
-
this.currentModule = module;
|
|
82
|
-
|
|
83
|
-
// 更新状态
|
|
84
|
-
stateManager.set({
|
|
85
|
-
service: 'running',
|
|
86
|
-
server: this.serverState,
|
|
87
|
-
module: module
|
|
88
|
-
});
|
|
89
|
-
stateManager.resetFailureCount();
|
|
90
|
-
|
|
91
|
-
this.logger.info('Service started successfully', { serverState: this.serverState });
|
|
92
|
-
this.emitStatusUpdate(stateManager);
|
|
93
|
-
|
|
94
|
-
return true;
|
|
95
|
-
|
|
96
|
-
} catch (error) {
|
|
97
|
-
this.logger.error('Failed to start service', error);
|
|
98
|
-
|
|
99
|
-
// 更新状态
|
|
100
|
-
stateManager.set('service', 'error');
|
|
101
|
-
this.serverState = { status: 'error', message: error.message };
|
|
102
|
-
const failureCount = stateManager.incrementFailureCount();
|
|
103
|
-
|
|
104
|
-
// 处理错误
|
|
105
|
-
const shouldRestart = await this.errorHandler.handleServiceStartError(
|
|
106
|
-
error,
|
|
107
|
-
failureCount,
|
|
108
|
-
this.logger.getLogFilePath()
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
if (shouldRestart) {
|
|
112
|
-
this.logger.info('User requested application restart');
|
|
113
|
-
this.emitRestartRequested();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
this.emitStatusUpdate(stateManager);
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async stopService(stateManager) {
|
|
122
|
-
this.logger.info('Stopping service');
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
// 验证服务状态
|
|
126
|
-
if (!stateManager.canStopService()) {
|
|
127
|
-
this.logger.warn('Service cannot be stopped in current state', {
|
|
128
|
-
currentState: stateManager.get('service')
|
|
129
|
-
});
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// 更新状态
|
|
134
|
-
stateManager.set('service', 'stopping');
|
|
135
|
-
this.emitStatusUpdate(stateManager);
|
|
136
|
-
|
|
137
|
-
// 停止服务
|
|
138
|
-
if (this.currentModule && typeof this.currentModule.stopServer === 'function') {
|
|
139
|
-
await this.currentModule.stopServer();
|
|
140
|
-
this.logger.info('Server stopped successfully');
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// 清理状态
|
|
144
|
-
this.currentModule = null;
|
|
145
|
-
this.serverState = null;
|
|
146
|
-
|
|
147
|
-
// 更新状态
|
|
148
|
-
stateManager.set({
|
|
149
|
-
service: 'stopped',
|
|
150
|
-
server: null,
|
|
151
|
-
module: null
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
// 清理模块缓存
|
|
155
|
-
const serverRoot = stateManager.get('runtimeRoot');
|
|
156
|
-
if (serverRoot) {
|
|
157
|
-
this.moduleLoader.clearCache(serverRoot);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
this.logger.info('Service stopped successfully');
|
|
161
|
-
this.emitStatusUpdate(stateManager);
|
|
162
|
-
|
|
163
|
-
return true;
|
|
164
|
-
|
|
165
|
-
} catch (error) {
|
|
166
|
-
this.logger.error('Failed to stop service', error);
|
|
167
|
-
|
|
168
|
-
// 更新状态
|
|
169
|
-
stateManager.set('service', 'error');
|
|
170
|
-
this.errorHandler.handleError('SERVICE_STOP_FAILED', error);
|
|
171
|
-
this.emitStatusUpdate(stateManager);
|
|
172
|
-
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async stopExistingService(module) {
|
|
178
|
-
if (!module || typeof module.stopServer !== 'function') {
|
|
179
|
-
this.logger.debug('No existing service to stop');
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
try {
|
|
184
|
-
this.logger.debug('Stopping existing service instance');
|
|
185
|
-
await module.stopServer();
|
|
186
|
-
this.logger.info('Existing service instance stopped');
|
|
187
|
-
} catch (error) {
|
|
188
|
-
this.logger.warn('Failed to stop existing service instance', error);
|
|
189
|
-
// 不抛出错误,继续启动新服务
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
getServiceStatus() {
|
|
194
|
-
return {
|
|
195
|
-
state: this.serverState,
|
|
196
|
-
module: this.currentModule,
|
|
197
|
-
isRunning: this.currentModule !== null
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
emitStatusUpdate(stateManager) {
|
|
202
|
-
this.emit('status-update', stateManager.getServiceStatus());
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
emitRestartRequested() {
|
|
206
|
-
this.emit('restart-requested');
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
module.exports = ServiceManager;
|