@heybox/hb-sdk 0.8.0-alpha.3 → 0.8.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +136 -422
  2. package/README.md +28 -18
  3. package/dist/cli-chunks/{build-qWzAbpS7.cjs → build-PYCNacya.cjs} +8 -5
  4. package/dist/cli-chunks/{context-CtS2Thp0.cjs → context-m2W2XbL0.cjs} +42 -59
  5. package/dist/cli-chunks/{create-PV5ua977.cjs → create-BdAg3WGA.cjs} +2 -2
  6. package/dist/cli-chunks/{dev-CwKbAm_H.cjs → dev-CyZuw7Yn.cjs} +76 -64
  7. package/dist/cli-chunks/{doctor-tJUGOYrm.cjs → doctor-DU8rCfUF.cjs} +1 -1
  8. package/dist/cli-chunks/{index-DVuD75Hr.cjs → index-DATObqzK.cjs} +2 -2
  9. package/dist/cli-chunks/{index-De687C6-.cjs → index-MMW2ibQm.cjs} +32 -21
  10. package/dist/cli-chunks/{index.esm-B-4yrLNm.cjs → index.esm-BiAaAUFC.cjs} +8 -8
  11. package/dist/cli-chunks/{login-DolpqD8K.cjs → login-B3TThMss.cjs} +2 -2
  12. package/dist/cli-chunks/{project-vite-1rvkK-M8.cjs → project-vite-BQj8YLI4.cjs} +1 -1
  13. package/dist/cli-chunks/{remote-rIAQE_G2.cjs → remote-DNvI7tHH.cjs} +57 -25
  14. package/dist/cli-chunks/{runtime-gate-DFjw66kF.cjs → runtime-gate-BEFp1w_s.cjs} +11 -3
  15. package/dist/cli-chunks/{runtime-permission-env-CjsCe5bp.cjs → runtime-permission-env-CtL8rsjB.cjs} +351 -0
  16. package/dist/cli-chunks/{session-CzaM2Cq3.cjs → session-DjBkjaF8.cjs} +1 -1
  17. package/dist/cli-chunks/{skill-CM40_9WH.cjs → skill-cR_wnaw2.cjs} +2 -2
  18. package/dist/cli-chunks/{version-Bz-AfXQU.cjs → version-yEn1E2Bg.cjs} +1 -1
  19. package/dist/cli.cjs +1 -1
  20. package/dist/devtools/browser-dev-host/assets/browser-dev-host-TzYf9L6C.js +99 -0
  21. package/dist/devtools/browser-dev-host/assets/{index-CeP6SLB3.js → index-C5MZZDa5.js} +4 -4
  22. package/dist/devtools/browser-dev-host/assets/index-P-ra4m1y.css +1 -0
  23. package/dist/devtools/browser-dev-host/assets/workbench-state-BwV7bm4n.js +5 -0
  24. package/dist/devtools/browser-dev-host/index.html +3 -3
  25. package/dist/index.cjs.js +100 -2
  26. package/dist/index.esm.js +100 -2
  27. package/dist/protocol.cjs.js +189 -47
  28. package/dist/protocol.esm.js +189 -47
  29. package/dist/templates/{vue3-vite-ts → vanilla-vite-js}/.gitignore.ejs +0 -1
  30. package/dist/templates/vanilla-vite-js/README.md.ejs +14 -0
  31. package/dist/templates/vanilla-vite-js/index.html.ejs +20 -0
  32. package/dist/templates/vanilla-vite-js/package.json.ejs +22 -0
  33. package/dist/templates/vanilla-vite-js/src/assets/heybox-logo.svg +8 -0
  34. package/dist/templates/vanilla-vite-js/src/main.js +39 -0
  35. package/dist/templates/vanilla-vite-js/src/styles.css +155 -0
  36. package/dist/templates/{vue3-vite-ts/vite.config.ts → vanilla-vite-js/vite.config.js} +1 -2
  37. package/dist/vite.cjs.js +281 -13
  38. package/dist/vite.esm.js +281 -13
  39. package/package.json +13 -9
  40. package/skill/SKILL.md +8 -9
  41. package/skill/references/api-protocol.md +1 -3
  42. package/skill/references/api-root.md +65 -21
  43. package/skill/references/cli.md +17 -5
  44. package/skill/references/examples.md +3 -3
  45. package/skill/references/recipes.md +40 -41
  46. package/skill/references/safety-boundaries.md +9 -5
  47. package/skill/skill.json +5 -5
  48. package/types/core/mini-dev-console.d.ts +36 -0
  49. package/types/miniapp-manifest/index.d.ts +1 -0
  50. package/types/miniapp-manifest/node.d.ts +3 -0
  51. package/types/miniapp-manifest/permissions.d.ts +37 -0
  52. package/types/miniapp-manifest/schema.d.ts +5 -0
  53. package/types/modules/share/index.d.ts +1 -1
  54. package/types/modules/share/show-share-menu.d.ts +1 -1
  55. package/types/modules/share/types.d.ts +2 -4
  56. package/types/vite/index.d.ts +3 -1
  57. package/dist/devtools/browser-dev-host/assets/browser-dev-host-uq-Wac6k.js +0 -97
  58. package/dist/devtools/browser-dev-host/assets/index-KD2f3Jdz.css +0 -1
  59. package/dist/devtools/browser-dev-host/assets/workbench-state-D-1U0JRq.js +0 -5
  60. package/dist/templates/vue3-vite-ts/README.md.ejs +0 -47
  61. package/dist/templates/vue3-vite-ts/index.html.ejs +0 -12
  62. package/dist/templates/vue3-vite-ts/package.json.ejs +0 -33
  63. package/dist/templates/vue3-vite-ts/src/App.vue +0 -78
  64. package/dist/templates/vue3-vite-ts/src/__tests__/App.spec.ts +0 -148
  65. package/dist/templates/vue3-vite-ts/src/auth-handoff.ts +0 -46
  66. package/dist/templates/vue3-vite-ts/src/main.ts +0 -5
  67. package/dist/templates/vue3-vite-ts/src/styles.css +0 -60
  68. package/dist/templates/vue3-vite-ts/src/vite-env.d.ts +0 -1
  69. package/dist/templates/vue3-vite-ts/tsconfig.app.json +0 -17
  70. package/dist/templates/vue3-vite-ts/tsconfig.json +0 -11
  71. package/dist/templates/vue3-vite-ts/tsconfig.node.json +0 -11
  72. package/dist/templates/vue3-vite-ts/vitest.config.ts +0 -10
package/README.md CHANGED
@@ -21,6 +21,8 @@ npm run dev
21
21
 
22
22
  脚手架已包含 SDK、Vite 配置和常用开发命令。浏览器调试通过后,再从调试台的二维码入口使用手机小黑盒 APP 验收。
23
23
 
24
+ 当前 `0.8` 已发布为稳定版本,创建命令使用 `@latest`。
25
+
24
26
  ### 接入已有项目
25
27
 
