@yfwu2020/dsh-selection-explain 0.1.0 → 0.1.1

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 CHANGED
@@ -81,7 +81,17 @@
81
81
 
82
82
  ## 安装
83
83
 
84
- ### 方式一:从 Release 安装(推荐)
84
+ ### 方式一:从 npm 安装
85
+
86
+ 已发布到 npm registry:
87
+
88
+ ```bash
89
+ npm install @yfwu2020/dsh-selection-explain
90
+ # 或直接装配进 profile(npm 包名可直接用)
91
+ dsh plugin --profile web add @yfwu2020/dsh-selection-explain
92
+ ```
93
+
94
+ ### 方式二:从 Release 安装
85
95
 
86
96
  每个 Release 都附了**构建好的 `.tgz`**(含 `lib/` 产物,装完即可用,无需 clone 源码):
87
97
 
@@ -98,12 +108,15 @@ tar -xzf yfwu2020-dsh-selection-explain-0.1.0.tgz \
98
108
  dsh plugin --profile web add ~/.dsh/plugins/dsh-selection-explain
99
109
  ```
100
110
 
101
- ### 方式二:从源码安装
111
+ ### 方式三:从源码安装
102
112
 
103
113
  ```bash
104
114
  git clone git@github.com:yfwu2020/dsh-selection-explain.git
105
115
  cd dsh-selection-explain
106
116
 
117
+ # 装依赖(编译用的 @deepseek-ai/* 类型与 tsc 都在 devDependencies 里)
118
+ npm install
119
+
107
120
  # 构建(自动探测 DSH 运行时;host 走 tsc,client 是手写 bundle 直接拷贝)
108
121
  bash scripts/build.sh
109
122
 
@@ -111,7 +124,8 @@ bash scripts/build.sh
111
124
  dsh plugin --profile web add .
112
125
  ```
113
126
 
114
- > `scripts/build.sh` 会自动探测 DSH 运行时(`DSH_CHECKOUT` → PATH 上的 `dsh` → 常见 checkout → npx 缓存)。
127
+ > `scripts/build.sh` 会按顺序探测 DSH 运行时:**项目自己的 `node_modules`**(CI/`npm install` 走这条)
128
+ > → `DSH_CHECKOUT` → PATH 上的 `dsh` → 常见 checkout → npx 缓存。
115
129
  > 探测不到时可以显式指定:`DSH_CHECKOUT=/path/to/dsh-harness bash scripts/build.sh`。
116
130
 
117
131
  装好后**刷新页面**,选中文字试试。浏览器控制台会打印 `[dsh-selection-explain] 划词解读已就绪(选中文字试试)`。
@@ -281,6 +295,24 @@ node scripts/dump-prompt.mjs <sessionId> "<选中文字>"
281
295
  node scripts/dump-prompt.mjs --no-session "<选中文字>" # 不带会话背景的对照组
282
296
  ```
283
297
 
298
+ ## 发布(维护者)
299
+
300
+ 发布走 **Trusted Publishing**(GitHub OIDC),**不需要任何长期 npm token**:
301
+
302
+ ```bash
303
+ # 1) 改 package.json 的 version(例如 0.2.0)
304
+ # 2) 打同名 tag 推上去
305
+ git tag v0.2.0 && git push origin v0.2.0
306
+ ```
307
+
308
+ `.github/workflows/publish.yml` 会自动:校验 tag 与 `version` 一致 → `npm install` → 构建 → 跑 463 条测试 → 类型检查 → `npm publish --provenance`(附 provenance 签名)。
309
+
310
+ npm 侧只需配一次:包设置 → **Trusted Publisher** → GitHub Actions,填 `yfwu2020` / `dsh-selection-explain` / `publish.yml`,并勾选允许 **`npm publish`**(默认只允许 `npm stage publish`)。
311
+
312
+ > 注意:`devDependencies` 里的 `@deepseek-ai/*` 钉在 **`0.1.5-rc.3`**(与插件开发所依据的运行时一致)。
313
+ > npm 上这些包的 `latest` 还停在 `0.0.1-rc.x`,API 更旧(缺 `WebServer`、`createSystemMessage` 等),
314
+ > 用 `latest` 会编译失败——升级运行时时要同步改这里。
315
+
284
316
  ## 目录
