dsh-plugin-rea 2.0.4 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,36 @@
2
2
 
3
3
  本插件遵循语义化版本。版本号变化说明「配置组合」这层的变化,与 REA 自身的版本无关。
4
4
 
5
+ ## 2.0.5 — 2026-10-18
6
+
7
+ **市场更新失败已定位并修复:清单里没有 `main`,市场因此在每次更新时判「新构建没有可加载入口」并回滚。**
8
+
9
+ ### 修复
10
+
11
+ - **应用内市场更新到 2.0.4 失败**:`dshmarket` 的更新路径用 `hasLoadableEntry` 判断新构建能不能加载
12
+ (实现是 `entryArtifactExists`:读 `main`/`exports`,两者都没有就回落 `index.js`)。本包是纯 bundle
13
+ (只有 `dsh.bundle.patch`),从未声明 `main`;而它挂载的 `@deepseek-ai/dsh-mcp-client` 属于 harness 自身
14
+ 构建、不在 `node_modules` 里,于是该检查要看的 profile / 包内 / workspace 三个位置全空。结果:2.0.1 → 2.0.4
15
+ 的更新连续三次报「更新后缺少入口文件……且未能验证恢复原版本文件」,而回滚其实成功了 —— 旧版同样没有入口,
16
+ 所以回滚后的复检依然为 false。现已声明 `main: ./index.mjs`,并让该文件进入 `files[]`。
17
+ - **`index.mjs` 是真空插件,不是占位**:导出 `name` 与空 `apply()`,不注册行、不起进程、不写状态;真正起作用的
18
+ 行仍由 `cordis.patch.yml` 插入。若 harness 某天真把它当插件加载,行为仍是「什么都不做」,因此不会造出第二条
19
+ `serverName: rea`。
20
+
21
+ ### 文档
22
+
23
+ - 两份 README 新增「在市场里更新 / Updating from the market」小节:说明该报错来自市场侧的入口判定、与 tarball
24
+ 完整性无关、profile 未受损,以及 2.0.5 起的做法。
25
+
26
+ ### 验证
27
+
28
+ - 用 `dshmarket` 自身的 `hasLoadableEntry` 实测:把本包按 profile 布局模拟安装,修复前 `false`、修复后 `true`;
29
+ 对仓库目录 `entryArtifactExists` 修复后为 `true`。
30
+ - 单测 42 条(新增 2 条:清单声明的入口必须存在且被 `files[]` 收录;入口可 `import`、导出的 `name` 与包名一致、
31
+ `apply()` 返回 `undefined`):Windows 39 过 + 3 平台跳过。
32
+ - `verify-branch.ps1` 新增 `main` 存在性与 `files[]` 收录检查;`validate.yml` 的清单检查与 npm 载荷检查加了同一条守卫。
33
+ - `npm pack --dry-run`:20 个文件,含 `index.mjs`。
34
+
5
35
  ## 2.0.4 — 2026-10-17
6
36
 
7
37
  **两处已核实的缺陷:只读 bundle 目录下 shim 启动即崩;与上游 `rea setup --client dsh` 的同名注册冲突现在会被明确提示。**
package/README.md CHANGED
@@ -262,6 +262,27 @@ macOS/Linux.
262
262
 
263
263
  ## Updating — three independent things
264
264
 