26
28
  ```bash
@@ -45,7 +47,9 @@ hb-sdk skill install --all-agents
45
47
 
46
48
  使用 `hb-sdk doctor` 或 `hb-sdk doctor --json` 检查各 Agent 的 Skill 是否与当前 npm 包匹配;已有内容被修改时,使用 `--force` 才会覆盖。
47
49
 
48
- SDK 会在导入时启动握手,能力调用会自动等待 SDK 就绪。未开通网络权限的小程序通过 `user.getInfo()` 静默读取当前小程序内的隔离身份和公开资料:
50
+ SDK 会在导入时启动握手,能力调用会自动等待 SDK 就绪。声明 `userInfo` 后,可通过 `user.getInfo()` 读取当前小程序内的隔离身份和已授权资料:
51
+
52
+ 使用受保护能力前,需要在项目根 `package.json#heybox.permissions` 中显式声明。`auth.login()` 和 `user.getInfo()` 都只需要 `userInfo`,不依赖公开 `network` 权限;`network` 仅约束 `network.request()` 与 `network.download()`,并且声明后仍需平台批准。完整配置、无需声明白名单和 0.8/0.9 迁移规则见[权限声明](https://docs.xiaoheihe.cn/hb_sdk/guide/permissions)。
49
53
 
50
54
  需要可信用户手势的按钮不能在点击回调里等待握手。页面加载时先用 `getHandshakeState()` 读取当前状态,再用 `onHandshakeStateChange()` 订阅并立即接收当前状态;仅在 `status === 'ready'` 时启用按钮,并在组件或页面销毁时调用订阅返回的取消函数。`ready` 生命周期事件只是握手成功瞬间派发、不会重放的边沿通知,不能用来判断当前状态。
51
55
 
