dsh-db-tool 0.1.9 → 1.2.0

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/README.md CHANGED
@@ -36,7 +36,7 @@ dsh plugin --profile web add "dsh-db-tool@github:mengqi1436/dsh-db-tool"
36
36
  dsh plugin --profile web add "link:E:\path\to\dsh-db-tool"
37
37
  ```
38
38
 
39
- GaussDB 官方驱动未发布 npm,需先构建 vendor:`npm run build:gaussdb`(PowerShell)或 `bash scripts/build-gaussdb.sh`;运行要求 Node ≥ 22.12(产物依赖 p-limit@7 纯 ESM)。oracledb 安装脚本需 `npm approve-scripts oracledb`。详见 [docs/install.md](docs/install.md)。
39
+ GaussDB 官方驱动未发布 npm,需先构建 vendor:`npm run build:gaussdb`(PowerShell)或 `bash scripts/build-gaussdb.sh`;运行要求 Node ≥ 22.12(产物依赖 p-limit@7 纯 ESM)。oracledb 安装脚本需 `npm approve-scripts oracledb`。mongodb 驱动已 bundle 进发布包(`vendor/mongodb-driver.cjs`,esbuild 构建,`npm run build:mongodb` 可重建),依赖树中不含 mongodb/punycode,详见 [docs/install.md](docs/install.md)。
40
40
 
41
41
  ## 安全模型
42
42
 
@@ -63,6 +63,8 @@ npm run patch:dsh:sh # macOS / Linux
63
63
 
64
64
  脚本自动探测 DSH 安装根(`--dsh-root` 可显式指定);补丁文件见 `patches/dsh-app-boot-route-scoped-hotfix.patch`,仅对 `0.1.7-rc.2` 声明兼容,其他版本会警告(`--force` 覆盖)。上游 issue:<https://github.com/mengqi1436/dsh-db-tool/issues>(占位,待上游仓库开放后替换)。
65
65
 
66
+ **本插件的免补丁路径(0.1.9+)**:该 bug 的触发条件是依赖树中出现 core-module 同名包,mongodb 链(`mongodb-connection-string-url → whatwg-url → tr46 → punycode`)正是元凶。0.1.9 起 mongodb 驱动经 esbuild bundle 为 `vendor/mongodb-driver.cjs` 随包发布,生产依赖树不再含 mongodb 与 punycode——在**未修复宿主**(含桌面版 app.asar,无法打补丁)上也能正常安装加载。守卫测试 `tests/guard/deps-core-collision.spec.ts` 断言依赖树永不回退(dependencies 白名单 + lockfile 生产树 core 同名包零交集)。`patch:dsh` 仍保留,用于修复**其他**含同类依赖的插件或宿主自身报错。
67
+
66
68
  ## 测试
67
69
 
68
70
  ```bash
@@ -79,8 +81,8 @@ npx stryker run # 变异测试(范围 lib/guard + lib/manager + lib/store,
79
81
  lib/ host 插件(store / adapters×8 / guard / manager / http / index)
80
82
  client/ 侧边栏单文件产物(client.js,即源码)
81
83
  skills/ db-admin skill
82
- scripts/ GaussDB vendor 构建、DSH dsh-app-boot 热修复(patch:dsh)
84
+ scripts/ GaussDB vendor 构建、mongodb 驱动 bundle(build:mongodb)、DSH dsh-app-boot 热修复(patch:dsh)
83
85
  docs/ 安装、HTTP 契约(api-contract.md)、skill 说明
84
86
  tests/ vitest(离线 mock + DBT_TEST_* 门控真机)
