@ponydev/dsh-phone 0.4.0 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.1 — 2026-10-26
4
+
5
+ ### 修复
6
+ - 鸿蒙模拟器名称规则与 CLI 对齐:只允许字母、数字、空格、下划线 `_`、加号 `+`(之前允许连字符/中文,`-create` 会直接失败);创建表单占位符改为 `dsh_phone_hos`
7
+ - 鸿蒙模拟器 `start` 改为 detached 启动 + 截图 warm-up:`Emulator -start` 是常驻进程,之前前台等待导致 90s 超时;现在启动后等显示服务就绪再返回
8
+
9
+ ### 验证(0.4.1 发布前实测)
10
+ - iOS 模拟器:create → boot → 截图 → H.264 流(idb,100 帧/3s)→ tap / Home 通过;像素→point(density=3)用真实弹窗按钮验证
11
+ - HarmonyOS 模拟器:create → start(45s 含 warm-up)→ 截图 → tap / swipe → stop → delete 全链路通过
12
+ - HarmonyOS 真机(Mate 60 Pro):截图 / keyEvent / tap / text 通过
13
+
3
14
  ## 0.4.0 — 2026-10-26
4
15
 
5
16
  ### 新增
package/README.md CHANGED
@@ -120,9 +120,19 @@ DSH 右侧栏第一次打开是**视口宽度的 45%**(1440 屏 ≈ 648px)
120
120
  | HarmonyOS 真机 | hdc | 截图轮询 1–2fps | tap / swipe / 按键 / 文字 | — |
121
121
 
122
122
  - iOS 依赖 macOS + Xcode;实时画面/输入需要 `idb`(`brew install facebook/fb/idb-companion` + `pip install fb-idb`),未装时只读预览。
123
+ - 如果 Homebrew 报 "Xcode alone is not sufficient on Tahoe"(需要 Command Line Tools),可改用官方 release 手工安装:
124
+ `gh release download v1.7.7 --repo facebook/idb --pattern 'idb-companion.macos-arm64.tar.gz'`,解压到 `~/.local/lib/idb-companion`,
125
+ 再把 `idb_companion` / `idb-repl` 软链到 `~/.local/bin`;客户端 `python3 -m venv ~/.local/venvs/fb-idb && ~/.local/venvs/fb-idb/bin/pip install fb-idb`。
123
126
  - HarmonyOS 依赖 DevEco Studio(`Emulator` CLI)与 `hdc`;hdc 没有 screenrecord,只能截图轮询。
124
127
  - 物理 iPhone / iPad 不支持(Apple 未公开投屏/注入 API,Claude Code Desktop 也只支持模拟器)。
125
128
 
129
+ ## 验证状态
130
+
131
+ - **iOS 模拟器**(iPhone Air / iOS 26.5):create / boot / 截图 / idb H.264 流 / tap / Home 实测通过;像素→point 坐标换算用真实弹窗验证。
132
+ - **HarmonyOS 模拟器**(HarmonyOS 7.0.0):create / start / 截图 / tap / swipe / rotate / stop / delete 全链路实测通过。
133
+ - **HarmonyOS 真机**(Mate 60 Pro,hdc):截图 / keyEvent / tap / text 实测通过。
134
+ - **Android**:真机与云手机的 tap / swipe / back / home / 截图 / H.264 流实测通过。
135
+
126
136
  ## 已知限制
127
137
 
128
138
  - Android 画面默认走 H.264 流(约 20–30fps);没有 WebCodecs 的浏览器会回退到 1–2fps 轮询截图。