@@ -60,7 +64,7 @@ async function getCurrentUser() {
60
64
 
61
65
  `user.getInfo()` 不展示授权弹窗,也不要求可信用户手势。未登录时返回 `isHeyboxAppLoggedIn: false`;已登录时隐式授予当前本地用户 scopes(`identity`、`profile`),返回当前小程序隔离的 `app_user_id` 以及 `profile.nickname`、`profile.avatar`。
62
66
 
63
- 已开通网络权限的小程序在按钮点击等明确的用户操作中调用 `auth.login()`:
67
+ 声明 `userInfo` 后,小程序可在按钮点击等明确的用户操作中调用 `auth.login()`:
64
68
 
65
69
  ```ts
66
70
  async function loginFromUserAction() {
@@ -74,7 +78,7 @@ async function loginFromUserAction() {
74
78
  }
75
79
  ```
76
80
 
77
- `auth.login()` 返回 `{ code, expiresIn: 300, scopes }`,其中 `identity` 隐式强制包含;它不返回 user、profile、`app_user_id` 或 token。页面只能把 code 交给开发者服务端,再由服务端按后端 OpenAPI 文档换取 token。未开通网络权限的小程序使用 `user.getInfo()`,无法获取 code 或调用 OpenAPI。完整边界见[用户身份与登录](https://docs.xiaoheihe.cn/hb_sdk/guide/auth)。
81
+ `auth.login()` 返回 `{ code, expiresIn: 300, scopes }`,其中 `identity` 隐式强制包含;它不返回 user、profile、`app_user_id` 或 token。页面只能把 code 交给开发者服务端,再由服务端按后端 OpenAPI 文档换取 token。它内部使用 Host 受信请求,不依赖公开 `network` 权限;示例中的 `network.request()` 本身仍需声明并获得 `network` 平台批准。完整边界见[用户身份与登录](https://docs.xiaoheihe.cn/hb_sdk/guide/auth)。
78
82
 
79
83
  用户可以在明确的按钮点击和二次确认后撤销当前小程序的授权:
80
84
 
@@ -88,7 +92,7 @@ async function revokeAuthorizationFromUserAction() {
88
92
  }
89
93
  ```
90
94
 
91
- `user.revokeAuthorization()` 在已开通和未开通网络权限的小程序中都可用,不接收小程序 ID、黑盒用户 ID 或手势参数。调用必须来自可信用户操作,否则返回 `USER_GESTURE_REQUIRED`。撤销成功后,后续身份或授权读取会重新进入授权流程;SDK 不会替业务清理已经持久化的数据。
95
+ `user.revokeAuthorization()` 位于无需声明白名单,不接收小程序 ID、黑盒用户 ID 或手势参数。调用必须来自可信用户操作,否则返回 `USER_GESTURE_REQUIRED`。撤销成功后,后续身份或授权读取会重新进入授权流程;SDK 不会替业务清理已经持久化的数据。
92
96
 
93
97
  已有 Vite 项目还需要启用构建插件:
94
98
 
@@ -132,14 +136,14 @@ const downloaded = await hbSDK.network.download({
132
136
  console.log(await downloaded.readText());
133
137
  ```
134
138
 
135
- 本次保留 `files`、`network.download()`、协议 wire 和 Runtime Host Port 体系,但暂不在现有 Host
136
- 开放。此前的实现用于适配旧版小黑盒 PC;由于新版 PC 即将启用,这套旧版适配不再发布,后续会
137
- 直接按照新版 PC Host 架构接入。当前 Host 调用这些 method 时返回 `METHOD_FORBIDDEN`;旧
138
- Runtime 不认识新 method 时返回 `METHOD_NOT_FOUND`,且不提供 Blob 或内存假实现。
139
+ PC 已开放 `files` sandbox、单文件/目录 picker 与公网 `network.download()`;其它 Host 尚未开放
140
+ 对应 primitive 时返回 `METHOD_FORBIDDEN`,旧 Runtime 不认识新 method 时返回 `METHOD_NOT_FOUND`。
141
+ PC 的下载字节留在插件 Node 并直接原子落盘,不提供 Blob 或内存假实现。
139
142
 
140
- 外部 picker 必须从可信用户操作触发。`files.saveFile({ suggestedName })` 会打开系统保存对话框并返回
141
- 精确 File 授权,不会额外保留 Directory 授权;V1 不接受 `accept`。只有 `pickFiles()` 支持扩展名过滤。完整路径、创建、存在性和
142
- 下载语义见[快速开始](https://docs.xiaoheihe.cn/hb_sdk/guide/quick-start#文件与下载)
143
+ 外部 picker 必须从可信用户操作触发。`files.saveFile({ suggestedName })` 返回精确 File 授权,
144
+ 不会额外保留 Directory 授权;新 PC 当前先让用户选择保存目录,再以 `suggestedName` 独占创建空文件,
145
+ 同名文件返回 `FILE_ALREADY_EXISTS`。V1 不接受 `accept`,只有 `pickFiles()` 支持扩展名过滤。完整路径、
146
+ 创建、存在性和下载语义见[快速开始](https://docs.xiaoheihe.cn/hb_sdk/guide/quick-start#文件与下载)。
143
147
 
144
148
  `remove()` 只允许删除 sandbox 对象;外部 picker、`saveFile()` 及其派生项即使是 `readwrite` 也返回 `FILE_ACCESS_DENIED`。单个 Runtime session 最多保留 1024 个外部 direct handle,超限返回 `FILE_QUOTA_EXCEEDED`。Host 文件错误只向 SDK 暴露稳定 code 与 canonical 安全文案,业务不要依赖平台原始 message 或附加 data。
145
149
 
@@ -188,16 +192,16 @@ SDK 还提供 `getHandshakeState()` / `onHandshakeStateChange()` 管理持久握
188
192
  - `ready` 生命周期事件是不可重放的边沿通知,不代表可查询状态,也不能替代握手状态 API。
189
193
  - `auth.login({ scopes? })` 只返回 `{ code, expiresIn: 300, scopes }`;identity 隐式强制包含,code 只能提交给开发者服务端。
190
194
  - 需要授权 UI 时,`auth.login()` 必须来自可信用户手势,否则返回 `USER_GESTURE_REQUIRED`;用户取消或关闭时返回 `AUTHORIZATION_CANCELLED`。已授权时可以静默返回新 code。
191
- - `user.getSteamGameList()` 仅供未开通网络权限的小程序通过 Host 读取;已开通网络权限时返回 `SERVER_API_REQUIRED`,且授权码与 OpenAPI 不提供 Steam 游戏库 scope 或资源接口。
192
- - 已开通网络权限时,公开的 `user.getInfo()` 返回 `SERVER_API_REQUIRED`;对应身份和资料数据应由开发者服务端通过 OpenAPI 获取。
193
- - 未开通网络权限时,`user.getInfo()` 隐式授予 `identity` 与 `profile`,静默返回 `userInfo.app_user_id`、昵称和头像;这条路径仍不能获取 code 或调用 OpenAPI。
195
+ - `auth.login()`、`user.getInfo()` `user.getSteamGameList()` 的可用性不由 `network` 决定;它们分别要求 `userInfo` `steamLibrary`,并继续遵循各自的用户授权和数据规则。
194
196
  - `user.revokeAuthorization()` 在两种网络模式下都要求可信用户手势;成功后必须停止使用并清理业务缓存的旧身份、资料和服务端会话。
195
197
  - `on()` 返回取消监听函数,组件卸载或页面销毁时需要调用。
196
198
  - 业务网络请求使用 `network.request()`,不要依赖浏览器原生网络出口。
197
199
  - 公开 `network.request()` 与 `network.download()` 均使用 no-follow redirect policy;3xx 不会在 Host 内静默跳转。
198
200
  - `network.request()` 不能访问平台保留的 runtime auth 与 OpenAPI 内部路径;页面不得持有或交换服务端应用凭据。
199
201
  - `network.request` 不支持 `multipart/form-data`;App Host 仅支持 form(`application/x-www-form-urlencoded`)与 JSON。表单请用 `URLSearchParams#toString()` 作为 `data`,文件上传请走专用上传能力。
200
- - 当前 Host(包括旧版 PC、Mobile、Web Browser Dev Host)均未开放 `files` / `network.download()`;新版 PC 将按同一公开合同重新接入,期间不提供内存或 Blob 假实现。
202
+ - PC 开放持久 sandbox、单文件/目录 picker、精确 File 保存与公网 `network.download()`;多选和小黑盒域名流式下载暂不支持。Mobile、Web Browser Dev Host 仍未开放这些 primitive。
203
+ - 新 PC 支持 `network.request`,但会忽略 `withCredentials`;它不改变请求路由或凭据行为。其他平台继续遵循各自的 Host 语义。
204
+ - 新 PC 不支持振动、分享菜单和截图分享;`navigation.openAppPage` 仅支持 `game_detail`,用户/帖子详情会明确失败。调用平台相关能力时应处理 `METHOD_FORBIDDEN` 或 `REQUEST_UNSUPPORTED`。
201
205
  - picker 必须来自可信用户手势;取消选择返回 `FILE_PICKER_CANCELLED`。`saveFile()` 只接受 `suggestedName`,不接受 `accept`。
202
206
  - `pickFiles()` / `pickDirectory()` 默认只读;`pickFiles()` 仅在显式传入 `multiple: true` 时允许 Host 返回多个文件。要写入或作为下载目标时显式请求 `mode: 'readwrite'`。
203
207
  - `file()` / `directory()` 只接受以 `/` 分隔的规范相对路径;拒绝绝对路径、空 segment、`.`、`..`、反斜杠、尾分隔符和控制字符。
@@ -207,12 +211,16 @@ SDK 还提供 `getHandshakeState()` / `onHandshakeStateChange()` 管理持久握
207
211
  - 单个 Runtime session 最多保留 1024 个 picker/saveFile direct handle;整批授权超限时返回 `FILE_QUOTA_EXCEEDED`,不会保留部分结果。
208
212
  - Host 文件错误只保留稳定 code 与 canonical 安全文案;原始 message、data、native path 和 credential 不会进入小程序。
209
213
  - `FileStat.size` 是非负 safe integer;`modifiedAt` / `createdAt` 若存在,单位为 Unix epoch milliseconds。
210
- - 仅当远端已启用 `network.request` 时,`hb-sdk dev` 与 `hb-sdk remote deploy` 构建才会跳过平台 CSP;其他构建继续注入平台 CSP。`useOfficialDomain` 不参与 CSP 跳过判定。
214
+ - 仅当当前版本声明且平台已批准 `network` 时,`hb-sdk dev` 与 `hb-sdk remote deploy` 构建才会跳过平台 CSP;其他构建继续注入平台 CSP。`useOfficialDomain` 不参与 CSP 跳过判定。
211
215
  - 构建必须启用 `miniappManifest({ platforms })` 并显式声明实际承诺适配的平台,推荐统一使用 `hb-sdk build`。新项目模板中的 `['android', 'ios', 'ohos']` 只是初始配置,不代表已经完成真机验收。
212
216
 
217
+ 私有 Page Channel 适配没有修改 `@heybox/hb-sdk` 公开 API 或 iframe wire;本次 files/download
218
+ Runtime 能力随 `0.8.0-alpha.11` release family 发布,部署 detail 前必须保证
219
+ `@heybox/hb-sdk`、`@heybox/hb-sdk-runtime` 与 `@heybox/hb-sdk-protocol` 三个 package 均可解析。
220
+
213
221
  ## 分享扩展数据
214
222
 
215
- 默认分享和复制链接可以携带小程序自行定义的页面状态:
223
+ `share.showShareMenu()` 的落地页固定为当前小程序的 `common_share`,不接受自定义 `url`。分享和复制链接可以携带小程序自行定义的页面状态:
216
224
 
217
225
  ```ts
218
226
  await hbSDK.share.showShareMenu({
@@ -270,7 +278,9 @@ hb-sdk remote deploy --release-note "本次更新说明"
270
278
  | `hb-sdk build` | 执行生产构建与产物校验,不包含类型检查 |
271
279
  | `hb-sdk remote deploy` | 构建、上传并提交当前版本审核 |
272
280
 
273
- `hb-sdk dev` 会启动 Vue 3 Browser Dev Host。左侧诊断面板集中展示调用日志、当前小程序作用域内的 Storage 快照和问题,右侧设备预览可在 iPhone 16 Pro Max(`440 x 956`)与 Pixel 9 Pro(`410 x 914`)间切换。切换预设只改变设备外框和 viewport,不会重新加载 iframe 或重启 Runtime;授权与操作弹窗、Toast、Loading 和振动反馈都限制在预览框内。
281
+ `hb-sdk create` 默认生成可直接删除的 Vanilla JavaScript + Vite HelloWorld,只保留 SDK Toast 示例、三端 Manifest 和开发/构建/部署命令,不绑定业务框架或项目内测试工具。
282
+
283
+ `hb-sdk dev` 会启动 Vue 3 Browser Dev Host。左侧诊断面板集中展示日志、当前小程序作用域内的 Storage 快照和问题,右侧设备预览可在 iPhone 16 Pro Max(`440 x 956`)与 Pixel 9 Pro(`410 x 914`)间切换。切换预设只改变设备外框和 viewport,不会重新加载 iframe 或重启 Runtime;授权与操作弹窗、Toast、Loading 和振动反馈都限制在预览框内。
274
284
 
275
285
  浏览器调试入口不要求 CLI 登录或项目绑定;需要账号或远端配置的能力会按当前可用上下文返回结果或明确失败。调试台不提供权限修改入口,远端权限快照始终是 Runtime 的规范输入。手机验收统一从右上角二维码入口进入,选择手机可达的局域网网卡后扫码;二维码使用 `open_inapp` 与 `heybox://` `openWindow` 包装的短链。完整操作见 [CLI 指南](https://docs.xiaoheihe.cn/hb_sdk/guide/cli),实现与安全边界由 [Runtime Host 迁移](./docs/runtime-host-migration.md) 维护。
276
286
 
@@ -3,10 +3,11 @@
3
3
  var fs$1 = require('node:fs');
4
4
  var fs = require('node:fs/promises');
5
5
  var path = require('node:path');
6
- var runtimePermissionEnv = require('./runtime-permission-env-CjsCe5bp.cjs');
7
- var runtimeGate = require('./runtime-gate-DFjw66kF.cjs');
8
- var index = require('./index-De687C6-.cjs');
9
- var projectVite = require('./project-vite-1rvkK-M8.cjs');
6
+ var runtimePermissionEnv = require('./runtime-permission-env-CtL8rsjB.cjs');
7
+ var runtimeGate = require('./runtime-gate-BEFp1w_s.cjs');
8
+ var index = require('./index-MMW2ibQm.cjs');
9
+ var projectVite = require('./project-vite-BQj8YLI4.cjs');
10
+ require('./version-yEn1E2Bg.cjs');
10
11
  require('node:async_hooks');
11
12
  require('node:module');
12
13
  require('path');
@@ -27,6 +28,7 @@ async function runBuildCommand(_options = {}, runtime = {}) {
27
28
  const env = runtime.env ?? process.env;
28
29
  const mode = env.HB_SDK_ENV?.trim() || undefined;
29
30
  const skipPlatformCsp = runtimePermissionEnv.shouldSkipMiniappPlatformCspFromEnv(env);
31
+ runtimePermissionEnv.readMiniappPermissionsFromPackageJson(projectRoot, runtimePermissionEnv.resolveMiniappSdkVersion());
30
32
  await fs.rm(distDir, { force: true, recursive: true });
31
33
  try {
32
34
  return await runtimePermissionEnv.withMiniappPlatformCspEnv(skipPlatformCsp, async () => {
@@ -91,7 +93,7 @@ async function validateBuildOutput(projectRoot, logger) {
91
93
  if (hadBom) {
92
94
  logger.warn('dist/manifest.json 包含 BOM,已自动剥离');
93
95
  }
94
- const { platforms, sdkVersion, version } = runtimeGate.validateMiniappManifestForDeploy(manifest);
96
+ const { platforms, sdkVersion, version, permissions } = runtimeGate.validateMiniappManifestForDeploy(manifest);
95
97
  const allFiles = await walkDistFiles(distDir);
96
98
  const uploadFiles = allFiles.filter((file) => runtimePermissionEnv.shouldUploadDistFile(file.relativePath));
97
99
  const sizeError = runtimePermissionEnv.validateUploadTotalSize(uploadFiles);
@@ -103,6 +105,7 @@ async function validateBuildOutput(projectRoot, logger) {
103
105
  fileCount: uploadFiles.length,
104
106
  sdkVersion,
105
107
  platforms,
108
+ ...(permissions === undefined ? {} : { permissions }),
106
109
  totalBytes: uploadFiles.reduce((total, file) => total + file.size, 0),
107
110
  version,
108
111
  };
@@ -1,53 +1,14 @@
1
1
  'use strict';
2
2
 
3
- var session = require('./session-CzaM2Cq3.cjs');
4
- var runtimePermissionEnv = require('./runtime-permission-env-CjsCe5bp.cjs');
3
+ var runtimePermissionEnv = require('./runtime-permission-env-CtL8rsjB.cjs');
4
+ var session = require('./session-DjBkjaF8.cjs');
5
5
  var fs$1 = require('node:fs');
6
6
  var fs = require('node:fs/promises');
7
7
  var path = require('node:path');
8
8
 
9
- const MANAGED_RUNTIME_PERMISSION_KEYS = new Set(['network.request']);
10
- function isManagedMiniProgramRuntimePermissionKey(key) {
11
- return MANAGED_RUNTIME_PERMISSION_KEYS.has(key);
12
- }
13
- function parseMiniProgramRuntimePermissions(snapshot) {
14
- if (!isRecord(snapshot) || snapshot.schema_version !== 1 || !Array.isArray(snapshot.entries)) {
15
- return { valid: false, permissions: {} };
16
- }
17
- if (snapshot.revision !== undefined && (typeof snapshot.revision !== 'number' || !Number.isInteger(snapshot.revision) || snapshot.revision < 0)) {
18
- return { valid: false, permissions: {} };
19
- }
20
- const seenKeys = new Set();
21
- const permissions = {};
22
- for (const rawEntry of snapshot.entries) {
23
- if (!isRecord(rawEntry) || typeof rawEntry.key !== 'string' || !rawEntry.key.trim()) {
24
- return { valid: false, permissions: {} };
25
- }
26
- const key = rawEntry.key.trim();
27
- if (seenKeys.has(key) || (rawEntry.status !== 'enabled' && rawEntry.status !== 'disabled') || !isRecord(rawEntry.config)) {
28
- return { valid: false, permissions: {} };
29
- }
30
- seenKeys.add(key);
31
- if (!isManagedMiniProgramRuntimePermissionKey(key))
32
- continue;
33
- if (key === 'network.request' && typeof rawEntry.config.useOfficialDomain !== 'boolean') {
34
- return { valid: false, permissions: {} };
35
- }
36
- permissions[key] = {
37
- key,
38
- status: rawEntry.status,
39
- config: { useOfficialDomain: rawEntry.config.useOfficialDomain },
40
- };
41
- }
42
- return { valid: true, permissions };
43
- }
44
- function isRecord(value) {
45
- return typeof value === 'object' && value !== null && !Array.isArray(value);
46
- }
47
-
48
9
  function shouldSkipMiniappPlatformCsp(runtimePermissions) {
49
- const parsed = parseMiniProgramRuntimePermissions(runtimePermissions);
50
- const networkRequest = parsed.permissions['network.request'];
10
+ const parsed = runtimePermissionEnv.parseMiniProgramRuntimePermissions(runtimePermissions);
11
+ const networkRequest = parsed.permissions.network;
51
12
  return parsed.valid && networkRequest?.status === 'enabled';
52
13
  }
53
14
  const DEFAULT_PERMISSION_STATE = {
@@ -57,8 +18,8 @@ const DEFAULT_PERMISSION_STATE = {
57
18
  },
58
19
  };
59
20
  function createBrowserDevPermissionState(context) {
60
- const parsed = parseMiniProgramRuntimePermissions(context.runtimePermissions);
61
- const networkRequest = parsed.permissions['network.request'];
21
+ const parsed = runtimePermissionEnv.parseMiniProgramRuntimePermissions(context.runtimePermissions);
22
+ const networkRequest = parsed.permissions.network;
62
23
  return parsed.valid && networkRequest
63
24
  ? {
64
25
  networkRequest: {
@@ -68,25 +29,48 @@ function createBrowserDevPermissionState(context) {
68
29
  }
69
30
  : cloneDefaultPermissionState();
70
31
  }
71
- function createBrowserDevRuntimePermissions(state) {
72
- return {
73
- schema_version: 1,
74
- entries: [
75
- {
76
- key: 'network.request',
77
- status: state.networkRequest.status,
78
- config: {
79
- useOfficialDomain: state.networkRequest.useOfficialDomain,
80
- },
81
- },
82
- ],
83
- };
32
+ function createBrowserDevRuntimePermissions(snapshot, state) {
33
+ return createDevRuntimePermissions(snapshot, state, state.networkRequest.useOfficialDomain);
84
34
  }
85
35
  function cloneDefaultPermissionState() {
86
36
  return {
87
37
  networkRequest: { ...DEFAULT_PERMISSION_STATE.networkRequest },
88
38
  };
89
39
  }
40
+ function createDevRuntimePermissions(snapshot, state, useOfficialDomain) {
41
+ const parsed = runtimePermissionEnv.parseMiniProgramRuntimePermissions(snapshot);
42
+ if (!parsed.valid) {
43
+ return { schema_version: 1, entries: [] };
44
+ }
45
+ const entries = [];
46
+ for (const key of runtimePermissionEnv.MINI_PROGRAM_PERMISSION_KEYS) {
47
+ const entry = parsed.permissions[key];
48
+ if (!entry)
49
+ continue;
50
+ entries.push(key === 'network'
51
+ ? {
52
+ key,
53
+ status: state.networkRequest.status,
54
+ config: { useOfficialDomain },
55
+ }
56
+ : {
57
+ key,
58
+ status: entry.status,
59
+ config: {},
60
+ });
61
+ }
62
+ const revision = isPlainRecord(snapshot) && typeof snapshot.revision === 'number' && Number.isInteger(snapshot.revision) && snapshot.revision >= 0
63
+ ? snapshot.revision
64
+ : undefined;
65
+ return {
66
+ schema_version: 1,
67
+ ...(revision === undefined ? {} : { revision }),
68
+ entries,
69
+ };
70
+ }
71
+ function isPlainRecord(value) {
72
+ return typeof value === 'object' && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
73
+ }
90
74
 
91
75
  function createRemoteApiClient(options) {
92
76
  return {
@@ -401,7 +385,6 @@ exports.createBrowserDevRuntimePermissions = createBrowserDevRuntimePermissions;
401
385
  exports.createLocalDevLaunchToken = createLocalDevLaunchToken;
402
386
  exports.createRemoteApiClientForEnvironment = createRemoteApiClientForEnvironment;
403
387
  exports.getBoundMiniProgram = getBoundMiniProgram;
404
- exports.parseMiniProgramRuntimePermissions = parseMiniProgramRuntimePermissions;
405
388
  exports.readBoundMiniProgramId = readBoundMiniProgramId;
406
389
  exports.readBoundMiniProgramIdFromPackageJson = readBoundMiniProgramIdFromPackageJson;
407
390
  exports.readMiniProgramProjectPackage = readMiniProgramProjectPackage;
@@ -5,7 +5,7 @@ var fs = require('node:fs/promises');
5
5
  var path = require('node:path');
6
6
  var require$$0 = require('fs');
7
7
  var require$$1 = require('path');
8
- var index = require('./index-De687C6-.cjs');
8
+ var index = require('./index-MMW2ibQm.cjs');
9
9
  require('node:module');
10
10
  require('os');
11
11
  require('readline');
@@ -1238,7 +1238,7 @@ function requireEjs () {
1238
1238
 
1239
1239
  var ejsExports = requireEjs();
1240
1240
 
1241
- const TEMPLATE_NAME = 'vue3-vite-ts';
1241
+ const TEMPLATE_NAME = 'vanilla-vite-js';
1242
1242
  const TEMPLATE_ROOT_CANDIDATES = [
1243
1243
  path.resolve(__dirname, 'templates', TEMPLATE_NAME),
1244
1244
  path.resolve(__dirname, '..', 'templates', TEMPLATE_NAME),
@@ -8,17 +8,17 @@ var node_crypto = require('node:crypto');
8
8
  var promises = require('node:dns/promises');
9
9
  var node_http = require('node:http');
10
10
  var node_module = require('node:module');
11
- var context = require('./context-CtS2Thp0.cjs');
11
+ var context = require('./context-m2W2XbL0.cjs');
12
+ var runtimePermissionEnv = require('./runtime-permission-env-CtL8rsjB.cjs');
12
13
  var undici = require('undici');
13
14
  var fs$1 = require('node:fs/promises');
14
- var index = require('./index-De687C6-.cjs');
15
+ var index = require('./index-MMW2ibQm.cjs');
15
16
  var require$$0$1 = require('crypto');
16
17
  var require$$0 = require('buffer');
17
- var session = require('./session-CzaM2Cq3.cjs');
18
+ var session = require('./session-DjBkjaF8.cjs');
18
19
  var browser = require('./browser-RAy8e8cV.cjs');
19
- var projectVite = require('./project-vite-1rvkK-M8.cjs');
20
- var version = require('./version-Bz-AfXQU.cjs');
21
- var runtimePermissionEnv = require('./runtime-permission-env-CjsCe5bp.cjs');
20
+ var projectVite = require('./project-vite-BQj8YLI4.cjs');
21
+ var version = require('./version-yEn1E2Bg.cjs');
22
22
 
23
23
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
24
24
  class Locked extends Error {
@@ -1149,11 +1149,11 @@ function validateDevSessionCreatePayload(payload, allowedMiniUrlOrigins) {
1149
1149
  !allowedMiniUrlOrigins.has(miniUrlOrigin.origin)) {
1150
1150
  throw new TypeError('mini_url_origin must be an allowed LAN origin');
1151
1151
  }
1152
- const parsedPermissions = context.parseMiniProgramRuntimePermissions(payload.runtime_permissions);
1152
+ const parsedPermissions = runtimePermissionEnv.parseMiniProgramRuntimePermissions(payload.runtime_permissions);
1153
1153
  if (!parsedPermissions.valid) {
1154
1154
  throw new TypeError('runtime_permissions is invalid');
1155
1155
  }
1156
- const networkPermission = parsedPermissions.permissions['network.request'];
1156
+ const networkPermission = parsedPermissions.permissions.network;
1157
1157
  if (networkPermission?.config.useOfficialDomain !== false) {
1158
1158
  throw new TypeError('Dev Session does not allow official domains');
1159
1159
  }
@@ -1605,7 +1605,7 @@ async function selectAvailablePort(options) {
1605
1605
  port: createPortCandidates(options.startPort, maxAttempts),
1606
1606
  });
1607
1607
  if (port < options.startPort || port >= options.startPort + maxAttempts) {
1608
- throw new Error(`无法找到可用 ${options.label} 端口,起始端口: ${options.startPort}`);
1608
+ throw new Error(`无法找到可用端口(${options.label}),起始端口:${options.startPort}`);
1609
1609
  }
1610
1610
  return port;
1611
1611
  }
@@ -1851,8 +1851,8 @@ const DEFAULT_CHANNEL_TRANSPORT = {
1851
1851
  };
1852
1852
  const IPV4_LOOPBACK_ADDRESSES = new net.BlockList();
1853
1853
  IPV4_LOOPBACK_ADDRESSES.addSubnet('127.0.0.0', 8, 'ipv4');
1854
- const { WebSocket, WebSocketServer } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-CwKbAm_H.cjs', document.baseURI).href)))('ws');
1855
- const { parse: parseHtml } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-CwKbAm_H.cjs', document.baseURI).href)))('parse5');
1854
+ const { WebSocket, WebSocketServer } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-CyZuw7Yn.cjs', document.baseURI).href)))('ws');
1855
+ const { parse: parseHtml } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli-chunks/dev-CyZuw7Yn.cjs', document.baseURI).href)))('parse5');
1856
1856
  const BROWSER_DEV_HOST_ROOT_CANDIDATES = [
1857
1857
  path.resolve(__dirname, 'devtools/browser-dev-host'),
1858
1858
  path.resolve(__dirname, '../devtools/browser-dev-host'),
@@ -2968,7 +2968,7 @@ async function listenHttpServer(server, options, getPortImpl, listen) {
2968
2968
  const port = await selectAvailablePort({
2969
2969
  getPort: getPortImpl,
2970
2970
  host: DEV_PORT_PROBE_HOST,
2971
- label: 'browser dev host',
2971
+ label: '浏览器调试页服务',
2972
2972
  maxAttempts: lastCandidate - nextCandidate + 1,
2973
2973
  startPort: nextCandidate,
2974
2974
  });
@@ -2987,7 +2987,7 @@ async function listenHttpServer(server, options, getPortImpl, listen) {
2987
2987
  nextCandidate = port + 1;
2988
2988
  }
2989
2989
  }
2990
- throw lastAddressInUseError ?? new Error(`无法找到可用 browser dev host 端口,起始端口: ${options.port}`);
2990
+ throw lastAddressInUseError ?? new Error(`无法找到可用浏览器调试页服务端口,起始端口:${options.port}`);
2991
2991
  }
2992
2992
  const listenBrowserDevHostServer = (server, options) => new Promise((resolve, reject) => {
2993
2993
  const onError = (error) => {
@@ -5217,6 +5217,9 @@ function resolveRuntimeLogTelemetryReportId(entry) {
5217
5217
  function shouldReportRuntimeLogToTelemetry(entry, options = {}) {
5218
5218
  const minLevel = options.minLevel ?? DEFAULT_RUNTIME_LOG_TELEMETRY_MIN_LEVEL;
5219
5219
  const ignoreMessages = options.ignoreMessages ?? [...DEFAULT_RUNTIME_LOG_TELEMETRY_IGNORE_MESSAGES];
5220
+ if (options.shouldReport && !options.shouldReport(entry)) {
5221
+ return false;
5222
+ }
5220
5223
  return isRuntimeLogLevelEnabled(entry.level, minLevel) && !ignoreMessages.some((pattern) => pattern.test(entry.message));
5221
5224
  }
5222
5225
  /**
@@ -5518,10 +5521,14 @@ function createTelemetryRuntimeLogSink(telemetry, options = {}) {
5518
5521
  severity: entry.level,
5519
5522
  });
5520
5523
  const { requestId, ...reportContext } = entry.context ?? {};
5524
+ // 调用方可在上下文里显式给出聚合指纹(例如全局错误捕获已按统一规则算好);
5525
+ // 显式值优先于按本条日志错误推导的指纹,避免同一错误在直接上报与 sink 转发
5526
+ // 两条出口下落到不同 issue。
5527
+ const contextFingerprint = reportContext.runtime_error_fingerprint;
5521
5528
  currentTelemetry.reportError({
5522
5529
  addition: {
5523
5530
  ...reportContext,
5524
- runtime_error_fingerprint: entry.errorInfo?.fingerprint,
5531
+ runtime_error_fingerprint: typeof contextFingerprint === 'string' && contextFingerprint ? contextFingerprint : entry.errorInfo?.fingerprint,
5525
5532
  runtime_log_domain: entry.domain,
5526
5533
  runtime_log_id: entry.id,
5527
5534
  runtime_log_level: entry.level,
@@ -10895,7 +10902,7 @@ function assertNodeRuntimeCapabilities(ctx, settings) {
10895
10902
  function createNodeRealtimeDriverRegistry() {
10896
10903
  const registry = createRuntimeRealtimeDriverRegistry();
10897
10904
  registry.registerLazy('node-websocket', async () => {
10898
- const mod = await Promise.resolve().then(function () { return require('./index.esm-B-4yrLNm.cjs'); });
10905
+ const mod = await Promise.resolve().then(function () { return require('./index.esm-BiAaAUFC.cjs'); });
10899
10906
  return mod.createNodeWebSocketRuntimeDriver();
10900
10907
  });
10901
10908
  return registry;
@@ -14908,7 +14915,7 @@ function createLaunchSnapshot(source, requestOrigin) {
14908
14915
  if (storedContextUrl) {
14909
14916
  snapshot.dev_context_url = projectStoredDevContextUrl(storedContextUrl, requestOrigin.hostname);
14910
14917
  }
14911
- if (context.parseMiniProgramRuntimePermissions(source.runtimePermissions).valid && source.runtimePermissions) {
14918
+ if (runtimePermissionEnv.parseMiniProgramRuntimePermissions(source.runtimePermissions).valid && source.runtimePermissions) {
14912
14919
  snapshot.runtime_permissions = JSON.parse(JSON.stringify(source.runtimePermissions));
14913
14920
  }
14914
14921
  return snapshot;
@@ -14960,7 +14967,7 @@ const DEFAULT_BROWSER_DEV_HOST_PORT = 5174;
14960
14967
  const DEFAULT_DEV_CONTEXT_TIMEOUT_MS = 3000;
14961
14968
  const DEV_LISTEN_HOST = '0.0.0.0';
14962
14969
  const LOCAL_DEV_URL_HOST = '127.0.0.1';
14963
- // Shared with Heybox H5 Vite plugins so hb-sdk can own the launch output.
14970
+ // Heybox H5 Vite 插件共享,让 hb-sdk 统一管理启动输出。
14964
14971
  const MANAGED_DEV_OUTPUT_ENV = 'HEYBOX_DEV_SERVER_MANAGED_OUTPUT';
14965
14972
  const VITE_LOG_LEVEL = 'warn';
14966
14973
  async function runDevCommand(options, runtime = {}) {
@@ -14975,7 +14982,7 @@ async function runDevCommand(options, runtime = {}) {
14975
14982
  const appPort = await selectAvailablePort({
14976
14983
  getPort: getPortImpl,
14977
14984
  host: DEV_LISTEN_HOST,
14978
- label: 'Vite dev server',
14985
+ label: 'Vite 开发服务器',
14979
14986
  startPort: options.port ?? DEFAULT_APP_PORT,
14980
14987
  });
14981
14988
  const resolvedOptions = {
@@ -14991,9 +14998,9 @@ async function runDevCommand(options, runtime = {}) {
14991
14998
  projectRoot,
14992
14999
  timeoutMs: runtime.devContextTimeoutMs ?? DEFAULT_DEV_CONTEXT_TIMEOUT_MS,
14993
15000
  });
14994
- const launchRuntimePermissions = context.createBrowserDevRuntimePermissions(context.createBrowserDevPermissionState(devLaunchContext));
14995
- const vite = await logger.task('正在加载项目 Vite', () => projectVite.loadProjectVite(projectRoot), { successText: '已加载项目 Vite' });
14996
- const appServer = await logger.task('正在创建 Vite dev server', () => runtimePermissionEnv.withMiniappPlatformCspEnv(context.shouldSkipMiniappPlatformCsp(devLaunchContext.runtimePermissions), () => withManagedDevOutputEnv(() => vite.createServer(createViteServerOptions(projectRoot, resolvedOptions)), runtime.env), runtime.env), { successText: '已创建 Vite dev server' });
15001
+ const launchRuntimePermissions = context.createBrowserDevRuntimePermissions(devLaunchContext.runtimePermissions, context.createBrowserDevPermissionState(devLaunchContext));
15002
+ const vite = await runDevStartupTask(logger, '正在加载项目 Vite', () => projectVite.loadProjectVite(projectRoot), '已加载项目 Vite');
15003
+ const appServer = await runDevStartupTask(logger, '正在创建 Vite 开发服务器', () => runtimePermissionEnv.withMiniappPlatformCspEnv(context.shouldSkipMiniappPlatformCsp(devLaunchContext.runtimePermissions), () => withManagedDevOutputEnv(() => vite.createServer(createViteServerOptions(projectRoot, resolvedOptions)), runtime.env), runtime.env), '已创建 Vite 开发服务器');
14997
15004
  const mobileDevContextUrlsByOrigin = new Map();
14998
15005
  let currentLocalDevLaunchToken = devLaunchContext.localDevLaunchToken;
14999
15006
  appServer.middlewares.use((request, response, next) => {
@@ -15007,12 +15014,8 @@ async function runDevCommand(options, runtime = {}) {
15007
15014
  if (!handled)
15008
15015
  next();
15009
15016
  });
15010
- await logger.task('正在启动 Vite dev server', () => appServer.listen(appPort), {
15011
- successText: 'Vite dev server 已启动',
15012
- });
15013
- const appUrl = await logger.task('正在探测小程序入口', () => resolveViteAppUrl(appServer, resolvedOptions, fetchImpl), {
15014
- successText: '已确定小程序入口',
15015
- });
15017
+ await runDevStartupTask(logger, '正在启动 Vite 开发服务器', () => appServer.listen(appPort), 'Vite 开发服务器已启动');
15018
+ const appUrl = await runDevStartupTask(logger, '正在探测小程序入口', () => resolveViteAppUrl(appServer, resolvedOptions, fetchImpl), '已确定小程序入口');
15016
15019
  const buildLanAddresses = () => createLanAddressCandidates({
15017
15020
  appUrl,
15018
15021
  devContextUrlsByMiniUrlOrigin: mobileDevContextUrlsByOrigin,
@@ -15027,7 +15030,7 @@ async function runDevCommand(options, runtime = {}) {
15027
15030
  const closers = [() => appServer.close()];
15028
15031
  let browserDevHost;
15029
15032
  try {
15030
- browserDevHost = await logger.task('正在启动 Browser Dev Host', () => startBrowserDevHostServer({
15033
+ browserDevHost = await runDevStartupTask(logger, '正在启动浏览器调试页服务', () => startBrowserDevHostServer({
15031
15034
  appUrl,
15032
15035
  devContext: devLaunchContext,
15033
15036
  fetchImpl,
@@ -15048,7 +15051,7 @@ async function runDevCommand(options, runtime = {}) {
15048
15051
  root: runtime.browserDevHostRoot,
15049
15052
  rootCandidates: runtime.browserDevHostRootCandidates,
15050
15053
  runtimePermissions: devLaunchContext.runtimePermissions,
15051
- }), { successText: 'Browser Dev Host 已启动' });
15054
+ }), '浏览器调试页服务已启动');
15052
15055
  }
15053
15056
  catch (error) {
15054
15057
  await closeAll(closers);
@@ -15079,23 +15082,29 @@ async function runDevCommand(options, runtime = {}) {
15079
15082
  });
15080
15083
  closers.push(tokenSession.close);
15081
15084
  }
15082
- logger.success('Browser Dev Host 已就绪');
15083
- logger.info(`Browser Dev Host: ${browserDevHost.url}`);
15084
- logger.info(`Mini program URL: ${appUrl}`);
15085
- logger.info('Mobile APP: scan the QR code in Browser Dev Host');
15085
+ logger.success('本地调试已启动');
15086
+ logger.info(`调试页:${browserDevHost.url}`);
15087
+ logger.info(`小程序地址:${appUrl}`);
15088
+ logger.info('手机调试:请在调试页扫码');
15086
15089
  if (options.open !== false) {
15087
15090
  logger.debug('正在打开浏览器调试页');
15088
15091
  void openUrl(browserDevHost.url);
15089
15092
  }
15090
15093
  installShutdownHandlers(closers, runtime.process ?? process);
15091
15094
  }
15095
+ function runDevStartupTask(logger, text, action, successText) {
15096
+ if (!logger.verbose) {
15097
+ return action();
15098
+ }
15099
+ return logger.task(text, () => action(), { successText });
15100
+ }
15092
15101
  async function loadDevLaunchContext(options) {
15093
15102
  const miniProgramId = await context.readBoundMiniProgramId(options.projectRoot);
15094
15103
  if (!miniProgramId) {
15095
15104
  throw new Error('hb-sdk dev 需要绑定小程序。请先运行 hb-sdk remote create 或 hb-sdk remote bind <mini-program-id>。');
15096
15105
  }
15097
15106
  try {
15098
- const { detail, miniProgramId: verifiedMiniProgramId } = await options.logger.task('正在读取远端小程序权限', () => loadRemoteDevContextWithTimeout({
15107
+ const { detail, miniProgramId: verifiedMiniProgramId } = await runDevStartupTask(options.logger, '正在读取远端小程序权限', () => loadRemoteDevContextWithTimeout({
15099
15108
  fetchImpl: options.fetchImpl,
15100
15109
  load: (fetchImpl) => context.getBoundMiniProgram({
15101
15110
  cwd: options.projectRoot,
@@ -15103,12 +15112,16 @@ async function loadDevLaunchContext(options) {
15103
15112
  fetchImpl,
15104
15113
  }),
15105
15114
  timeoutMs: options.timeoutMs,
15106
- }), { successText: '已读取远端小程序权限' });
15115
+ }), '已读取远端小程序权限');
15116
+ const localPermissions = runtimePermissionEnv.readMiniappPermissionsFromPackageJson(options.projectRoot, runtimePermissionEnv.resolveMiniappSdkVersion());
15117
+ if (!localPermissions.declared)
15118
+ options.logger.warn(runtimePermissionEnv.getMissingPermissionsWarning());
15119
+ const effectivePermissions = runtimePermissionEnv.composeMiniappRuntimePermissions(localPermissions, detail.runtime_permissions).runtimePermissions;
15107
15120
  return {
15108
15121
  context: {
15109
15122
  source: 'remote',
15110
15123
  miniProgramId: verifiedMiniProgramId,
15111
- runtimePermissions: detail.runtime_permissions,
15124
+ runtimePermissions: effectivePermissions,
15112
15125
  },
15113
15126
  miniProgram: createMiniProgramSummary(verifiedMiniProgramId, detail),
15114
15127
  };
@@ -15303,25 +15316,30 @@ async function closeAll(closers) {
15303
15316
  function startLocalDevLaunchTokenSession(options) {
15304
15317
  let closed = false;
15305
15318
  let expirationTimer;
15319
+ let hasReportedTokenFailure = false;
15306
15320
  let refreshTimer;
15307
15321
  const refresh = async () => {
15308
15322
  try {
15309
15323
  const issued = await issueLocalDevLaunchToken(options);
15310
15324
  if (closed)
15311
15325
  return;
15312
- if (!issued) {
15313
- schedule(options.timeoutMs);
15314
- return;
15315
- }
15316
15326
  options.onToken(issued.token);
15317
15327
  const ttl = Math.max(0, issued.expiresAt - Date.now());
15318
15328
  scheduleExpiration(ttl);
15319
15329
  schedule(Math.max(1_000, Math.floor(ttl * 0.8)));
15330
+ if (hasReportedTokenFailure) {
15331
+ hasReportedTokenFailure = false;
15332
+ options.logger.success('手机扫码调试已恢复');
15333
+ }
15320
15334
  }
15321
15335
  catch (error) {
15322
15336
  if (closed)
15323
15337
  return;
15324
- options.logger.warn(`刷新本地调试启动票失败,将自动重试:${index.readRedactedErrorMessage(error)}`);
15338
+ if (!hasReportedTokenFailure) {
15339
+ options.logger.warn('手机扫码调试凭证获取失败,将自动重试');
15340
+ }
15341
+ hasReportedTokenFailure = true;
15342
+ options.logger.debug(`手机扫码调试凭证获取失败,详细原因:${index.readRedactedErrorMessage(error, { verbose: true })}`);
15325
15343
  schedule(options.timeoutMs);
15326
15344
  }
15327
15345
  };
@@ -15352,31 +15370,25 @@ function startLocalDevLaunchTokenSession(options) {
15352
15370
  };
15353
15371
  }
15354
15372
  async function issueLocalDevLaunchToken(options) {
15355
- try {
15356
- const issued = await loadRemoteDevContextWithTimeout({
15357
- fetchImpl: options.fetchImpl,
15358
- timeoutMs: options.timeoutMs,
15359
- load: (fetchImpl) => context.createLocalDevLaunchToken({
15360
- cwd: options.projectRoot,
15361
- env: options.env,
15362
- fetchImpl,
15363
- miniProgramId: options.miniProgramId,
15364
- }),
15365
- });
15366
- const token = typeof issued.token === 'string' ? issued.token.trim() : '';
15367
- if (!token) {
15368
- throw new Error('响应缺少 token');
15369
- }
15370
- const expiresAt = normalizeExpiresAt(issued.expires_at);
15371
- if (!expiresAt || expiresAt <= Date.now()) {
15372
- throw new Error('响应缺少有效 expires_at');
15373
- }
15374
- return { expiresAt, token };
15373
+ const issued = await loadRemoteDevContextWithTimeout({
15374
+ fetchImpl: options.fetchImpl,
15375
+ timeoutMs: options.timeoutMs,
15376
+ load: (fetchImpl) => context.createLocalDevLaunchToken({
15377
+ cwd: options.projectRoot,
15378
+ env: options.env,
15379
+ fetchImpl,
15380
+ miniProgramId: options.miniProgramId,
15381
+ }),
15382
+ });
15383
+ const token = typeof issued.token === 'string' ? issued.token.trim() : '';
15384
+ if (!token) {
15385
+ throw new Error('响应缺少 token');
15375
15386
  }
15376
- catch (error) {
15377
- options.logger.warn(`未能签发本地调试启动票,扫码后公开详情可能仍拿不到未发布小程序身份:${index.readRedactedErrorMessage(error)}`);
15378
- return undefined;
15387
+ const expiresAt = normalizeExpiresAt(issued.expires_at);
15388
+ if (!expiresAt || expiresAt <= Date.now()) {
15389
+ throw new Error('响应缺少有效 expires_at');
15379
15390
  }
15391
+ return { expiresAt, token };
15380
15392
  }
15381
15393
  function normalizeExpiresAt(value) {
15382
15394
  if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)