dsh-db-tool 0.1.8 → 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 +6 -4
- package/dist/adapters/index.js +1 -1
- package/dist/adapters/mongodb/index.js +51 -16
- package/docs/dsh-db-tool /351/224/231/350/257/257/350/256/260/345/275/225.txt" +66 -0
- package/docs/dsh/346/217/222/344/273/266/345/256/211/350/243/205/351/227/256/351/242/230/350/256/260/345/275/225-20260928.md +309 -0
- package/docs/dsh/346/217/222/344/273/266/351/227/256/351/242/230/350/247/243/345/206/263/346/226/271/346/241/210-20260928.md +166 -0
- package/docs/install.md +1 -0
- package/docs//344/270/212/346/270/270issue/350/215/211/347/250/277-20260928.md +133 -0
- package/package.json +8 -3
- package/patches/dsh-app-boot-route-scoped-hotfix.patch +15 -1
- package/scripts/build-mongodb.mjs +62 -0
- package/scripts/hotfix-core.d.mts +5 -0
- package/scripts/hotfix-core.mjs +99 -25
- package/vendor/mongodb-driver.cjs +28132 -0
|
@@ -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
|
@@ -73,5 +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` 已知缺陷,触发条件是插件依赖树含 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`。
|
|
76
77
|
- **API 全部报错**:host 端 webServer 须注册前缀 `/dsh-db-tool/api`(loopback only);前端与 host 前缀不一致是最常见的接错方式。
|
|
77
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": "
|
|
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
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
# DSH
|
|
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
|
+
}
|
|
@@ -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');
|
|
@@ -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;
|
package/scripts/hotfix-core.mjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* DSH
|
|
3
|
+
* DSH dsh-app-boot resolve.paths hotfix —— 核心逻辑(纯 Node、零依赖、可被测试 import)。
|
|
4
4
|
*
|
|
5
|
-
* 修复的 bug:@deepseek-ai/dsh-app-boot
|
|
6
|
-
* `createRequire(parent).resolve.paths(name)`
|
|
7
|
-
* 同名包(punycode 等)返回 null
|
|
8
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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:
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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:
|
|
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
|
-
|
|
199
|
-
|
|
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(
|
|
215
|
-
|
|
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
|
-
|
|
304
|
-
|
|
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
|
|
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(
|
|
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
|
|