dsh-db-tool 0.1.7 → 0.1.9

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`;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`。详见 [docs/install.md](docs/install.md)。
40
40
 
41
41
  ## 安全模型
42
42
 
@@ -52,7 +52,7 @@ GaussDB 官方驱动未发布 npm,需先构建 vendor:`npm run build:gaussdb
52
52
 
53
53
  症状:DSH 启动后插件加载报 `failed to import`,伴随 `TypeError: Cannot read properties of null (reading 'Symbol(Symbol.iterator)')`,栈指向 `dsh-app-boot` 的 `routeScoped`。
54
54
 
55
- 根因:上游 `@deepseek-ai/dsh-app-boot` 对 `createRequire(parent).resolve.paths(name)` 直接做 `for..of`,而 Node 对 core-module 同名包(`punycode` 等)返回 `null`,hoisted profile 下凡依赖树含此类 npm 包的插件都会炸。
55
+ 根因:上游 `@deepseek-ai/dsh-app-boot` 对 `createRequire(parent).resolve.paths(name)` 直接做 `for..of`,而 Node 对 core-module 同名包(`punycode` 等)返回 `null`,hoisted profile 下凡依赖树含此类 npm 包的插件都会炸。`0.1.7-rc.2` 中共两处同型缺陷:`routeScoped()` 与 `nativePackageDir()`,本补丁一并修复(两处分别独立判定,已打过旧版单处补丁的机器只会补上缺的那处)。
56
56
 
57
57
  一键修复(幂等,应用前自动备份为 `index.js.bak-hotfix`;`--revert` 可还原):
58
58
 
@@ -1,8 +1,20 @@
1
1
  /**
2
2
  * GaussDB(openGauss)适配器。
3
3
  *
4
- * ⚠️ 官方驱动 openGauss-connector-nodejs 未发布 npm,按 openGauss 官方 README/文档实现,
5
- * 使用 vendor/gaussdb-pg 构建产物(fork 自 pg 8.7 时代,API 与 pg 完全兼容),未真机验证。
4
+ * ⚠️ 官方驱动 openGauss-connector-nodejs 未发布 npm(华为云 HCS 26.861.0 官方文档
5
+ * 应用程序接口清单仅 JDBC/ODBC/libpq/Psycopg/Go/ECPG,无 Node 驱动),Node 接入基于
6
+ * PostgreSQL 协议兼容自行验证。使用 vendor/gaussdb-pg 构建产物(fork 自 pg 8.6.0,
7
+ * API 与 pg 完全兼容),未真机验证。
8
+ *
9
+ * 已按官方文档对齐(证据见 skills/db-admin/SKILL.md GaussDB 节):
10
+ * - 认证:驱动实现 openGauss SHA256 / MD5-SHA256 / SM3 握手(vendor pg-protocol parser.ts:335-357、
11
+ * rfc5802.js:44),对应服务端 password_encryption_type 默认 2 (sha256);
12
+ * - 默认端口 8000(集中式 DN / 分布式 CN)、ro 会话 `SET SESSION CHARACTERISTICS AS
13
+ * TRANSACTION READ ONLY` 两形态官方支持(rf-dist/gaussdb-08-0400、rf-cent/gaussdb-38-0416);
14
+ * - 已知限制:SERIALIZABLE 功能不支持(等价 REPEATABLE READ,与 PG 的 SSI 行为不同);
15
+ * SSL 仅布尔开关(不支持 sslmode require/verify-ca/verify-full 细分);
16
+ * information_schema.tables/columns 集中式有官方视图文档、分布式参考无原生视图文档
17
+ * (仅 M-Compatibility 模式),跨库浏览在分布式实例上以真机行为为准。
6
18
  *
7
19
  * 构建方式:bash scripts/build-gaussdb.sh(或 npm run build:gaussdb)
8
20
  * 核心逻辑与 postgresql 适配器共享(sql-shared/pg-like.ts)。
@@ -10,9 +10,13 @@ function poolConfig(conn) {
10
10
  }
11
11
  else if (conn.fields) {
12
12
  const f = conn.fields;
13
+ // 兜底默认值按 kind 区分(仅表单字段被清空时触发):GaussDB 官方默认端口 8000
14
+ // (华为云 HCS 26.861.0:集中式 DN 与分布式 CN 均为 8000,devg-cent/gaussdb-42-0020、
15
+ // devg-dist/gaussdb-12-0319)、默认管理用户 gaussdb;postgresql 维持社区默认 5432/postgres 不变。
16
+ const gb = conn.meta.kind === 'gaussdb';
13
17
  base.host = String(f.host ?? '127.0.0.1');
14
- base.port = Number(f.port ?? 5432);
15
- base.user = String(f.user ?? f.username ?? 'postgres');
18
+ base.port = Number(f.port ?? (gb ? 8000 : 5432));
19
+ base.user = String(f.user ?? f.username ?? (gb ? 'gaussdb' : 'postgres'));
16
20
  if (f.password != null)
17
21
  base.password = String(f.password);
18
22
  if (f.database != null)
@@ -28,20 +32,23 @@ function poolConfig(conn) {
28
32
  export async function createPgLikeAdapter(kind, Driver, conn, opts) {
29
33
  const pool = new Driver.Pool(poolConfig(conn));
30
34
  const readOnly = opts?.mode === 'ro';
31
- // 服务器级 ro 强制(官方手段):每个新连接会话设为只读事务。
35
+ // 所有池(主池 + 跨库池)统一走此初始化。
36
+ // pg 约定:release(err) 若无等待者会 emit pool 'error',无监听器会崩进程——
37
+ // 官方 Pool 文档要求必须注册 error 监听(空闲连接因后端故障销毁时经此冒泡),无条件兜底。
38
+ // 服务器级 ro 强制(官方手段):每个新连接会话设为只读事务,仅 ro 模式注册。
32
39
  // fail-closed:SET 失败时 release(err) 让驱动销毁该连接、等待的 acquire 收到错误——
33
40
  // 会话级只读是 ro 的最后防线,不允许静默降级成可写连接。
34
- const setupReadOnly = (p) => {
35
- p.on('connect', (c) => {
36
- void c.query('SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY').catch((err) => {
37
- c.release(err instanceof Error ? err : new Error(String(err)));
38
- });
39
- });
40
- // pg 约定:release(err) 若无等待者会 emit pool 'error',不监听会崩进程
41
+ const setupPool = (p) => {
41
42
  p.on('error', () => { });
43
+ if (readOnly) {
44
+ p.on('connect', (c) => {
45
+ void c.query('SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY').catch((err) => {
46
+ c.release(err instanceof Error ? err : new Error(String(err)));
47
+ });
48
+ });
49
+ }
42
50
  };
43
- if (readOnly)
44
- setupReadOnly(pool);
51
+ setupPool(pool);
45
52
  // 事务激活期间所有 query/execute 路由到同一 client
46
53
  let txClient = null;
47
54
  const run = (sql, params) => txClient ? txClient.query(sql, params) : pool.query(sql, params);
@@ -67,8 +74,7 @@ export async function createPgLikeAdapter(kind, Driver, conn, opts) {
67
74
  if (cached)
68
75
  return cached;
69
76
  const created = new Driver.Pool({ ...poolConfig(conn), database: db });
70
- if (readOnly)
71
- setupReadOnly(created);
77
+ setupPool(created); // 与主池一致:error 兜底 + ro 会话只读
72
78
  dbPools.set(db, created);
73
79
  return created;
74
80
  };
package/dist/index.js CHANGED
@@ -30,7 +30,9 @@ action 说明:
30
30
  - preview:预览行,limit≤50(conn_id, table, database?, limit?)。
31
31
  - run_script:子进程沙箱脚本(conn_id, code),60s 超时强杀,permission model 禁文件
32
32
  访问 + 新 vm realm 双层隔离,仅注入受限 db.query/db.execute 句柄(经完整
33
- guard/审计链路),rw 连接才有写能力。`;
33
+ guard/审计链路),rw 连接才有写能力。句柄签名 db.query(sql, params?, database?):
34
+ 第三参为跨库路由目标(pg/gaussdb 等按库路由,同 Navicat 式),多库统计在脚本内
35
+ 循环逐库调用即可一次完成。`;
34
36
  export function apply(ctx) {
35
37
  const log = ctx.logger ?? { info: () => { }, error: () => { } };
36
38
  const store = new DbToolStore();
package/dist/manager.js CHANGED
@@ -249,8 +249,8 @@ export class DbToolService {
249
249
  try {
250
250
  const result = await runScriptInChild({
251
251
  code: src,
252
- dbQuery: async (sql, params) => this.unwrapForScript(await this.query(key, connId, sql, params, challengeId)),
253
- dbExecute: async (statement, params) => this.unwrapForScript(await this.execute(key, connId, statement, params, challengeId)),
252
+ dbQuery: async (sql, params, database) => this.unwrapForScript(await this.query(key, connId, sql, params, challengeId, database)),
253
+ dbExecute: async (statement, params, database) => this.unwrapForScript(await this.execute(key, connId, statement, params, challengeId, database)),
254
254
  onLog: (level, text) => level === 'error' ? console.error('[db-script]', text) : console.log('[db-script]', text),
255
255
  onTimeout: () => {
256
256
  void sessionAdapter.close().catch(() => { });
@@ -118,11 +118,13 @@ export async function runScriptInChild(opts) {
118
118
  });
119
119
  });
120
120
  async function handleDbCall(method, args) {
121
- const [statement, params] = args;
121
+ const [statement, params, rawDatabase] = args;
122
+ // IPC JSON 序列化会把 undefined 变 null,归一回 undefined 保持与直连调用语义一致
123
+ const database = rawDatabase ?? undefined;
122
124
  if (method === 'query')
123
- return opts.dbQuery(statement, params);
125
+ return opts.dbQuery(statement, params, database);
124
126
  if (method === 'execute')
125
- return opts.dbExecute(statement, params);
127
+ return opts.dbExecute(statement, params, database);
126
128
  throw new DbToolError('INVALID_ARGUMENT', `未知 db 方法: ${method}`);
127
129
  }
128
130
  function errorOf(e) {
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * IPC 协议:
12
12
  * 父→worker {type:'run', code}
13
- * worker→父 {type:'db', reqId, method, args}
13
+ * worker→父 {type:'db', reqId, method, args} // args=[statement, params, database?],database 即跨库路由目标
14
14
  * 父→worker {type:'dbResult', reqId, ok, result, error}
15
15
  * worker→父 {type:'done', ok, result, error}
16
16
  */
@@ -28,11 +28,11 @@ async function runScript(code) {
28
28
  const pending = new Map();
29
29
  let reqSeq = 0;
30
30
 
31
- const dbCall = (method) => (statement, params) =>
31
+ const dbCall = (method) => (statement, params, database) =>
32
32
  new Promise((resolve, reject) => {
33
33
  const reqId = ++reqSeq;
34
34
  pending.set(reqId, { resolve, reject });
35
- process.send({ type: 'db', reqId, method, args: [statement, params] });
35
+ process.send({ type: 'db', reqId, method, args: [statement, params, database] });
36
36
  });
37
37
 
38
38
  const db = {
@@ -0,0 +1,154 @@
1
+ # dsh-db-tool 项目架构图
2
+
3
+ > 由 cbm(codebase-memory)索引数据绘制:843 节点 / 2702 边,模块分层见各图。
4
+ > 索引项目名:`E-Code-dsh-dsh-db-tool`。
5
+
6
+ ## 1. 总体分层架构
7
+
8
+ ```mermaid
9
+ flowchart TB
10
+ subgraph UI["客户端层(DSH 侧边栏)"]
11
+ CLIENT["client/client.js<br/>连接管理 · 对象树浏览 · SQL 控制台 · 授权面板"]
12
+ end
13
+
14
+ subgraph API["接入层"]
15
+ HTTP["lib/http<br/>HTTP API(13 条路由)"]
16
+ TOOL["lib/index.ts<br/>DatabaseManager 工具定义<br/>list_connections / query / execute /<br/>schema / preview / run_script"]
17
+ end
18
+
19
+ subgraph SVC["服务编排层"]
20
+ MGR["lib/manager.ts<br/>ro/rw 项目授权校验 · 适配器生命周期 · 事务编排"]
21
+ end
22
+
23
+ subgraph SEC["安全层"]
24
+ GUARD["lib/guard<br/>SQL/命令分类 · 危险操作识别 ·<br/>一次性 challenge 确认(5 分钟有效)"]
25
+ STORE["lib/store<br/>connections · grants · secrets(脱敏)·<br/>audit 审计 · normalize · io"]
26
+ SCRIPT["lib/script<br/>runner.ts + worker.cjs<br/>子进程沙箱(vm realm · 禁文件 · 60s 超时)"]
27
+ end
28
+
29
+ subgraph ADPT["适配器层 lib/adapters(8 库)"]
30
+ REG["index.ts<br/>kind → 工厂注册表 + driverMissingError"]
31
+ SQLSHARED["sql-shared/<br/>common.ts(标识符/分页/规范化)<br/>pg-like.ts(PostgreSQL + GaussDB 共享)"]
32
+ A1["mysql"]
33
+ A2["postgresql"]
34
+ A3["gaussdb<br/>vendor/gaussdb-pg 驱动加载"]
35
+ A4["sqlite"]
36
+ A5["redis"]
37
+ A6["mongodb"]
38
+ A7["oracle"]
39
+ A8["dmdb"]
40
+ TYPES["types.ts<br/>DatabaseAdapter 契约"]
41
+ end
42
+
43
+ subgraph DRV["驱动层"]
44
+ D1["pg / mysql2 / sqlite3 / redis<br/>mongodb / oracledb / dmdb"]
45
+ D2["vendor/gaussdb-pg<br/>openGauss-connector-nodejs 构建产物"]
46
+ end
47
+
48
+ CLIENT -->|HTTP| HTTP
49
+ CLIENT -.->|注入| TOOL
50
+ TOOL --> MGR
51
+ MGR --> GUARD
52
+ MGR --> STORE
53
+ MGR --> SCRIPT
54
+ MGR --> REG
55
+ REG --> SQLSHARED
56
+ REG --> A1 & A4 & A5 & A6 & A7 & A8
57
+ A2 --> SQLSHARED
58
+ A3 --> SQLSHARED
59
+ SQLSHARED --> TYPES
60
+ A1 & A2 & A3 & A4 & A5 & A6 & A7 & A8 -.-> DRV
61
+ SCRIPT -.->|db.query / db.execute 经完整 guard 链路| MGR
62
+ ```
63
+
64
+ ## 2. 请求时序(query 与危险操作确认流)
65
+
66
+ ```mermaid
67
+ sequenceDiagram
68
+ autonumber
69
+ participant U as 用户(聊天)
70
+ participant T as DatabaseManager 工具
71
+ participant M as lib/manager.ts
72
+ participant G as lib/guard
73
+ participant S as lib/store
74
+ participant A as 适配器(8 库)
75
+ participant DB as 数据库
76
+
77
+ U->>T: query/execute(conn_id, sql)
78
+ T->>S: 授权校验(项目路径 → 连接 → ro/rw)
79
+ alt 未授权
80
+ S-->>T: UNAUTHORIZED_PROJECT
81
+ else ro 授权 + 写操作
82
+ S-->>T: READ_ONLY(拒绝)
83
+ else 通过
84
+ T->>G: 语句分类
85
+ alt 危险操作(DDL/DML/维护命令)
86
+ G-->>T: NEEDS_CONFIRMATION + challengeId(一次性,5 分钟)
87
+ T-->>U: 说明语句与风险,等待明确同意
88
+ U->>T: 携同一 challengeId 重试
89
+ T->>G: 校验 challenge(绑定语句 hash)
90
+ end
91
+ T->>M: 执行
92
+ M->>A: query/execute(可选 database 跨库路由,pg/gaussdb)
93
+ A->>DB: 参数化 SQL(ro 会话级 READ ONLY 双保险)
94
+ DB-->>A: 结果
95
+ A-->>T: 规范化结果(DECIMAL→string 等)
96
+ T->>S: 审计记录
97
+ T-->>U: {columns, rows, rowCount} / {affectedRows, message}
98
+ end
99
+ ```
100
+
101
+ ## 3. 适配器体系(8 库 × 共享实现)
102
+
103
+ ```mermaid
104
+ flowchart LR
105
+ subgraph 契约
106
+ T["types.ts<br/>DatabaseAdapter / TableInfo / ColumnInfo<br/>DbKind = 8 种"]
107
+ end
108
+ subgraph 工厂注册
109
+ R["adapters/index.ts<br/>kind → lazy import 工厂<br/>缺失驱动 → driverMissingError(附构建提示)"]
110
+ end
111
+ subgraph SQL系共享
112
+ PG["pg-like.ts<br/>PostgreSQL + GaussDB<br/>pool · ro 会话 · 跨库路由 ·<br/>库→schema→表 三层浏览"]
113
+ CM["common.ts<br/>assertIdent · quoteIdent<br/>clampLimit/Offset · normalizeCell"]
114
+ end
115
+ subgraph 独立实现
116
+ MY["mysql"]
117
+ SQ["sqlite"]
118
+ RD["redis(SCAN 禁 KEYS)"]
119
+ MG["mongodb(强制显式 filter)"]
120
+ OR["oracle"]
121
+ DM["dmdb"]
122
+ end
123
+ PG --> CM
124
+ MY & SQ & RD & MG & OR & DM -.-> CM
125
+ R --> PG & MY & SQ & RD & MG & OR & DM
126
+ PG -.->|"gaussdb 差异仅驱动加载<br/>createRequire vendor/gaussdb-pg"| G["gaussdb/index.ts"]
127
+ T --- R
128
+ ```
129
+
130
+ ## 4. 模块分层职责(cbm layers 数据)
131
+
132
+ | 层 | 模块 | 特征 |
133
+ |---|---|---|
134
+ | entry | lib、script、e2e-mock | 仅出边(调用下游) |
135
+ | api | http | 含 13 条 HTTP 路由定义 |
136
+ | core | adapters(4 入 0 出)、store(12 入 0 出) | 高扇入核心,不依赖上层 |
137
+ | internal | manager(7 入 12 出)、tool、http | 编排枢纽 |
138
+ | leaf | guard | 仅入边,纯分类/判定 |
139
+
140
+ ## 5. GaussDB 专项链路
141
+
142
+ ```mermaid
143
+ flowchart TB
144
+ T["DatabaseManager(query/execute/schema/preview, database=...)"] --> M["manager"]
145
+ M --> A3["gaussdb/index.ts<br/>loadGaussDriver():createRequire<br/>vendor/gaussdb-pg/packages/pg(CJS)"]
146
+ A3 --> PL["sql-shared/pg-like.ts<br/>kind='gaussdb'"]
147
+ PL -->|poolConfig:host/port 8000/user/password/database/ssl| P["PgLikePool"]
148
+ PL -->|database 参数| PF["poolFor(db):按库缓存池(Navicat 式跨库)"]
149
+ PL -->|ro 模式| SRO["每个新连接 SET SESSION CHARACTERISTICS<br/>AS TRANSACTION READ ONLY(fail-closed)"]
150
+ P --> DB[("GaussDB 服务端<br/>openGauss 协议")]
151
+ ```
152
+
153
+ - 驱动构建:`scripts/build-gaussdb.sh` / `.ps1` → 克隆 openGauss-connector-nodejs → `vendor/gaussdb-pg/packages/pg`(gitignore)。
154
+ - 测试:`tests/adapters-sql/gaussdb.test.ts`(驱动加载/工厂,mock 离线)+ `pg-like.test.ts`(pg/gaussdb 共享行为)。
@@ -0,0 +1,309 @@
1
+ # dsh 插件安装/更新问题记录
2
+
3
+ - **记录时间**:2026-09-28
4
+ - **任务范围**:按 `G:\CLI\DSH插件包.txt` 的 12 个插件清单,对 dsh web profile 执行安装/更新
5
+ - **目标 profile**:`C:\Users\Sjc\.dsh\profiles\web`
6
+ - **环境**:Windows 11 + Git Bash;Node v24.19.0;dsh 起始版本 0.1.7-rc.1(任务中途升级至 0.1.7-rc.2);pnpm 双版本共存(`G:\Env\nodejs\pnpm` 12.3.4 / `C:\Users\Sjc\AppData\Roaming\npm\pnpm` 12.6.0);内网 registry `http://10.192.2.173:8081/repository/group-npm/`
7
+
8
+ ## 一、最终结果
9
+
10
+ | 项目 | 结果 |
11
+ |---|---|
12
+ | 版本达标 | **11/11**(清单第 12 个 `dsh-zh-thinking` 确认从未发布,跳过) |
13
+ | 服务状态 | dsh web 正常运行(PID 83020,监听 127.0.0.1:3080),11 个插件全部激活 |
14
+ | 附带修复 | ① 依赖残缺态恢复;② 3 条被覆写丢失的 MCP 配置恢复 + 拆块加固;③ dsh-db-tool 加载修复 |
15
+ | 遗留 | MongoDB 连接功能不可用(需上游修复宿主缺陷);`dsh-zh-thinking` 来源待确认 |
16
+
17
+ ---
18
+
19
+ ## 二、问题清单(按严重度)
20
+
21
+ | # | 问题 | 严重度 | 状态 |
22
+ |---|---|---|---|
23
+ | 1 | dsh-db-tool 插件加载失败(宿主 loader 缺陷) | 高 | 已修复(插件侧绕过)+ 上游待修 |
24
+ | 2 | MCP 配置被 mcp-manager 覆写丢失 3 条 | 高 | 已恢复 + 已加固 |
25
+ | 3 | `ERR_PNPM_UNUSED_PATCH` 阻断所有升级操作 | 高 | 已解决 |
26
+ | 4 | pnpm 崩溃 exit 3221226505 遗留依赖残缺态 | 中 | 已恢复(崩溃本身不可复现) |
27
+ | 5 | dsh-zh-thinking 不存在于任何 registry | 中 | 待确认 |
28
+ | 6 | better-sqlite3 构建失败(nodejs.org 被拦) | 中 | 已解决 |
29
+ | 7 | skill-explorer 宿主版本门槛不满足 | 中 | 已豁免(rc.2 后自然满足) |
30
+ | 8 | 诊断探针插件被 dsh 拒绝加载 | 低 | 已知规避 |
31
+ | 9 | mcp-intellij 未拉起 | 低 | 外部依赖(IDEA 未运行) |
32
+ | 10 | Git Bash 的 http_proxy 拦截本机 curl | 低 | 已知规避 |
33
+ | 11 | 清单记载的 rtk 路径错误 | 低 | 已澄清 |
34
+ | 12 | cordis.patch.yml 注释过期 | 低 | 已标注 |
35
+ | 13 | cpu-features 原生构建失败(既有问题) | 低 | 不阻塞 |
36
+ | 14 | GitHub 直装包 specifier 为浮动引用 | 低 | 已知风险 |
37
+
38
+ ---
39
+
40
+ ## 三、问题详情
41
+
42
+ ### 问题 1:dsh-db-tool 插件加载失败【高】
43
+
44
+ **现象**
45
+
46
+ 插件安装成功、版本正确(0.1.8),但启动日志始终出现:
47
+
48
+ ```
49
+ dsh: warning: 1 entry did not activate
50
+ db-tool (dsh-db-tool): failed to import
51
+ ```
52
+
53
+ 宿主 0.1.7-rc.1 与升级后的 0.1.7-rc.2 均复现;纯 Node 环境下导入该插件完全正常。
54
+
55
+ **根因(四层因果)**
56
+
57
+ 1. **宿主缺陷**:dsh 为实现 profile 感知解析,用 `node-addon-require-builtin` 深入 Node 内部模块并改写 CJS 的 `Module._resolveFilename`。每个请求都会经过 `dsh-app-boot/lib/index.js:1422`:
58
+
59
+ ```js
60
+ for (const searchPath of createRequire(parent).resolve.paths(name)) {
61
+ ```
62
+
63
+ 该行**无空值保护**(对比同类调用 `profile.ts:596` 有 `?? []`),在特定请求上抛:
64
+
65
+ ```
66
+ TypeError: createRequire.resolve.paths is not a function or its return value is not iterable
67
+ at ResolutionRouter.routeScoped (.../dsh-app-boot/lib/index.js:1422:58)
68
+ at ResolutionRouter.routePath (...:1554)
69
+ at Module.wrappedFilename [as _resolveFilename] (...:1771)
70
+ ```
71
+
72
+ 2. **为何是 mongodb 触发**:dsh 的 `selfReferenceName()` 只在包声明了 `exports` 字段时才短路解析。`mongodb@7.6.0` 是**无 exports 的老式布局**(132 个 js 文件),内部请求全部经过缺陷循环;而 `bson@7.3.3` 有 exports,不触发。纯 Node 下穷举 mongodb(132 文件 × 8 种子路径请求)的 `resolve.paths` 调用:**零异常**,证明包本身无问题。
73
+
74
+ 3. **为何是 db-tool 撞上**:其加载链 `dist/index.js → manager.js → guard/index.js:10 静态导入 ../adapters/mongodb/index.js → 顶层 import 'mongodb'`。即**加载插件必然加载 mongodb**——而 db-tool 的适配器本设计为惰性加载(`adapters/index.js` 中 8 个驱动全是 `() => import(...)`),是 guard 为取两个常量做的静态导入破坏了惰性。
75
+
76
+ 4. **为何看不到线索**:dsh 启动审计把加载异常吞掉,只打印 `failed to import`(源头 `app-boot` 的 `inactiveEntries`)。
77
+
78
+ **定位方法**:影子入口二分法——在同一 profile 挂 7 个探针入口(分别导入 db-tool 的各子模块),一次实验即锁定:不碰 mongodb 的 `store`/`adapters` 成功,`guard`/`manager`/`http`/`script` 全部失败。
79
+
80
+ **修复(插件侧绕过,三处最小改动)**
81
+
82
+ 1. 新增 `dist/adapters/mongodb/ops.js`:把 `DANGEROUS_OPS`、`READ_OPS` 两个纯数据集合拆出;
83
+ 2. `dist/guard/index.js` 改为从 `ops.js` 导入这两个常量;
84
+ 3. `dist/adapters/mongodb/index.js` 改为从 `ops.js` 转发导出(对外接口不变)。
85
+
86
+ **固化**:pnpm patch 官方机制 —— `patches/dsh-db-tool@0.1.8.patch`(3454 字节)+ `pnpm-workspace.yaml` 的 `patchedDependencies`,重装后自动应用。
87
+
88
+ **验证**:纯 Node 导入成功且不再加载 mongodb → 实例启动 11/11 激活 → 生产服务重启后 `GET /dsh-db-tool/api/connections` 返回 **200**(修复前 404,响应体 `{"ok":true,"data":[]}`)。
89
+
90
+ **遗留**:真正建立 MongoDB 连接时仍会加载 mongodb 包、仍会踩中同一缺陷,**MongoDB 连接功能暂不可用**(其余 6 种数据库正常)。彻底修复需上游给 1422 行加空值保护(建议反馈上游)。
91
+
92
+ ---
93
+
94
+ ### 问题 2:MCP 配置被 mcp-manager 覆写丢失【高】
95
+
96
+ **现象**
97
+
98
+ `cordis.patch.yml` 在 2026-09-28 08:51(dsh web 运行期间)被从约 82 行改写为 49 行:原本 4 条 MCP 挂载只剩 `mcp-cbmem` 一条,`mcp-intellij`(IDEA)、`mcp-laya`、`mcp-mcphub` 三条丢失;且 `mcp-cbmem` 的 `serverName` 被从 `cbmem` 改成 `cbm`。
99
+
100
+ **根因(读 `@xxxyz/dsh-mcp-manager@2.2.7/lib/index.js` 确认)**
101
+
102
+ - 该插件按"块"而非"条目"处理:`insertBlockRange()`(678-691 行)一旦在某 `- insert:` 块内匹配到目标 id,返回范围就是**从 `- insert:` 到下一个顶层 `- ` 的整块**;
103
+ - `removeEntryAll()`(713-721 行)把整块并入删除集;UI 的"编辑"(1074-1079 行)与"删除"(1160-1163 行)都会先删整块再(或不)追加;
104
+ - 因此**同块内的无标记条目会被连带删除**——4 条 MCP 同处一个 insert 块,误操作一次即全丢;
105
+ - 给手工条目补 `# dsh-mcp-manager:server:<id>` 标记也**不能免疫**(`markerRanges()` 657-677 行同样按整块计算)。
106
+
107
+ 这与历史记录中的同类事故(`G:\CLI\github-项目安装核查报告.md` 第 507-509 行)完全吻合。
108
+
109
+ **处置**
110
+
111
+ 1. 恢复:按历史原文补回丢失的 3 条(`mcp-intellij` / `mcp-laya` / `mcp-mcphub`),`dsh --profile web --dump-config` 验证 4 条全部加载;
112
+ 2. **加固**:把 4 条 MCP 拆成 **4 个独立 `- insert:` 块**,把"误删一条导致全部丢失"降级为"只丢一条";
113
+ 3. 备份:`.backup-20260928/cordis.patch.yml.before-mcp-restore`(恢复前)、`cordis.patch.yml.before-split`(拆块前)。
114
+
115
+ **遗留与建议**
116
+
117
+ - ⚠️ **不要在 dsh-mcp-manager 的 UI 里"编辑/删除"任何 MCP 条目**(新增/启用/禁用/重启是安全的);
118
+ - `mcp-cbmem` 的 serverName 仍为 `cbm`(工具前缀 `mcp__cbm__*`,原基线 `cbmem`);恢复时未改回以避免二次风险,如需修回只能手工改文件 + 重启。
119
+
120
+ ---
121
+
122
+ ### 问题 3:`ERR_PNPM_UNUSED_PATCH` 阻断升级【高】
123
+
124
+ **现象**:`pnpm add billion-context@0.1.163` 稳定失败:
125
+
126
+ ```
127
+ Error: ERR_PNPM_UNUSED_PATCH
128
+ × adding a new package
129
+ ╰─▶ The following patches were not used: billion-context@0.1.147
130
+ ```
131
+
132
+ **根因**:`pnpm-workspace.yaml` 的 `patchedDependencies` 声明了 `billion-context@0.1.147: patches/billion-context@0.1.147.patch`。一旦执行升级命令、0.1.147 退出依赖树,该 patch 立即"未被使用",pnpm 直接拒绝整个操作。**这不是崩溃、不是网络问题,而是版本的确定性阻塞**——换任何 pnpm 版本都会失败。
133
+
134
+ **处置**:为 0.1.163 重新生成 patch(新版 dist 是压缩格式,旧 patch 上下文无法落位,必须重新生成)→ 复制 patch 文件 → 修改 `pnpm-workspace.yaml` 的 `patchedDependencies` 指向 0.1.163 → **紧接**执行升级 add(切换与升级之间不插入其他 pnpm 命令,最小化不一致窗口)。
135
+
136
+ **说明**:该 patch 修复的不是安装问题,而是 billion-context 的隧道 DNS 校验:本机内网 DNS 不解析外网域名,导致其内置 deepseek 路由被 403 拒绝。补丁让 DNS 解析失败时对回环客户端放行(非回环仍拒绝,SSRF 防护不变)。
137
+
138
+ **验证**:patch 文件 sha256 与 lock 记录一致;`node_modules/billion-context/dist/index.js` 中 `ctx.clientLoopback` 出现 3 次(原 2 + 补丁 1);`catch{` 后紧跟补丁注释与 `if(ctx.clientLoopback)return{ok:true}`。
139
+
140
+ ---
141
+
142
+ ### 问题 4:pnpm 崩溃 exit 3221226505 遗留依赖残缺态【中】
143
+
144
+ **现象**:2026-09-28 08:12 一次 `pnpm add "billion-context@0.1.163"` 以 Windows 崩溃码 **3221226505(0xC0000409,fail-fast)** 退出,日志 76 字节无输出;其后 node_modules 残缺,任何 `dsh plugin` 命令都在 reconcile 阶段报 `cannot resolve profile bundle` 并异常退出。
145
+
146
+ **诊断实验**:临时目录 × {pnpm 12.3.4, 12.6.0} × {小包, billion-context} × {共享 store, 干净 store} 全矩阵 —— **全部 exit 0,崩溃不可复现**;同期生产日志(08:41)显示同一命令已变为稳定的 `exit 1`(即 ERR_PNPM_UNUSED_PATCH),说明崩溃是一次偶发的 Windows fail-fast,非确定性缺陷。
147
+
148
+ **规避**:所有 dsh/pnpm 命令统一用 PATH 前置强制走 pnpm 12.6.0:
149
+
150
+ ```bash
151
+ PATH="/c/Users/Sjc/AppData/Roaming/npm:$PATH" dsh plugin --profile web <args>
152
+ ```
153
+
154
+ (dsh 从 PATH 解析 pnpm,`operations.js:271` 有 `options.command ?? 'pnpm'`;已验证前置生效。)
155
+
156
+ **遗留**:崩溃根因未定位(无输出、不可复现),升级 pnpm 属合理规避但无直接证据证明根治。
157
+
158
+ ---
159
+
160
+ ### 问题 5:dsh-zh-thinking 不存在于任何 registry【中】
161
+
162
+ **现象**:清单记载 `dsh-zh-thinking@0.1.1` 且给出 npm 链接,但三方核实全部 404:
163
+
164
+ | 来源 | 结果 |
165
+ |---|---|
166
+ | 内网 Nexus(group-npm) | 404 |
167
+ | 公网 registry.npmjs.org | 404(`{"error":"Not found"}`) |
168
+ | npmjs 网页 | 本机 TLS 不可达(环境问题,非包问题) |
169
+
170
+ **结论**:该包从未发布或已下架/改名。**待你确认来源**。另有相似包 `@hytime/dsh-thinking-effort@0.3.5`(用途不同),如需请告知。
171
+
172
+ ---
173
+
174
+ ### 问题 6:better-sqlite3 构建失败【中】
175
+
176
+ **现象**:安装 `dsh-db-tool@0.1.8` 时其依赖 `better-sqlite3@^13.0.3` 的 `node-gyp rebuild` 失败 —— 需要从 `https://nodejs.org` 下载 node headers,本机网络返回 SSL `wrong version number`(被代理/防火墙拦截)。经测试 CCProxy(`http://10.192.2.117:8085`)**只放行 github.com,不放行 nodejs.org**(HTTP 000)。
177
+
178
+ **处置(方案 2 成功,仅 16 秒)**:使用本机已有的 node headers 强制源码编译:
179
+
180
+ ```bash
181
+ npm_config_nodedir=C:/nodedir npm_config_build_from_source=true \
182
+ PATH="/c/Users/Sjc/AppData/Roaming/npm:$PATH" dsh plugin --profile web add -E dsh-db-tool@0.1.8
183
+ ```
184
+
185
+ `C:\nodedir` 内已有完整 headers 与 node.lib;node-gyp 找到 VS2022 与 Python 3.13.15,编译通过。
186
+
187
+ **验证**:`require('better-sqlite3')` 加载成功、SQL 往返 `{"x":42}` 通过。注:该版本 binding.gyp 为 `type:'none'`(prebuildify 设计),运行时加载包内 `prebuilds/win32-x64.node`,无 build/Release 产物属正常。
188
+
189
+ ---
190
+
191
+ ### 问题 7:skill-explorer 宿主版本门槛不满足【中】
192
+
193
+ **现象**:`@linxin666/dsh-client-ui-skill-explorer@0.4.3` 声明 `dsh.engines.dsh = ">=0.1.7-rc.2"`,而当时宿主为 0.1.7-rc.1,被兼容性预检拒绝。
194
+
195
+ **处置**:按 dsh 官方机制授予精确版本豁免:
196
+
197
+ ```bash
198
+ dsh plugin --profile web allow-version '@linxin666/dsh-client-ui-skill-explorer@0.4.3' \
199
+ --dsh-version 0.1.7-rc.1 --accept-risk
200
+ ```
201
+
202
+ 记录持久化在 `compatibility.json`:`{"@linxin666/dsh-client-ui-skill-explorer@0.4.3": ["0.1.7-rc.1"]}`。功能实测正常。
203
+
204
+ **后续**:宿主升级到 rc.2 后门槛自然满足、**不再需要豁免**;旧豁免记录保留无副作用(未撤销以避免无谓变更)。
205
+
206
+ ---
207
+
208
+ ### 问题 8:诊断探针插件被 dsh 拒绝加载【低】
209
+
210
+ **现象**:凡是在插件顶层或 `apply()` 内**包装 `Module._resolveFilename` 或 `Module.prototype.require`** 的探针插件,自身都会 `failed to import`;甚至顶层 `import Module from 'node:module'` 也可能触发。普通探针(只做动态 import、读文件)正常。
211
+
212
+ **解释**:dsh 对模块加载链有自我保护/一致性校验,包装会破坏其内部假设。
213
+
214
+ **规避**:诊断时改用"影子入口二分法"(只做静态导入、不包装加载器)或"层外探针 + 延迟执行"。
215
+
216
+ ---
217
+
218
+ ### 问题 9:mcp-intellij 未拉起【低】
219
+
220
+ **现象**:服务启动期 IDEA MCP 反复 11 轮 `io.ktor.client.plugins.sse.SSEClientException: Connection refused`,之后放弃;`curl 127.0.0.1:64342` 返回 000。
221
+
222
+ **原因**:IDE 未运行(其 MCP Server 未监听 64342),非配置问题(stdio 拉起命令被反复执行、Java 进程反复启停可证)。**启动 IDEA 后即可用。**
223
+
224
+ ---
225
+
226
+ ### 问题 10:Git Bash 的 http_proxy 拦截本机 curl【低】
227
+
228
+ **现象**:在本机探测 `http://127.0.0.1:3080` 等地址时,Git Bash 环境的小写 `http_proxy=http://10.192.2.117:8085` 会拦截请求,返回统一 403,导致误判"端口无监听"。
229
+
230
+ **规避**:本机探测一律加 `--noproxy '*'`:
231
+
232
+ ```bash
233
+ curl -s --noproxy '*' -o /dev/null -w "%{http_code}" http://127.0.0.1:3080/
234
+ ```
235
+
236
+ ---
237
+
238
+ ### 问题 11:清单记载的 rtk 路径错误【低】
239
+
240
+ 清单写 `~/.local/bin/rtk.exe`,实际路径为 `G:\CLI\Rtk\rtk.exe`(PATH 可用)。`dsh-pwsh-rtk-rewrite` 按 `RTK_BIN || 'rtk'` 解析,**功能不受影响**,仅文档偏差。
241
+
242
+ ---
243
+
244
+ ### 问题 12:cordis.patch.yml 注释过期【低】
245
+
246
+ 文件第 30-37 行的 billion-context 注释仍称补丁文件为 `billion-context@0.1.147.patch`,而实际引用的已是 0.1.163。旧 patch 文件已保留(未删除)但不再被引用。建议后续手工更新注释。
247
+
248
+ ---
249
+
250
+ ### 问题 13:cpu-features 原生构建失败【低】
251
+
252
+ `dsh-pwsh-rtk-rewrite` 与 `dsh-db-tool` 两次安装中均出现 `cpu-features`(`ssh2` 的可选依赖)原生构建失败(同问题 6 的 nodejs.org 拦截),但 pnpm 容忍、主操作成功。**既有问题,非本次引入,不阻塞。**
253
+
254
+ ---
255
+
256
+ ### 问题 14:GitHub 直装包 specifier 为浮动引用【低】
257
+
258
+ `dsh-ssh-tunnel` 的依赖 specifier 为 `github:thirsty5034/dsh-ssh-tunnel#main`(跟随 main)。当前 lockfile 已锁死 commit `c88da56b...`(与远端 main 一致),但**非 frozen 安装可能漂移**。`dsh-restart-fab` 则锁定 commit `99cd34aa...`(与远端最新一致,无漂移风险)。
259
+
260
+ ---
261
+
262
+ ## 四、避坑与操作建议
263
+
264
+ 1. **改插件版本前,先对齐 patch 声明**:`pnpm-workspace.yaml` 的 `patchedDependencies` 必须与实际依赖树匹配,否则一律 `ERR_PNPM_UNUSED_PATCH`。
265
+ 2. **不要用 dsh-mcp-manager 的 UI 编辑/删除 MCP 条目**(会把整块删掉);手工编辑 `cordis.patch.yml` 更安全(现已拆成 4 块进一步降低影响面)。
266
+ 3. **安装/升级前先停 dsh web**(Windows 文件占用会导致 EPERM/EBUSY);升级后必须重启服务才生效。
267
+ 4. **统一用 pnpm 12.6.0**:`PATH="/c/Users/Sjc/AppData/Roaming/npm:$PATH" dsh plugin ...`(规避 12.3.4 的偶发 fail-fast)。
268
+ 5. **构建原生模块时用本地 headers**:`npm_config_nodedir=C:/nodedir npm_config_build_from_source=true`(本机到 nodejs.org 被拦)。
269
+ 6. **本机 HTTP 探测加 `--noproxy '*'`**(Git Bash 的 http_proxy 会拦截回环请求)。
270
+ 7. **改配置前先备份**:本次全程滚动快照在 `.dsh\profiles\web\.backup-20260928\`。
271
+ 8. 兼容性被拒时用官方豁免(`dsh plugin --profile web allow-version <pkg>@<ver> --dsh-version <ver> --accept-risk`),并记录豁免清单。
272
+
273
+ ---
274
+
275
+ ## 五、关键路径索引
276
+
277
+ | 用途 | 路径 |
278
+ |---|---|
279
+ | 改造前基线快照 | `C:\Users\Sjc\.dsh\profiles\web\.backup-20260928\snapshot-0\` |
280
+ | 最终快照(含修复) | `…\.backup-20260928\snapshot-final2\` |
281
+ | db-tool 修复说明 | `…\.backup-20260928\db-tool-修复说明.md` |
282
+ | MCP 恢复前备份 | `…\.backup-20260928\cordis.patch.yml.before-mcp-restore` |
283
+ | MCP 拆块前备份 | `…\.backup-20260928\cordis.patch.yml.before-split` |
284
+ | billion-context patch | `…\patches\billion-context@0.1.163.patch` |
285
+ | db-tool patch | `…\patches\dsh-db-tool@0.1.8.patch` |
286
+ | 宿主缺陷代码位置 | `…\@deepseek-ai\dsh\node_modules\@deepseek-ai\dsh-app-boot\lib\index.js:1422` |
287
+
288
+ **回滚方式**:用目标快照覆盖 `package.json` / `pnpm-workspace.yaml` / `pnpm-lock.yaml` / `cordis.patch.yml`(+ `patches/`),再执行 `dsh plugin --profile web install`。
289
+
290
+ ---
291
+
292
+ ## 六、待办与上游反馈建议
293
+
294
+ > 解决方案详见 [dsh插件问题解决方案-20260928.md](./dsh插件问题解决方案-20260928.md)。
295
+
296
+ **待你决策**
297
+
298
+ 1. `dsh-zh-thinking` 的来源(清单给了 npm 链接但三方 404);
299
+ 2. MongoDB 连接功能是否接受"暂不可用"(等上游修复),或要求我尝试给宿主 app-boot 打补丁(改动全局 npm 包,dsh 升级会被覆盖)。
300
+
301
+ **建议反馈上游(deepseek-harness)**
302
+
303
+ 1. `dsh-app-boot` 的 `routeScoped` 中 `createRequire(parent).resolve.paths(name)` 缺空值保护(对比 `profile.ts:596` 有 `?? []`),建议统一加固;同一模式在 rc.2 中仍有 5 处无保护;
304
+ 2. `selfReferenceName()` 对无 `exports` 字段的老式包会走完整路由路径,建议增加降级处理;
305
+ 3. 启动审计对插件加载异常只输出 `failed to import`,建议附带原始错误与堆栈(可加调试开关)。
306
+
307
+ **建议反馈 `@xxxyz/dsh-mcp-manager`**
308
+
309
+ `insertBlockRange` / `removeEntryAll` 的"整块删除"语义会把同块内非目标条目一并删除,建议改为按条目粒度定位(配合缩进解析),或在文档中明确警示。
@@ -0,0 +1,166 @@
1
+ # dsh 插件问题解决方案
2
+
3
+ - **日期**:2026-09-28
4
+ - **目标 profile**:`C:\Users\ASUS\.dsh\profiles\web`(原记录文档中的 `C:\Users\Sjc\...` 为当时的记录口径,本机实际用户目录为 `C:\Users\ASUS`)
5
+ - **原问题记录**:[dsh插件安装问题记录-20260928.md](./dsh插件安装问题记录-20260928.md)(问题现象、完整排查过程与全部 14 项问题清单见该文档)
6
+ - **环境**:Windows 11 + Git Bash;Node v24.19.0;dsh 0.1.7-rc.2;pnpm 12.6.0;内网 registry `http://10.192.2.173:8081/repository/group-npm/`
7
+
8
+ ---
9
+
10
+ ## 一、问题 1 根因官方定论(dsh-db-tool 加载失败)
11
+
12
+ ### 官方语义依据
13
+
14
+ Node.js 官方文档([Modules: CommonJS modules — require.resolve.paths(request)](https://nodejs.org/api/modules.html))明确规定:
15
+
16
+ > `require.resolve.paths(request)`
17
+ > Returns:
18
+ > - `<string[]> | null`
19
+ >
20
+ > Returns an array containing the paths searched during resolution of `request` or **`null` if the `request` string references a core module, for example `fs` or `node:fs`**, or **if the request is a relative or absolute path identifier**(即以 `/`、`./`、`../` 开头的请求).
21
+
22
+ 即:**当 request 引用 core module(`fs`、`node:fs` 等)或以 `/`、`./`、`../` 开头的路径时,`resolve.paths` 必然返回 `null`**——这不是边界情况,而是文档写明的正常返回值。
23
+
24
+ ### 缺陷位置
25
+
26
+ 原记录所指缺陷形态(无空值保护的 `resolve.paths` 迭代)在 `@deepseek-ai/dsh-app-boot/lib/index.js` 中有多处。**0.1.7-rc.2 实测现状**:
27
+
28
+ ```js
29
+ // 原始缺陷形态(无保护,for..of 对 null 迭代即抛 TypeError):
30
+ for (const searchPath of createRequire(parent).resolve.paths(name)) {
31
+ ```
32
+
33
+ - `L1422-1429`(`routeScoped` 内):**已于 2026-09-25 被打入一份 local hotfix**(fallback 到 `_nodeModulePaths`),语义上已挡住该路径的崩溃,原堆栈中的 `1422:58` 即修复前的这里;
34
+ - **`L1247`(`nativePackageDir`):与上述缺陷形态逐字相同、无保护,即本次修复对象**;
35
+ - 另有 `L1482`、`L2026`、`L3270` 三处同类无保护调用(属同类隐患,归上游 issue 一并加固,本次不动)。
36
+
37
+ 该形态**无空值保护**(对比同项目 `profile.ts:596` 的同类调用带 `?? []` 兜底)。凡 core module 或相对/绝对路径请求流经此处,对 `null` 做 `for...of` 迭代即抛出。
38
+
39
+ ### 原错误堆栈(摘自原记录文档)
40
+
41
+ ```
42
+ TypeError: createRequire.resolve.paths is not a function or its return value is not iterable
43
+ at ResolutionRouter.routeScoped (.../dsh-app-boot/lib/index.js:1422:58)
44
+ at ResolutionRouter.routePath (...:1554)
45
+ at Module.wrappedFilename [as _resolveFilename] (...:1771)
46
+ ```
47
+
48
+ ### 触发链(四层因果摘要)
49
+
50
+ 1. dsh 为实现 profile 感知解析,改写 CJS 的 `Module._resolveFilename`,**每个模块请求**都流经被改写解析路径上的 `resolve.paths` 调用(含本次缺陷行 `L1247`);
51
+ 2. `mongodb@7.6.0` 为**无 `exports` 字段的老式布局**(132 个 js 文件),`selfReferenceName()` 不短路,内部请求全部进入缺陷路径;请求 core module(如 `fs`)时 `resolve.paths` 返回 `null` → 崩溃(`bson@7.3.3` 有 exports,不触发);
52
+ 3. db-tool 的加载链 `dist/guard/index.js:10` 对 mongodb 适配器做了静态导入,**加载插件必然加载 mongodb**;
53
+ 4. dsh 启动审计吞掉原始异常,只打印 `failed to import`,无堆栈线索。
54
+
55
+ ---
56
+
57
+ ## 二、三层处置
58
+
59
+ ### ① 宿主最小修复(host-fixer 已执行完成,2026-09-28)
60
+
61
+ - 位置:`@deepseek-ai/dsh-app-boot/lib/index.js:1247`(`nativePackageDir`)
62
+ - 改法:`createRequire(parent).resolve.paths(name)` 后补 `?? []`(与同项目 `profile.ts:596` 的既有模式一致),一行最小改动,语义与 Node 官方文档的 `null` 返回约定对齐。
63
+ - **与既有 hotfix 的关系**:`L1422-1429` 已存在另一份 local hotfix(fallback `_nodeModulePaths`,2026-09-25 打入),本次未触碰,两者共存。
64
+ - **patch 留档**:`C:\Users\ASUS\.dsh\profiles\web\.backup-20260928\dsh-app-boot@0.1.7-rc.2-resolve-paths-fix.patch`(含修改前后对照与重放说明)。
65
+ - **重放说明**:该文件位于全局 npm 包 `@deepseek-ai/dsh` 内,**dsh 全局升级会覆盖此修改**。每次升级 dsh 后需重放 patch(含 2026-09-25 的 L1422 那份),直至上游根治版本发布。
66
+ - **产品化更新(2026-09-28)**:上述两处宿主修复已并入插件分发流程——其他电脑装好插件后运行 `npm run patch:dsh`(macOS/Linux 用 `patch:dsh:sh`)即可一键修复两处(`patches/dsh-app-boot-route-scoped-hotfix.patch` 含双 hunk;幂等,已打过旧版单处补丁的机器只补缺的那处;git apply 与内建替换双路径产出逐字节一致)。详见 README「Troubleshooting」。
67
+
68
+ ### ② 插件侧 patch:保持不变
69
+
70
+ - `patches/dsh-db-tool@0.1.8.patch`(3454 字节):将 `DANGEROUS_OPS` / `READ_OPS` 两个纯数据集合拆入 `dist/adapters/mongodb/ops.js`,使 `guard/index.js` 的静态导入不再牵连 mongodb → 插件激活不再踩中宿主缺陷。
71
+ - 该 patch 解决的是"插件能否激活";宿主缺陷未根治前,**实际建立 MongoDB 连接仍会加载 mongodb 并触发同一缺陷**。宿主修复(①)落地后两者配合,MongoDB 功能才能完整恢复。
72
+ - 升级 dsh-db-tool 版本时按官方流程重新生成 patch(见下文第四节)。
73
+
74
+ ### ③ 上游根治(issue 草稿另由专人撰写)
75
+
76
+ - 反馈目标:[deepseek-ai/deepseek-harness](https://github.com/deepseek-ai/deepseek-harness) 的 `packages/boot/app-boot`。
77
+ - 核心诉求:给 `routeScoped` 中的 `resolve.paths` 调用补空值保护;同一无保护模式在 rc.2 中仍有多处(原记录文档第六节列有 5 处)。
78
+ - 配套建议:`selfReferenceName()` 对无 `exports` 的老式包增加降级处理;启动审计附带原始错误与堆栈。
79
+
80
+ ---
81
+
82
+ ## 三、修复验证标准
83
+
84
+ 宿主修复后依次满足以下三条,问题 1 方可判定关闭:
85
+
86
+ 1. **插件全激活**:dsh web 重启后 **11/11 插件激活**,启动日志无 `failed to import`;
87
+ 2. **API 可达**:`GET /dsh-db-tool/api/connections` 返回 **200**;
88
+ 3. **真实连接**:实际建立一次 MongoDB 测试连接**成功**(此前插件侧 patch 已达成 1、2 两条,但第 3 条一直失败——此项是宿主修复生效的直接判据)。
89
+
90
+ ---
91
+
92
+ ## 四、pnpm patch 官方升级流程
93
+
94
+ 引用:[pnpm 官方文档 — pnpm patch](https://pnpm.io/cli/patch)。当被 patch 的包需要升级版本时,按以下顺序操作(以 billion-context 从 0.1.147 → 0.1.163、dsh-db-tool 升级为例):
95
+
96
+ 1. `pnpm patch <pkg>@<新版本>` —— 按新版本重新生成可编辑副本,在新副本上重做补丁修改(新版 dist 若为压缩格式,旧 patch 上下文无法落位,**必须重新生成**);
97
+ 2. 用 `--patch-dir` 输出的目录内容生成/复制新 patch 文件到 `patches/`;
98
+ 3. 修改 `pnpm-workspace.yaml` 的 `patchedDependencies`,将声明指向新版本;
99
+ 4. **紧接**执行 `pnpm add <pkg>@<新版本>` —— 切换 patch 声明与升级之间**不插入任何其他 pnpm 命令**,否则旧版本退出依赖树的瞬间 patch 声明失配,触发 `ERR_PNPM_UNUSED_PATCH` 直接拒绝整个操作。
100
+
101
+ > 本机实证见原记录文档问题 3:0.1.147 声明未对齐时,`pnpm add billion-context@0.1.163` 稳定失败;按上述流程操作后通过。
102
+
103
+ ---
104
+
105
+ ## 五、原生模块本地 headers 构建(node-gyp nodedir)
106
+
107
+ **适用场景**:本机到 `https://nodejs.org` 被网络策略拦截(SSL `wrong version number`),`better-sqlite3`、`cpu-features` 等 node-gyp 原生构建下载 headers 失败时。
108
+
109
+ 依据 node-gyp 官方参数([npmjs — node-gyp](https://www.npmjs.com/package/node-gyp)):`--nodedir` 指定本地 node headers 目录;经 npm 配置传递即 `npm_config_nodedir`。本机验证可行的完整命令(沿用原记录文档问题 6 的成功方案,实测 16 秒完成):
110
+
111
+ ```bash
112
+ npm_config_nodedir=C:/nodedir npm_config_build_from_source=true \
113
+ PATH="/c/Users/ASUS/AppData/Roaming/npm:$PATH" dsh plugin --profile web add -E <包名>@<版本>
114
+ ```
115
+
116
+ - `C:\nodedir` 内需有完整 headers 与 `node.lib`;
117
+ - `npm_config_build_from_source=true` 强制源码编译(跳过 prebuild 下载);
118
+ - PATH 前置用于强制 pnpm 12.6.0(规避 12.3.4 的偶发 fail-fast,见原记录文档问题 4)。
119
+
120
+ ---
121
+
122
+ ## 六、dsh-zh-thinking 结论与替代品
123
+
124
+ **核实结论**:`dsh-zh-thinking@0.1.1` 经三方核查(内网 Nexus group-npm、公网 registry.npmjs.org、npmjs 网页)**全部 404**——该包从未发布或已下架,任何安装方式均不可行。
125
+
126
+ **功能相近替代品**(均已核实存在于 npm):
127
+
128
+ | 包名 | 版本 | 链接 |
129
+ |---|---|---|
130
+ | `dsh-thinking-levels` | 3.4.3 | https://www.npmjs.com/package/dsh-thinking-levels |
131
+ | `@hytime/dsh-thinking-effort` | 0.3.5 | https://www.npmjs.com/package/@hytime/dsh-thinking-effort |
132
+
133
+ **待用户决策项 ①**:`dsh-zh-thinking` 从插件清单中**移除**,还是**改用**上述替代品之一?
134
+
135
+ **待用户决策项 ②**:`mcp-cbmem` 的 `serverName` 当前为 `cbm`(工具前缀 `mcp__cbm__*`),与原基线 `cbmem` 不一致。是否改回 `cbmem`?如需修回,只能手工编辑 `cordis.patch.yml` + 重启(且不可使用 dsh-mcp-manager UI 编辑,见原记录文档问题 2 的整块删除风险)。
136
+
137
+ ---
138
+
139
+ ## 七、context7/mcphub MCP 待修复项
140
+
141
+ - **现象**:`mcp-context7` 与 `mcp-mcphub` 两个 MCP server 当前 `tools: 0`——工具未同步、资源接口拒绝。
142
+ - **修复方式**:通过 MCP 管理器对这两个 server 各执行一次**重启**,即可重新同步工具列表。
143
+ - **兜底路径**(重启无效时):直接使用 context7.com 公开 llms.txt API,已验证可用,形如:
144
+
145
+ ```
146
+ https://context7.com/<owner>/<repo>/llms.txt?topic=<topic>&tokens=<n>
147
+ ```
148
+
149
+ ---
150
+
151
+ ## 参考链接
152
+
153
+ | 资料 | 链接 |
154
+ |---|---|
155
+ | Node.js 官方文档(`resolve.paths` 返回 `null` 语义) | https://nodejs.org/api/modules.html |
156
+ | pnpm 官方文档(patch 升级流程) | https://pnpm.io/cli/patch |
157
+ | node-gyp(`--nodedir` / `npm_config_nodedir`) | https://www.npmjs.com/package/node-gyp |
158
+ | deepseek-harness 上游仓库(`packages/boot/app-boot`) | https://github.com/deepseek-ai/deepseek-harness |
159
+ | dsh-thinking-levels(替代品) | https://www.npmjs.com/package/dsh-thinking-levels |
160
+ | @hytime/dsh-thinking-effort(替代品) | https://www.npmjs.com/package/@hytime/dsh-thinking-effort |
161
+
162
+ ---
163
+
164
+ > **执行状态(2026-09-28 更新)**:宿主 `L1247` 修复已完成并重启验证——dsh web 正常监听 127.0.0.1:3080;`GET /dsh-db-tool/api/connections` 返回 200(PostgreSQL 连接 c-muh7giif-gzbl 在列);`POST /dsh-db-tool/api/test-draft`(mongodb://127.0.0.1:27017/test)由 mongodb@7.6.0 驱动真实加载并返回结构化驱动错误(本机无 MongoDB 实例,`ECONNREFUSED` 为预期),不再 500/不再使插件崩溃。
165
+ >
166
+ > **因果边界说明**:主崩溃路径(原始堆栈中的 `routeScoped`,即 L1422 区域)在 2026-09-25 打入既有 hotfix 后即已挡住——hotfix 之后、本次 L1247 加固之前实测该接口已返回结构化错误;本次 L1247 修复属**同型隐患的防御性加固**(`nativePackageDir` 同处解析链上,加固前任何经它的 core-module 请求仍会崩)。原始缺陷完全未修时(两处修复之前)此操作必崩。插件激活数以 dsh web 启动日志为准(stdout 进控制台窗口,无文件日志可直接核对)。上游 issue 草稿见 `docs/上游issue草稿-20260928.md`。
package/docs/install.md CHANGED
@@ -43,6 +43,8 @@ rm -rf ../gaussdb-pg && cp -r packages/pg ../gaussdb-pg
43
43
 
44
44
  产物为 `vendor/gaussdb-pg`(CJS,结构同 `pg`:`{ Pool, Client, ... }`)。
45
45
 
46
+ > **Node 版本要求**:≥ 22.12。构建脚本会补装 `p-limit@7`(纯 ESM,源码 package.json 未声明),依赖 Node 原生 require(esm);更旧 Node 加载 GaussDB 驱动会抛 `ERR_REQUIRE_ESM`(其余 7 库不受影响)。
47
+
46
48
  ## oracledb approve-scripts
47
49
 
48
50
  `oracledb` 安装时需要运行安装脚本编译/下载二进制。仓库 `package.json` 已配置白名单:
@@ -71,5 +73,6 @@ rm -rf ../gaussdb-pg && cp -r packages/pg ../gaussdb-pg
71
73
  ## 常见问题
72
74
 
73
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」。
74
77
  - **API 全部报错**:host 端 webServer 须注册前缀 `/dsh-db-tool/api`(loopback only);前端与 host 前缀不一致是最常见的接错方式。
75
78
  - **连接测试通过但查询报 UNAUTHORIZED_PROJECT**:当前项目路径未被授权,在「授权」面板为该项目路径授 ro 或 rw。
@@ -0,0 +1,133 @@
1
+ # 上游 Issue 草稿(2026-09-28)
2
+
3
+ > 状态:**草稿,未提交**。仅供审阅后人工提交。
4
+ > 作者:issue-drafter(teammate)
5
+ > 环境:Windows 11 · Node v24.19.0 · dsh 0.1.7-rc.1 / rc.2 均复现
6
+ > 素材来源:dsh-db-tool 插件加载失败排查过程(详见 `docs/dsh插件安装问题记录-20260928.md`)
7
+
8
+ ---
9
+
10
+ ## 草稿一:deepseek-harness 宿主缺陷(app-boot 模块解析崩溃)
11
+
12
+ - 上游仓库:https://github.com/deepseek-ai/deepseek-harness
13
+ - 语言习惯:英文正文 + 中文备注
14
+ - 提交入口:https://github.com/deepseek-ai/deepseek-harness/issues/new
15
+
16
+ ### 建议标题
17
+
18
+ ```
19
+ [app-boot] TypeError: createRequire.resolve.paths is not iterable — legacy packages without `exports` (e.g. mongodb@7.6.0) crash Module._resolveFilename and break plugin loading
20
+ ```
21
+
22
+ ### 正文(可整段复制提交)
23
+
24
+ **Environment**
25
+
26
+ - OS: Windows 11
27
+ - Node.js: v24.19.0
28
+ - dsh: 0.1.7-rc.1 and 0.1.7-rc.2 (both affected)
29
+
30
+ **Summary**
31
+
32
+ Plugin loading fails with `failed to import` and no actionable error whenever a plugin dependency uses a legacy package layout without an `exports` field. Root cause is an unguarded call to `require.resolve.paths()` inside `ResolutionRouter.routeScoped`, which returns `null` for core-module requests per the [official Node.js docs](https://nodejs.org/api/modules.html).
33
+
34
+ **Crash location**
35
+
36
+ `@deepseek-ai/dsh-app-boot/lib/index.js` line 1422 (`ResolutionRouter.routeScoped`):
37
+
38
+ ```js
39
+ for (const searchPath of createRequire(parent).resolve.paths(name)) {
40
+ ```
41
+
42
+ `require.resolve.paths(request)` returns **null** for:
43
+
44
+ - core modules (`fs`, `node:fs`, …)
45
+ - requests beginning with `/`, `./`, or `../`
46
+
47
+ This line has no null guard, so a core-module request flowing through the rewritten `Module._resolveFilename` throws:
48
+
49
+ ```
50
+ TypeError: createRequire.resolve.paths is not a function or its return value is not iterable
51
+ at ResolutionRouter.routeScoped (.../dsh-app-boot/lib/index.js:1422:58)
52
+ at ResolutionRouter.routePath (...:1554)
53
+ at Module.wrappedFilename [as _resolveFilename] (...:1771)
54
+ ```
55
+
56
+ Note: the sibling call in the same project (`profile.ts:596`) already guards with `?? []` — this line was missed.
57
+
58
+ **Trigger surface**
59
+
60
+ - `mongodb@7.6.0` ships a legacy layout with no `exports` field (132 js files). All of its internal requests route through the rewritten `Module._resolveFilename`; the first core-module request crashes.
61
+ - Packages that declare `exports` (e.g. `bson@7.3.3`) do **not** trigger the bug, because modern resolution bypasses this path.
62
+ - Impact: any plugin depending on `mongodb` (in our case `dsh-db-tool`) fails to load (`failed to import`), and MongoDB connectivity is completely unavailable.
63
+
64
+ **Suggested fix**
65
+
66
+ ```js
67
+ for (const searchPath of createRequire(parent).resolve.paths(name) ?? []) {
68
+ ```
69
+
70
+ We also audited the unmodified rc.2 sources and found **3 more unguarded `resolve.paths()` calls** (bundled `lib/index.js`, approximately lines 1482, 2026, 3270) — recommend hardening all of them in one pass.
71
+
72
+ **Additional observations (same area, lower severity)**
73
+
74
+ 1. `selfReferenceName()` short-circuits self-reference resolution only for packages that declare an `exports` field. Legacy packages without `exports` fall through to the full routing path; consider adding a fallback for them.
75
+ 2. The plugin startup audit only reports `failed to import` (originating from app-boot's `inactiveEntries`), swallowing the original error and stack trace. We could only localize this bug with a "shadow entry bisection" technique (7 probe entries on the same profile, each importing a submodule one by one). Please consider surfacing the original error and adding a debug flag.
76
+
77
+ > **中文摘要**:`dsh-app-boot` 第 1422 行对 `require.resolve.paths()` 的返回值无空值保护,core 模块请求直接抛 TypeError;无 `exports` 的老式包(mongodb@7.6.0)内部请求全部流经被改写的 `Module._resolveFilename`,导致依赖它的插件(dsh-db-tool)加载失败且 MongoDB 功能不可用。建议修复为 `resolve.paths(name) ?? []`,并加固同类另外 3 处无保护调用(bundled `lib/index.js` 约 L1482/L2026/L3270)。附带两个观察:`selfReferenceName()` 对无 `exports` 包缺少降级处理;插件审计日志吞掉原始错误与堆栈,建议输出原始错误并加调试开关。
78
+
79
+ ---
80
+
81
+ ## 草稿二:@xxxyz/dsh-mcp-manager 配置覆写缺陷(按块误删条目)
82
+
83
+ - 包:`@xxxyz/dsh-mcp-manager@2.2.7`
84
+ - 语言习惯:中文正文
85
+ - 提交入口:https://github.com/xxxyz/dsh-mcp-manager/issues/new
86
+
87
+ ### 建议标题
88
+
89
+ ```
90
+ 配置编辑/删除按「块」而非「条目」粒度处理,同一 `- insert:` 块内的无标记条目被连带删除
91
+ ```
92
+
93
+ ### 正文(可整段复制提交)
94
+
95
+ **环境**
96
+
97
+ - 包版本:`@xxxyz/dsh-mcp-manager@2.2.7`
98
+ - 平台:Windows 11 · Node v24.19.0 · dsh 0.1.7-rc.2
99
+
100
+ **摘要**
101
+
102
+ 插件通过 patch 文件向宿主配置追加 `- insert:` 块。经阅读 `lib/index.js` 确认根因:**所有定位与删除操作都以「块」为单位,而不是以「条目」为单位**。同一块内由用户手工添加的、未被插件标记的条目,会在一次编辑或删除操作中被连带删除。
103
+
104
+ **根因(源码定位,`lib/index.js`)**
105
+
106
+ | 位置 | 函数 | 行为 |
107
+ |---|---|---|
108
+ | 678–691 行 | `insertBlockRange()` | 匹配到目标 id 后,返回从 `- insert:` 行到下一个顶层 `- ` 行的**整块**范围 |
109
+ | 713–721 行 | `removeEntryAll()` | 将 `insertBlockRange()` 返回的**整块**并入删除集 |
110
+ | 1074–1079 行 | UI「编辑」 | 先删整块再重写 |
111
+ | 1160–1163 行 | UI「删除」 | 先删整块 |
112
+ | 657–677 行 | `markerRanges()` | 插件自身标记的范围同样按整块计算 |
113
+
114
+ **实际事故还原**
115
+
116
+ - 事故场景:4 条 MCP 配置位于同一个 `- insert:` 块内,用户在 UI 中编辑/删除其中 1 条,结果 4 条全部丢失。
117
+ - 文件证据:`cordis.patch.yml` 从 82 行被改写为 49 行,最终仅剩 1/4 条 MCP。
118
+ - 免疫尝试失败:给手工条目补上 `# dsh-mcp-manager:server:<id>` 标记注释也**不能**避免连带删除——`markerRanges()`(657–677 行)按整块计算标记范围,块内任何操作仍波及全部条目。
119
+
120
+ **建议修复**
121
+
122
+ 按「条目」粒度定位(解析条目缩进层级,定位单条 `- name:` 起始范围)替代整块删除;若短期内不便重构,至少在文档与 UI 中明确警示「同一 `- insert:` 块内的多条配置会被一起删除」。
123
+
124
+ > **中文摘要**:`insertBlockRange()` / `removeEntryAll()` / UI 编辑与删除均按整块处理,同块内无标记(或甚至有标记,`markerRanges()` 同样按块计算)的条目被连带删除,曾导致 4 条 MCP 配置一次操作全丢(cordis.patch.yml 82 行 → 49 行)。建议改为条目级定位删除,或在文档/UI 明确警示。
125
+
126
+ ---
127
+
128
+ ## 附:提交核对清单(提交前人工确认)
129
+
130
+ - [ ] 草稿一:确认 deepseek-harness 仓库 issue 模板要求(若有模板字段需补填)
131
+ - [ ] 草稿一:复现版本号与堆栈行号以最终提交时的 rc 版本为准
132
+ - [ ] 草稿二:确认 @xxxyz/dsh-mcp-manager 的 issue 入口(仓库地址按 npm 包实际 homepage 核对)
133
+ - [ ] 两份草稿均为独立 issue,不互相引用提交(可在正文 environment 处交叉提及)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-db-tool",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
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",
@@ -1,5 +1,7 @@
1
- # DSH ResolutionRouter routeScoped hotfix (upstream bug: createRequire(parent).resolve.paths(name)
1
+ # DSH dsh-app-boot resolve.paths hotfix (upstream bug: createRequire(parent).resolve.paths(name)
2
2
  # returns null for core-module names like "punycode", breaking hoisted profile installs).
3
+ # Covers BOTH unguarded for..of sites in 0.1.7-rc.2: routeScoped() (@@ -1419) and
4
+ # nativePackageDir() (@@ -1244).
3
5
  # Target-Version: 0.1.7-rc.2
4
6
  # Apply manually: git apply -p1 -d <dsh-app-boot package root> <this file>
5
7
  # Or one command: npm run patch:dsh (scripts/apply-dsh-hotfix.ps1)
@@ -24,3 +26,15 @@ index 8f6041d..89a138e 100644
24
26
  if (!searchPath.startsWith(layer.localPrefix)) break;
25
27
  localSearchPaths.push(searchPath);
26
28
  const candidate = localPackageCandidate(searchPath, name, flavor);
29
+ @@ -1244,7 +1244,10 @@
30
+ return treePrefix + (end < 0 ? rest : rest.slice(0, end));
31
+ }
32
+ function nativePackageDir(parent, name) {
33
+ - for (const searchPath of createRequire(parent).resolve.paths(name)) {
34
+ + /* local hotfix: nativePackageDir: resolve.paths returns null for core-module names
35
+ + (fs, node:fs, ...); an empty list means no local candidate, matching the
36
+ + guard in profile.ts:596. */
37
+ + for (const searchPath of createRequire(parent).resolve.paths(name) ?? []) {
38
+ const candidate = join(searchPath, name);
39
+ if (existsSync(join(candidate, "package.json"))) return candidate;
40
+ }
@@ -6,8 +6,13 @@ export declare const HOTFIX_MARKER: string;
6
6
  export declare const BOOT_INDEX_REL: string;
7
7
  export declare const BACKUP_SUFFIX: string;
8
8
  export declare const ORIGINAL_LINE: string;
9
+ export declare const ORIGINAL_NPD_BLOCK: string;
10
+ export declare const NPD_FIXED_LINE: string;
11
+ export declare const FIXED_NPD_BLOCK: string;
12
+ export declare const HOTFIX_BLOCK: string;
9
13
 
10
14
  export declare function isApplied(content: string): boolean;
15
+ export declare function isNpdApplied(content: string): boolean;
11
16
  export declare function applyToContent(content: string): {
12
17
  ok: boolean;
13
18
  reason?: string;
@@ -1,11 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * DSH ResolutionRouter hotfix —— 核心逻辑(纯 Node、零依赖、可被测试 import)。
3
+ * DSH dsh-app-boot resolve.paths hotfix —— 核心逻辑(纯 Node、零依赖、可被测试 import)。
4
4
  *
5
- * 修复的 bug:@deepseek-ai/dsh-app-boot 的 routeScoped() 直接对
6
- * `createRequire(parent).resolve.paths(name)` 做 for..of,而 Node 对 core-module
7
- * 同名包(punycode 等)返回 null,导致 hoisted profile 下 npm 安装插件导入失败
8
- * (TypeError: Cannot read properties of null)。
5
+ * 修复的 bug:@deepseek-ai/dsh-app-boot@0.1.7-rc.2 对
6
+ * `createRequire(parent).resolve.paths(name)` 的 for..of 无保护,而 Node 对
7
+ * core-module 同名包(punycode 等)返回 null,for..of null 直接抛 TypeError,
8
+ * 导致 hoisted profile 下 npm 安装插件导入失败。rc.2 中共两处同型缺陷:
9
+ * 1. routeScoped()(L1419 区域)—— 症状直接暴露;
10
+ * 2. nativePackageDir()(L1246 区域)—— 第二处,症状相同,一并修复。
9
11
  *
10
12
  * 用法:node scripts/hotfix-core.mjs [--dsh-root <dir>] [--patch-file <file>]
11
13
  * [--force] [--revert] [--dry-run]
@@ -29,8 +31,27 @@ export const BACKUP_SUFFIX = '.bak-hotfix';
29
31
  export const ORIGINAL_LINE =
30
32
  '\t\tfor (const searchPath of createRequire(parent).resolve.paths(name)) {';
31
33
 
34
+ /** 原始(未打补丁)nativePackageDir 块 —— rc.2 中第二处同型缺陷(函数签名行做锚,保证匹配唯一)。 */
35
+ export const ORIGINAL_NPD_BLOCK = [
36
+ 'function nativePackageDir(parent, name) {',
37
+ '\tfor (const searchPath of createRequire(parent).resolve.paths(name)) {',
38
+ ].join('\n');
39
+
40
+ /** nativePackageDir 修复后的 for 行(同时是幂等判据:`?? []` 形态全文件唯一)。 */
41
+ export const NPD_FIXED_LINE =
42
+ '\tfor (const searchPath of createRequire(parent).resolve.paths(name) ?? []) {';
43
+
44
+ /** nativePackageDir 热修复块 —— 与 patches/*.patch 第二个 hunk 的 + 行逐字节一致。 */
45
+ export const FIXED_NPD_BLOCK = [
46
+ 'function nativePackageDir(parent, name) {',
47
+ '\t/* local hotfix: nativePackageDir: resolve.paths returns null for core-module names',
48
+ '\t (fs, node:fs, ...); an empty list means no local candidate, matching the',
49
+ '\t guard in profile.ts:596. */',
50
+ NPD_FIXED_LINE,
51
+ ].join('\n');
52
+
32
53
  /** 热修复代码块 —— 与 patches/dsh-app-boot-route-scoped-hotfix.patch 的 + 行一致。 */
33
- const HOTFIX_BLOCK = [
54
+ export const HOTFIX_BLOCK = [
34
55
  '\t\t/* local hotfix: resolve.paths returns null for core-module names (e.g. "punycode"),',
35
56
  '\t\t which made the for..of throw TypeError and broke every hoisted profile install',
36
57
  '\t\t whose dependency tree requires such an npm package. Fall back to the standard',
@@ -46,28 +67,65 @@ export function isApplied(content) {
46
67
  return content.includes(HOTFIX_MARKER);
47
68
  }
48
69
 
70
+ /** nativePackageDir 修复是否已在(幂等判据:`?? []` 形态全文件唯一)。 */
71
+ export function isNpdApplied(content) {
72
+ return content.includes(NPD_FIXED_LINE);
73
+ }
74
+
75
+ /** 单处修复的三态应用:already(已在)/ applied(本次注入)/ missing(原始行不在)。 */
76
+ function applyOne(content, isDone, original, fixed) {
77
+ if (isDone(content)) return { content, status: 'already' };
78
+ if (content.includes(original)) {
79
+ return { content: content.replace(original, fixed), status: 'applied' };
80
+ }
81
+ return { content, status: 'missing' };
82
+ }
83
+
49
84
  /**
50
- * 在文件内容上应用热修复(幂等:已应用时返回 already-applied)。
85
+ * 在文件内容上应用全部热修复(幂等)。
86
+ * 两处缺陷(routeScoped / nativePackageDir)独立判定、独立注入:
87
+ * - 任一「本次注入」→ ok:true;
88
+ * - 全部缺失原始行 → target-line-not-found(可能不是目标文件);
89
+ * - 其余(全部已在)→ already-applied。
90
+ * 已打 FIX1 的机器只会补上 FIX2,反之亦然。
51
91
  * @returns {{ ok: boolean, reason?: string, content: string }}
52
92
  */
53
93
  export function applyToContent(content) {
54
- if (isApplied(content)) return { ok: false, reason: 'already-applied', content };
55
- if (!content.includes(ORIGINAL_LINE)) {
56
- return { ok: false, reason: 'target-line-not-found', content };
94
+ const r1 = applyOne(content, isApplied, ORIGINAL_LINE, HOTFIX_BLOCK);
95
+ const r2 = applyOne(r1.content, isNpdApplied, ORIGINAL_NPD_BLOCK, FIXED_NPD_BLOCK);
96
+
97
+ if (r1.status === 'applied' || r2.status === 'applied') {
98
+ return { ok: true, content: r2.content };
99
+ }
100
+ if (r1.status === 'missing' && r2.status === 'missing') {
101
+ return { ok: false, reason: 'target-line-not-found', content: r2.content };
57
102
  }
58
- return { ok: true, content: content.replace(ORIGINAL_LINE, HOTFIX_BLOCK) };
103
+ return { ok: false, reason: 'already-applied', content: r2.content };
59
104
  }
60
105
 
61
106
  /**
62
107
  * 还原热修复(优先用内建块反替换;调用方在磁盘层面应优先用 .bak-hotfix 备份)。
108
+ * 注意:内建反替换只认分发版英文注释块;手工打过的非标准块(如中文注释)
109
+ * 反替换不到,届时请用 .bak-hotfix 备份整体还原。
63
110
  * @returns {{ ok: boolean, reason?: string, content: string }}
64
111
  */
65
112
  export function revertContent(content) {
66
- if (!isApplied(content)) return { ok: false, reason: 'not-applied', content };
67
- if (!content.includes(HOTFIX_BLOCK)) {
68
- return { ok: false, reason: 'hotfix-block-not-found', content };
113
+ let changed = false;
114
+ let result = content;
115
+
116
+ if (isApplied(result)) {
117
+ if (!result.includes(HOTFIX_BLOCK)) {
118
+ return { ok: false, reason: 'hotfix-block-not-found', content };
119
+ }
120
+ result = result.replace(HOTFIX_BLOCK, ORIGINAL_LINE);
121
+ changed = true;
122
+ }
123
+ if (result.includes(FIXED_NPD_BLOCK)) {
124
+ result = result.replace(FIXED_NPD_BLOCK, ORIGINAL_NPD_BLOCK);
125
+ changed = true;
69
126
  }
70
- return { ok: true, content: content.replace(HOTFIX_BLOCK, ORIGINAL_LINE) };
127
+ if (!changed) return { ok: false, reason: 'not-applied', content };
128
+ return { ok: true, content: result };
71
129
  }
72
130
 
73
131
  /** 从补丁文本解析注释头声明的目标版本(无则返回 undefined)。 */
@@ -195,9 +253,13 @@ function commonGlobalRoots() {
195
253
  export function applyToIndexFile(indexFile, patchFile) {
196
254
  if (isGitApplyable(indexFile, patchFile)) {
197
255
  const bootRoot = path.dirname(path.dirname(indexFile));
198
- execFileSync('git', ['apply', '-p1', '--directory', '.', patchFile], {
199
- cwd: bootRoot,
200
- });
256
+ // autocrlf=false:阻止 Windows 上 git apply 把整个文件转成 CRLF——
257
+ // 转换会破坏与内建替换路径的逐字节等价,并使内建 revert 兜底失配。
258
+ execFileSync(
259
+ 'git',
260
+ ['-c', 'core.autocrlf=false', 'apply', '-p1', '--directory', '.', patchFile],
261
+ { cwd: bootRoot },
262
+ );
201
263
  return 'git-apply';
202
264
  }
203
265
  const content = fs.readFileSync(indexFile, 'utf8');
@@ -211,9 +273,11 @@ function isGitApplyable(indexFile, patchFile) {
211
273
  const bootRoot = path.dirname(path.dirname(indexFile));
212
274
  try {
213
275
  // --directory 只接受相对路径(git 拒绝绝对目标 "invalid path"),故以包根为 cwd。
214
- execFileSync('git', ['apply', '--check', '-p1', '--directory', '.', patchFile], {
215
- cwd: bootRoot,
216
- });
276
+ execFileSync(
277
+ 'git',
278
+ ['-c', 'core.autocrlf=false', 'apply', '--check', '-p1', '--directory', '.', patchFile],
279
+ { cwd: bootRoot },
280
+ );
217
281
  return true;
218
282
  } catch {
219
283
  return false;
@@ -300,18 +364,28 @@ export function run(argv = process.argv.slice(2)) {
300
364
  return 0;
301
365
  }
302
366
 
303
- if (isApplied(content)) {
304
- console.log('[dsh-hotfix] already applied; nothing to do.');
367
+ /** 当前内容中尚未修复的缺陷名列表(dry-run 预览与结果汇报共用)。 */
368
+ function pendingFixes(c) {
369
+ const pending = [];
370
+ if (!isApplied(c)) pending.push('routeScoped');
371
+ if (!isNpdApplied(c)) pending.push('nativePackageDir');
372
+ return pending;
373
+ }
374
+
375
+ if (isApplied(content) && isNpdApplied(content)) {
376
+ console.log('[dsh-hotfix] already applied (both fixes); nothing to do.');
305
377
  return 0;
306
378
  }
307
379
  if (dryRun) {
308
- console.log(`[dsh-hotfix] dry-run: would apply patch to ${indexFile}.`);
380
+ console.log(`[dsh-hotfix] dry-run: would fix [${pendingFixes(content).join(', ')}] in ${indexFile}.`);
309
381
  return 0;
310
382
  }
311
383
  const backup = indexFile + BACKUP_SUFFIX;
312
384
  if (!fs.existsSync(backup)) fs.copyFileSync(indexFile, backup);
313
385
  const method = applyToIndexFile(indexFile, patchFile);
314
- console.log(`[dsh-hotfix] applied to ${indexFile} (via ${method}; backup at ${backup}).`);
386
+ console.log(
387
+ `[dsh-hotfix] fixed [${pendingFixes(content).join(', ')}] in ${indexFile} (via ${method}; backup at ${backup}).`,
388
+ );
315
389
  return 0;
316
390
  }
317
391
 
@@ -99,7 +99,7 @@ DatabaseManager({ action: "run_script", conn_id: "mongo1", code: '{"find":"users
99
99
  |---|---|---|---|---|
100
100
  | MySQL | `?` | `information_schema` | `LIMIT n OFFSET m` | |
101
101
  | PostgreSQL | `$1, $2…` | `information_schema` / `pg_catalog` | `LIMIT n OFFSET m` | |
102
- | GaussDB | `$1, $2…` | `information_schema` / `pg_catalog` | `LIMIT n OFFSET m` | PG 系 |
102
+ | GaussDB | `$1, $2…` | `information_schema` / `pg_catalog` | `LIMIT n OFFSET m` | PG 系(默认端口 8000,见下方注记) |
103
103
  | SQLite | `?` | `sqlite_master` / `PRAGMA` | `LIMIT n OFFSET m` | 库列表固定 `['main']` |
104
104
  | Redis | 无(命令数组) | `SCAN`(禁用 `KEYS` 遍历) | `SCAN cursor` | `run_script` 传 `["SCAN","0","MATCH","user:*","COUNT","100"]` |
105
105
  | MongoDB | 无(BSON 文档) | `listCollections` | `find().skip().limit()` | `run_script` 传 `{"find":"users","filter":{},"limit":20,"skip":40}` |
@@ -108,6 +108,18 @@ DatabaseManager({ action: "run_script", conn_id: "mongo1", code: '{"find":"users
108
108
 
109
109
  **类型规范化**(工具返回值已自动处理,引用结果时注意):`DECIMAL/NUMERIC` → string(避免精度丢失);`Date/TIMESTAMP` → ISO 8601 string;`LOB/CLOB/BLOB` → string;MongoDB `ObjectId` → string。生成写语句时,DECIMAL 直接传字符串值、日期传 ISO 字符串即可。
110
110
 
111
+ ## GaussDB 官方对齐注记(HCS 26.861.0,集中式/分布式)
112
+
113
+ 依据华为云 GaussDB 官方文档(doc.hcs.huawei.com/db/zh-cn,26.861.0 双形态)与 openGauss 官方文档源仓库对齐;未真机验证处以代码内标注为准。
114
+
115
+ - **认证**:驱动实现 openGauss SHA256 / MD5-SHA256 / SM3 握手,匹配服务端默认 `password_encryption_type = 2`(sha256)。连接报 `Invalid username/password, login denied` 时依次排查:① 认证配置(gs_hba.conf)需含 `host all all 0.0.0.0/0 sha256`;② 服务端密码加密方式变更后必须重设密码或新建用户(旧口令仍是旧加密);③ 账号锁定用 `ALTER USER ... ACCOUNT UNLOCK`。
116
+ - **默认端口 8000**(集中式 DN / 分布式 CN,官方 gsql 示例 `-p 8000`);SSL 默认不强制(服务端可 require_ssl 强制)。本工具 SSL 仅开/关,**不支持** sslmode(require/verify-ca/verify-full)证书校验细分。
117
+ - **ro 只读会话**:`SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY` 为官方语法(集中式/分布式均支持,rf-dist/gaussdb-08-0400、rf-cent/gaussdb-38-0416),连接建立时执行一次即对该连接所有后续事务生效。
118
+ - **隔离级别差异**:READ UNCOMMITTED 等价 READ COMMITTED;**SERIALIZABLE 功能不支持,等价 REPEATABLE READ**——与 PG 真 SSI 行为不同,依赖 PG 串行化语义的脚本注意。
119
+ - **元数据**:`pg_database` / `pg_catalog.pg_namespace` 为官方系统表;`information_schema.tables/columns` 集中式有官方视图文档,分布式参考无原生视图(仅 M-Compatibility 模式有),分布式实例上浏览以真机行为为准。
120
+ - **兼容模式**:openGauss 有 sql_compatibility(A/B/C/PG)兼容模式,部分语法语义随模式变化(如 B 模式 SET SESSION TRANSACTION 需 `b_format_behavior_compat_options` 含 set_session_transaction 才生效),本工具按 PG 语义使用。
121
+ - 驱动/默认值差异的代码内证据与证据 URL 见 `lib/adapters/gaussdb/index.ts` 头注释与 `lib/adapters/sql-shared/pg-like.ts`。
122
+
111
123
  ## 危险操作确认流程(NEEDS_CONFIRMATION)
112
124
 
113
125
  收到形如以下的返回时: