dsh-maoying 0.1.4 → 0.1.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.
Files changed (2) hide show
  1. package/index.mjs +116 -18
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -197,42 +197,117 @@ export function hostRejectsInlineDelegation(version) {
197
197
  }
198
198
 
199
199
  /** 宿主包版本:从 <home>/node_modules require('@deepseek-ai/dsh/package.json')。 */
200
- export function readHostVersion(dshHome) {
201
- // ① 运行入口锚定(最真):argv[1] 是宿主自己的入口脚本——CLI 下是
202
- // @deepseek-ai/dsh 包里的 bin/入口,桌面应用下是 app.asar/dsh 内的入口
203
- // (Electron 主进程的 fs 能透读 asar)。向上找最近的 @deepseek-ai/dsh
204
- // package.json,读到的就是当前进程真实版本。home 全局安装的版本可能
205
- // 陈旧(例如 CLI 升级前留下的 rc.7),绝不能只信它。
206
- try {
207
- let dir = path.dirname(path.resolve(process.argv?.[1] ?? ''))
208
- for (let i = 0; i < 8 && dir && dir !== path.dirname(dir); i++, dir = path.dirname(dir)) {
209
- const pkgPath = path.join(dir, 'package.json')
210
- if (!existsSync(pkgPath)) continue
200
+ /** 从 start 向上最多 8 层找最近的 @deepseek-ai/dsh package.json,命中记轨迹 */
201
+ function anchorVersion(start, probe, trace) {
202
+ let dir = path.dirname(path.resolve(start))
203
+ for (let i = 0; i < 8 && dir && dir !== path.dirname(dir); i++, dir = path.dirname(dir)) {
204
+ const pkgPath = path.join(dir, 'package.json')
205
+ if (!existsSync(pkgPath)) continue
206
+ try {
211
207
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
212
208
  if (pkg?.name === '@deepseek-ai/dsh' && typeof pkg.version === 'string' && pkg.version !== '') {
209
+ trace.push({ probe, hit: pkg.version, via: pkgPath })
213
210
  return pkg.version
214
211
  }
212
+ } catch { /* 单个坏 package.json 跳过继续走 */ }
213
+ }
214
+ trace.push({ probe, hit: null })
215
+ return null
216
+ }
217
+
218
+ /**
219
+ * 宿主版本探测(带轨迹)。五级优先,前四级都锚定「当前进程的真实宿主」,
220
+ * home 全局那份(老 CLI 残留,版本可能差几个大版本)只做最后兜底:
221
+ * ① argv[1]——CLI 下就是 @deepseek-ai/dsh 包内入口;
222
+ * ② 全 argv 扫描——Electron 主进程常见 argv 里带 app 入口或 .asar 路径;
223
+ * ③ app.asar 锚定——桌面应用 execPath 反推 Resources/app.asar/dsh(Electron
224
+ * 主进程 fs 可透读 asar);
225
+ * ④ Electron app.getVersion()——桌面应用版本即其内置 dsh 版本;
226
+ * ⑤ home 全局安装兜底。
227
+ * 轨迹经 trace 数组透出(host-debug 路由用),方便现场诊断每级看到了什么。
228
+ */
229
+ function collectHostVersion(dshHome, trace) {
230
+ // ① argv[1]
231
+ const argv1 = process.argv?.[1]
232
+ if (typeof argv1 === 'string' && argv1 !== '') {
233
+ const hit = anchorVersion(argv1, 'argv[1]', trace)
234
+ if (hit) return hit
235
+ } else {
236
+ trace.push({ probe: 'argv[1]', hit: null, note: 'argv[1] 缺席' })
237
+ }
238
+ // ② 全 argv 扫描
239
+ let scanned = 0
240
+ for (const arg of process.argv ?? []) {
241
+ if (typeof arg !== 'string' || arg === argv1 || arg.startsWith('-')) continue
242
+ if (!/\.asar($|\/)|\.(mjs|cjs|js)$/.test(arg)) continue
243
+ scanned++
244
+ const hit = anchorVersion(arg, 'argv-scan', trace)
245
+ if (hit) return hit
246
+ }
247
+ if (scanned === 0) trace.push({ probe: 'argv-scan', hit: null, note: '无可锚定的 argv 项' })
248
+ // ③ app.asar 锚定(仅 Electron 进程内有意义;纯 Node 的 fs 读不了 asar)
249
+ if (process.versions?.electron) {
250
+ const execDir = path.dirname(process.execPath)
251
+ const candidates = [
252
+ path.resolve(execDir, '..', 'Resources', 'app.asar', 'dsh', 'package.json'),
253
+ path.resolve(execDir, 'resources', 'app.asar', 'dsh', 'package.json'),
254
+ path.resolve(execDir, '..', 'Resources', 'app.asar', 'package.json'),
255
+ path.resolve(execDir, 'resources', 'app.asar', 'package.json'),
256
+ ]
257
+ let probed = false
258
+ for (const pkgPath of candidates) {
259
+ try {
260
+ if (!existsSync(pkgPath)) continue
261
+ probed = true
262
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
263
+ if (pkg?.name === '@deepseek-ai/dsh' && typeof pkg.version === 'string' && pkg.version !== '') {
264
+ trace.push({ probe: 'asar', hit: pkg.version, via: pkgPath })
265
+ return pkg.version
266
+ }
267
+ } catch { /* 单个候选坏掉换下一个 */ }
215
268
  }
216
- } catch { /* 入口缺失或不可读时落下一档 */ }
217
- // ② Electron 主进程:桌面应用版本即其内置 dsh 的版本。纯 Node 下 require
218
- // ('electron') 得到的是路径字符串(或直接找不到),类型守卫天然挡掉。
269
+ if (!probed) trace.push({ probe: 'asar', hit: null, note: 'execPath 反推的 asar 路径均不可读' })
270
+ } else {
271
+ trace.push({ probe: 'asar', hit: null, note: '非 Electron 进程' })
272
+ }
273
+ // ④ Electron app.getVersion()。纯 Node 下 require('electron') 得到路径字符串
274
+ // 或直接找不到,类型守卫天然挡掉。
219
275
  try {
220
276
  const electron = createRequire(import.meta.url)('electron')
221
277
  if (electron && typeof electron === 'object' && typeof electron.app?.getVersion === 'function') {
222
278
  const v = electron.app.getVersion()
223
- if (typeof v === 'string' && v !== '') return v
279
+ const hit = typeof v === 'string' && v !== '' ? v : null
280
+ trace.push({ probe: 'electron-api', hit })
281
+ if (hit) return hit
282
+ } else {
283
+ trace.push({ probe: 'electron-api', hit: null, note: 'require 结果非 API 对象' })
224
284
  }
225
- } catch { /* 非 Electron 环境常态 */ }
226
- // ③ 兜底:home 全局安装(老 CLI 链路;可能陈旧,只在①②都拿不到时兜底)
285
+ } catch {
286
+ trace.push({ probe: 'electron-api', hit: null, note: 'require 失败' })
287
+ }
288
+ // ⑤ home 全局兜底
227
289
  try {
228
290
  const req = createRequire(path.join(dshHome, 'node_modules', 'package.json'))
229
291
  const pkg = req('@deepseek-ai/dsh/package.json')
230
- return typeof pkg?.version === 'string' && pkg.version !== '' ? pkg.version : null
292
+ const hit = typeof pkg?.version === 'string' && pkg.version !== '' ? pkg.version : null
293
+ trace.push({ probe: 'home-global', hit, note: hit ? '兜底命中——home 全局版本可能陈旧' : null })
294
+ return hit
231
295
  } catch {
296
+ trace.push({ probe: 'home-global', hit: null })
232
297
  return null
233
298
  }
234
299
  }
235
300
 
301
+ export function readHostVersion(dshHome) {
302
+ return collectHostVersion(dshHome, [])
303
+ }
304
+
305
+ /** host-debug 路由用:版本 + 每级探针轨迹 */
306
+ export function readHostVersionDebug(dshHome) {
307
+ const trace = []
308
+ return { version: collectHostVersion(dshHome, trace), trace }
309
+ }
310
+
236
311
  // ── 预设源解析 ──────────────────────────────────────────────────────────────
237
312
 
238
313
  /**
@@ -666,6 +741,29 @@ export async function registerCreatorRoutes(ctx) {
666
741
  return
667
742
  }
668
743
 
744
+ if (route === 'host-debug' && req.method === 'GET') {
745
+ // 现场诊断:宿主版本五级探针轨迹 + 形状判定。与 client.css 同级信任
746
+ // 围栏(loopback + 可信 Host 才放行),只暴露版本与路径类信息。
747
+ const home = resolveDshHome()
748
+ const dbg = readHostVersionDebug(home)
749
+ let shape = null
750
+ try { shape = composeManagedRows(home, ctx.logger).shape } catch { /* 诊断信息尽力而为 */ }
751
+ json(res, 200, {
752
+ ok: true,
753
+ hostVersion: dbg.version,
754
+ trace: dbg.trace,
755
+ shape,
756
+ runtime: {
757
+ argv: process.argv,
758
+ execPath: process.execPath,
759
+ cwd: process.cwd(),
760
+ electron: process.versions?.electron ?? null,
761
+ node: process.version,
762
+ },
763
+ })
764
+ return
765
+ }
766
+
669
767
  if (route === 'workspaces' && req.method === 'GET') {
670
768
  // 工作区清单:宿主注册表逐根探测 novel/project.yml,面板顶部
671
769
  // 工作区选择器取数(hasNovel 的排前由客户端做,这里保序即可)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-maoying",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "毛颖 — DSH 网文创作辅助插件:主编 + 五帮手编排(大纲/写手/审校/设定/考据),novel/ 结构化文件为数据库",
5
5
  "type": "module",
6
6
  "license": "MIT",