285
317
 
286
318
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yfwu2020/dsh-selection-explain",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "DSH 划词解读:选中文字浮出按钮,点击弹窗流式显示专业中英翻译 + 该文字在当前上下文中的含义详解",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -14,12 +14,29 @@
14
14
  "README.md"
15
15
  ],
16
16
  "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/yfwu2020/dsh-selection-explain.git"
20
+ },
21
+ "homepage": "https://github.com/yfwu2020/dsh-selection-explain#readme",
22
+ "bugs": {
23
+ "url": "https://github.com/yfwu2020/dsh-selection-explain/issues"
24
+ },
17
25
  "peerDependencies": {
18
- "@deepseek-ai/cordis": ">=4 <5",
19
- "@deepseek-ai/dsh-agent-default-model": ">=0.0.1-rc <2",
20
- "@deepseek-ai/dsh-host-webserver": ">=0.0.1-rc <2",
21
- "@deepseek-ai/dsh-llm": ">=0.0.1-rc <2",
22
- "@deepseek-ai/schemastery": ">=3 <4"
26
+ "@deepseek-ai/cordis": ">=4.0.2 <5",
27
+ "@deepseek-ai/dsh-agent-default-model": ">=0.1.5-rc <2",
28
+ "@deepseek-ai/dsh-host-webserver": ">=0.1.5-rc <2",
29
+ "@deepseek-ai/dsh-llm": ">=0.1.5-rc <2",
30
+ "@deepseek-ai/schemastery": ">=3.18 <4"
31
+ },
32
+ "devDependencies": {
33
+ "@deepseek-ai/cordis": "4.0.2",
34
+ "@deepseek-ai/dsh-agent-default-model": "0.1.5-rc.3",
35
+ "@deepseek-ai/dsh-host-webserver": "0.1.5-rc.3",
36
+ "@deepseek-ai/dsh-llm": "0.1.5-rc.3",
37
+ "@deepseek-ai/schemastery": "3.18.2",
38
+ "@types/node": "^22.14.0",
39
+ "typescript": "^5.9.3"
23
40
  },
