tirtc-device-builder 0.2.0 → 0.3.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/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +10 -0
- package/README.md +123 -26
- package/bin/esp32-kit-metadata.js +8 -0
- package/bin/install-esp32-kit.js +208 -0
- package/bin/setup-esp32.js +796 -0
- package/bin/tirtc-device-builder.js +19 -2
- package/package.json +2 -1
- package/skills/tirtc-esp32-builder/SKILL.md +7 -6
- package/skills/tirtc-esp32-builder/USAGE.md +16 -11
- package/skills/tirtc-esp32-builder/references/environment.md +25 -4
- package/skills/tirtc-esp32-builder/scripts/doctor.py +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
This project follows Semantic Versioning.
|
|
4
4
|
|
|
5
|
+
## 0.3.0
|
|
6
|
+
|
|
7
|
+
- Add reproducible packaging for a minimal, checksummed ESP32-S3 Device Kit release asset.
|
|
8
|
+
- Download, verify, cache, and reuse the pinned Device Kit instead of cloning the full ThingConnect source repository during automatic setup.
|
|
9
|
+
- Add a read-only `setup esp32` check that reports the smallest next action.
|
|
10
|
+
- Add `setup esp32 --install` for resumable user-space installation of the Codex Skill, versioned ESP32 Device Kit, ESP-IDF 5.5.4, and ESP32-S3 tools.
|
|
11
|
+
- Reuse valid existing workspaces and ESP-IDF installations without overwriting them.
|
|
12
|
+
- Keep system package installation and persistent shell-profile changes outside automatic setup.
|
|
13
|
+
- Save a path-only managed configuration and environment helper for later Skill runs.
|
|
14
|
+
|
|
5
15
|
## 0.2.0
|
|
6
16
|
|
|
7
17
|
- Publish the repository as the `tirtc-device-builder` npm package.
|
package/README.md
CHANGED
|
@@ -12,11 +12,101 @@ TiRTC Device Builder 是一个面向设备开发者的 Codex Plugin 仓库。它
|
|
|
12
12
|
|
|
13
13
|
## 能力边界
|
|
14
14
|
|
|
15
|
-
ESP32
|
|
15
|
+
ESP32 开发资源以独立、带版本和 SHA-256 的 Device Kit 发布。Kit 的事实源是公开的 [ThingConnect 示例仓库](https://github.com/tangeai/tirtc-server-example),但普通开发者不需要克隆该服务端仓库。Device Kit 不包含设备凭证、板卡 BSP 或用户媒体。
|
|
16
|
+
|
|
17
|
+
## 维护者:发布最小 ESP32 资源包
|
|
18
|
+
|
|
19
|
+
最小资源包只包含工程生成器、`esp32-h5-ai` 模板、`platform_client`、`runtime_config`、`wifi_manager`、TiRTC ESP32-S3 SDK 2.3.0 和相关设备协议文档。打包命令会检查白名单资源、记录源码提交、生成确定性压缩包和 SHA-256:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cd /home/workspace/tirtc-device-builder
|
|
23
|
+
|
|
24
|
+
npm run pack:esp32-kit -- \
|
|
25
|
+
--source /home/workspace/tirtc-server-example/thing-connect \
|
|
26
|
+
--kit-version 1.0.0
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
输出文件位于 `dist/`:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
tirtc-esp32s3-kit-1.0.0.tar.gz
|
|
33
|
+
tirtc-esp32s3-kit-1.0.0.tar.gz.sha256
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
从 `dist/` 目录校验附件:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
cd /home/workspace/tirtc-device-builder/dist
|
|
40
|
+
sha256sum -c tirtc-esp32s3-kit-1.0.0.tar.gz.sha256
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
发布前使用独立标签,并把两个文件作为 GitHub Release 附件上传:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
git tag -a kit-esp32s3-v1.0.0 -m "TiRTC ESP32-S3 Device Kit 1.0.0"
|
|
47
|
+
git push origin kit-esp32s3-v1.0.0
|
|
48
|
+
|
|
49
|
+
gh release create kit-esp32s3-v1.0.0 \
|
|
50
|
+
dist/tirtc-esp32s3-kit-1.0.0.tar.gz \
|
|
51
|
+
dist/tirtc-esp32s3-kit-1.0.0.tar.gz.sha256 \
|
|
52
|
+
--repo tangeai/tirtc-device-builder \
|
|
53
|
+
--verify-tag \
|
|
54
|
+
--latest=false \
|
|
55
|
+
--title "TiRTC ESP32-S3 Device Kit 1.0.0" \
|
|
56
|
+
--notes "ESP-IDF 5.5.x;TiRTC SDK 2.3.0;包含 H5/AI 工程生成资源。"
|
|
57
|
+
```
|
|
16
58
|
|
|
17
59
|
模板工程提供配网、绑定、MQTT、TiRTC、H5 和 AI 会话骨架。具体开发板仍需接入摄像头、H.264 编码器、麦克风、扬声器、Codec、I2S 和按键。工程编译成功不等于 Web 已经出图或 AI 对讲已通过实机验收。
|
|
18
60
|
|
|
19
|
-
##
|
|
61
|
+
## 推荐:一键检查和安装
|
|
62
|
+
|
|
63
|
+
新人先执行一条只读命令:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx tirtc-device-builder@latest setup esp32
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
环境完整时它直接运行 Doctor 并输出 `SETUP: READY`。存在缺失项时,它会显示具体状态和下一条命令:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npx tirtc-device-builder@latest setup esp32 --install
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`--install` 自动完成:
|
|
76
|
+
|
|
77
|
+
- 安装 `tirtc-esp32-builder` 到 Codex Skill 目录;
|
|
78
|
+
- 下载并校验 ESP32 Device Kit 1.0.0,缓存后供多个工程复用;
|
|
79
|
+
- 复用已激活的 ESP-IDF 5.5.x,缺失时安装固定版本 ESP-IDF 5.5.4;
|
|
80
|
+
- 安装 Espressif 管理的 ESP32-S3 工具链;
|
|
81
|
+
- 生成只包含路径的 `config.json` 和 `env.sh`;
|
|
82
|
+
- 自动激活安装环境并重新运行 Doctor。
|
|
83
|
+
|
|
84
|
+
默认托管目录是 `~/.tirtc-device-builder`。自动安装不执行 `sudo`、不修改 `.bashrc` 或 `.zshrc`、不覆盖已有但内容不完整的目录。缺少 Git、Python 或其他系统依赖时,它会停止并给出可复制的系统安装命令;修复后重复同一条 `--install` 命令即可从断点继续。
|
|
85
|
+
|
|
86
|
+
需要自定义位置时:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npx tirtc-device-builder@latest setup esp32 --install \
|
|
90
|
+
--root /absolute/path/tirtc-dev
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
安装完成后启动新的 Codex 会话,只需输入板卡和目标:
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
$tirtc-esp32-builder
|
|
97
|
+
|
|
98
|
+
开发板:<厂商> <完整型号> <PCB 版本>
|
|
99
|
+
板卡资料:/absolute/path/board-materials
|
|
100
|
+
输出工程:/absolute/path/my-esp32-device
|
|
101
|
+
目标:H5 实时音视频、H5 对讲、AI 双向对讲
|
|
102
|
+
|
|
103
|
+
使用一键安装产生的托管环境。先分析资料并生成 Hardware IR,
|
|
104
|
+
达到 READY_TO_PORT 后生成和编译;本轮不烧录。
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
到这里,新人只需要“执行 `setup --install` → 重启 Codex → 输入板卡需求”。下面保留完整流程,供自定义目录、手动安装和故障排查使用。
|
|
108
|
+
|
|
109
|
+
## 完整手动流程和排障
|
|
20
110
|
|
|
21
111
|
推荐先跑通“安装 → 环境 → 板卡分析 → 生成 → 编译”,确认报告无误后再单独授权烧录和实机验收。整个过程使用同一份板卡事实,避免在代码生成阶段猜测引脚、器件或媒体能力。
|
|
22
112
|
|
|
@@ -45,7 +135,7 @@ TIRTC_PORTING_REPORT.md
|
|
|
45
135
|
| ESP-IDF | 5.5.x |
|
|
46
136
|
| TiRTC SDK | `espressif-esp32s3/2.3.0` |
|
|
47
137
|
| 业务 | H5 实时音视频、H5 对讲、AI 双向语音 |
|
|
48
|
-
| 工程生成 |
|
|
138
|
+
| 工程生成 | 使用本地缓存的 ESP32 Device Kit 1.0.0 |
|
|
49
139
|
| 烧录 | 必须明确给出串口并授权 |
|
|
50
140
|
|
|
51
141
|
“板上有摄像头”不等于可以输出 H5 视频。H5 视频还需要可用的 H.264 Annex-B 编码输出、SPS/PPS、IDR 和关键帧请求控制;对讲还需要完整的麦克风采集、G.711 A-law 8 kHz 编码、下行解码、I2S/Codec/功放和扬声器路径。
|
|
@@ -58,16 +148,17 @@ TIRTC_PORTING_REPORT.md
|
|
|
58
148
|
|
|
59
149
|
```bash
|
|
60
150
|
export TIRTC_WORKSPACE=/home/your-user/workspace
|
|
61
|
-
export TIRTC_THING_CONNECT_ROOT="$TIRTC_WORKSPACE/tirtc-server-example/thing-connect"
|
|
62
151
|
export TIRTC_BOARD_DOCS="$TIRTC_WORKSPACE/board-materials"
|
|
63
152
|
export TIRTC_PROJECT_DIR="$TIRTC_WORKSPACE/my-esp32-device"
|
|
153
|
+
|
|
154
|
+
source ~/.tirtc-device-builder/env.sh
|
|
64
155
|
```
|
|
65
156
|
|
|
66
157
|
逐项确认:
|
|
67
158
|
|
|
68
159
|
```bash
|
|
69
160
|
printf 'workspace: %s\n' "$TIRTC_WORKSPACE"
|
|
70
|
-
printf '
|
|
161
|
+
printf 'ESP32 Device Kit: %s\n' "$TIRTC_THING_CONNECT_ROOT"
|
|
71
162
|
printf 'board docs: %s\n' "$TIRTC_BOARD_DOCS"
|
|
72
163
|
printf 'output project: %s\n' "$TIRTC_PROJECT_DIR"
|
|
73
164
|
```
|
|
@@ -130,14 +221,14 @@ npm config set registry https://registry.npmjs.org/
|
|
|
130
221
|
|
|
131
222
|
```bash
|
|
132
223
|
npm view tirtc-device-builder version
|
|
133
|
-
npx tirtc-device-builder@
|
|
134
|
-
npx tirtc-device-builder@
|
|
224
|
+
npx tirtc-device-builder@latest list
|
|
225
|
+
npx tirtc-device-builder@latest install esp32
|
|
135
226
|
```
|
|
136
227
|
|
|
137
228
|
成功时最后会看到类似输出:
|
|
138
229
|
|
|
139
230
|
```text
|
|
140
|
-
Installed tirtc-esp32-builder
|
|
231
|
+
Installed tirtc-esp32-builder <version> to /home/.../.codex/skills/tirtc-esp32-builder
|
|
141
232
|
Start a new Codex session, then invoke $tirtc-esp32-builder.
|
|
142
233
|
```
|
|
143
234
|
|
|
@@ -146,13 +237,13 @@ Start a new Codex session, then invoke $tirtc-esp32-builder.
|
|
|
146
237
|
如果目标目录已经存在,安装器会保护已有内容并退出。先确认目录中的本地修改是否需要保留;只有确定要替换时才执行:
|
|
147
238
|
|
|
148
239
|
```bash
|
|
149
|
-
npx tirtc-device-builder@
|
|
240
|
+
npx tirtc-device-builder@latest install esp32 --force
|
|
150
241
|
```
|
|
151
242
|
|
|
152
243
|
自定义 Skill 根目录时使用绝对路径:
|
|
153
244
|
|
|
154
245
|
```bash
|
|
155
|
-
npx tirtc-device-builder@
|
|
246
|
+
npx tirtc-device-builder@latest install esp32 \
|
|
156
247
|
--skills-dir /absolute/path/to/skills
|
|
157
248
|
```
|
|
158
249
|
|
|
@@ -162,7 +253,7 @@ npx tirtc-device-builder@0.2.0 install esp32 \
|
|
|
162
253
|
$skill-installer
|
|
163
254
|
|
|
164
255
|
安装:
|
|
165
|
-
https://github.com/tangeai/tirtc-device-builder/tree/v0.
|
|
256
|
+
https://github.com/tangeai/tirtc-device-builder/tree/v0.3.0/skills/tirtc-esp32-builder
|
|
166
257
|
```
|
|
167
258
|
|
|
168
259
|
Linux/macOS 也可以执行:
|
|
@@ -170,27 +261,33 @@ Linux/macOS 也可以执行:
|
|
|
170
261
|
```bash
|
|
171
262
|
python3 ~/.codex/skills/.system/skill-installer/scripts/install-skill-from-github.py \
|
|
172
263
|
--repo tangeai/tirtc-device-builder \
|
|
173
|
-
--ref v0.
|
|
264
|
+
--ref v0.3.0 \
|
|
174
265
|
--path skills/tirtc-esp32-builder
|
|
175
266
|
```
|
|
176
267
|
|
|
177
268
|
仓库根目录还包含 `.codex-plugin/plugin.json`,可作为 skills-only Plugin 使用。直接从 npm 或 GitHub 安装不依赖 Plugin Directory 审核。
|
|
178
269
|
|
|
179
|
-
### 第 4 步:准备
|
|
270
|
+
### 第 4 步:准备 ESP32 Device Kit
|
|
180
271
|
|
|
181
|
-
|
|
272
|
+
Device Kit 包含生成新工程需要的最小资源:
|
|
182
273
|
|
|
183
274
|
- ESP32-S3 H5/AI 工程生成器;
|
|
184
275
|
- 起步模板和平台公共组件;
|
|
185
276
|
- TiRTC ESP32-S3 SDK 头文件、静态库和构建契约;
|
|
186
277
|
- H5、AI、设备接入和会话协议文档。
|
|
187
278
|
|
|
188
|
-
|
|
279
|
+
一键安装会下载固定 Release、校验 SHA-256,并缓存到 `~/.tirtc-device-builder/kits/esp32s3/1.0.0/`。不需要克隆 `tirtc-server-example`:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
npx tirtc-device-builder@latest setup esp32 --install
|
|
283
|
+
source ~/.tirtc-device-builder/env.sh
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
维护者刚生成 Release、附件还没有上传时,可以使用本地包完成同样的安装验证:
|
|
189
287
|
|
|
190
288
|
```bash
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
"$TIRTC_WORKSPACE/tirtc-server-example"
|
|
289
|
+
npx tirtc-device-builder@latest setup esp32 --install \
|
|
290
|
+
--kit-archive /absolute/path/tirtc-esp32s3-kit-1.0.0.tar.gz
|
|
194
291
|
```
|
|
195
292
|
|
|
196
293
|
验证关键文件:
|
|
@@ -202,13 +299,13 @@ ls -l "$TIRTC_THING_CONNECT_ROOT/device-sim/sdk/espressif-esp32s3/2.3.0/lib/libT
|
|
|
202
299
|
ls -l "$TIRTC_THING_CONNECT_ROOT/device-sim/sdk/espressif-esp32s3/2.3.0/manifest/build-contract.env"
|
|
203
300
|
```
|
|
204
301
|
|
|
205
|
-
|
|
302
|
+
四条命令都应显示真实文件。资源来源版本记录在 Kit 清单中:
|
|
206
303
|
|
|
207
304
|
```bash
|
|
208
|
-
|
|
305
|
+
python3 -m json.tool "$TIRTC_THING_CONNECT_ROOT/manifest.json"
|
|
209
306
|
```
|
|
210
307
|
|
|
211
|
-
生成器会把必要模块和 TiRTC SDK 复制到新工程的 `third_party/tirtc
|
|
308
|
+
生成器会把必要模块和 TiRTC SDK 复制到新工程的 `third_party/tirtc/`,所以已生成的工程可以移动并独立编译。一个已校验的 Kit 缓存可以供多个工程使用。
|
|
212
309
|
|
|
213
310
|
### 第 5 步:检查或安装 ESP-IDF 5.5.x
|
|
214
311
|
|
|
@@ -259,7 +356,7 @@ command -v xtensa-esp32s3-elf-gcc
|
|
|
259
356
|
不安装 Skill 也可以通过 npm CLI 调用同一个检查脚本:
|
|
260
357
|
|
|
261
358
|
```bash
|
|
262
|
-
npx tirtc-device-builder@
|
|
359
|
+
npx tirtc-device-builder@latest doctor esp32 \
|
|
263
360
|
--expected-idf 5.5 \
|
|
264
361
|
--target esp32s3 \
|
|
265
362
|
--thing-connect-root "$TIRTC_THING_CONNECT_ROOT" \
|
|
@@ -283,7 +380,7 @@ python3 ~/.codex/skills/tirtc-esp32-builder/scripts/doctor.py \
|
|
|
283
380
|
| Python、Git | `PASS` |
|
|
284
381
|
| `idf.py` | `PASS`,版本为 5.5.x |
|
|
285
382
|
| target compiler | `PASS`,找到 ESP32-S3 编译器 |
|
|
286
|
-
|
|
|
383
|
+
| ESP32 Device Kit | `PASS` |
|
|
287
384
|
| TiRTC SDK | `PASS` |
|
|
288
385
|
| serial discovery | 还未接板时允许 `WARN` |
|
|
289
386
|
| OVERALL | `PASS` |
|
|
@@ -294,7 +391,7 @@ python3 ~/.codex/skills/tirtc-esp32-builder/scripts/doctor.py \
|
|
|
294
391
|
|
|
295
392
|
- `idf.py not found`:当前终端没有激活 ESP-IDF,先执行 `source <idf-dir>/export.sh`。
|
|
296
393
|
- `target compiler ... not found`:ESP-IDF 环境未激活,或安装时没有包含 `esp32s3`。
|
|
297
|
-
- `
|
|
394
|
+
- `ESP32 Device Kit ... not found`:先执行一键安装,或传入 Kit 根目录;兼容路径也可以是完整仓库根目录或其 `thing-connect/` 子目录。
|
|
298
395
|
- `TiRTC SDK ... missing`:检查仓库是否拉取完整,以及 SDK 的头文件、静态库和 `manifest/build-contract.env` 是否存在。
|
|
299
396
|
- `serial discovery: no serial device detected`:生成和编译阶段可以继续;烧录前必须解决。
|
|
300
397
|
|
|
@@ -324,7 +421,7 @@ FAIL TiRTC build contract [required]: no sdkconfig or sdkconfig.defaults in ...
|
|
|
324
421
|
- BOM:/absolute/path/board-materials/<file>
|
|
325
422
|
- BSP/厂商示例:/absolute/path/vendor-bsp
|
|
326
423
|
- 摄像头/Codec/功放数据手册:/absolute/path/board-materials/<files>
|
|
327
|
-
-
|
|
424
|
+
- Device Kit:使用 `setup esp32 --install` 生成的托管环境
|
|
328
425
|
|
|
329
426
|
目标:
|
|
330
427
|
- H5 实时视频和音频
|
|
@@ -554,7 +651,7 @@ AI 验收:
|
|
|
554
651
|
- [ ] 板卡厂商、完整型号、模组、PCB 版本一致。
|
|
555
652
|
- [ ] 原理图/BOM/BSP/数据手册路径均为绝对路径且可读。
|
|
556
653
|
- [ ] Node.js 版本不低于 18,Skill 已安装并重启 Codex。
|
|
557
|
-
- [ ]
|
|
654
|
+
- [ ] ESP32 Device Kit 的生成器、TiRTC 头文件、静态库和构建契约存在。
|
|
558
655
|
- [ ] 当前终端已激活 ESP-IDF 5.5.x 和 ESP32-S3 工具链。
|
|
559
656
|
- [ ] 生成前 Doctor 的 `OVERALL` 为 `PASS`。
|
|
560
657
|
- [ ] 所有请求能力均为 `READY_TO_PORT` 或 `HIL_VERIFIED`。
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const ESP32_KIT = Object.freeze({
|
|
2
|
+
archiveName: "tirtc-esp32s3-kit-1.0.0.tar.gz",
|
|
3
|
+
archiveRoot: "tirtc-esp32s3-kit-1.0.0",
|
|
4
|
+
releaseTag: "kit-esp32s3-v1.0.0",
|
|
5
|
+
sha256: "d2333878ae499a349b125cd81db0a64aef62bfae8fd7c0898c8eaa5ff2a49b65",
|
|
6
|
+
url: "https://github.com/tangeai/tirtc-device-builder/releases/download/kit-esp32s3-v1.0.0/tirtc-esp32s3-kit-1.0.0.tar.gz",
|
|
7
|
+
version: "1.0.0",
|
|
8
|
+
});
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import {
|
|
6
|
+
existsSync,
|
|
7
|
+
lstatSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
mkdtempSync,
|
|
10
|
+
readFileSync,
|
|
11
|
+
readdirSync,
|
|
12
|
+
renameSync,
|
|
13
|
+
rmSync,
|
|
14
|
+
writeFileSync,
|
|
15
|
+
} from "node:fs";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { ESP32_KIT } from "./esp32-kit-metadata.js";
|
|
20
|
+
|
|
21
|
+
const REQUIRED_FILES = [
|
|
22
|
+
"manifest.json",
|
|
23
|
+
"device-sim/scripts/create_esp32_project.py",
|
|
24
|
+
"device-sim/templates/esp32-h5-ai/CMakeLists.txt",
|
|
25
|
+
"device-sim/sdk/espressif-esp32s3/2.3.0/include/tirtc/tiRTC.h",
|
|
26
|
+
"device-sim/sdk/espressif-esp32s3/2.3.0/lib/libTiRTC.a",
|
|
27
|
+
"device-sim/sdk/espressif-esp32s3/2.3.0/manifest/build-contract.env",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
function hashFile(path) {
|
|
31
|
+
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function runTar(args) {
|
|
35
|
+
const result = spawnSync("tar", args, { encoding: "utf8" });
|
|
36
|
+
if (result.error) {
|
|
37
|
+
throw new Error(`could not run tar: ${result.error.message}`);
|
|
38
|
+
}
|
|
39
|
+
if (result.status !== 0) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`tar exited with status ${result.status}: ${result.stderr.trim()}`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return result.stdout;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function assertArchivePaths(archive, archiveRoot) {
|
|
48
|
+
const entries = runTar(["-tzf", archive])
|
|
49
|
+
.split(/\r?\n/)
|
|
50
|
+
.filter(Boolean);
|
|
51
|
+
if (entries.length === 0) {
|
|
52
|
+
throw new Error("Device Kit archive is empty");
|
|
53
|
+
}
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
if (
|
|
56
|
+
entry.startsWith("/") ||
|
|
57
|
+
entry.split("/").includes("..") ||
|
|
58
|
+
!(entry === archiveRoot || entry.startsWith(`${archiveRoot}/`))
|
|
59
|
+
) {
|
|
60
|
+
throw new Error(`unsafe Device Kit archive path: ${entry}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function assertNoLinks(root, current = root) {
|
|
66
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
67
|
+
const path = join(current, entry.name);
|
|
68
|
+
const stat = lstatSync(path);
|
|
69
|
+
if (stat.isSymbolicLink()) {
|
|
70
|
+
throw new Error(`Device Kit contains a symbolic link: ${relative(root, path)}`);
|
|
71
|
+
}
|
|
72
|
+
if (stat.isDirectory()) {
|
|
73
|
+
assertNoLinks(root, path);
|
|
74
|
+
} else if (!stat.isFile()) {
|
|
75
|
+
throw new Error(`Device Kit contains an unsupported entry: ${relative(root, path)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function validateExtractedKit(root, metadata) {
|
|
81
|
+
const missing = REQUIRED_FILES.filter((path) => !existsSync(join(root, path)));
|
|
82
|
+
if (missing.length > 0) {
|
|
83
|
+
throw new Error(`Device Kit is incomplete: ${missing.join(", ")}`);
|
|
84
|
+
}
|
|
85
|
+
assertNoLinks(root);
|
|
86
|
+
let manifest;
|
|
87
|
+
try {
|
|
88
|
+
manifest = JSON.parse(readFileSync(join(root, "manifest.json"), "utf8"));
|
|
89
|
+
} catch (error) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Device Kit manifest is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (manifest.kit_version !== metadata.version) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Device Kit version mismatch: expected ${metadata.version}, got ${manifest.kit_version}`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (manifest.target !== "esp32s3" || manifest.tirtc_sdk_version !== "2.3.0") {
|
|
100
|
+
throw new Error("Device Kit target or TiRTC SDK version is incompatible");
|
|
101
|
+
}
|
|
102
|
+
if (!manifest.files || typeof manifest.files !== "object") {
|
|
103
|
+
throw new Error("Device Kit manifest has no file checksums");
|
|
104
|
+
}
|
|
105
|
+
for (const [path, expected] of Object.entries(manifest.files)) {
|
|
106
|
+
if (
|
|
107
|
+
typeof expected !== "string" ||
|
|
108
|
+
path.startsWith("/") ||
|
|
109
|
+
path.split("/").includes("..")
|
|
110
|
+
) {
|
|
111
|
+
throw new Error(`Device Kit manifest contains an unsafe entry: ${path}`);
|
|
112
|
+
}
|
|
113
|
+
const file = join(root, path);
|
|
114
|
+
if (!existsSync(file) || hashFile(file) !== expected) {
|
|
115
|
+
throw new Error(`Device Kit file checksum mismatch: ${path}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function installEsp32KitArchive(archive, target, metadata = ESP32_KIT) {
|
|
121
|
+
const resolvedArchive = resolve(archive);
|
|
122
|
+
const resolvedTarget = resolve(target);
|
|
123
|
+
if (!existsSync(resolvedArchive)) {
|
|
124
|
+
throw new Error(`Device Kit archive does not exist: ${resolvedArchive}`);
|
|
125
|
+
}
|
|
126
|
+
const actualHash = hashFile(resolvedArchive);
|
|
127
|
+
if (actualHash !== metadata.sha256) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Device Kit SHA-256 mismatch: expected ${metadata.sha256}, got ${actualHash}`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
if (existsSync(resolvedTarget)) {
|
|
133
|
+
throw new Error(`refusing to overwrite existing Device Kit: ${resolvedTarget}`);
|
|
134
|
+
}
|
|
135
|
+
mkdirSync(dirname(resolvedTarget), { recursive: true });
|
|
136
|
+
const temporary = mkdtempSync(join(dirname(resolvedTarget), ".kit-install-"));
|
|
137
|
+
try {
|
|
138
|
+
assertArchivePaths(resolvedArchive, metadata.archiveRoot);
|
|
139
|
+
runTar(["-xzf", resolvedArchive, "-C", temporary]);
|
|
140
|
+
const extracted = join(temporary, metadata.archiveRoot);
|
|
141
|
+
validateExtractedKit(extracted, metadata);
|
|
142
|
+
renameSync(extracted, resolvedTarget);
|
|
143
|
+
} finally {
|
|
144
|
+
rmSync(temporary, { force: true, recursive: true });
|
|
145
|
+
}
|
|
146
|
+
console.log(`Installed ESP32 Device Kit ${metadata.version} to ${resolvedTarget}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function download(url, destination) {
|
|
150
|
+
console.log(`GET ${url}`);
|
|
151
|
+
const response = await fetch(url, { redirect: "follow" });
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
throw new Error(`Device Kit download failed: HTTP ${response.status}`);
|
|
154
|
+
}
|
|
155
|
+
const content = Buffer.from(await response.arrayBuffer());
|
|
156
|
+
writeFileSync(destination, content, { mode: 0o600 });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function parseArgs(args) {
|
|
160
|
+
let archive = null;
|
|
161
|
+
let target = null;
|
|
162
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
163
|
+
const argument = args[index];
|
|
164
|
+
if (argument === "--archive" || argument === "--target") {
|
|
165
|
+
const value = args[index + 1];
|
|
166
|
+
if (!value || value.startsWith("--")) {
|
|
167
|
+
throw new Error(`${argument} requires a path`);
|
|
168
|
+
}
|
|
169
|
+
if (argument === "--archive") {
|
|
170
|
+
archive = resolve(value);
|
|
171
|
+
} else {
|
|
172
|
+
target = resolve(value);
|
|
173
|
+
}
|
|
174
|
+
index += 1;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
throw new Error(`unknown Device Kit installer option: ${argument}`);
|
|
178
|
+
}
|
|
179
|
+
if (!target) {
|
|
180
|
+
throw new Error("--target is required");
|
|
181
|
+
}
|
|
182
|
+
return { archive, target };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function main() {
|
|
186
|
+
const options = parseArgs(process.argv.slice(2));
|
|
187
|
+
let archive = options.archive;
|
|
188
|
+
let downloadDirectory = null;
|
|
189
|
+
try {
|
|
190
|
+
if (!archive) {
|
|
191
|
+
downloadDirectory = mkdtempSync(join(tmpdir(), "tirtc-kit-download-"));
|
|
192
|
+
archive = join(downloadDirectory, ESP32_KIT.archiveName);
|
|
193
|
+
await download(ESP32_KIT.url, archive);
|
|
194
|
+
}
|
|
195
|
+
installEsp32KitArchive(archive, options.target);
|
|
196
|
+
} finally {
|
|
197
|
+
if (downloadDirectory) {
|
|
198
|
+
rmSync(downloadDirectory, { force: true, recursive: true });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
204
|
+
main().catch((error) => {
|
|
205
|
+
console.error(`ERROR: ${error instanceof Error ? error.message : String(error)}`);
|
|
206
|
+
process.exitCode = 1;
|
|
207
|
+
});
|
|
208
|
+
}
|