265
+ ### Updating from the market
266
+
267
+ Update it where you installed it. Updating **to 2.0.5 or later** is routine; updating an installed 2.0.1–2.0.4
268
+ *through the in-app market* failed with this, three times on a healthy profile:
269
+
270
+ > dsh-plugin-rea 更新后缺少入口文件(package.json 的 main/exports 指向的文件不存在),且未能验证恢复原版本文件
271
+
272
+ That message is not about a broken tarball. On every update the market asks whether the new build's
273
+ `package.json` points at an entry file that exists (`hasLoadableEntry` in `dshmarket`), and rolls the build back
274
+ when the answer is false. A bundle may legitimately own no entry — but only if a package it *mounts* has one, and
275
+ the package this bundle mounts (`@deepseek-ai/dsh-mcp-client`) ships inside the harness build rather than in
276
+ `node_modules`, so every location that test probes comes back empty. The 1.x and 2.0.x manifests also declared
277
+ no `main` at all, so the answer was false even though the profile was working; the rollback was then reported as
278
+ unverifiable for the same reason. Nothing in the profile was actually damaged — the update simply did not apply.
279
+
280
+ 2.0.5 declares `main: ./index.mjs`. That file is a real, deliberately empty Cordis plugin: it registers nothing,
281
+ spawns nothing, touches no state — the working row is still inserted by `cordis.patch.yml` — and it exists so the
282
+ question "can this package load?" has an honest answer, and so `import 'dsh-plugin-rea'` resolves. On an older
283
+ version, update to `>=2.0.5`; if the market refuses before that version exists, install `dsh-plugin-rea@2.0.5`
284
+ (Desktop: through the plugin manager) instead of updating in place.
285
+
265
286
  ### Upgrading from 1.x
266
287
 
267
288
  Install 2.x, then run provisioning **once** before restarting dsh:
