@ttmg/cli 0.4.0-beta.1 → 0.4.1-beta.wasm1

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 (77) hide show
  1. package/CHANGELOG.md +256 -0
  2. package/dist/index.js +365 -19
  3. package/dist/index.js.map +1 -1
  4. package/dist/package.json +1 -1
  5. package/dist/public/assets/{Card-C35olIke.js → Card-ASGGlJ8S.js} +1 -1
  6. package/dist/public/assets/Detail-DY__Zw0r.js +1 -0
  7. package/dist/public/assets/Detail-DY__Zw0r.js.br +0 -0
  8. package/dist/public/assets/MonetizationMode-sHN_4ybQ.js +1 -0
  9. package/dist/public/assets/MonetizationModeSummary-DLvyriLM.js +1 -0
  10. package/dist/public/assets/{SectionHeader-DRXnax7L.js → SectionHeader-B7dueBCf.js} +1 -1
  11. package/dist/public/assets/{Tag-CgPHrk93.js → Tag-BsX0d6UL.js} +1 -1
  12. package/dist/public/assets/{arrow-left-CNdHGsDz.js → arrow-left-uixm7Sd1.js} +1 -1
  13. package/dist/public/assets/{baseForm-C2Ms-wqt.js → baseForm-C8XfTFyG.js} +1 -1
  14. package/dist/public/assets/baseForm-C8XfTFyG.js.br +0 -0
  15. package/dist/public/assets/boxes-C7RoIbId.js +1 -0
  16. package/dist/public/assets/{chevron-right-CX-Bo3I2.js → chevron-right-B1WBZrrD.js} +1 -1
  17. package/dist/public/assets/circle-check-wUxXeINd.js +1 -0
  18. package/dist/public/assets/{compass-BUqxZo39.js → compass-VJNhHwqW.js} +1 -1
  19. package/dist/public/assets/{index-Dd2rc2_1.js → index-BBR3CPkN.js} +1 -1
  20. package/dist/public/assets/index-BBR3CPkN.js.br +0 -0
  21. package/dist/public/assets/{index-CxeatYOZ.js → index-BH099q_U.js} +1 -1
  22. package/dist/public/assets/{index-ROKxx4f7.css → index-BJZ4gOGZ.css} +1 -1
  23. package/dist/public/assets/index-BJZ4gOGZ.css.br +0 -0
  24. package/dist/public/assets/{index-CLXkCzG6.js → index-BNc8JtET.js} +1 -1
  25. package/dist/public/assets/{index-B8dmfh3A.js → index-BO5MBsdm.js} +1 -1
  26. package/dist/public/assets/index-Bd-f8ERX.js +1 -0
  27. package/dist/public/assets/{index-B9C7FZes.js → index-C1C34gtn.js} +1 -1
  28. package/dist/public/assets/{index-AGEFeR2m.js → index-C7Tmy9g3.js} +1 -1
  29. package/dist/public/assets/index-CCKb9Y-j.js +14 -0
  30. package/dist/public/assets/index-CCKb9Y-j.js.br +0 -0
  31. package/dist/public/assets/index-CKhfKyA3.js +1 -0
  32. package/dist/public/assets/index-CKhfKyA3.js.br +0 -0
  33. package/dist/public/assets/{index-BuGJ38RF.js → index-C_npSvtQ.js} +1 -1
  34. package/dist/public/assets/{index-BOl1-Siv.css → index-DHXlsTU6.css} +1 -1
  35. package/dist/public/assets/index-DHXlsTU6.css.br +0 -0
  36. package/dist/public/assets/index-DMsjcIN8.js +1 -0
  37. package/dist/public/assets/index-DNd8ZTok.js +1 -0
  38. package/dist/public/assets/index-DNd8ZTok.js.br +0 -0
  39. package/dist/public/assets/{index-8z6tNstf.js → index-DbxY65Om.js} +1 -1
  40. package/dist/public/assets/{index-DCZsvwKh.js → index-Dn5HHf-0.js} +1 -1
  41. package/dist/public/assets/index-DpkktYot.css +1 -0
  42. package/dist/public/assets/index-DtoDrber.js +1 -0
  43. package/dist/public/assets/index-DtoDrber.js.br +0 -0
  44. package/dist/public/assets/index-Dx5VGEQm.css +1 -0
  45. package/dist/public/assets/{index-BULJJdeE.js → index-ZHPtAdJM.js} +1 -1
  46. package/dist/public/assets/index-ZHPtAdJM.js.br +0 -0
  47. package/dist/public/assets/{index-D0DgrJ_K.js → index-ZtlCPCV9.js} +1 -1
  48. package/dist/public/assets/{index-CUV27PJL.js → index-a-rMUS2V.js} +1 -1
  49. package/dist/public/assets/{index-Dk9_4hQ4.js → index-bQUQ2Wmz.js} +1 -1
  50. package/dist/public/assets/{index-ByqGLZ4G.js → index-qqnHwFjO.js} +1 -1
  51. package/dist/public/assets/{index-CIIptoBi.js → index-r3aSeNL1.js} +1 -1
  52. package/dist/public/assets/{index-EorOvXWq.js → index-s_30KWiA.js} +1 -1
  53. package/dist/public/assets/index-t4JeqZ4a.js +1 -0
  54. package/dist/public/assets/layers-Biv61qtE.js +1 -0
  55. package/dist/public/assets/{sparkles-C54nzN2M.js → sparkles-Bv2GxrFr.js} +1 -1
  56. package/dist/public/assets/{zap-g-acVHwy.js → zap-CjkC_qWZ.js} +1 -1
  57. package/dist/public/index.html +2 -2
  58. package/package.json +1 -1
  59. package/dist/public/assets/Detail-Bw_dl9hw.js +0 -1
  60. package/dist/public/assets/Detail-Bw_dl9hw.js.br +0 -0
  61. package/dist/public/assets/MonetizationMode-C7Wyv9Mg.js +0 -1
  62. package/dist/public/assets/MonetizationModeSummary-D-zuX05a.js +0 -1
  63. package/dist/public/assets/baseForm-C2Ms-wqt.js.br +0 -0
  64. package/dist/public/assets/index-BOl1-Siv.css.br +0 -0
  65. package/dist/public/assets/index-BULJJdeE.js.br +0 -0
  66. package/dist/public/assets/index-B_iiHvzl.js +0 -1
  67. package/dist/public/assets/index-B_iiHvzl.js.br +0 -0
  68. package/dist/public/assets/index-BmmpJdOy.js +0 -1
  69. package/dist/public/assets/index-BmmpJdOy.js.br +0 -0
  70. package/dist/public/assets/index-CjInIkcE.js +0 -1
  71. package/dist/public/assets/index-D28D1GWM.js +0 -1
  72. package/dist/public/assets/index-Dd2rc2_1.js.br +0 -0
  73. package/dist/public/assets/index-Dvg_oNs7.css +0 -1
  74. package/dist/public/assets/index-ROKxx4f7.css.br +0 -0
  75. package/dist/public/assets/index-cC0QApVl.js +0 -14
  76. package/dist/public/assets/index-cC0QApVl.js.br +0 -0
  77. package/dist/public/assets/index-l16xoaSk.js +0 -1
