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.
@@ -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, onExit } = this.options;
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
- // 首次使用或依赖不完整时自动安装 plugin 依赖
25
- // 仅检查 node_modules 目录是否存在不够健壮——目录可能存在但关键依赖缺失
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 (!hasCompleteDeps) {
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
- onExit?.(code);
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.4')
57
+ .version('0.1.5')
58
58
  .option('--json', '以 JSON 格式输出(适用于程序化调用)');
59
59
  program
60
60
  .command('connect')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remnote-bridge",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "RemNote 自动化桥接工具集:CLI + MCP Server + Plugin",
5
5
  "type": "module",
6
6
  "bin": {