@windypro-rourou/dsh-logcat 0.4.1 → 0.5.0-preview.0

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
@@ -43,6 +43,24 @@ DSH Web GUI 的安卓实机调试面板(类似 Android Studio 的 Logcat 视
43
43
  # 方式一(推荐,npm 安装):
44
44
  dsh plugin --profile web add @windypro-rourou/dsh-logcat
45
45
 
46
+ # 更新到最新版(不会自动更新;装旧版时面板/agent 通告会提示有新版本):
47
+ dsh plugin --profile web update # 升到当前 major 内最新
48
+ # 或强制最新:dsh plugin --profile web add @windypro-rourou/dsh-logcat@latest
49
+ # 更新后重启 GUI(dsh web)生效
50
+
51
+ # 尝鲜 preview 版(日常迭代高频更新,可能不稳定):
52
+ dsh plugin --profile web add @windypro-rourou/dsh-logcat@preview
53
+ ```
54
+
55
+ ## 发布策略(main / preview 双通道)
56
+
57
+ - **`main` 分支 + npm `latest` 标签**:稳定正式版,**低频发布**,每次至少凑够 3-4 个功能再发,
58
+ 避免频繁更新提醒打扰用户。
59
+ - **`preview` 分支 + npm `preview` 标签**:日常迭代,有啥更啥(高频),供尝鲜用户测试;
60
+ 凑够足够功能后合并回 `main` 批量发布正式版。
61
+ - 版本自检会按安装通道提示(正式版用户只看 `latest`,preview 用户只看 `preview`,互不打扰)。
62
+
63
+ ```bash
46
64
  # 方式二(源码本地链接,实时生效无需重启):把插件链进 web profile,
47
65
  # 并在 ~/.dsh/profiles/web/cordis.patch.yml 增加一行:
48
66
  pnpm --dir "%USERPROFILE%\.dsh\profiles\web" add link:F:\dsh-logcat
package/lib/client.js CHANGED
@@ -114,6 +114,11 @@ window.__ModuleLoader__.load({
114
114
  const [currentPackage, setCurrentPackage] = useState("");
115
115
  const [pkgInput, setPkgInput] = useState("");
116
116
  const [installingAdb, setInstallingAdb] = useState(false);
117
+ const [currentVersion, setCurrentVersion] = useState("");
118
+ const [latestVersion, setLatestVersion] = useState("");
119
+ const [updateAvailable, setUpdateAvailable] = useState(false);
120
+ const [updateKind, setUpdateKind] = useState("");
121
+ const [updateHint, setUpdateHint] = useState("");
117
122
  const [level, setLevel] = useState("");
118
123
  const [keyword, setKeyword] = useState("");
119
124
  const [autoScroll, setAutoScroll] = useState(true);
@@ -249,6 +254,11 @@ window.__ModuleLoader__.load({
249
254
  setDevices(body.devices ?? []);
250
255
  setStreaming(body.streaming ?? []);
251
256
  setCurrentPackage(body.currentPackage ?? "");
257
+ setCurrentVersion(body.currentVersion ?? "");
258
+ setLatestVersion(body.latestVersion ?? "");
259
+ setUpdateAvailable(body.updateAvailable === true);
260
+ setUpdateKind(body.updateKind ?? "");
261
+ setUpdateHint(body.updateHint ?? "");
252
262
  devicesRef.current = body.devices ?? [];
253
263
  if (serialRef.current === "" || !(body.devices ?? []).some((d) => d.serial === serialRef.current)) {
254
264
  const first = (body.devices ?? []).find((d) => d.state === "device");
@@ -449,6 +459,12 @@ window.__ModuleLoader__.load({
449
459
  : null),
450
460
  h("span", null, "设备 " + devices.length + " · 在线 " + devices.filter((d) => d.state === "device").length),
451
461
  h("span", null, "显示 " + filtered.length + " / 缓冲 " + entries.length + " 行"),
462
+ h("span", { title: "插件版本" }, "v" + (currentVersion || "?")),
463
+ updateAvailable === true
464
+ ? h("span", { style: { color: updateKind === "preview" ? "#4fc3f7" : "#fbc02d" } },
465
+ h("b", null, updateHint || "有新版本"),
466
+ updateKind === "preview" ? "(preview 尝鲜版)" : "(dsh plugin --profile web update 后重启 GUI)")
467
+ : null,
452
468
  currentPackage !== ""
453
469
  ? h("span", { title: "当前测试应用包名(agent 通过 logcat_set_package 设置)" }, "测试: " + currentPackage)
454
470
  : null,
package/lib/index.js CHANGED
@@ -15,13 +15,75 @@
15
15
  */
16
16
 
17
17
  import { spawn, execFile } from 'node:child_process'
18
- import { existsSync } from 'node:fs'
18
+ import { existsSync, readFileSync } from 'node:fs'
19
19
  import { homedir } from 'node:os'
20
20
  import { dirname, join } from 'node:path'
21
21
  import { createInterface } from 'node:readline'
22
22
  import { WebSocket, WebSocketServer } from 'ws'
23
23
  import { defineTool } from '@deepseek-ai/dsh-tools'
24
24
 
25
+ /** This package's manifest (name + version), read from the installed location. */
26
+ const OWN_MANIFEST = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
27
+
28
+ /** Latest published versions per dist-tag (cached 1h; null until first check). */
29
+ let latestVersion = null // dist-tags.latest (stable)
30
+ let previewVersion = null // dist-tags.preview (experimental)
31
+ let lastVersionCheck = 0
32
+
33
+ /** Simple semver compare: 1 when a > b, -1 when a < b, 0 equal. Prerelease < release. */
34
+ function semverCompare(a, b) {
35
+ const parse = (v) => {
36
+ const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(v ?? '')
37
+ return m ? { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]), pre: m[4] ?? null } : null
38
+ }
39
+ const pa = parse(a)
40
+ const pb = parse(b)
41
+ if (pa === null || pb === null) return 0
42
+ for (const key of ['major', 'minor', 'patch']) {
43
+ if (pa[key] !== pb[key]) return pa[key] > pb[key] ? 1 : -1
44
+ }
45
+ if (pa.pre === pb.pre) return 0
46
+ if (pa.pre === null) return 1 // release > prerelease
47
+ if (pb.pre === null) return -1
48
+ return pa.pre > pb.pre ? 1 : -1
49
+ }
50
+
51
+ /** Refresh the cached npm dist-tags of this package (offline-safe). */
52
+ async function ensureVersionCheck() {
53
+ if ((latestVersion !== null || previewVersion !== null) && Date.now() - lastVersionCheck < 3600_000) return
54
+ lastVersionCheck = Date.now()
55
+ try {
56
+ const res = await fetch(`https://registry.npmjs.org/-/package/${encodeURIComponent(OWN_MANIFEST.name)}/dist-tags`, {
57
+ signal: AbortSignal.timeout(8000),
58
+ })
59
+ if (res.ok) {
60
+ const tags = await res.json()
61
+ latestVersion = tags.latest ?? null
62
+ previewVersion = tags.preview ?? null
63
+ }
64
+ } catch { /* offline / registry unreachable — keep whatever we had */ }
65
+ }
66
+
67
+ /** Whether this install is a prerelease (preview line). */
68
+ const IS_PREVIEW = OWN_MANIFEST.version.includes('-')
69
+
70
+ /** The update hint for the current install, or null when up to date. */
71
+ function updateHint() {
72
+ if (IS_PREVIEW) {
73
+ if (previewVersion !== null && semverCompare(previewVersion, OWN_MANIFEST.version) > 0) {
74
+ return { kind: 'preview', version: previewVersion, text: `新 preview 版本 ${previewVersion} 可更新(npm 标签 preview;正式版 ${latestVersion ?? '?'} 未受影响)` }
75
+ }
76
+ if (previewVersion === null && latestVersion !== null && semverCompare(latestVersion, OWN_MANIFEST.version) > 0) {
77
+ return { kind: 'stable', version: latestVersion, text: `正式版 ${latestVersion} 已发布,可切回稳定版` }
78
+ }
79
+ return null
80
+ }
81
+ if (latestVersion !== null && semverCompare(latestVersion, OWN_MANIFEST.version) > 0) {
82
+ return { kind: 'stable', version: latestVersion, text: `新版本 ${latestVersion} 可更新` }
83
+ }
84
+ return null
85
+ }
86
+
25
87
  /** Stable cordis plugin name. */
26
88
  export const name = 'logcat'
27
89
 
@@ -48,7 +110,7 @@ export const LOGCAT_GUIDANCE =
48
110
  'adb 命令消耗真实设备资源,破坏性操作(卸载/重启/清数据/杀进程)先向用户确认再执行。' +
49
111
  '用户提到「Logcat / 安卓日志 / 实机调试 / adb 日志 / 逆向 / 读内存 / 抓包定位」时即指本插件,请据此主动协作。'
50
112
 
51
- /** Dynamic model-facing announcement: base guidance plus currently attached devices. */
113
+ /** Dynamic model-facing announcement: base guidance plus live device/package/version facts. */
52
114
  function logcatGuidance(engine) {
53
115
  return () => {
54
116
  const devices = engine.deviceList()
@@ -59,7 +121,13 @@ function logcatGuidance(engine) {
59
121
  const pkgLine = engine.currentPackage !== ''
60
122
  ? `当前测试应用包名:${engine.currentPackage}(logcat_recent 默认按它过滤日志)。`
61
123
  : ''
62
- return `${LOGCAT_GUIDANCE}\n${deviceLine}${pkgLine !== '' ? `\n${pkgLine}` : ''}`
124
+ const versionLine = (() => {
125
+ const hint = updateHint()
126
+ if (hint === null) return ''
127
+ const tag = IS_PREVIEW ? 'preview' : 'stable'
128
+ return `dsh-logcat 更新提醒(${tag} 通道):${hint.text}(升级:dsh plugin --profile web update 或 add @windypro-rourou/dsh-logcat@${hint.kind === 'preview' ? 'preview' : 'latest'},然后重启 GUI)。`
129
+ })()
130
+ return `${LOGCAT_GUIDANCE}\n${deviceLine}${pkgLine !== '' ? `\n${pkgLine}` : ''}${versionLine !== '' ? `\n${versionLine}` : ''}`
63
131
  }
64
132
  }
65
133
 
@@ -484,6 +552,8 @@ function makeRoutes(engine) {
484
552
  handler: async (req, res) => {
485
553
  if (!isLoopbackRequest(req)) { writeJson(res, 403, { error: 'forbidden: loopback-only' }); return }
486
554
  if ((req.method ?? 'GET') !== 'GET') { writeJson(res, 405, { error: 'method not allowed' }); return }
555
+ await ensureVersionCheck()
556
+ const hint = updateHint()
487
557
  writeJson(res, 200, {
488
558
  adbPath: engine.adb,
489
559
  adbVersion: engine.adbVersion,
@@ -491,6 +561,13 @@ function makeRoutes(engine) {
491
561
  devices: engine.deviceList(),
492
562
  streaming: [...engine.streams.keys()],
493
563
  currentPackage: engine.currentPackage,
564
+ currentVersion: OWN_MANIFEST.version,
565
+ latestVersion,
566
+ previewVersion,
567
+ updateAvailable: hint !== null,
568
+ updateKind: hint?.kind ?? null,
569
+ updateTarget: hint?.version ?? null,
570
+ updateHint: hint?.text ?? null,
494
571
  })
495
572
  },
496
573
  },
@@ -1830,6 +1907,7 @@ export function apply(ctx, config) {
1830
1907
  const initOnce = () => {
1831
1908
  if (inited) return
1832
1909
  inited = true
1910
+ void ensureVersionCheck()
1833
1911
  void engine.init().then((ok) => {
1834
1912
  if (ok) engine.startPolling()
1835
1913
  })
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@windypro-rourou/dsh-logcat",
3
3
  "description": "Android Logcat viewer for the dsh web GUI: auto-connects to any adb device in debug mode, live logcat stream with level/keyword filters, pause/clear/export, plus agent tools (logcat_recent). Hot-pluggable — mounted via ~/.dsh/cordis.patch.yml + a profile node_modules copy, no dsh source changes.",
4
- "version": "0.4.1",
4
+ "version": "0.5.0-preview.0",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.22.0",
7
7
  "engines": {