dsh-plugin-rea 2.0.6 → 2.0.8
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 +86 -0
- package/README.md +30 -4
- package/README.zh-CN.md +28 -4
- package/bin/rea-shim.mjs +666 -31
- package/check-updates.ps1 +33 -5
- package/package.json +2 -2
- package/scripts/host-compat.mjs +29 -4
- package/scripts/rea-bundle-bootstrap.ps1 +5 -0
- package/scripts/setup.mjs +30 -1
- package/scripts/setup.test.mjs +509 -8
- package/scripts/verify-branch.ps1 +8 -0
package/check-updates.ps1
CHANGED
|
@@ -73,9 +73,13 @@ function Test-NodeEngines([string]$node, [string]$engines) {
|
|
|
73
73
|
# bundle.json:把全局路径当默认是 1.x 的答案,在 2.x 机器上会让这一节指向错误的
|
|
74
74
|
# 对象("全局包丢了"),并给出错误的升级动作(npm i -g)。
|
|
75
75
|
$bundleRootEarly = $env:REA_BUNDLE_ROOT
|
|
76
|
+
# Resolve the home unconditionally: the profile section below needs it whether or
|
|
77
|
+
# not REA_BUNDLE_ROOT was set, and computing it only inside the fallback branch
|
|
78
|
+
# left it $null — `Join-Path $null 'profiles'` throws, so the section degraded to
|
|
79
|
+
# "no profile" on exactly the machines that use the override.
|
|
80
|
+
$homeEarly = $env:DSH_HOME
|
|
81
|
+
if (-not $homeEarly) { $homeEarly = Join-Path $HOME '.dsh' }
|
|
76
82
|
if (-not $bundleRootEarly) {
|
|
77
|
-
$homeEarly = $env:DSH_HOME
|
|
78
|
-
if (-not $homeEarly) { $homeEarly = Join-Path $HOME '.dsh' }
|
|
79
83
|
$bundleRootEarly = Join-Path $homeEarly 'plugins\rea-bundle'
|
|
80
84
|
}
|
|
81
85
|
$bundleState = $null
|
|
@@ -106,8 +110,12 @@ $reaFromBundle = [bool]($bundleReaPackageDir -and $ReaPackageDir -eq $bundleReaP
|
|
|
106
110
|
if (-not $ProfileDir) {
|
|
107
111
|
if ($env:DSH_PROFILE_DIR) { $ProfileDir = $env:DSH_PROFILE_DIR }
|
|
108
112
|
else {
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
# Same home rule the bundle uses everywhere else: DSH_HOME, then ~/.dsh.
|
|
114
|
+
# Hardcoding $env:USERPROFILE here was a second algorithm in the same script,
|
|
115
|
+
# so on a custom DSH_HOME the profile section reported "no profile found"
|
|
116
|
+
# while the rest of the run had already resolved that home correctly.
|
|
117
|
+
$profiles = @(Get-ChildItem (Join-Path $homeEarly 'profiles') -Directory -ErrorAction SilentlyContinue)
|
|
118
|
+
$ProfileDir = if ($profiles.Count -gt 0) { $profiles[0].FullName } else { Join-Path $homeEarly 'profiles\desktop' }
|
|
111
119
|
}
|
|
112
120
|
}
|
|
113
121
|
|
|
@@ -340,7 +348,27 @@ if (-not (Test-Path $manifestPath)) {
|
|
|
340
348
|
Warn ("TOFU(无厂商校验和,取首次实测值): " + (@($state.unverifiedChecksums) -join ', '))
|
|
341
349
|
}
|
|
342
350
|
$mirrored = Join-Path $bundleRoot 'bin\rea-shim.mjs'
|
|
343
|
-
if (Test-Path $mirrored) {
|
|
351
|
+
if (Test-Path $mirrored) {
|
|
352
|
+
Ok 'MCP 行指向的 shim 已就位'
|
|
353
|
+
# The row launches this COPY, so a plugin upgrade that does not re-run
|
|
354
|
+
# setup leaves the old launcher in place — visible only as "the new
|
|
355
|
+
# version behaves like the old one". Compare it with the checkout the
|
|
356
|
+
# mirror record names.
|
|
357
|
+
$mirrorRecord = Join-Path $bundleRoot 'mirror.json'
|
|
358
|
+
if (Test-Path $mirrorRecord) {
|
|
359
|
+
try {
|
|
360
|
+
$record = Get-Content $mirrorRecord -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
361
|
+
$pluginShim = if ($record.pluginRoot) { Join-Path $record.pluginRoot 'bin\rea-shim.mjs' } else { $null }
|
|
362
|
+
if ($pluginShim -and (Test-Path $pluginShim)) {
|
|
363
|
+
$same = (Get-FileHash $mirrored -Algorithm SHA256).Hash -eq (Get-FileHash $pluginShim -Algorithm SHA256).Hash
|
|
364
|
+
if ($same) { Ok '镜像 shim 与插件一致(行跑的就是这个版本)' }
|
|
365
|
+
else { Warn "镜像 shim 落后于插件($($record.pluginRoot))—— 行仍在跑旧副本;重跑 node scripts/setup.mjs 让它换成新版本" }
|
|
366
|
+
} else {
|
|
367
|
+
Warn "mirror.json 指向的插件目录已不存在($($record.pluginRoot))—— 无法比对镜像 shim 是否最新"
|
|
368
|
+
}
|
|
369
|
+
} catch { Warn "mirror.json 解析失败: $($_.Exception.Message)" }
|
|
370
|
+
}
|
|
371
|
+
}
|
|
344
372
|
else { Bad "缺少 MCP 行指向的 shim: $mirrored —— 重跑 setup --reuse" }
|
|
345
373
|
# 镜像副本的 setup 用它自己所在的位置推 plugin root,所以 manifest 必须
|
|
346
374
|
# 一起镜像。旧版镜像缺这个文件:auto-provision 会以 ENOENT 崩掉且不可见,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-rea",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8",
|
|
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
6
|
"main": "./index.mjs",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
},
|
|
64
64
|
"engines": {
|
|
65
65
|
"node": "^22.19.0 || ^24.11.0 || >=26.0.0",
|
|
66
|
-
"dsh": ">=0.1.7-rc.2 <0.3.0"
|
|
66
|
+
"dsh": ">=0.1.7-rc.2 <0.3.0-0"
|
|
67
67
|
},
|
|
68
68
|
"publishConfig": {
|
|
69
69
|
"registry": "https://registry.npmjs.org/"
|
package/scripts/host-compat.mjs
CHANGED
|
@@ -24,6 +24,15 @@
|
|
|
24
24
|
* shape for a gate. When a release is added upstream, add it here too — that
|
|
25
25
|
* edit is the reminder to re-check the row against the new host.
|
|
26
26
|
*
|
|
27
|
+
* SEMANTICS: this file evaluates the declaration the way `includePrerelease:
|
|
28
|
+
* true` does, and that choice is not stylistic. Both consumers of `engines.dsh`
|
|
29
|
+
* in this ecosystem pass that option — dshmarket's discovery
|
|
30
|
+
* (`satisfiesRange(host, declared, { includePrerelease: true })` in
|
|
31
|
+
* `deriveHostCompatibility`) and the host's own peer gate — so a plain semver
|
|
32
|
+
* reading, where `0.2.0-rc.2` does NOT satisfy `>=0.1.7-rc.2 <0.3.0-0` (a
|
|
33
|
+
* prerelease must be named by a comparator on its own tuple), would disagree
|
|
34
|
+
* with the verdict users actually see. The tests pin the option's effect.
|
|
35
|
+
*
|
|
27
36
|
* Usage: `node scripts/host-compat.mjs` (exit 0 = every listed release is
|
|
28
37
|
* covered by the declared range; exit 1 = at least one is not).
|
|
29
38
|
*/
|
|
@@ -59,6 +68,12 @@ export const HOST_RELEASES = [
|
|
|
59
68
|
/** Hosts that must fall OUTSIDE the declaration, for the other direction. */
|
|
60
69
|
export const OUTSIDE_HOSTS = [
|
|
61
70
|
'0.3.0',
|
|
71
|
+
// Prereleases of the NEXT line are the case a plain `<0.3.0` ceiling lets
|
|
72
|
+
// through: with `includePrerelease` they sort below `0.3.0` itself. The
|
|
73
|
+
// declared ceiling is `<0.3.0-0`, semver's idiom for "nothing from 0.3.0",
|
|
74
|
+
// and these entries are what proves it does what it says.
|
|
75
|
+
'0.3.0-rc.1',
|
|
76
|
+
'0.3.0-alpha.1',
|
|
62
77
|
'0.1.7-rc.1',
|
|
63
78
|
'0.1.7-alpha.2',
|
|
64
79
|
]
|
|
@@ -67,15 +82,25 @@ const SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/
|
|
|
67
82
|
|
|
68
83
|
/**
|
|
69
84
|
* Split a version into comparable parts.
|
|
85
|
+
*
|
|
86
|
+
* Build metadata (`+sha`) is dropped, as semver says it must be for precedence
|
|
87
|
+
* and as node-semver does for comparison: a host that reports
|
|
88
|
+
* `0.2.0-rc.2+build.7` is running 0.2.0-rc.2. Everything else is deliberately
|
|
89
|
+
* strict — a leading `v` or a zero-padded identifier is rejected rather than
|
|
90
|
+
* guessed at, because this gate exists to catch a declaration nobody checked and
|
|
91
|
+
* a guess would put the guess in charge.
|
|
70
92
|
* @param {string} version - a plain semver string, no range operators.
|
|
71
93
|
* @returns {{ parts: number[], pre: (string|number)[] } | null} null when unparseable.
|
|
72
94
|
*/
|
|
73
95
|
export function parseVersion(version) {
|
|
74
|
-
const
|
|
96
|
+
const cleaned = String(version ?? '').trim().replace(/\+.*$/, '')
|
|
97
|
+
const match = SEMVER.exec(cleaned)
|
|
75
98
|
if (match === null) return null
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
99
|
+
const raw = match[4] === undefined ? [] : match[4].split('.')
|
|
100
|
+
// semver forbids a zero-padded numeric prerelease identifier (`0.2.1-01`);
|
|
101
|
+
// accepting one would make this gate read a typo as a version.
|
|
102
|
+
if (raw.some(part => /^\d+$/.test(part) && part.length > 1 && part.startsWith('0'))) return null
|
|
103
|
+
const pre = raw.map(part => (/^\d+$/.test(part) ? Number(part) : part))
|
|
79
104
|
return { parts: [Number(match[1]), Number(match[2]), Number(match[3])], pre }
|
|
80
105
|
}
|
|
81
106
|
|
|
@@ -30,6 +30,11 @@ function Write-Step([string]$Message) { Write-Host "[bootstrap] $Message" }
|
|
|
30
30
|
|
|
31
31
|
# --- where things go -------------------------------------------------------
|
|
32
32
|
|
|
33
|
+
# Same precedence as setup.mjs, the shim and check-updates.ps1: explicit override,
|
|
34
|
+
# then DSH_HOME, then ~/.dsh. An override this script alone ignored would put
|
|
35
|
+
# ~800 MB into <DSH_HOME>/plugins/rea-bundle while every reader looked in
|
|
36
|
+
# REA_BUNDLE_ROOT, saw an empty bundle, and started a second run.
|
|
37
|
+
if (-not $Root -or $Root.Trim() -eq '') { $Root = $env:REA_BUNDLE_ROOT }
|
|
33
38
|
if (-not $Root -or $Root.Trim() -eq '') {
|
|
34
39
|
$home2 = $env:DSH_HOME
|
|
35
40
|
if (-not $home2 -or $home2.Trim() -eq '') { $home2 = Join-Path $HOME '.dsh' }
|
package/scripts/setup.mjs
CHANGED
|
@@ -1553,7 +1553,11 @@ if (!options.dryRun) {
|
|
|
1553
1553
|
const shim = spawnSync(process.execPath, [mirroredShim, "--version"], {
|
|
1554
1554
|
encoding: "utf8",
|
|
1555
1555
|
timeout: 120000,
|
|
1556
|
-
|
|
1556
|
+
// Pin the bundle path: this step exists to confirm the shim relays the REA
|
|
1557
|
+
// THIS run provisioned. Under the default `auto` mode a machine that also
|
|
1558
|
+
// has a newer global REA answers with its own version, and the step would
|
|
1559
|
+
// report success for something this run never verified.
|
|
1560
|
+
env: { ...process.env, REA_BUNDLE_ROOT: options.root, REA_BUNDLE_LAUNCH: "bundle" },
|
|
1557
1561
|
});
|
|
1558
1562
|
const output = `${shim.stdout ?? ""}${shim.stderr ?? ""}`.trim();
|
|
1559
1563
|
const ok = shim.status === 0 && /\d+\.\d+\.\d+/.test(output);
|
|
@@ -1632,6 +1636,31 @@ function mirrorLauncher(context) {
|
|
|
1632
1636
|
*/
|
|
1633
1637
|
function writeMirrorRecord(context, source) {
|
|
1634
1638
|
const record = { schema: 1, pluginRoot: source, mirroredAt: new Date().toISOString() };
|
|
1639
|
+
// Provenance for the self-healing check: the mirrored shim compares itself
|
|
1640
|
+
// with `<pluginRoot>/bin/rea-shim.mjs` and re-execs that one when the plugin
|
|
1641
|
+
// has moved on. These two fields are what `--shim-status` shows when the
|
|
1642
|
+
// comparison cannot be made (missing checkout, unreadable file).
|
|
1643
|
+
try {
|
|
1644
|
+
record.shimSha256 = createHash("sha256")
|
|
1645
|
+
.update(readFileSync(path.join(context.root, "bin", "rea-shim.mjs")))
|
|
1646
|
+
.digest("hex");
|
|
1647
|
+
} catch {
|
|
1648
|
+
/* the copy is missing or unreadable; the shim reports the comparison itself */
|
|
1649
|
+
}
|
|
1650
|
+
try {
|
|
1651
|
+
record.pluginVersion = JSON.parse(readFileSync(path.join(source, "package.json"), "utf8")).version ?? null;
|
|
1652
|
+
} catch {
|
|
1653
|
+
record.pluginVersion = null;
|
|
1654
|
+
}
|
|
1655
|
+
// The MCP child the harness starts has every DSH_* variable stripped, so the
|
|
1656
|
+
// mirrored shim cannot re-derive a CUSTOM home from its environment — it would
|
|
1657
|
+
// read `~/.dsh` and miss a duplicate `serverName: rea` living in the real one.
|
|
1658
|
+
// Record the home setup ran under; the shim prefers the environment, then this.
|
|
1659
|
+
try {
|
|
1660
|
+
record.dshHome = dshHome();
|
|
1661
|
+
} catch {
|
|
1662
|
+
record.dshHome = null;
|
|
1663
|
+
}
|
|
1635
1664
|
try {
|
|
1636
1665
|
mkdirSync(context.root, { recursive: true });
|
|
1637
1666
|
writeFileSync(path.join(context.root, "mirror.json"), JSON.stringify(record, null, 2) + "\n");
|