package/README.zh-CN.md CHANGED
@@ -246,6 +246,25 @@ shim** 的 MCP `initialize` 握手返回 `serverInfo {name: rea, version: 6.5.0}
246
246
 
247
247
  ## 更新 —— 三件互相独立的事
248
248
 
249
+ ### 在市场里更新
250
+
251
+ 在哪儿装的就在哪儿更新。更新到 **2.0.5 及以上**是常规操作;而把已装的 2.0.1–2.0.4 通过应用内市场**更新到
252
+ 2.0.4**,在健康的 profile 上连续失败了三次:
253
+
254
+ > dsh-plugin-rea 更新后缺少入口文件(package.json 的 main/exports 指向的文件不存在),且未能验证恢复原版本文件
255
+
256
+ 这条消息与 tarball 是否完整无关。市场在每次更新时都会问一句:新构建的 `package.json` 是否指向一个真实存在的
257
+ 入口文件(`dshmarket` 里的 `hasLoadableEntry`);答案是「否」,就把这次更新判为坏构建并回滚。bundle 本来允许
258
+ 自己没有入口 —— 前提是它**挂载**的某个包有入口;而本包挂载的 `@deepseek-ai/dsh-mcp-client` 属于 harness 自身
259
+ 构建,不在 `node_modules` 里,于是这项检查要看的每个位置都是空的。1.x 与 2.0.x 的清单又根本没有声明 `main`,
260
+ 所以答案必然是「否」,哪怕 profile 一直是好的;紧接着的回滚也因此被报成「无法验证」。实际上 profile 没有被
261
+ 破坏,只是这次更新没有生效。
262
+
263
+ 2.0.5 声明了 `main: ./index.mjs`。那是一个真实存在、刻意留空的 Cordis 插件:不注册任何行、不启动任何进程、
264
+ 不碰任何状态(真正起作用的行仍由 `cordis.patch.yml` 插入),它的作用只是让「这个包能不能加载」有一个诚实的
265
+ 答案,并让 `import 'dsh-plugin-rea'` 能解析。还停在旧版本时,更新到 `>=2.0.5` 即可;若市场在 2.0.5 之前就拒绝,
266
+ 就直接安装 `dsh-plugin-rea@2.0.5`(桌面端:在插件管理器里装),不要原地更新。
267
+
249
268
  ### 从 1.x 升级
250
269
 
251
270
  装上 2.x 后,**先跑一次供给**,再重启 dsh:
package/index.mjs ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Package entry — the smallest thing that makes this bundle a *loadable* package.
3
+ *
4
+ * Everything this plugin DOES lives in `cordis.patch.yml`: it inserts one row of
5
+ * `@deepseek-ai/dsh-mcp-client` that launches `bin/rea-shim.mjs`, and that row is
6
+ * what the harness loads. This file registers nothing, starts nothing and
7
+ * provisions nothing.
8
+ *
9
+ * It exists because a package that declares no `main`/`exports` is judged by its
10
+ * consumers as "has no entry file", whether or not the loader ever needs one:
11
+ *
12
+ * - The market's update path (`hasLoadableEntry` in dshmarket) asks that
13
+ * question on every update and, when the answer is false, treats the new
14
+ * build as broken and rolls back. A bundle with no entry of its own is
15
+ * *supposed* to survive the test through the packages it mounts — but the
16
+ * package this one mounts (`@deepseek-ai/dsh-mcp-client`) is part of the
17
+ * harness build, not of `node_modules`, so every location that test probes
18
+ * comes back empty. Measured on 2026-10-18: updating an installed, working
19
+ * 2.0.1 to 2.0.4 failed three times with "updated build has no loadable
20
+ * entry", and the rollback was then reported as unverifiable for the same
21
+ * reason.
22
+ * - `npm pack` metadata and `import 'dsh-plugin-rea'` both resolve `main`;
23
+ * absent, both fall back to `index.js`, which this repository never shipped.
24
+ *
25
+ * So this is a real module with a real Cordis plugin shape rather than a stub
26
+ * for a checker: if the harness ever does load this package as a plugin, it
27
+ * loads cleanly and does nothing.
28
+ */
29
+
30
+ /** Cordis plugin name. Also the package name, which is what the loader expects. */
31
+ export const name = 'dsh-plugin-rea'
32
+
33
+ /**
34
+ * Intentionally empty.
35
+ *
36
+ * The row that does the work is inserted by the bundle patch, not by this
37
+ * function: registering the MCP client from here would create a second
38
+ * `serverName: rea` entry, which the harness refuses as a duplicate (the same
39
+ * collision `bin/rea-shim.mjs` warns about for upstream's `rea setup --client
40
+ * dsh`). Doing nothing is the correct behaviour, and the function present keeps
41
+ * this a valid Cordis plugin instead of an arbitrary module.
42
+ */
43
+ export function apply() {}
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "dsh-plugin-rea",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "description": "One-plugin bundle: provisions Node/REA/JDK/Ghidra for you, then registers the upstream REA MCP server as mcp__rea__* tools. · 一体包:自动准备 Node/REA/JDK/Ghidra,并把上游 REA 的 MCP 服务接入 DeepSeek Harness。",
5
5
  "type": "module",
6
+ "main": "./index.mjs",
6
7
  "license": "MIT",
7
8
  "author": "czcdr",
8
9
  "homepage": "https://github.com/czcdr/dsh-plugin-rea",
@@ -33,6 +34,7 @@
33
34
  },
34
35
  "files": [
35
36
  "cordis.patch.yml",
37
+ "index.mjs",
36
38
  "bin/",
37
39
  "scripts/",
38
40
  "manifest/",
@@ -18,7 +18,7 @@ import { createServer } from "node:http";
18
18
  import { tmpdir } from "node:os";
19
19
  import path from "node:path";
20
20
  import test from "node:test";
21
- import { fileURLToPath } from "node:url";
21
+ import { fileURLToPath, pathToFileURL } from "node:url";
22
22
 
23
23
  /* ------------------------------------------------- probe token substitution */
24
24
 
@@ -1168,3 +1168,34 @@ test("--json releases the lock on success and marks it failed on failure", () =>
1168
1168
  rmSync(root, { recursive: true, force: true });
1169
1169
  }
1170
1170
  });
1171
+
1172
+ /* --------------------------------------------------------- the package entry */
1173
+
1174
+ // The market judges an update by asking whether the new build's package.json
1175
+ // points at an entry file that exists (`hasLoadableEntry` -> `entryArtifactExists`
1176
+ // in dshmarket). This package declared neither main nor exports, and the package
1177
+ // it mounts (`@deepseek-ai/dsh-mcp-client`) ships inside the harness build rather
1178
+ // than in node_modules, so every location that test probes came back empty:
1179
+ // updating a healthy 2.0.1 profile to 2.0.4 failed with "updated build has no
1180
+ // loadable entry", and the rollback was then reported unverifiable for the same
1181
+ // reason (measured 2026-10-18). These two tests are that regression.
1182
+ test("package.json declares an entry file that is actually shipped", () => {
1183
+ const pkg = JSON.parse(readFileSync(path.join(REPO_ROOT, "package.json"), "utf8"));
1184
+ assert.equal(typeof pkg.main, "string", "package.json must declare main; without it consumers probe index.js, which does not exist");
1185
+ assert.equal(existsSync(path.join(REPO_ROOT, pkg.main)), true, `${pkg.main} is declared but missing`);
1186
+ // `main` alone is not enough: npm pack ships only what files[] lists, and a
1187
+ // tarball without the entry reproduces the very failure this fixes.
1188
+ const shipped = String(pkg.main).replace(/^\.\//, "");
1189
+ assert.equal(pkg.files.includes(shipped), true, `files[] must ship ${shipped}`);
1190
+ });
1191
+
1192
+ test("the entry loads as a cordis plugin and does nothing", async () => {
1193
+ const pkg = JSON.parse(readFileSync(path.join(REPO_ROOT, "package.json"), "utf8"));
1194
+ const mod = await import(pathToFileURL(path.join(REPO_ROOT, pkg.main)).href);
1195
+ assert.equal(mod.name, pkg.name, "the loader resolves the plugin by this name");
1196
+ assert.equal(typeof mod.apply, "function", "a cordis plugin needs apply()");
1197
+ // Loading and applying must not register a row, spawn anything or touch state:
1198
+ // registering the MCP client here would add a second `serverName: rea` entry,
1199
+ // which the harness refuses. Returning undefined is the whole contract.
1200
+ assert.equal(mod.apply(), undefined);
1201
+ });
@@ -12,7 +12,7 @@ function Check($name, [bool]$ok, $detail) {
12
12
  }
13
13
 
14
14
  Write-Host "`n=== 1. JS syntax ===" -ForegroundColor Cyan
