@moonquake2004/dsh-doctor 0.4.20 → 0.4.21
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/dsh-doctor.mjs +104 -0
- package/package.json +1 -1
package/dsh-doctor.mjs
CHANGED
|
@@ -2106,6 +2106,49 @@ function runBootCheckSync(profileDir) {
|
|
|
2106
2106
|
return out;
|
|
2107
2107
|
}
|
|
2108
2108
|
|
|
2109
|
+
|
|
2110
|
+
/* ---- 升级前后基线(2026-09):升级 dsh 前记一次,升级后自动复检 ---- */
|
|
2111
|
+
|
|
2112
|
+
const PRE_UPGRADE_FILE = '.dsh-doctor-pre-upgrade.json';
|
|
2113
|
+
|
|
2114
|
+
/** 取当前 dsh 核心版本:先问 CLI,再从 PATH 里 dsh 的安装树读 package.json。 */
|
|
2115
|
+
function coreVersion() {
|
|
2116
|
+
try {
|
|
2117
|
+
const bin = process.platform === 'win32' ? 'dsh.cmd' : 'dsh';
|
|
2118
|
+
const r = spawnSync(bin, ['--version'], { encoding: 'utf8', timeout: 10000, shell: process.platform === 'win32' });
|
|
2119
|
+
const m = String(r.stdout || '').trim().match(/\d+\.\d+\.\d+[-\w.]*/);
|
|
2120
|
+
if (m) return m[0];
|
|
2121
|
+
} catch { /* 落到安装树 */ }
|
|
2122
|
+
for (const p of (process.env.PATH || '').split(PATH_DELIM)) {
|
|
2123
|
+
if (p.endsWith('node_modules/.bin') && existsSync(join(p, 'dsh'))) {
|
|
2124
|
+
try {
|
|
2125
|
+
const mf = join(dirname(p), '@deepseek-ai', 'dsh', 'package.json');
|
|
2126
|
+
return JSON.parse(readFileSync(mf, 'utf8')).version ?? null;
|
|
2127
|
+
} catch { /* 继续找 */ }
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
return null;
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
/** 升级前基线:核心版本 + bundle 版本 + entry 列表 + manifest 原文(用于必要时人工比对)。 */
|
|
2134
|
+
function writePreUpgrade(profileDir) {
|
|
2135
|
+
const snap = bootSnapshot(profileDir);
|
|
2136
|
+
const payload = {
|
|
2137
|
+
kind: 'pre-upgrade',
|
|
2138
|
+
at: snap.at,
|
|
2139
|
+
coreVersion: coreVersion(),
|
|
2140
|
+
bundles: snap.bundles,
|
|
2141
|
+
entries: snap.entries,
|
|
2142
|
+
bundleCount: Object.keys(snap.bundles).length,
|
|
2143
|
+
};
|
|
2144
|
+
writeFileSync(join(profileDir, PRE_UPGRADE_FILE), JSON.stringify(payload, null, 2) + '\n');
|
|
2145
|
+
return payload;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
function readPreUpgrade(profileDir) {
|
|
2149
|
+
try { return JSON.parse(readFileSync(join(profileDir, PRE_UPGRADE_FILE), 'utf8')); } catch { return null; }
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2109
2152
|
async function runBootCheck(profileDir) {
|
|
2110
2153
|
const entries = collectBootEntries(profileDir);
|
|
2111
2154
|
const targets = entries.filter((e) => e.name);
|
|
@@ -2158,6 +2201,67 @@ async function run() {
|
|
|
2158
2201
|
const quarantineArg = flagValue('--quarantine');
|
|
2159
2202
|
const unquarantineArg = flagValue('--unquarantine');
|
|
2160
2203
|
const safeAddArg = flagValue('--safe-add');
|
|
2204
|
+
const preUpgradeArg = process.argv.includes('--pre-upgrade');
|
|
2205
|
+
const postUpgradeArg = process.argv.includes('--post-upgrade');
|
|
2206
|
+
if (preUpgradeArg || postUpgradeArg) {
|
|
2207
|
+
const profDir = resolveProfile(profileArg || 'web');
|
|
2208
|
+
if (preUpgradeArg) {
|
|
2209
|
+
const base = writePreUpgrade(profDir);
|
|
2210
|
+
if (jsonOut) console.log(JSON.stringify({ ok: true, ...base }, null, 2));
|
|
2211
|
+
else {
|
|
2212
|
+
console.log(`✓ 已记录升级前基线(${profDir})`);
|
|
2213
|
+
console.log(` 核心版本: ${base.coreVersion ?? '(未能确定)'} | bundle ${base.bundleCount} 个 | entry ${Object.keys(base.entries).length} 条`);
|
|
2214
|
+
console.log(' 下一步:升级 dsh,然后运行');
|
|
2215
|
+
console.log(` npx @moonquake2004/dsh-doctor --post-upgrade --profile ${profileArg || 'web'}`);
|
|
2216
|
+
console.log(' (--post-upgrade 会自动对比核心与插件变化、跑装载模拟,并给出修复/隔离命令)');
|
|
2217
|
+
}
|
|
2218
|
+
process.exit(0);
|
|
2219
|
+
}
|
|
2220
|
+
const base = readPreUpgrade(profDir);
|
|
2221
|
+
if (!base) {
|
|
2222
|
+
console.error(`✗ 没找到升级前基线(${join(profDir, PRE_UPGRADE_FILE)})——请先在升级前运行 --pre-upgrade`);
|
|
2223
|
+
process.exit(1);
|
|
2224
|
+
}
|
|
2225
|
+
const nowCore = coreVersion();
|
|
2226
|
+
const nowSnap = bootSnapshot(profDir);
|
|
2227
|
+
const drift = diffSnapshot({ bundles: base.bundles, entries: base.entries }, nowSnap) ?? {};
|
|
2228
|
+
const results = runBootCheckSync(profDir);
|
|
2229
|
+
const failed = results.filter((r) => r.status === 'failed');
|
|
2230
|
+
const coreChanged = base.coreVersion !== nowCore;
|
|
2231
|
+
const lines = [];
|
|
2232
|
+
lines.push(`核心版本: ${base.coreVersion ?? '?'} → ${nowCore ?? '?'}${coreChanged ? '(已变化)' : '(未变化)'}`);
|
|
2233
|
+
if ((drift.addedBundles ?? []).length) lines.push(`新增 bundle: ${drift.addedBundles.join(', ')}`);
|
|
2234
|
+
if ((drift.removedBundles ?? []).length) lines.push(`移除 bundle: ${drift.removedBundles.join(', ')}`);
|
|
2235
|
+
if ((drift.versionChanged ?? []).length) lines.push(`插件版本变化: ${drift.versionChanged.join('; ')}`);
|
|
2236
|
+
if ((drift.addedEntries ?? []).length) lines.push(`新增/变更 entry: ${drift.addedEntries.join('; ')}`);
|
|
2237
|
+
if (jsonOut) {
|
|
2238
|
+
console.log(JSON.stringify({ ok: failed.length === 0, profile: profDir, baselineAt: base.at, coreChanged, from: base.coreVersion, to: nowCore, drift, checked: results.length, failed: failed.length, results }, null, 2));
|
|
2239
|
+
process.exit(failed.length ? 2 : 0);
|
|
2240
|
+
}
|
|
2241
|
+
console.log(`升级后复检(基线取自 ${String(base.at).slice(0, 16)}):`);
|
|
2242
|
+
for (const l of lines) console.log(` · ${l}`);
|
|
2243
|
+
if (!failed.length) {
|
|
2244
|
+
console.log(' ✓ 装载模拟通过——升级未破坏任何可探测 entry');
|
|
2245
|
+
writeSnapshot(profDir, nowSnap);
|
|
2246
|
+
} else {
|
|
2247
|
+
console.log(` ✗ 装载模拟失败 ${failed.length} 条(这就是"升级后起不来"的直接原因):`);
|
|
2248
|
+
for (const f of failed) {
|
|
2249
|
+
console.log(` [${f.bundle}] ${f.id} → ${f.spec}`);
|
|
2250
|
+
console.log(` ${f.kind}: ${f.error}`);
|
|
2251
|
+
console.log(` 修复方向:${f.hint}`);
|
|
2252
|
+
console.log(` 先起来:npx @moonquake2004/dsh-doctor --quarantine ${f.bundle} --profile ${profileArg || 'web'}`);
|
|
2253
|
+
}
|
|
2254
|
+
if (process.argv.includes('--auto-quarantine')) {
|
|
2255
|
+
const q = [];
|
|
2256
|
+
for (const name of new Set(failed.map((f) => f.bundle))) {
|
|
2257
|
+
if (name === '(user patch)') continue;
|
|
2258
|
+
try { const r = quarantineBundle(profDir, name, false); if (r.ok) q.push(name); } catch { /* 忽略 */ }
|
|
2259
|
+
}
|
|
2260
|
+
if (q.length) console.log(` → 已自动隔离: ${q.join(', ')}(重启 dsh 即可;用 --unquarantine 放回)`);
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
process.exit(failed.length ? 2 : 0);
|
|
2264
|
+
}
|
|
2161
2265
|
if (safeAddArg) {
|
|
2162
2266
|
// 装完立即预检、坏了自动回滚——"装了个不兼容插件导致 dsh 起不来"的预防手段
|
|
2163
2267
|
const profDir = resolveProfile(profileArg || 'web');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moonquake2004/dsh-doctor",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.21",
|
|
4
4
|
"description": "Offline diagnostic for DeepSeek Harness — 28 built-in + 5 catalog checks across env/profile/session (Layer A checks-as-data), self-update (Layer B), and a semi-automatic LLM observer (Layer C, --observe); Doctor panel in web UI settings.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|