85
- vendor/ gaussdb-pg 构建产物(gitignore,不入库)
87
+ vendor/ gaussdb 构建源/产物与 mongodb-driver.cjs bundle(gitignore,发布经 files 白名单收录 bundle)
86
88
  ```
@@ -15,7 +15,7 @@ const INSTALL_HINTS = {
15
15
  gaussdb: 'npm run build:gaussdb(构建 vendor/gaussdb-pg)',
16
16
  sqlite: 'npm install better-sqlite3',
17
17
  redis: 'npm install redis',
18
- mongodb: 'npm install mongodb',
18
+ mongodb: 'npm run build:mongodb(构建 vendor/mongodb-driver.cjs;发布包已内置产物,仅在缺失时需要)',
19
19
  oracle: 'npm install oracledb',
20
20
  dmdb: 'npm install dmdb',
21
21
  };
@@ -10,8 +10,36 @@
10
10
  * - execute() 写白名单;updateMany/deleteMany 空 filter 直接拒绝(适配器级防线)。
11
11
  * - 错误判定用 instanceof MongoServerError(官方建议不解析 message)。
12
12
  * - 只读账号由用户配置官方内置角色 read / readWrite(见文档)。
13
+ * - 驱动不进依赖树:mongodb 经 esbuild bundle 成 vendor/mongodb-driver.cjs(见
14
+ * scripts/build-mongodb.mjs),运行时经 createRequire 相对加载(不经宿主
15
+ * dsh-app-boot 的模块解析器,规避其 resolve.paths 未防护的 for..of bug,
16
+ * 该 bug 会因依赖树中的 punycode 等核心模块同名包直接炸掉插件加载)。
17
+ * bson 保持 dependencies(bundle 的 external),保证 BSON 类实例与外部一致。
13
18
  */
14
- import { Binary, Decimal128, MongoClient, MongoServerError, Long, ObjectId, Timestamp, } from 'mongodb';
19
+ import { createRequire } from 'node:module';
20
+ let vendorCache;
21
+ /** 容错加载 vendor bundle:缺失时返回 undefined(调用方降级处理),成功后缓存。
22
+ * 失败不缓存 → vendor 构建完成后无需重启即可恢复。 */
23
+ function vendorOrNull() {
24
+ if (vendorCache === undefined) {
25
+ try {
26
+ // 相对路径从本模块出发:dist/adapters/mongodb/ 或 lib/adapters/mongodb/ 均指向包根 vendor/
27
+ vendorCache = createRequire(import.meta.url)('../../../vendor/mongodb-driver.cjs');
28
+ }
29
+ catch {
30
+ return undefined;
31
+ }
32
+ }
33
+ return vendorCache;
34
+ }
35
+ /** 严格加载 vendor bundle:缺失时抛带修复指引的错误 */
36
+ function vendor() {
37
+ const m = vendorOrNull();
38
+ if (m === undefined) {
39
+ throw new Error('mongodb 驱动不可用:缺少 vendor/mongodb-driver.cjs。请在插件目录运行 npm run build:mongodb 构建后重试');
40
+ }
41
+ return m;
42
+ }
15
43
  /** 危险操作:服务层拦截器用于确认流程。deleteMany/updateMany 仅空 filter 时危险(适配器已拦截);createIndex 在大集合上可能阻塞。createIndexes/dropIndexes 为单数形式别名,renameCollection 影响集合可见性。 */
16
44
  export const DANGEROUS_OPS = new Set([
17
45
  'dropDatabase', 'dropCollection', 'renameCollection', 'deleteMany', 'updateMany',
@@ -33,7 +61,9 @@ const CELL_TRUNC = 1000;
33
61
  function trunc(s, max = CELL_TRUNC) {
34
62
  return s.length > max ? `${s.slice(0, max)}…[截断,共${s.length}字符]` : s;
35
63
  }
36
- /** NormalizedCell 规范化:ObjectId/Decimal128/Long/Timestamp → toString();Binary → hex 截断;Date → ISO;嵌套对象/数组 → JSON 截断 1000 */
64
+ /** NormalizedCell 规范化:ObjectId/Decimal128/Long/Timestamp → toString();Binary → hex 截断;Date → ISO;嵌套对象/数组 → JSON 截断 1000。
65
+ * BSON 类型判别依赖 vendor bundle 的类(external bson,与外部 require('bson') 同实例);
66
+ * vendor 缺失时降级为普通对象处理,不阻断非 BSON 值的规范化。 */
37
67
  export function normalizeMongoCell(v) {
38
68
  if (v === null || v === undefined)
39
69
  return null;
@@ -45,10 +75,11 @@ export function normalizeMongoCell(v) {
45
75
  return v ? 'true' : 'false';
46
76
  if (v instanceof Date)
47
77
  return v.toISOString();
48
- if (v instanceof ObjectId || v instanceof Decimal128 || v instanceof Long || v instanceof Timestamp) {
78
+ const M = vendorOrNull();
79
+ if (M && (v instanceof M.ObjectId || v instanceof M.Decimal128 || v instanceof M.Long || v instanceof M.Timestamp)) {
49
80
  return trunc(v.toString());
50
81
  }
51
- if (v instanceof Binary)
82
+ if (M && v instanceof M.Binary)
52
83
  return trunc(Buffer.from(v.buffer ?? []).toString('hex') || String(v));
53
84
  if (typeof v === 'object') {
54
85
  try {
@@ -120,7 +151,7 @@ export async function createMongoAdapter(conn, opts) {
120
151
  }
121
152
  else {
122
153
  const uri = conn.url ?? buildUri(conn.fields, conn.ssl);
123
- client = new MongoClient(uri, {
154
+ client = new (vendor().MongoClient)(uri, {
124
155
  maxPoolSize: 10,
125
156
  readPreference: 'primary',
126
157
  w: 'majority',
@@ -138,7 +169,8 @@ export async function createMongoAdapter(conn, opts) {
138
169
  : defaultDbName(conn.fields);
139
170
  const db = client.db(dbName);
140
171
  function wrap(e) {
141
- if (e instanceof MongoServerError) {
172
+ const M = vendorOrNull();
173
+ if (M && e instanceof M.MongoServerError) {
142
174
  return new Error(`MongoDB 服务端错误:${e.message}${e.codeName ? `(${e.codeName})` : ''}`);
143
175
  }
144
176
  return new Error(`MongoDB 错误:${e instanceof Error ? e.message : String(e)}`);
@@ -471,16 +503,19 @@ function objectToResult(obj) {
471
503
  function inferBsonType(v) {
472
504
  if (v === null)
473
505
  return 'null';
474
- if (v instanceof ObjectId)
475
- return 'ObjectId';
476
- if (v instanceof Decimal128)
477
- return 'Decimal128';
478
- if (v instanceof Long)
479
- return 'Long';
480
- if (v instanceof Timestamp)
481
- return 'Timestamp';
482
- if (v instanceof Binary)
483
- return 'Binary';
506
+ const M = vendorOrNull();
507
+ if (M) {
508
+ if (v instanceof M.ObjectId)
509
+ return 'ObjectId';
510
+ if (v instanceof M.Decimal128)
511
+ return 'Decimal128';
512
+ if (v instanceof M.Long)
513
+ return 'Long';
514
+ if (v instanceof M.Timestamp)
515
+ return 'Timestamp';
516
+ if (v instanceof M.Binary)
517
+ return 'Binary';
518
+ }
484
519
  if (v instanceof Date)
485
520
  return 'date';
486
521
  if (Array.isArray(v))
@@ -0,0 +1,66 @@
1
+ DSH 插件 dsh-db-tool 加载错误记录
2
+ =====================================
3
+ 记录时间:2026-09-28
4
+ 当前状态:已从 desktop profile 卸载;卸载后该错误归零,其余 12 个第三方插件全部正常。
5
+
6
+ 一、错误原文
7
+ -------------------------------------
8
+ dsh: error: [/197] createRequire.resolve.paths is not a function or its return value is not iterable
9
+
10
+ 对应条目:/197 id=db-tool name=dsh-db-tool status=error
11
+
12
+ 出现场景:DSH 0.1.7-rc.2 桌面版,desktop profile 的插件组合检查
13
+ (等价 `dsh --profile desktop --dump-config-schema` 的官方加载路径)。
14
+ 同一检查中,其余 12 个第三方插件均无错误。
15
+
16
+ 二、根因(DSH 0.1.7-rc.2 自带代码的 bug)
17
+ -------------------------------------
18
+ 1) DSH 自带 @deepseek-ai/dsh-app-boot 的 lib/index.js 里有 6 处
19
+ `createRequire(parent).resolve.paths(name)` 调用,其中 5 处未做空值防护。
20
+ 2) Node 对「内置模块同名包」调用该 API 会返回 null
21
+ (本机实测:resolve.paths('punycode') 返回 null;普通包返回数组)。
22
+ 对 null 做 for..of 即抛 TypeError —— 这就是上面的错误消息。
23
+ 3) dsh-db-tool 的依赖链包含 npm 包 punycode:
24
+ dsh-db-tool → mongodb → mongodb-connection-string-url → whatwg-url → tr46 → punycode
25
+ 因此该插件的加载路径必然触发这个 bug。
26
+
27
+ 三、本机复现证据
28
+ -------------------------------------
29
+ 运行环境:Electron Node(DeepSeek Harness.exe 加 --expose-internals),可直接访问 asar 内模块。
30
+
31
+ 复现结果(最小用例):
32
+ createRequire('.../node_modules/dsh-db-tool/dist/index.js').resolve.paths('punycode') => null
33
+ createRequire('.../node_modules/tr46/index.js').resolve.paths('punycode') => null
34
+ createRequire('.../node_modules/dsh-db-tool/dist/index.js').resolve.paths('ssh2') => array[11]
35
+
36
+ DSH 官方检查的诊断输出:
37
+ 卸载前:F:\DSH\_recon\dump-schema-err.txt (含 /197 db-tool 错误)
38
+ 卸载后:F:\DSH\_recon\dump-schema-err-2.txt (该错误消失,只剩 DSH 内置 agent-preset 的 4 条既有诊断)
39
+
40
+ 四、插件作者已知该问题(包内自带 hotfix)
41
+ -------------------------------------
42
+ dsh-db-tool 包内提供:
43
+ scripts/hotfix-core.mjs
44
+ patches/dsh-app-boot-route-scoped-hotfix.patch
45
+ 目标:@deepseek-ai/dsh-app-boot 0.1.7-rc.2 中未防护的那行 for..of。
46
+ 修法:resolve.paths 返回 null 时回退到标准 node_modules 搜索链。
47
+
48
+ 限制(本机为何没能直接用):
49
+ 该脚本按「文件系统安装的 DSH」定位目标(找 DSH_HOME、PATH 上的 dsh、全局 npm 目录),
50
+ 而桌面版把 dsh-app-boot 打包在 F:\DSH\resources\app.asar 内,脚本找不到文件,无法应用。
51
+
52
+ 五、可选修复路径(供向 DSH/插件作者反馈)
53
+ -------------------------------------
54
+ 1) 等 DSH 上游修复(推荐):该 bug 属于 0.1.7-rc.2 自带代码,任意「依赖树含 core-module 同名包」的插件都会触发。
55
+ 2) 重打包 app.asar 应用该 hotfix:需先关闭 DSH 应用,修改应用安装文件,版本更新时会被覆盖,有风险。
56
+ 3) 插件侧规避:依赖树中避免引入 core-module 同名包(例如替换 punycode 的实现)。
57
+
58
+ 六、本次卸载记录
59
+ -------------------------------------
60
+ 卸载方式:DSH plugin-manager operations(pnpm remove + 从 dsh.profile.bundles 移除,等价插件页卸载)。
61
+ 结果核对:依赖 13 → 12;bundles 17 → 16;传递依赖 mongodb / punycode 等已一并清理。
62
+ 其余 12 个插件的运行时依赖健康检查全部通过(ssh2 纯 JS 可用、better-sqlite3/oracledb 可用、rtk 0.50.0 可用)。
63
+
64
+ 相关产物:
65
+ 报告目录:F:\DSH\_recon\(install-results.json、各 recon 报告、dump 诊断、验证脚本)
66
+ 安装前备份:F:\DSH\_backup\profile-20260928-162128\
package/docs/install.md CHANGED
@@ -73,6 +73,6 @@ rm -rf ../gaussdb-pg && cp -r packages/pg ../gaussdb-pg
73
73
  ## 常见问题
74
74
 
75
75
  - **侧边栏没有「数据库」tab**:确认 dsh-better-sidebar >= 0.12 已随 DSH 加载;本地开发模式确认 `--patch ./cordis.patch.yml` 生效。
76
- - **插件导入失败(punycode / resolve.paths)**:宿主 `dsh-app-boot@0.1.7-rc.2` 已知缺陷,在插件目录运行 `npm run patch:dsh`(macOS/Linux 用 `patch:dsh:sh`)一键修复,详见 README「Troubleshooting」。
76
+ - **插件导入失败(punycode / resolve.paths)**:宿主 `dsh-app-boot@0.1.7-rc.2` 已知缺陷,触发条件是插件依赖树含 core-module 同名包(mongodb 链的 `tr46 → punycode` 即元凶)。**0.1.9 起 mongodb 驱动已 bundle 进发布包(`vendor/mongodb-driver.cjs`),本插件依赖树不含 punycode,正常安装不再触发此 bug**——包括无法打补丁的桌面版 app.asar 形态。若错误仍指向其他插件,或需修复宿主自身,在插件目录运行 `npm run patch:dsh`(macOS/Linux 用 `patch:dsh:sh`)一键修复,详见 README「Troubleshooting」。从源码安装(`npm install` 后直接用)时构建链会自动产出 bundle;缺失时可单独跑 `npm run build:mongodb`。
77
77
  - **API 全部报错**:host 端 webServer 须注册前缀 `/dsh-db-tool/api`(loopback only);前端与 host 前缀不一致是最常见的接错方式。
78
78
  - **连接测试通过但查询报 UNAUTHORIZED_PROJECT**:当前项目路径未被授权,在「授权」面板为该项目路径授 ro 或 rw。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-db-tool",
3
- "version": "0.1.9",
3
+ "version": "1.2.0",
4
4
  "description": "DSH community plugin: chat-operated multi-database admin tool with sidebar management, project-scoped grants, ro/rw modes and dangerous-operation confirmation",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,7 +26,9 @@
26
26
  "test": "vitest run",
27
27
  "test:watch": "vitest",
28
28
  "typecheck": "tsc --noEmit",
29
- "build": "tsc -p tsconfig.build.json && node -e \"const fs=require('fs'); fs.mkdirSync('dist/script',{recursive:true}); fs.copyFileSync('lib/script/worker.cjs','dist/script/worker.cjs'); console.log('build OK')\"",
29
+ "build": "tsc -p tsconfig.build.json && node -e \"const fs=require('fs'); fs.mkdirSync('dist/script',{recursive:true}); fs.copyFileSync('lib/script/worker.cjs','dist/script/worker.cjs'); console.log('build OK')\" && node scripts/build-mongodb.mjs",
30
+ "build:mongodb": "node scripts/build-mongodb.mjs",
31
+ "prepack": "npm run build",
30
32
  "build:gaussdb": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/build-gaussdb.ps1",
31
33
  "build:gaussdb:sh": "bash scripts/build-gaussdb.sh",
32
34
  "patch:dsh": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/apply-dsh-hotfix.ps1",
@@ -45,8 +47,8 @@
45
47
  },
46
48
  "dependencies": {
47
49
  "better-sqlite3": "^13.0.3",
50
+ "bson": "^7.2.0",
48
51
  "dmdb": "^1.0.52452",
49
- "mongodb": "^7.6.0",
50
52
  "mysql2": "^3.24.4",
51
53
  "oracledb": "^7.0.1",
52
54
  "pg": "^8.23.0",
@@ -57,6 +59,8 @@
57
59
  "@stryker-mutator/vitest-runner": "^10.0.0",
58
60
  "@types/better-sqlite3": "latest",
59
61
  "@types/node": "latest",
62
+ "esbuild": "^0.28.0",
63
+ "mongodb": "^7.6.0",
60
64
  "stryker-cli": "^1.1.0",
61
65
  "typescript": "^5.9.3",
62
66
  "vitest": "^4.1.11"
@@ -76,6 +80,7 @@
76
80
  "docs",
77
81
  "patches",
78
82
  "scripts",
83
+ "vendor/mongodb-driver.cjs",
79
84
  "README.md"
80
85
  ]
81
86
  }
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 构建 vendor/mongodb-driver.cjs:把 mongodb 驱动 bundle 成单文件(esbuild JS API)。
4
+ *
5
+ * 背景:DSH 宿主 @deepseek-ai/dsh-app-boot(<= 0.1.7-rc.2)在插件安装/加载时对依赖树
6
+ * 逐包调 `require.resolve.paths(name)` 且未防护 null 返回值(for..of null → TypeError)。
7
+ * mongodb → mongodb-connection-string-url → whatwg-url → tr46 → punycode 链会让生产依赖树
8
+ * 出现核心模块同名包 punycode,在未修复宿主上直接炸掉整个插件。bundle 后 punycode/
9
+ * whatwg-url/tr46/@mongodb-js/saslprep 全部内联进产物,不再出现在依赖树。
10
+ *
11
+ * --external:bson 是刻意设计:bson 保留在 dependencies,bundle 内 mongodb re-export 的
12
+ * ObjectId/Binary 等来自运行时 require('bson'),与测试/插件其他代码 require 的 bson 是
13
+ * 同一模块实例(instanceof 兼容,避免双份 BSON 类)。其余 externals 为 mongodb 全部
14
+ * optional peer 依赖(驱动对其本就 try/catch 降级,缺失不影响功能)。
15
+ *
16
+ * 每次执行全量重建(esbuild 亚秒级),不做幂等跳过,避免"源升级产物旧"。
17
+ */
18
+ import { build } from 'esbuild';
19
+ import { mkdirSync, statSync } from 'node:fs';
20
+ import path from 'node:path';
21
+ import { fileURLToPath } from 'node:url';
22
+
23
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
24
+ const outfile = path.join(root, 'vendor', 'mongodb-driver.cjs');
25
+
26
+ /** bson 必须 external(实例一致性,见文件头注释);其余为 mongodb optional peers */
27
+ const externals = [
28
+ 'bson',
29
+ 'bson-ext',
30
+ 'kerberos',
31
+ 'snappy',
32
+ 'socks',
33
+ 'gcp-metadata',
34
+ '@aws-sdk/credential-providers',
35
+ '@mongodb-js/zstd',
36
+ 'mongodb-client-encryption',
37
+ ];
38
+
39
+ const entry = path.join(root, 'node_modules', 'mongodb', 'lib', 'index.js');
40
+ if (!statSync(entry, { throwIfNoEntry: false })) {
41
+ console.error('[build-mongodb] 未找到 node_modules/mongodb/lib/index.js —— 请先 npm install(mongodb 在 devDependencies,提供 bundle 源)');
42
+ process.exit(1);
43
+ }
44
+
45
+ mkdirSync(path.dirname(outfile), { recursive: true });
46
+ await build({
47
+ entryPoints: [entry],
48
+ outfile,
49
+ bundle: true,
50
+ platform: 'node',
51
+ format: 'cjs',
52
+ target: 'node20',
53
+ external: externals,
54
+ legalComments: 'none',
55
+ sourcemap: false,
56
+ minify: false,
57
+ logLevel: 'warning',
58
+ });
59
+
60
+ const size = statSync(outfile).size;
61
+ console.log(`[build-mongodb] wrote vendor/mongodb-driver.cjs (${(size / 1024 / 1024).toFixed(2)} MB)`);
62
+ console.log('[build-mongodb] done');