package/lib/client.js CHANGED
@@ -1848,7 +1848,7 @@ window.__ModuleLoader__.load({
1848
1848
  h('input', {
1849
1849
  className: 'dph-input',
1850
1850
  value: hosForm.name,
1851
- placeholder: 'dsh-phone-hos',
1851
+ placeholder: 'dsh_phone_hos',
1852
1852
  onChange: (event) => setHosForm((prev) => ({ ...prev, name: event.target.value })),
1853
1853
  spellCheck: false,
1854
1854
  }),
package/lib/hos.js CHANGED
@@ -8,7 +8,7 @@
8
8
  // 按键和文字没有 CLI 能力,内部转给 hdc uitest。
9
9
  // HarmonyHdc 真机 / 云手机:hdc list targets,截图走 snapshot_display + file recv(JPEG),
10
10
  // 输入走 uitest uiInput。默认从列表里过滤 model=emulator 的本地模拟器。
11
- import { execFile } from 'node:child_process'
11
+ import { execFile, spawn } from 'node:child_process'
12
12
  import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs'
13
13
  import { homedir, tmpdir } from 'node:os'
14
14
  import { join } from 'node:path'
@@ -16,7 +16,7 @@ import { join } from 'node:path'
16
16
  /** hdc serial 白名单:字母数字开头,允许 . _ : -,最长 128 */
17
17
  export const HDC_SERIAL_RE = /^[A-Za-z0-9][A-Za-z0-9._:\-]{0,127}$/
18
18
  /** 模拟器实例名:字母/数字/中文开头,允许空格 . _ -,最长 64;不允许路径分隔符 */
19
- export const EMULATOR_NAME_RE = /^[A-Za-z0-9\u4e00-\u9fa5][A-Za-z0-9\u4e00-\u9fa5 ._\-]{0,63}$/
19
+ export const EMULATOR_NAME_RE = /^[A-Za-z0-9_+][A-Za-z0-9 _+]{0,63}$/
20
20
  /**
21
21
  * uitest 文字白名单:字母、数字、空格和少量安全标点。
22
22
  * hdc shell 最终会经过设备 shell,`$` `;` `` ` `` `|` `&` 引号等一律不收。
@@ -449,7 +449,7 @@ export function normalizeDeviceType(value) {
449
449
  export function normalizeEmulatorName(value) {
450
450
  const name = text(value)
451
451
  if (!EMULATOR_NAME_RE.test(name)) {
452
- throw fail('bad-emulator-name', '模拟器名称不合法(字母/数字/中文开头,允许空格 . _ -,最长 64)')
452
+ throw fail('bad-emulator-name', '模拟器名称不合法:只能用字母、数字、空格、下划线 _、加号 +(最长 64,CLI 不允许中文和连字符)')
453
453
  }
454
454
  return name
455
455
  }
@@ -973,12 +973,27 @@ export class HarmonyEmulator {
973
973
  const instance = normalizeEmulatorName(name)
974
974
  const existing = await this.find(instance)
975
975
  if (existing.state === 'device') return existing
976
- await this._run(['-start', instance], { timeout: this.startTimeout })
977
- const started = await this._poll(instance, (device) => device.state === 'device', this.startTimeout)
978
- if (!started) {
979
- throw fail('start-timeout', `模拟器 ${instance} 在 ${Math.round(this.startTimeout / 1000)}s 内没有启动完成`)
976
+ // Emulator -start 是常驻进程(前台阻塞直到模拟器退出),必须 detached 启动再轮询状态。
977
+ // 状态变成 device 之后显示服务还需要一点时间,所以用一次真实截图做 warm-up。
978
+ await this.resolve()
979
+ const child = spawn(this.bin, ['-start', instance, '-noWindow'], { detached: true, stdio: 'ignore' })
980
+ child.unref()
981
+ const deadline = Date.now() + this.startTimeout
982
+ while (Date.now() < deadline) {
983
+ const list = await this.list()
984
+ const device = list.find((item) => item.serial === instance)
985
+ if (device && device.state === 'device') {
986
+ try {
987
+ await this.shot(instance)
988
+ return device
989
+ } catch {
990
+ await sleep(2000)
991
+ continue
992
+ }
993
+ }
994
+ await sleep(1000)
980
995
  }
981
- return started
996
+ throw fail('start-timeout', `模拟器 ${instance} 在 ${Math.round(this.startTimeout / 1000)}s 内没有启动完成`)
982
997
  }
983
998
 
984
999
  /** 停止并等到 isRunning=false。已经停止就直接返回。 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ponydev/dsh-phone",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "DSH Web 插件:右侧栏预览并控制 Android / iOS 模拟器 / HarmonyOS 设备与模拟器",
5
5
  "keywords": [
6
6
  "dsh",