@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 +11 -0
- package/README.md +10 -0
- package/lib/client.js +1 -1
- package/lib/hos.js +23 -8
- package/package.json +1 -1
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: '
|
|
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-
|
|
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', '
|
|
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
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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
|
-
|
|
996
|
+
throw fail('start-timeout', `模拟器 ${instance} 在 ${Math.round(this.startTimeout / 1000)}s 内没有启动完成`)
|
|
982
997
|
}
|
|
983
998
|
|
|
984
999
|
/** 停止并等到 isRunning=false。已经停止就直接返回。 */
|