remnote-bridge 0.1.4 → 0.1.5
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/dist/cli/daemon/dev-server.js +60 -7
- package/dist/cli/main.js +1 -1
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* webpack-dev-server 子进程管理
|
|
3
3
|
*
|
|
4
4
|
* 在 remnote-plugin 目录下启动 npm run dev。
|
|
5
|
+
* 具备崩溃重试和依赖自动修复能力。
|
|
5
6
|
*/
|
|
6
7
|
import { spawn, execSync } from 'child_process';
|
|
7
8
|
import path from 'path';
|
|
@@ -9,29 +10,54 @@ import fs from 'fs';
|
|
|
9
10
|
export class DevServerManager {
|
|
10
11
|
child = null;
|
|
11
12
|
options;
|
|
13
|
+
retryCount = 0;
|
|
14
|
+
maxRetries;
|
|
15
|
+
stopping = false;
|
|
12
16
|
constructor(options) {
|
|
13
17
|
this.options = options;
|
|
18
|
+
this.maxRetries = options.maxRetries ?? 2;
|
|
14
19
|
}
|
|
15
20
|
/**
|
|
16
21
|
* 启动 webpack-dev-server。
|
|
17
22
|
* 如果 remnote-plugin 目录不存在,抛出错误。
|
|
18
23
|
*/
|
|
19
24
|
start() {
|
|
20
|
-
const { pluginDir, port, onLog
|
|
25
|
+
const { pluginDir, port, onLog } = this.options;
|
|
21
26
|
if (!fs.existsSync(path.join(pluginDir, 'package.json'))) {
|
|
22
27
|
throw new Error(`Plugin 目录不存在或缺少 package.json: ${pluginDir}`);
|
|
23
28
|
}
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
this.ensureDependencies(false);
|
|
30
|
+
this.spawnDevServer();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 确保依赖完整。
|
|
34
|
+
* @param cleanInstall true = 删除 node_modules 后重装(修复损坏)
|
|
35
|
+
*/
|
|
36
|
+
ensureDependencies(cleanInstall) {
|
|
37
|
+
const { pluginDir, onLog } = this.options;
|
|
26
38
|
const nodeModulesDir = path.join(pluginDir, 'node_modules');
|
|
27
39
|
const hasCompleteDeps = fs.existsSync(nodeModulesDir) &&
|
|
28
40
|
fs.existsSync(path.join(nodeModulesDir, '.package-lock.json'));
|
|
29
|
-
if (
|
|
41
|
+
if (cleanInstall && fs.existsSync(nodeModulesDir)) {
|
|
42
|
+
onLog?.('[dev-server] 检测到依赖损坏,正在清洁重装...', 'warn');
|
|
43
|
+
// 删除 node_modules 和 package-lock.json 以彻底修复
|
|
44
|
+
fs.rmSync(nodeModulesDir, { recursive: true, force: true });
|
|
45
|
+
const lockFile = path.join(pluginDir, 'package-lock.json');
|
|
46
|
+
if (fs.existsSync(lockFile)) {
|
|
47
|
+
fs.unlinkSync(lockFile);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (cleanInstall || !hasCompleteDeps) {
|
|
30
51
|
onLog?.('[dev-server] remnote-plugin 依赖缺失或不完整,正在安装...', 'info');
|
|
31
52
|
execSync('npm install', { cwd: pluginDir, stdio: 'pipe' });
|
|
32
53
|
onLog?.('[dev-server] 依赖安装完成', 'info');
|
|
33
54
|
}
|
|
34
|
-
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 启动 dev-server 子进程并挂载事件监听。
|
|
58
|
+
*/
|
|
59
|
+
spawnDevServer() {
|
|
60
|
+
const { pluginDir, port, onLog, onExit } = this.options;
|
|
35
61
|
// shell: true 确保 Windows 上能找到 npm.cmd
|
|
36
62
|
this.child = spawn('npm', ['run', 'dev'], {
|
|
37
63
|
cwd: pluginDir,
|
|
@@ -46,9 +72,35 @@ export class DevServerManager {
|
|
|
46
72
|
onLog?.(`[dev-server] ${data.toString().trim()}`, 'warn');
|
|
47
73
|
});
|
|
48
74
|
this.child.on('exit', (code) => {
|
|
49
|
-
onLog?.(`webpack-dev-server 退出 (code: ${code})`, code === 0 ? 'info' : 'error');
|
|
50
75
|
this.child = null;
|
|
51
|
-
|
|
76
|
+
// 正常退出或正在停止中,不重试
|
|
77
|
+
if (code === 0 || this.stopping) {
|
|
78
|
+
onLog?.(`webpack-dev-server 退出 (code: ${code})`, 'info');
|
|
79
|
+
onExit?.(code);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
onLog?.(`webpack-dev-server 异常退出 (code: ${code})`, 'error');
|
|
83
|
+
// 尝试重试
|
|
84
|
+
if (this.retryCount < this.maxRetries) {
|
|
85
|
+
this.retryCount++;
|
|
86
|
+
const isCleanRetry = this.retryCount === 1;
|
|
87
|
+
onLog?.(`[dev-server] 第 ${this.retryCount}/${this.maxRetries} 次重试` +
|
|
88
|
+
(isCleanRetry ? '(清洁重装依赖)' : '') + '...', 'warn');
|
|
89
|
+
try {
|
|
90
|
+
// 第一次重试:清洁重装依赖(修复损坏的 node_modules)
|
|
91
|
+
// 后续重试:直接重启
|
|
92
|
+
this.ensureDependencies(isCleanRetry);
|
|
93
|
+
this.spawnDevServer();
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
onLog?.(`[dev-server] 重试失败: ${err.message}`, 'error');
|
|
97
|
+
onExit?.(code);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
onLog?.(`[dev-server] 已达最大重试次数 (${this.maxRetries}),放弃`, 'error');
|
|
102
|
+
onExit?.(code);
|
|
103
|
+
}
|
|
52
104
|
});
|
|
53
105
|
this.child.on('error', (err) => {
|
|
54
106
|
onLog?.(`webpack-dev-server 启动失败: ${err.message}`, 'error');
|
|
@@ -60,6 +112,7 @@ export class DevServerManager {
|
|
|
60
112
|
* 停止 webpack-dev-server。
|
|
61
113
|
*/
|
|
62
114
|
stop() {
|
|
115
|
+
this.stopping = true;
|
|
63
116
|
return new Promise((resolve) => {
|
|
64
117
|
if (!this.child) {
|
|
65
118
|
resolve();
|
package/dist/cli/main.js
CHANGED
|
@@ -54,7 +54,7 @@ function parseJsonInput(command, jsonStr, requiredFields = []) {
|
|
|
54
54
|
program
|
|
55
55
|
.name('remnote-bridge')
|
|
56
56
|
.description('RemNote Bridge — CLI + MCP Server + Plugin')
|
|
57
|
-
.version('0.1.
|
|
57
|
+
.version('0.1.5')
|
|
58
58
|
.option('--json', '以 JSON 格式输出(适用于程序化调用)');
|
|
59
59
|
program
|
|
60
60
|
.command('connect')
|