package/CHANGELOG.md ADDED
@@ -0,0 +1,256 @@
1
+ ## 0.1.0
2
+
3
+ 发布正式版本,支持 H5 小游戏和原生小游戏的初始化、开发调试和打包构建。支持 Windows 系统
4
+
5
+ ## 0.1.0-beta.1
6
+
7
+ 移除 Client WS 端口和 Http 端口默认 -1 的配置,变成通过和 Node CLI 通信显示发端口
8
+
9
+ ## 0.1.0-beta.8
10
+
11
+ 支持自动打开浏览器,且聚焦在 devTools 窗口
12
+
13
+ ## 0.1.0-beta.9
14
+
15
+ 1. 支持文件上传进度显示
16
+ 2. 支持自动打开浏览器调试模式
17
+
18
+ ## 0.1.0-beta.10
19
+
20
+ fix: 修复还未完成新编译就直接上传了的问题
21
+
22
+ ## 0.1.0-beta.11
23
+
24
+ feat: 先关闭自动上传能力
25
+
26
+ ## 0.1.0-beta.12
27
+
28
+ feat: 支持文件变更监听,告知浏览器文件变更,提醒开发者重新加载游戏
29
+
30
+ ## 0.1.0-beta.13
31
+
32
+ 1. 支持 sourcemap
33
+ 2. 优化上传环节产物,过滤 sourcemap 文件,提高上传效率
34
+
35
+ ## 0.1.0-beta.14
36
+
37
+ 1. 修复 client key 未成功写入 mock meta 的问题,导致不一致
38
+
39
+ ## 0.1.0-beta.15
40
+
41
+ 1. 还原 open url 历史方法
42
+
43
+ ## 0.1.1
44
+
45
+ 发布最新版本,提醒开发者升级最新版本
46
+
47
+ ## 0.1.2
48
+
49
+ 发布最新版本,支持 sourcemap
50
+
51
+ ## 0.1.3-beta.1
52
+
53
+ 优化显示二维码问题
54
+
55
+ ## 0.1.3-beta.2
56
+
57
+ 彻底修复二维码兼容问题
58
+
59
+ ## 0.1.3-beta.3
60
+
61
+ 修复 sourcemap 错乱问题
62
+
63
+ ## 0.1.3-beta.4
64
+
65
+ 修复循环依赖问题
66
+
67
+ ## 0.1.3-beta.5
68
+
69
+ 修复监听文件变化报错问题
70
+
71
+ ## 0.1.3
72
+
73
+ 发布优化版本 CLI
74
+
75
+ ## 0.1.4
76
+
77
+ 优化页面交互
78
+
79
+ ## 0.1.5
80
+
81
+ 1.fix: 控制台报错问题 2. feat: 优化
82
+
83
+ ## 0.1.6
84
+
85
+ 1. 优化控制台报错日志
86
+ 2. 优化调试启动环节耗时
87
+
88
+ ## 0.1.7
89
+
90
+ - 修复 Windows 调试资源加载失败问题
91
+
92
+ ## 0.1.8
93
+
94
+ 优化 DevTool 网页端启动时间
95
+
96
+ ## 0.1.9-beta.1
97
+
98
+ 发布测试版本,供客户端调试使用
99
+
100
+ ## 0.1.9-beta.2
101
+
102
+ 发布测试版本,用于 Windows 测试
103
+
104
+ ## 0.1.9
105
+
106
+ 发布正式版本,支持
107
+
108
+ - login
109
+ - 支持上传游戏包
110
+ - 支持启动模式
111
+ - 支持 Debug Settings
112
+
113
+ ## 0.2.0-beta.1
114
+
115
+ 发布测试版本,用于 Windows 测试 login 登录异常逻辑
116
+
117
+ ## 0.2.0
118
+
119
+ 发布正式版本,支持 Windows 登录加载报错提示
120
+
121
+ ## 0.2.1
122
+
123
+ 1. 移除项目详情部分,比较冗余,不常用,融合到代码预检中
124
+ 2. 打开调试页面地址增加 version 参数,来规避新老版本缓存问题和不同网页之间的隔离(未来支持)
125
+ 3. 优化登录失败提示,增加网络检查提示,并给出如何修改网络设置的文档链接
126
+ 4. 补充发送 meta 至客户端链路,增加调试链路日志信息,便于排查问题
127
+
128
+ ## 0.2.2
129
+
130
+ 1. 修复访问无权限的小游戏,顶部报错问题
131
+
132
+ ## 0.2.3
133
+
134
+ 优化扫码连接不上问题,支持手动绑定电脑本地 IP 地址
135
+
136
+ ## 0.2.4
137
+
138
+ 支持新的 DevTool 平台
139
+
140
+ ## 0.2.5
141
+
142
+ 1. 增加不在游戏根目录启动调试校验
143
+ 2. 支持手动绑定客户端 IP 地址和端口
144
+ 3. 调整启动模式中的场景列表表达文案
145
+ 4. 修复文件上传问题,避免偶现上传失败
146
+ 5. 支持展示游戏详情模块,支持开发者查看当前小游戏基本信息
147
+ 6. 支持调试环节消费平台配置的域名
148
+ 7. 升级 ttmg-pack 0.1.7,修复构建环节对开发者 JS 代码降级的问题
149
+ 8. 文档新增常见问题部分,替换原有的开发文档
150
+
151
+ ## 0.2.6
152
+
153
+ 修复启动模式中场景列表展示问题,重复展示默认启动模式
154
+
155
+ ## 0.2.7
156
+ 1. 升级 ttmg-pack 0.2.3,调整 esbuild 配置,目标 es2020
157
+ 2. 支持校验子包配置文件名是否符合规范,对于非法文件名,提示开发者修改
158
+ 3. 调整域名校验默认逻辑,默认开启,校验失败时,提示开发者修改
159
+
160
+ ## 0.2.8
161
+ 1. 升级 ttmg-pack 0.2.4,修复 open data context 文件丢失问题
162
+
163
+ ## 0.2.9
164
+ 1. 升级 ttmg-pack 0.2.5,修复 open data context 打包问题
165
+ ## 0.3.0
166
+ 新增/优化功能适用 TikTok 版本 iOS >=43.1 | Android>=43.1
167
+ 支持了开发信息蒙层,打开后可以查看游戏 FPS、设备信息、调试开关等信息,便于查看游戏运行状态 了解如何使用
168
+ 支持删除历史启动模式,便于管理历史启动模式
169
+ 调整调试设置模块名称为开发选项,和客户端内调开发者选项保持一致 了解如何使用
170
+ 调试设置新增定义 vConsole 和 开发信息浮层设置
171
+ 支持了变更日志,用户可以在调试环境下查看游戏的变更记录,部分新功能可通过更多信息查看使用
172
+ 支持了调试设置开关本地缓存,避免每次都需要重新设置
173
+ 修复上传名称为中文的游戏时,平台展示乱码的问题
174
+ 修复启动模式模块,自定义query 参数无法持久化的问题
175
+ 修复扫描二维码模块,内容过多,纵向无法滚动问题
176
+
177
+ ## 0.3.1
178
+ - 支持 unity 小游戏本地一些列调试功能,如 Wasm 分包、包大小校验、一键上传代码包至平台等 了解如何使用
179
+ - 增加用户登录过期的异常提示,提醒开发者重新登录
180
+ - 优化上传代码包至平台功能,开发者只用添加描述后点击上传便可自动压缩当前目录下的代码包上传至开发者平台
181
+ - 优化 ttmg login 终端网络代理异常导致无法登录的报错引导,提示用户检查网络设置
182
+
183
+ ## 0.3.2-beta.1
184
+ 支持 cocos 2.0 openDataContext 相关适配
185
+
186
+ ## 0.3.2
187
+ - 小游戏详情查询升级到 TikTok DevPortal v4 `devtool/info` 接口,字段映射更完整
188
+ - 支持一键打包当前目录并上传代码包到开发者平台;上传失败提示更明确,文件名非法字符自动处理
189
+ - Unity Wasm 分包:更新 `game.json` 子包配置写入逻辑(兼容 `subpackages`/`subPackages` 字段),Android/iOS main/sub 配置去重合并
190
+ - 启动校验增强:检查是否在小游戏根目录执行,并提示 `subPackages` 拼写错误
191
+ 增加对 unity 是否需要分包的检查,对于需要分包的 unity 项目,给出明确提示
192
+ 支持通过 `ttmg setup` 配置 CLI 语言,调试环境会跟随语言设置展示文案,首次调试时会自动同步默认语言,后续 CLI 与 IDE 语言保持一致,减少反复切换
193
+ 调试与预检支持多语言展示,关键提示与校验信息更易理解
194
+ 支持 Cocos 2.0 openDataContext 配置
195
+ 变更记录新增提醒功能,当有新版本发布时,会通过弹窗提醒开发者更新
196
+ ttmg login 支持 ttmg login --verbose 参数,打印更多登录信息
197
+ API 预检建议文案统一收敛,避免重复提示,阅读更清晰
198
+ 顶部区域透出项目预检错误与警告信息,便于开发者及时发现问题并修复
199
+ 语言选择策略优化:优先使用 CLI 本地设置,未设置时自动回退浏览器语言
200
+ 启动校验增强:检查是否在小游戏根目录执行,对于分包配置 subPackages 给出明确报错,引导开发者修改为 subpackages
201
+ 修复项目详情中展示的主包大小限制和代码预检不一致的情况
202
+
203
+ ## 0.3.3
204
+ 升级 ttmg-pack 到 0.4.5,解决安卓无法调试 unity 项目问题
205
+
206
+ ## 0.3.4
207
+ 优化校验逻辑,给出提示
208
+
209
+ ## 0.3.5
210
+ 优化连接和代理,帮助开发者自助解决问题
211
+
212
+ ## 0.3.6
213
+ 修复分包过程超时问题
214
+
215
+ ## 0.3.7
216
+ 新增/优化功能适用 TikTok 版本 iOS >=43.1 | Android >=43.1
217
+
218
+ 1. 新增能力接入助手,帮助开发者基于小游戏变现类型获取关键能力的接入引导。
219
+ 2. 优化上传发布环节,将代码预检与代码上传整合为完整的上传发布流程。
220
+ 3. 扩充包体大小预检说明,补充整包、主包、独立分包限制规则与详细文档入口,便于开发者理解和处理包体超限问题。
221
+
222
+ ## 0.3.8
223
+ 修复 `ttmg login` 后接口鉴权异常的问题:
224
+
225
+ 1. 登录时把 `Set-Cookie` 响应头中的 `Path / Domain / Max-Age / Expires / Secure / HttpOnly / SameSite` 等属性被当成 cookie 拼接到 `Cookie` 字段,导致 CLI 发请求时把属性当成"假 cookie"一并带上。
226
+ 2. 同一域名下同名 cookie(例如服务端用 `Max-Age=0` 显式删除的占位 cookie)也会被一起发送,污染掉真正有效的同名 cookie,浏览器会自动过滤这种情况,CLI 不会,造成登录态接口异常。
227
+ 3. 修复后与浏览器行为保持一致:剥离 cookie 属性、过滤 `Max-Age<=0` 或 `Expires` 已过期的 cookie,仅保留 `name=value` 用于请求。
228
+ 4. 已登录用户无需重新登录,下次发起请求时会自动清洗 rc 中已存的脏数据。
229
+
230
+ ## 0.3.9
231
+ 本次主要优化调试 IDE 的视觉、交互与多语言一致性:
232
+
233
+ 1. 调试 IDE 默认使用浅色主题,暗色模式改为开发者手动开启并记忆选择,避免跟随系统强制进入暗色导致不适应。
234
+ 2. 优化暗色模式下二维码显示,调整背景避免过亮刺眼,同时保证扫码识别率。
235
+ 3. Wasm 分包页面及相关组件补齐多语言,修复此前页面文案中英文混排的问题。
236
+ 4. 能力接入助手页面视觉升级:变现模式卡片新增图标与强调色,关键能力标签按「必需 / 可选 / 实验」分级展示,信息层级更清晰。
237
+ 5. 完成扫码连接后,首页标题与说明文案会切换为「已连接」状态,状态反馈更明确。
238
+ 6. 新版本提示由红点改为「有更新」文字标签,含义更直观。
239
+ 7. 上传游戏包按钮配色与产品主题色统一。
240
+ 8. 修复多个页面中标题与正文内容未对齐的问题,整体排版更整齐。
241
+ 9. 文案细节调整:「身份与授权」更名为「登录与授权」;移除侧边栏冗余的「查看文档」入口。
242
+
243
+ ## 0.3.9-beta.1
244
+ 本次测试版本主要补充启动模式场景能力,并优化场景命名可理解性:
245
+
246
+ 1. 启动模式新增 `inbox` 场景,支持在调试链路中模拟消息入口启动小游戏。
247
+
248
+ ## 0.4.0-beta.1
249
+
250
+ 本次测试版本主要补充直玩卡调试与上线链路,并优化能力接入助手体验:
251
+
252
+ 1. 能力接入助手的直玩卡联调流程扩展为三个阶段:先调试游戏包本身,再生成预览二维码验证卡面和承接路径,最后管理已验证直玩卡的上线状态。
253
+ 2. 阶段三新增「上线 / 下线直玩卡」能力,复用阶段二选中的直玩卡内容,调用平台接口修改直玩卡线上状态。
254
+ 3. 阶段三保留卡片名称、Content ID、场景、启动包、Queries 等信息,并单独展示「上线状态」,避免与阶段二「审核状态」混淆。
255
+ 4. 已上线卡片会展示「下线直玩卡」操作,未上线卡片展示「上线直玩卡」操作,确认后刷新最新线上状态。
256
+ 5. 优化直玩卡调试页面的区块间距,修复操作按钮与信息面板贴得过近的问题。
package/dist/index.js CHANGED
@@ -11318,36 +11318,79 @@ async function startSplit$1({ client_key, wasm_md5, }) {
11318
11318
  ctx: { logid: 'local', httpStatusCode: 400 },
11319
11319
  };