24
41
  "scripts": {
25
42
  "build": "bash scripts/build.sh",
package/scripts/build.sh CHANGED
@@ -23,6 +23,11 @@ probe() {
23
23
 
24
24
  find_runtime() {
25
25
  local found=""
26
+ # 0) 项目自己的 node_modules —— CI(GitHub Actions)走这条路:
27
+ # npm install 会把 peerDependencies(@deepseek-ai/dsh-llm 等)装进来,
28
+ # 没有这一步的话干净环境必然探测失败。
29
+ found="$(probe "$ROOT/node_modules" || true)"
30
+ if [ -n "$found" ]; then echo "$found"; return; fi
26
31
  # 1) 显式指定
27
32
  if [ -n "${DSH_CHECKOUT:-}" ]; then
28
33
  found="$(probe "$DSH_CHECKOUT" || true)"
@@ -66,15 +71,32 @@ fi
66
71
  echo "=== DSH 运行时:$RUNTIME ==="
67
72
 
68
73
  # ── ② 链接 @deepseek-ai / @types(整层 scope 链接,类型与运行时共用) ──
74
+ # 两种情况必须区分:
75
+ # · 本机开发:node_modules 里没有这些包,链接到 DSH 运行时(原来唯一的行为)
76
+ # · CI(npm install 过):peerDependencies / devDependencies 已经装成**真实目录**,
77
+ # 此时绝不能动它 —— 否则会把 node_modules/@deepseek-ai 链成它自己(自引用死循环,
78
+ # probe 随后报 "Too many levels of symbolic links",node_modules 被破坏)。
69
79
  node -e '
70
80
  const fs = require("fs")
71
81
  const path = require("path")
72
82
  const runtime = process.argv[1]
73
83
  const root = process.argv[2]
84
+ const real = (p) => { try { return fs.realpathSync(p) } catch { return "" } }
74
85
  for (const name of ["@deepseek-ai", "@types"]) {
75
86
  const target = path.join(runtime, name)
76
87
  if (!fs.existsSync(target)) continue
77
88
  const link = path.join(root, "node_modules", name)
89
+ const linkReal = real(link)
90
+ // 已是真实目录(非符号链接)→ 保留,npm 装的才是权威版本
91
+ if (linkReal && !fs.lstatSync(link).isSymbolicLink()) {
92
+ console.log("kept node_modules/" + name + "(真实目录,不链接)")
93
+ continue
94
+ }
95
+ // 已经指向同一个地方 → 无需重建
96
+ if (linkReal && linkReal === real(target)) {
97
+ console.log("ok node_modules/" + name + "(已指向运行时)")
98
+ continue
99
+ }
78
100
  fs.rmSync(link, { recursive: true, force: true })
79
101
  fs.mkdirSync(path.dirname(link), { recursive: true })
80
102
  fs.symlinkSync(target, link, process.platform === "win32" ? "junction" : "dir")
@@ -83,10 +105,14 @@ for (const name of ["@deepseek-ai", "@types"]) {
83
105
  ' "$RUNTIME" "$ROOT"
84
106
 
85
107
  # ── ③ 编译 host 半 ──
86
- TSC="$(command -v tsc || true)"
87
- if [ -z "$TSC" ] && [ -x "$RUNTIME/node_modules/.bin/tsc" ]; then TSC="$RUNTIME/node_modules/.bin/tsc"; fi
108
+ # tsc 查找顺序:项目自己的 node_modules/.bin(CI 走这条,devDependencies 里已声明)
109
+ # → PATH 上的全局 tsc → DSH 运行时里的 tsc。
110
+ TSC=""
111
+ if [ -x "$ROOT/node_modules/.bin/tsc" ]; then TSC="$ROOT/node_modules/.bin/tsc"; fi
112
+ if [ -z "$TSC" ]; then TSC="$(command -v tsc || true)"; fi
113
+ if [ -z "$TSC" ] && [ -x "$RUNTIME/.bin/tsc" ]; then TSC="$RUNTIME/.bin/tsc"; fi
88
114
  if [ -z "$TSC" ]; then
89
- echo "build: 找不到 tsc(npm i -g typescript 或设置 DSH_CHECKOUT)" >&2
115
+ echo "build: 找不到 tsc —— 在项目里装一个(npm i -D typescript)或设置 DSH_CHECKOUT" >&2
90
116
  exit 1
91
117
  fi
92
118
  echo "=== tsc $("$TSC" -v) ==="
@@ -903,15 +903,32 @@ assert('弹窗显示选中文字', !!panel && textOf(panel).indexOf('the migrati
903
903
 
904
904
  const hook = windowStub.__dshSelectionExplain
905
905
  assert('自检钩子可用', !!hook && typeof hook.state === 'function')
906
- const ping = await hook.ping()
907
- assert('host ping 可达', !!ping && ping.ok === true, JSON.stringify(ping && ping.route))
908
- assert('请求载荷携带当前会话 id', sent.length > 0 && sent[0].sessionId === 'session-stub-1', JSON.stringify(sent[0] && { sessionId: sent[0].sessionId, label: sent[0].label }))
909
- assert('载荷不带 effort(推理档位固定在 host 配置)', sent[0] && sent[0].effort === undefined, String(sent[0] && sent[0].effort))
910
- assert(
911
- '三档档位都能从 /ping 读到',
912
- !!ping && !!ping.reasoningEffortByStage && typeof ping.reasoningEffortByStage.translation === 'string' && typeof ping.reasoningEffortByStage.detail === 'string' && typeof ping.reasoningEffortByStage.chat === 'string',
913
- JSON.stringify(ping && ping.reasoningEffortByStage),
914
- )
906
+
907
+ // ── 宿主可达性探测 ──
908
+ // 本脚本的一部分断言是**真实 host 集成测试**(连 ORIGIN 上的 /ping 与 SSE /analyze)。
909
+ // 本地开发时 host 在跑,这些断言全跑;CI(GitHub Actions)里没有宿主,
910
+ // 此时**跳过**在线断言并打印说明,而不是抛 ECONNREFUSED 崩掉整条发布流水线。
911
+ let HOST_UP = false
912
+ let ping = null
913
+ try {
914
+ ping = await hook.ping()
915
+ HOST_UP = !!(ping && ping.ok === true)
916
+ } catch (error) {
917
+ HOST_UP = false
918
+ }
919
+ if (HOST_UP) {
920
+ assert('host ping 可达', !!ping && ping.ok === true, JSON.stringify(ping && ping.route))
921
+ assert('请求载荷携带当前会话 id', sent.length > 0 && sent[0].sessionId === 'session-stub-1', JSON.stringify(sent[0] && { sessionId: sent[0].sessionId, label: sent[0].label }))
922
+ assert('载荷不带 effort(推理档位固定在 host 配置)', sent[0] && sent[0].effort === undefined, String(sent[0] && sent[0].effort))
923
+ assert(
924
+ '三档档位都能从 /ping 读到',
925
+ !!ping && !!ping.reasoningEffortByStage && typeof ping.reasoningEffortByStage.translation === 'string' && typeof ping.reasoningEffortByStage.detail === 'string' && typeof ping.reasoningEffortByStage.chat === 'string',
926
+ JSON.stringify(ping && ping.reasoningEffortByStage),
927
+ )
928
+ } else {
929
+ console.log(`SKIP host 不可达(${ORIGIN})—— 跳过在线断言(/ping 可达性、SSE 流式渲染、面板交互)`)
930
+ console.log('SKIP 本地开发想跑全量:先启动 DSH(默认 http://127.0.0.1:3080)再执行本脚本')
931
+ }
915
932
  {
916
933
  // 默认值断言走源码(不依赖 3080 上那份部署配置改没改)
917
934
  const hostSrc = readFileSync(resolve(HERE, '..', 'src', 'index.ts'), 'utf8')
@@ -959,6 +976,10 @@ assert(
959
976
  )
960
977
  }
961
978
 
979
+ // ── 以下全部是真实 host 集成断言(SSE 流式渲染 / 面板交互 / 历史 / 清理)──
980
+ // CI 里没有宿主,跳过;本地开发有宿主时全跑。
981
+ if (HOST_UP) {
982
+
962
983
  const deadline = Date.now() + 90000
963
984
  while (Date.now() < deadline) {
964
985
  const state = hook.state()
@@ -2484,3 +2505,12 @@ for (const dispose of disposers.reverse()) {
2484
2505
  assert('清理后 DOM 归零', mount.children.length === 0 && body.children.indexOf(container) >= 0)
2485
2506
  assert('清理后钩子移除', windowStub.__dshSelectionExplain === undefined)
2486
2507
  console.log('\n=== 客户端集成测试结束 ===')
2508
+
2509
+ } else {
2510
+ console.log('\n=== 客户端集成测试结束(离线模式:仅跑了不依赖宿主的断言)===')
2511
+ console.log(` 跳过原因:${ORIGIN} 不可达。完整验证请在 DSH 运行中执行本脚本。`)
2512
+ }
2513
+
2514
+ // 显式退出:离线分支跳过了在线部分的 dispose(),客户端留下的定时器/监听会让
2515
+ // 事件循环一直空转(表现为"测试跑完但不退出")。按 process.exitCode 正常退出。
2516
+ process.exit(process.exitCode ?? 0)