15
- foreach ($f in @('bin\rea-shim.mjs','scripts\setup.mjs','scripts\audit-manifest.mjs','scripts\setup.test.mjs')) {
15
+ foreach ($f in @('index.mjs','bin\rea-shim.mjs','scripts\setup.mjs','scripts\audit-manifest.mjs','scripts\setup.test.mjs')) {
16
16
  $out = & node --check $f 2>&1
17
17
  Check $f ($LASTEXITCODE -eq 0) ($out -join ' ')
18
18
  }
@@ -73,9 +73,16 @@ Write-Host "`n=== 6. package payload lists what the bundle needs ===" -Foregroun
73
73
  # -Encoding UTF8 is required: the package description contains CJK text, and
74
74
  # PowerShell 5.1 reads a BOM-less file as ANSI, which breaks ConvertFrom-Json.
75
75
  $pkg = Get-Content (Join-Path $root 'package.json') -Raw -Encoding UTF8 | ConvertFrom-Json
76
- foreach ($entry in @('bin/', 'scripts/', 'manifest/', 'cordis.patch.yml')) {
76
+ foreach ($entry in @('index.mjs', 'bin/', 'scripts/', 'manifest/', 'cordis.patch.yml')) {
77
77
  Check "files[] includes $entry" (@($pkg.files) -contains $entry) ''
78
78
  }
79
+ # The market's update path asks whether package.json's main/exports points at a
80
+ # file that exists, and rolls the update back when it does not (dshmarket's
81
+ # hasLoadableEntry). This package declared neither until 2.0.5, so every update of
82
+ # a healthy profile failed with "updated build has no loadable entry". Asserting
83
+ # the pointer resolves keeps that from returning silently.
84
+ Check 'package.json declares main' (-not [string]::IsNullOrWhiteSpace($pkg.main)) ''
85
+ Check 'main resolves to a shipped file' ($pkg.main -and (Test-Path (Join-Path $root $pkg.main))) "main=$($pkg.main)"
79
86
  foreach ($script in @('bundle:setup', 'bundle:status', 'bundle:audit', 'bundle:test')) {
80
87
  Check "scripts.$script exists" ($pkg.scripts.PSObject.Properties.Name -contains $script) ''
81
88
  }