11320
11320
  }
11321
+ // ── Local boot-miss feedback (no server change) ──────────────────────
11322
+ // The runtime logs every sub-package function it had to pull in BEFORE the
11323
+ // first frame as `[wasmcode2] ... firstFrame=BEFORE` — each one is a
11324
+ // first-screen hitch. Drop those func ids into
11325
+ // `__TTMG_TEMP__/boot_extra_func_ids.txt` (one per line) and they get
11326
+ // merged into `alwaysInclude` so the next split pins them into the main
11327
+ // package, closing the gap WITHOUT re-enabling the indirect-closure flood.
11328
+ // No-op when the file is absent.
11329
+ const bootExtraPath = path$1.join(tempDir, 'boot_extra_func_ids.txt');
11330
+ let bootExtra = [];
11331
+ if (fs$1.existsSync(bootExtraPath)) {
11332
+ bootExtra = fs$1
11333
+ .readFileSync(bootExtraPath, 'utf-8')
11334
+ .split('\n')
11335
+ .map(l => parseInt(l.trim(), 10))
11336
+ .filter(n => Number.isInteger(n) && n > 0);
11337
+ }
11338
+ const alwaysIncludeIds = Array.from(new Set([...bootFuncIds, ...bootExtra]));
11339
+ // ── Collect coverage signal ──────────────────────────────────────────
11340
+ // With the indirect-call closure off, the main package ≈ the collected
11341
+ // set, so collect coverage now directly determines first-screen safety.
11342
+ // Surface it, and warn (never block) when it looks too thin to cover boot.
11343
+ const totalFuncs = Number(getGameJson()?.wasmFuncCount) || getLocalState().totalWasmFuncCount || 0;
11344
+ const coveragePct = totalFuncs ? (funcIds.length / totalFuncs) * 100 : 0;
11321
11345
  verboseLog(`[wasmtool] splitting with ${funcIds.length} func IDs` +
11346
+ ` (coverage ${coveragePct.toFixed(1)}% of ${totalFuncs})` +
11322
11347
  (bootFuncIds.length > 0
11323
- ? `, ${bootFuncIds.length} boot-phase func IDs (→ alwaysInclude)`
11324
- : ', no boot-phase info (legacy server, falling back to callClosure only)') +
11325
- `, archive=${archive}`);
11348
+ ? `, ${bootFuncIds.length} boot-phase func IDs`
11349
+ : ', no boot-phase info (legacy server, relying on collect + callClosure)') +
11350
+ (bootExtra.length > 0 ? `, +${bootExtra.length} boot-extra(local)` : '') +
11351
+ `, ${alwaysIncludeIds.length} → alwaysInclude, archive=${archive}`);
11352
+ // THIN_COLLECT_FLOOR is a heuristic, not a hard rule — tune from real
11353
+ // first-screen sizes. Warn-only: a thin collect with no boot hints means
11354
+ // first-screen functions may be served from the sub package before the
11355
+ // first frame (a visible hitch).
11356
+ const THIN_COLLECT_FLOOR = 8000;
11357
+ if (alwaysIncludeIds.length === 0 && funcIds.length < THIN_COLLECT_FLOOR) {
11358
+ verboseWarn(`[wasmtool] collect looks thin (${funcIds.length} funcs, no boot hints). ` +
11359
+ `First package may miss first-screen functions → pre-first-frame sub-package loads. ` +
11360
+ `Collect more scenarios (resume) before splitting, or add ids to ${bootExtraPath}.`);
11361
+ }
11326
11362
  try {
11327
11363
  const result = ttmgWasmtool.split({
11328
11364
  input: rawWasmPath,
11329
11365
  funcIds,
11330
- // Boot-phase func ids `alwaysInclude`. They are a subset of
11331
- // `funcIds` so this doesn't grow `collect_count`, but it DOES seed
11332
- // the direct-call closure BFS with the exact set needed for first
11333
- // frame, and the split tool's `alwaysIncludeAdded` counter is the
11334
- // observability signal when zero (= server didn't return boot info).
11335
- alwaysInclude: bootFuncIds.length > 0 ? bootFuncIds : undefined,
11366
+ // Boot-phase func ids (server) local boot-extra feedback
11367
+ // `alwaysInclude`. These seed the direct-call closure BFS with the
11368
+ // exact set needed for the first frame and force first-screen
11369
+ // functions into main; the split tool's `alwaysIncludeAdded` counter
11370
+ // is the observability signal (0 = no boot hints from any source).
11371
+ alwaysInclude: alwaysIncludeIds.length > 0 ? alwaysIncludeIds : undefined,
11336
11372
  // Always-on direct-call closure over (collect ∪ alwaysInclude ∪
11337
11373
  // start_func). Folds in func ids that collect missed (untaken
11338
11374
  // branches, race conditions during collect) so first-screen code
11339
11375
  // paths don't trap on archive trampolines. See the split tool's
11340
11376
  // `closure_added` counter for the per-build size impact.
11341
11377
  callClosure: true,
11342
- // Always-on indirect-call type-closure scoped to the boot subset.
11343
- // Catches IL2CPP virtual / interface / delegate dispatch which is
11344
- // the dominant source of remaining `firstFrame=BEFORE` archive
11345
- // trampoline hits after the runtime collect + direct closure
11346
- // passes (see `indirectClosureAdded` for the per-build size
11347
- // impact). Defaults to `true` in the wasmtool but we set it
11348
- // explicitly so a future tool default change can't silently turn
11349
- // it off in our pipeline.
11350
- callIndirectClosure: true,
11378
+ // OFF: the indirect-call type-closure is a static over-approximation
11379
+ // that pulls EVERY table function whose signature matches one used by
11380
+ // a boot root. In IL2CPP builds ~90% of functions live in the indirect
11381
+ // table and share only a few hundred signatures, so any non-trivial
11382
+ // boot set touches them all and this broadcasts almost the entire
11383
+ // module into main measured ~135k/136k funcs (~50MB) main, defeating
11384
+ // the split. First-screen indirect-call targets are instead covered
11385
+ // precisely by the runtime collect (logCall is injected into every
11386
+ // function body, so any function that actually runs before first frame
11387
+ // — including call_indirect targets — is already in `funcIds`) plus the
11388
+ // direct-call closure and `alwaysInclude`. Any residual first-frame
11389
+ // indirect hit is served on-demand by the archive loader at runtime;
11390
+ // keep those rare by improving collect-session coverage, not by
11391
+ // re-enabling this flood. Set explicitly so the wasmtool default
11392
+ // (currently `true`) can't silently turn it back on.
11393
+ callIndirectClosure: false,
11351
11394
  outputDir: splitOutputDir,
11352
11395
  archive,
11353
11396
  compress: true,
@@ -12941,6 +12984,306 @@ const gamePipelineModeGetRoute = {
12941
12984
  },
12942
12985
  };
12943
12986
 
12987
+ /**
12988
+ * 启动链路子包收集 —— DevTool CLI 侧契约类型。
12989
+ *
12990
+ * 协议字段与设计方案保持一致(见
12991
+ * topics/performance/startup-subpackage-collection-technical-design.md)。
12992
+ * `entry` 在协议层用单字段 name 表示:主包入口为内部约定 `__GAME__`,
12993
+ * 独立分包入口为该独立分包在源码 `subpackages[]` 中的 name。
12994
+ */
12995
+ /** 平台内部主包标识,只活在 DevTool / 客户端 / 编译服务等平台组件之间。 */
12996
+ const GAME_ENTRY = '__GAME__';
12997
+ /** 启动子包预下载配置字段名(源码侧 + 编译产物侧同名)。 */
12998
+ const PRELOAD_FIELD_NAME = 'parallelPreloadSubpackages';
12999
+
13000
+ /**
13001
+ * 从磁盘**新鲜读取**源码 game.json(不走 getGameJson 缓存,因为本流程要写)。
13002
+ * game.json 不存在或解析失败时抛错,由上层转成 failed 状态。
13003
+ */
13004
+ function readRawGameJson() {
13005
+ const filePath = path__namespace.join(process.cwd(), SUBPACKAGE_CONFIG_FILE_NAME);
13006
+ if (!fs__namespace.existsSync(filePath)) {
13007
+ throw new Error('game.json 不存在');
13008
+ }
13009
+ const raw = fs__namespace.readFileSync(filePath, 'utf-8');
13010
+ let json;
13011
+ try {
13012
+ json = JSON.parse(raw);
13013
+ }
13014
+ catch {
13015
+ throw new Error('game.json 解析失败');
13016
+ }
13017
+ const subpackagesField = SUBPACKAGE_FIELD_NAMES.find(k => Array.isArray(json[k])) ??
13018
+ SUBPACKAGE_FIELD_NAMES[0];
13019
+ return { filePath, raw, json, subpackagesField };
13020
+ }
13021
+ /** 只读读取一份 game.json;不存在 / 解析失败时返回 null,不抛错。 */
13022
+ function tryReadGameJson() {
13023
+ try {
13024
+ return readRawGameJson();
13025
+ }
13026
+ catch {
13027
+ return null;
13028
+ }
13029
+ }
13030
+ /** 取 subpackages 数组(保证返回数组)。 */
13031
+ function getSubpackages(game) {
13032
+ const list = game.json[game.subpackagesField];
13033
+ return Array.isArray(list) ? list : [];
13034
+ }
13035
+ /** 按 name 在 subpackages[] 中定位 entry。 */
13036
+ function findSubpackage(game, name) {
13037
+ return getSubpackages(game).find(s => s?.name === name);
13038
+ }
13039
+ /**
13040
+ * 把对象就地写回磁盘,沿用仓库 JSON 缩进 / 换行约定。
13041
+ */
13042
+ function writeGameJson(game) {
13043
+ fs__namespace.writeFileSync(game.filePath, JSON.stringify(game.json, null, JSON_INDENT) + JSON_EOL);
13044
+ }
13045
+ /** 失败回滚:把原始文本原样写回。 */
13046
+ function restoreGameJson(game) {
13047
+ fs__namespace.writeFileSync(game.filePath, game.raw);
13048
+ }
13049
+ /** entry 是否为主包入口。 */
13050
+ function isGameEntry(entry) {
13051
+ return entry === GAME_ENTRY;
13052
+ }
13053
+
13054
+ /**
13055
+ * 写入前校验:通用规则 + 独立分包入口增量规则。
13056
+ * 任意一条不满足直接抛 Error(上层转 failed,不写 game.json)。
13057
+ * 返回过滤掉防御性条目后的待写入列表(含 root)。
13058
+ */
13059
+ function validateCollect(game, entry, reported) {
13060
+ const subpackages = getSubpackages(game);
13061
+ if (!Array.isArray(game.json[game.subpackagesField])) {
13062
+ throw new Error('game.json.subpackages 缺失或不是数组');
13063
+ }
13064
+ // 独立分包入口增量校验
13065
+ if (!isGameEntry(entry)) {
13066
+ const entryPkg = findSubpackage(game, entry);
13067
+ if (!entryPkg) {
13068
+ throw new Error(`独立分包入口 ${entry} 不存在于 game.json.subpackages`);
13069
+ }
13070
+ if (entryPkg.independent !== true) {
13071
+ throw new Error(`入口 ${entry} 不是独立分包(independent !== true),不可作为启动入口采集`);
13072
+ }
13073
+ }
13074
+ const nameToPkg = new Map(subpackages.map(s => [s?.name, s]));
13075
+ const result = [];
13076
+ const seen = new Set();
13077
+ for (const item of reported) {
13078
+ const subPkgName = item?.subPkgName;
13079
+ // 客户端不应上报 __GAME__ 作为子包名:防御性忽略,不入库、不阻塞
13080
+ if (!subPkgName || subPkgName === GAME_ENTRY) {
13081
+ continue;
13082
+ }
13083
+ // 独立分包不预下载自己
13084
+ if (!isGameEntry(entry) && subPkgName === entry) {
13085
+ throw new Error(`独立分包入口 ${entry} 不能预下载自身`);
13086
+ }
13087
+ if (seen.has(subPkgName)) {
13088
+ continue;
13089
+ }
13090
+ seen.add(subPkgName);
13091
+ const pkg = nameToPkg.get(subPkgName);
13092
+ if (!pkg) {
13093
+ throw new Error(`子包 ${subPkgName} 未定义在 game.json.subpackages`);
13094
+ }
13095
+ if (!pkg.root || typeof pkg.root !== 'string') {
13096
+ throw new Error(`子包 ${subPkgName} 缺少 root 字段`);
13097
+ }
13098
+ const absDir = path__namespace.isAbsolute(pkg.root)
13099
+ ? pkg.root
13100
+ : path__namespace.join(process.cwd(), pkg.root);
13101
+ if (!fs__namespace.existsSync(absDir) || !fs__namespace.statSync(absDir).isDirectory()) {
13102
+ throw new Error(`子包 ${subPkgName} 的目录不存在:${pkg.root}`);
13103
+ }
13104
+ result.push({
13105
+ subPkgName,
13106
+ isStartup: !!item.isStartup,
13107
+ root: pkg.root,
13108
+ });
13109
+ }
13110
+ return result;
13111
+ }
13112
+
13113
+ /** 读出某个 entry 当前的 parallelPreloadSubpackages(保证返回数组)。 */
13114
+ function readEntryConfig(game, entry) {
13115
+ const container = isGameEntry(entry)
13116
+ ? game.json
13117
+ : findSubpackage(game, entry);
13118
+ const list = container?.[PRELOAD_FIELD_NAME];
13119
+ return Array.isArray(list) ? list : [];
13120
+ }
13121
+ /**
13122
+ * 按 entry 只读读取源码 game.json 中已有的 parallelPreloadSubpackages,
13123
+ * 用于 UI 初始化配置预览。game.json 不存在 / 字段缺失时返回空数组,不阻塞。
13124
+ */
13125
+ function readPreloadSubpackagesConfig(entry) {
13126
+ const game = tryReadGameJson();
13127
+ if (!game) {
13128
+ return { entry, parallelPreloadSubpackages: [] };
13129
+ }
13130
+ return { entry, parallelPreloadSubpackages: readEntryConfig(game, entry) };
13131
+ }
13132
+ /**
13133
+ * 列出某个 entry 下可被采集(可预下载)的候选子包,供 UI 基于真实可用包选择。
13134
+ *
13135
+ * 候选 = game.json.subpackages 中 root 目录真实存在、且 name 不等于当前 entry
13136
+ * 的子包(独立分包入口不可预下载自身)。仅供 mock 上报基于真实可用包选择,
13137
+ * 不含 size —— size 在编译期由 ttmg-compile 按压缩加密后体积回填。
13138
+ * game.json 不存在 / 字段缺失时返回空数组,不阻塞。
13139
+ */
13140
+ function listPreloadSubpackageCandidates(entry) {
13141
+ const game = tryReadGameJson();
13142
+ if (!game) {
13143
+ return { entry, candidates: [], totalSubpackages: 0 };
13144
+ }
13145
+ const subpackages = getSubpackages(game);
13146
+ const candidates = [];
13147
+ for (const sub of subpackages) {
13148
+ const subPkgName = sub?.name;
13149
+ const root = sub?.root;
13150
+ if (typeof subPkgName !== 'string' || !subPkgName)
13151
+ continue;
13152
+ if (subPkgName === entry)
13153
+ continue;
13154
+ if (typeof root !== 'string' || !root)
13155
+ continue;
13156
+ const absDir = path__namespace.isAbsolute(root)
13157
+ ? root
13158
+ : path__namespace.join(process.cwd(), root);
13159
+ if (!fs__namespace.existsSync(absDir) || !fs__namespace.statSync(absDir).isDirectory())
13160
+ continue;
13161
+ candidates.push({
13162
+ subPkgName,
13163
+ root,
13164
+ independent: sub.independent === true,
13165
+ });
13166
+ }
13167
+ return { entry, candidates, totalSubpackages: subpackages.length };
13168
+ }
13169
+ /**
13170
+ * 收集快照写入:校验 → 按 entry 覆盖写入对应位置 → 失败回滚。
13171
+ *
13172
+ * 只写入 `subPkgName` + `isStartup`,**不写 size**:size 是压缩+加密后的真实
13173
+ * 下发体积,由编译服务(ttmg-compile)在产出 STTPKG 时回填到编译产物,收集期
13174
+ * 拿不到也不写(见技术方案 3.4 / 3.5)。
13175
+ *
13176
+ * - 写入前校验失败:抛错,不修改 game.json。
13177
+ * - 进入写入阶段后异常:回滚原始 game.json 再抛错。
13178
+ * 返回本次 entry 下最终写入的 parallelPreloadSubpackages。
13179
+ */
13180
+ function collectPreloadSubpackages(entry, reported) {
13181
+ // readRawGameJson 抛错即「game.json 不存在 / 解析失败」,写入前阶段,不动文件
13182
+ const game = readRawGameJson();
13183
+ const validated = validateCollect(game, entry, reported);
13184
+ const parallelPreloadSubpackages = validated.map(item => ({
13185
+ subPkgName: item.subPkgName,
13186
+ isStartup: item.isStartup,
13187
+ }));
13188
+ let started = false;
13189
+ try {
13190
+ const container = isGameEntry(entry)
13191
+ ? game.json
13192
+ : findSubpackage(game, entry);
13193
+ if (!container) {
13194
+ // 独立分包入口已在 validate 阶段确认存在,这里是防御
13195
+ throw new Error(`入口 ${entry} 不存在`);
13196
+ }
13197
+ started = true;
13198
+ container[PRELOAD_FIELD_NAME] = parallelPreloadSubpackages;
13199
+ writeGameJson(game);
13200
+ }
13201
+ catch (err) {
13202
+ if (started) {
13203
+ restoreGameJson(game);
13204
+ }
13205
+ throw err;
13206
+ }
13207
+ return { entry, parallelPreloadSubpackages };
13208
+ }
13209
+
13210
+ /**
13211
+ * 读取某个启动入口已有的 parallelPreloadSubpackages(只读),用于 UI 初始化
13212
+ * 配置预览。对应设计方案的 `getPreloadSubpackagesConfig`。
13213
+ *
13214
+ * query: `entry`(缺省按主包 `__GAME__` 处理)。
13215
+ */
13216
+ const gamePreloadSubpackagesConfigRoute = {
13217
+ method: 'get',
13218
+ path: '/game/preload-subpackages-config',
13219
+ handler: async (req, res) => {
13220
+ const entry = req.query.entry || GAME_ENTRY;
13221
+ try {
13222
+ const data = readPreloadSubpackagesConfig(entry);
13223
+ res.send({ code: successCode, data });
13224
+ }
13225
+ catch (err) {
13226
+ res.send({
13227
+ code: errorCode,
13228
+ error: { message: err.message },
13229
+ });
13230
+ }
13231
+ },
13232
+ };
13233
+
13234
+ /**
13235
+ * 列出某个启动入口下可被采集(可预下载)的候选子包,供 UI mock 上报时
13236
+ * 基于真实可用包选择,而不是手填名字。候选直接来自 game.json.subpackages
13237
+ * 中 root 真实存在、name 不等于当前 entry 的子包,并附带压缩前体积。
13238
+ *
13239
+ * query: `entry`(缺省按主包 `__GAME__` 处理)。
13240
+ */
13241
+ const gamePreloadSubpackageCandidatesRoute = {
13242
+ method: 'get',
13243
+ path: '/game/preload-subpackage-candidates',
13244
+ handler: async (req, res) => {
13245
+ const entry = req.query.entry || GAME_ENTRY;
13246
+ try {
13247
+ const data = listPreloadSubpackageCandidates(entry);
13248
+ res.send({ code: successCode, data });
13249
+ }
13250
+ catch (err) {
13251
+ res.send({
13252
+ code: errorCode,
13253
+ error: { message: err.message },
13254
+ });
13255
+ }
13256
+ },
13257
+ };
13258
+
13259
+ /**
13260
+ * 收集快照写入:按 entry 校验 + 补体积 + 覆盖写入源码 game.json 对应位置,
13261
+ * 失败回滚。对应设计方案的 `collectPreloadSubpackages` /
13262
+ * `updatePreloadSubpackagesConfig`(HTTP 请求-响应实现,start 态由 UI 自身的
13263
+ * 处理中状态承载,CLI 只回 success / failed)。
13264
+ *
13265
+ * body: `{ entry, subpackages: [{ subPkgName, isStartup }] }`。
13266
+ */
13267
+ const gameCollectPreloadSubpackagesRoute = {
13268
+ method: 'post',
13269
+ path: '/game/collect-preload-subpackages',
13270
+ handler: async (req, res) => {
13271
+ const entry = req.body?.entry || GAME_ENTRY;
13272
+ const subpackages = req.body?.subpackages || [];
13273
+ console.log('collect-preload-subpackages', { entry, count: subpackages.length });
13274
+ try {
13275
+ const data = collectPreloadSubpackages(entry, subpackages);
13276
+ res.send({ code: successCode, data });
13277
+ }
13278
+ catch (err) {
13279
+ res.send({
13280
+ code: errorCode,
13281
+ error: { message: err.message },
13282
+ });
13283
+ }
13284
+ },
13285
+ };
13286
+
12944
13287
  const routes = [
12945
13288
  gameAssetPreviewUrlRoute,
12946
13289
  gameAssetsRoute,
@@ -12974,6 +13317,9 @@ const routes = [
12974
13317
  gamePipelineModeRoute,
12975
13318
  gamePipelineModeGetRoute,
12976
13319
  gameLanguageRoute,
13320
+ gamePreloadSubpackagesConfigRoute,
13321
+ gamePreloadSubpackageCandidatesRoute,
13322
+ gameCollectPreloadSubpackagesRoute,
12977
13323
  ];
12978
13324
  /**
12979
13325
  * Express 4 does not catch rejections from async route handlers — an
@@ -13474,7 +13820,7 @@ async function upload({ clientKey, note = '--', dir, }) {
13474
13820
  }
13475
13821
  }
13476
13822
 
13477
- var version = "0.4.0-beta.1";
13823
+ var version = "0.4.1-beta.wasm1";
13478
13824
  var pkg = {
13479
13825
  version: version};
13480
13826