@bangdao-ai/acw-tools 1.4.19 → 1.4.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/index.js CHANGED
@@ -26,27 +26,24 @@ const RETRY_BASE_DELAY = 2000; // 重试基础延迟(2秒)
26
26
  const COMPRESSION_ENABLED = process.env.MCP_COMPRESSION_ENABLED !== 'false'; // 默认启用
27
27
  const COMPRESSION_THRESHOLD = parseInt(process.env.MCP_COMPRESSION_THRESHOLD || '1048576', 10); // 默认1MB阈值
28
28
 
29
- // ==================== 数据库引擎加载(使用 better-sqlite3)====================
29
+ // ==================== 数据库引擎加载(使用 better-sqlite3,支持 ABI 版本自动修复)====================
30
+ import { loadBetterSqlite3WithAutoFix } from './prebuildFixer.js';
31
+
30
32
  let dbEngine = null;
31
33
  let dbEngineType = 'none';
32
34
  let chatGrabAvailable = false;
33
- let dbEngineLoadError = null; // 记录加载失败的具体错误
35
+ let dbEngineLoadError = null;
34
36
 
35
- // 尝试加载 better-sqlite3(原生模块,性能最好)
36
- try {
37
- const Database = (await import("better-sqlite3")).default;
38
- // 测试模块是否真正可用(有时 import 成功但 bindings 失败)
39
- const testDb = new Database(':memory:');
40
- testDb.close();
41
-
42
- dbEngine = Database;
37
+ // 使用自动修复模块加载 better-sqlite3
38
+ // 如果检测到 ABI 版本不匹配(如 npx 缓存问题),会自动从 OSS 下载正确版本的预编译包
39
+ const loadResult = await loadBetterSqlite3WithAutoFix(console.error);
40
+
41
+ if (loadResult.success) {
42
+ dbEngine = loadResult.Database;
43
43
  dbEngineType = 'better-sqlite3';
44
44
  chatGrabAvailable = true;
45
- } catch (betterSqlite3Error) {
46
- // 记录详细错误信息,帮助用户排查问题
47
- dbEngineLoadError = betterSqlite3Error.message || String(betterSqlite3Error);
48
-
49
- // 输出到 stderr(Cursor 会捕获显示)
45
+ } else {
46
+ dbEngineLoadError = loadResult.error;
50
47
  console.error(`[ACW-MCP] better-sqlite3 加载失败: ${dbEngineLoadError}`);
51
48
  console.error('[ACW-MCP] 对话抓取功能不可用。如需启用,请执行以下步骤:');
52
49
  if (process.platform === 'darwin') {
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ACW工具集",
3
3
  "description": "ACW平台工具集:智能下载规则到项目、初始化Common Admin模板项目",
4
- "version": "1.4.19",
4
+ "version": "1.4.20",
5
5
  "author": "邦道科技 - 产品技术中心",
6
6
  "homepage": "https://www.npmjs.com/package/@bangdao-ai/acw-tools",
7
7
  "repository": "https://www.npmjs.com/package/@bangdao-ai/acw-tools?activeTab=readme",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bangdao-ai/acw-tools",
3
- "version": "1.4.19",
3
+ "version": "1.4.20",
4
4
  "type": "module",
5
5
  "description": "MCP (Model Context Protocol) tools for ACW - download rules and initialize Common Admin projects",
6
6
  "main": "index.js",
@@ -51,6 +51,7 @@
51
51
  "index.js",
52
52
  "cursorConversationParser.js",
53
53
  "postinstall.js",
54
+ "prebuildFixer.js",
54
55
  "manifest.json",
55
56
  "README.md",
56
57
  "LICENSE"
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * better-sqlite3 预编译包运行时修复模块
4
+ *
5
+ * 解决的问题:
6
+ * - npx 缓存导致 Node 版本切换后 ABI 不匹配
7
+ * - postinstall 脚本没有正确执行
8
+ * - 网络问题导致预编译包下载失败
9
+ *
10
+ * 工作原理:
11
+ * 1. 检测当前 Node.js 的 ABI 版本
12
+ * 2. 检查已安装的 .node 文件是否匹配
13
+ * 3. 如果不匹配,从 OSS 下载正确版本的预编译包
14
+ * 4. 替换旧的 .node 文件
15
+ */
16
+
17
+ import fs from 'fs';
18
+ import path from 'path';
19
+ import { fileURLToPath } from 'url';
20
+ import { execSync } from 'child_process';
21
+ import https from 'https';
22
+ import http from 'http';
23
+ import zlib from 'zlib';
24
+
25
+ const __filename = fileURLToPath(import.meta.url);
26
+ const __dirname = path.dirname(__filename);
27
+
28
+ // OSS 预编译包镜像地址
29
+ const OSS_PREBUILD_BASE = 'https://bd-acw.oss-cn-beijing.aliyuncs.com/prebuilds/better-sqlite3';
30
+
31
+ // better-sqlite3 版本(需与 package.json 中的版本匹配)
32
+ const BETTER_SQLITE3_VERSION = '12.4.1';
33
+
34
+ // 获取当前 Node.js 的 ABI 版本
35
+ function getNodeAbi() {
36
+ return `v${process.versions.modules}`;
37
+ }
38
+
39
+ // 获取平台标识
40
+ function getPlatformTag() {
41
+ return `${process.platform}-${process.arch}`;
42
+ }
43
+
44
+ // 下载文件
45
+ function downloadFile(url, destPath) {
46
+ return new Promise((resolve, reject) => {
47
+ const protocol = url.startsWith('https') ? https : http;
48
+ const file = fs.createWriteStream(destPath);
49
+
50
+ protocol.get(url, (response) => {
51
+ if (response.statusCode === 301 || response.statusCode === 302) {
52
+ downloadFile(response.headers.location, destPath).then(resolve).catch(reject);
53
+ return;
54
+ }
55
+
56
+ if (response.statusCode !== 200) {
57
+ reject(new Error(`HTTP ${response.statusCode}`));
58
+ return;
59
+ }
60
+
61
+ response.pipe(file);
62
+ file.on('finish', () => {
63
+ file.close();
64
+ resolve();
65
+ });
66
+ }).on('error', (err) => {
67
+ fs.unlink(destPath, () => {});
68
+ reject(err);
69
+ });
70
+ });
71
+ }
72
+
73
+ // 解压 tar.gz 文件
74
+ function extractTarGz(tarPath, destDir) {
75
+ try {
76
+ execSync(`tar -xzf "${tarPath}" -C "${destDir}"`, { stdio: 'pipe' });
77
+ return true;
78
+ } catch (e) {
79
+ if (process.platform === 'win32') {
80
+ try {
81
+ const gunzipped = tarPath.replace('.tar.gz', '.tar');
82
+ const data = fs.readFileSync(tarPath);
83
+ const unzipped = zlib.gunzipSync(data);
84
+ fs.writeFileSync(gunzipped, unzipped);
85
+ execSync(`tar -xf "${gunzipped}" -C "${destDir}"`, { stdio: 'pipe' });
86
+ fs.unlinkSync(gunzipped);
87
+ return true;
88
+ } catch (e2) {
89
+ return false;
90
+ }
91
+ }
92
+ return false;
93
+ }
94
+ }
95
+
96
+ // 递归查找 .node 文件
97
+ function findNodeFile(dir) {
98
+ try {
99
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
100
+ for (const entry of entries) {
101
+ const fullPath = path.join(dir, entry.name);
102
+ if (entry.isDirectory()) {
103
+ const found = findNodeFile(fullPath);
104
+ if (found) return found;
105
+ } else if (entry.name.endsWith('.node')) {
106
+ return fullPath;
107
+ }
108
+ }
109
+ } catch (e) {
110
+ // 忽略错误
111
+ }
112
+ return null;
113
+ }
114
+
115
+ // 递归删除目录
116
+ function cleanupDir(dir) {
117
+ try {
118
+ if (!fs.existsSync(dir)) return;
119
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
120
+ for (const entry of entries) {
121
+ const fullPath = path.join(dir, entry.name);
122
+ if (entry.isDirectory()) {
123
+ cleanupDir(fullPath);
124
+ } else {
125
+ fs.unlinkSync(fullPath);
126
+ }
127
+ }
128
+ fs.rmdirSync(dir);
129
+ } catch (e) {
130
+ // 忽略清理错误
131
+ }
132
+ }
133
+
134
+ /**
135
+ * 检查错误是否是 ABI 版本不匹配
136
+ * @param {Error} error - 加载 better-sqlite3 时的错误
137
+ * @returns {{ isAbiMismatch: boolean, currentAbi: string, expectedAbi: string }}
138
+ */
139
+ export function checkAbiMismatch(error) {
140
+ const message = error?.message || '';
141
+
142
+ // 匹配错误信息中的 ABI 版本
143
+ // 示例: "NODE_MODULE_VERSION 115. This version of Node.js requires NODE_MODULE_VERSION 127"
144
+ const match = message.match(/NODE_MODULE_VERSION\s+(\d+).*NODE_MODULE_VERSION\s+(\d+)/);
145
+
146
+ if (match) {
147
+ return {
148
+ isAbiMismatch: true,
149
+ installedAbi: `v${match[1]}`,
150
+ requiredAbi: `v${match[2]}`
151
+ };
152
+ }
153
+
154
+ return { isAbiMismatch: false, installedAbi: null, requiredAbi: null };
155
+ }
156
+
157
+ /**
158
+ * 从 OSS 下载并安装正确版本的预编译包
159
+ * @param {Function} logFn - 日志函数(输出到 stderr)
160
+ * @returns {Promise<boolean>} - 是否成功
161
+ */
162
+ export async function fixPrebuild(logFn = console.error) {
163
+ const abi = getNodeAbi();
164
+ const platform = getPlatformTag();
165
+ const filename = `better-sqlite3-v${BETTER_SQLITE3_VERSION}-node-${abi}-${platform}.tar.gz`;
166
+ const url = `${OSS_PREBUILD_BASE}/v${BETTER_SQLITE3_VERSION}/${filename}`;
167
+
168
+ logFn(`[ACW-MCP] 检测到 ABI 版本不匹配,尝试自动修复...`);
169
+ logFn(`[ACW-MCP] 当前 Node ABI: ${abi}, 平台: ${platform}`);
170
+ logFn(`[ACW-MCP] 正在从 OSS 下载预编译包...`);
171
+
172
+ // 找到 better-sqlite3 的安装路径
173
+ const betterSqlite3Path = path.join(__dirname, 'node_modules', 'better-sqlite3');
174
+ if (!fs.existsSync(betterSqlite3Path)) {
175
+ logFn('[ACW-MCP] 找不到 better-sqlite3 目录');
176
+ return false;
177
+ }
178
+
179
+ // 下载预编译包
180
+ const tempDir = path.join(__dirname, '.prebuild-temp');
181
+ const tempFile = path.join(tempDir, filename);
182
+ const extractDir = path.join(tempDir, 'extracted');
183
+
184
+ try {
185
+ if (!fs.existsSync(tempDir)) {
186
+ fs.mkdirSync(tempDir, { recursive: true });
187
+ }
188
+ if (!fs.existsSync(extractDir)) {
189
+ fs.mkdirSync(extractDir, { recursive: true });
190
+ }
191
+
192
+ await downloadFile(url, tempFile);
193
+ logFn('[ACW-MCP] 下载成功');
194
+
195
+ if (!extractTarGz(tempFile, extractDir)) {
196
+ logFn('[ACW-MCP] 解压失败');
197
+ cleanupDir(tempDir);
198
+ return false;
199
+ }
200
+ logFn('[ACW-MCP] 解压成功');
201
+
202
+ const nodeFile = findNodeFile(extractDir);
203
+ if (!nodeFile) {
204
+ logFn('[ACW-MCP] 未找到 .node 文件');
205
+ cleanupDir(tempDir);
206
+ return false;
207
+ }
208
+
209
+ // 复制到 build/Release/ 目录
210
+ const targetDir = path.join(betterSqlite3Path, 'build', 'Release');
211
+ if (!fs.existsSync(targetDir)) {
212
+ fs.mkdirSync(targetDir, { recursive: true });
213
+ }
214
+
215
+ const targetFile = path.join(targetDir, 'better_sqlite3.node');
216
+
217
+ // 如果目标文件存在,先删除
218
+ if (fs.existsSync(targetFile)) {
219
+ fs.unlinkSync(targetFile);
220
+ }
221
+
222
+ fs.copyFileSync(nodeFile, targetFile);
223
+ logFn('[ACW-MCP] 预编译包安装成功');
224
+
225
+ // 清理临时文件
226
+ cleanupDir(tempDir);
227
+
228
+ return true;
229
+ } catch (e) {
230
+ logFn(`[ACW-MCP] 修复失败: ${e.message}`);
231
+ cleanupDir(tempDir);
232
+ return false;
233
+ }
234
+ }
235
+
236
+ /**
237
+ * 尝试加载 better-sqlite3,如果 ABI 不匹配则自动修复
238
+ * @param {Function} logFn - 日志函数
239
+ * @returns {Promise<{ success: boolean, Database: any, error: string }>}
240
+ */
241
+ export async function loadBetterSqlite3WithAutoFix(logFn = console.error) {
242
+ // 第一次尝试加载
243
+ try {
244
+ const Database = (await import('better-sqlite3')).default;
245
+ const testDb = new Database(':memory:');
246
+ testDb.close();
247
+ return { success: true, Database, error: null };
248
+ } catch (firstError) {
249
+ const { isAbiMismatch, installedAbi, requiredAbi } = checkAbiMismatch(firstError);
250
+
251
+ if (!isAbiMismatch) {
252
+ // 不是 ABI 不匹配问题,直接返回错误
253
+ return { success: false, Database: null, error: firstError.message };
254
+ }
255
+
256
+ logFn(`[ACW-MCP] ABI 版本不匹配: 已安装 ${installedAbi}, 需要 ${requiredAbi}`);
257
+
258
+ // 尝试自动修复
259
+ const fixSuccess = await fixPrebuild(logFn);
260
+
261
+ if (!fixSuccess) {
262
+ return {
263
+ success: false,
264
+ Database: null,
265
+ error: `ABI 版本不匹配且自动修复失败: ${firstError.message}`
266
+ };
267
+ }
268
+
269
+ // 修复后重新尝试加载
270
+ // 注意:ESM 模块有缓存,需要用特殊方式绕过
271
+ // 但由于 .node 文件是动态加载的,替换后应该能正常工作
272
+ try {
273
+ // 清除 require 缓存(对 CJS 有效)
274
+ try {
275
+ const modulePath = require.resolve('better-sqlite3');
276
+ delete require.cache[modulePath];
277
+ } catch (e) {
278
+ // ESM 环境可能没有 require
279
+ }
280
+
281
+ // 重新 import(ESM 会从缓存中取,但 bindings 会重新加载 .node)
282
+ const Database = (await import('better-sqlite3')).default;
283
+ const testDb = new Database(':memory:');
284
+ testDb.close();
285
+
286
+ logFn('[ACW-MCP] 自动修复成功,better-sqlite3 已加载');
287
+ return { success: true, Database, error: null };
288
+ } catch (secondError) {
289
+ return {
290
+ success: false,
291
+ Database: null,
292
+ error: `自动修复后仍无法加载: ${secondError.message}`
293
+ };
294
+ }
295
+ }
296
+ }