@leonxin/meetgames 0.1.7 → 0.1.11

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 (293) hide show
  1. package/.agents/skills/meet-sdk-regression/SKILL.md +93 -0
  2. package/.cursor/mcp.example.json +16 -0
  3. package/.cursor/mcp.json +11 -0
  4. package/.cursor/skills/meetgames-mcp/SKILL.md +48 -0
  5. package/.vite/vitest/results.json +1 -0
  6. package/README.md +31 -8
  7. package/dist/aab-converter/aab-entry.d.ts +3 -0
  8. package/dist/aab-converter/aab-entry.d.ts.map +1 -0
  9. package/dist/aab-converter/aab-entry.js +49 -0
  10. package/dist/aab-converter/aab-entry.js.map +1 -0
  11. package/dist/aab-converter/apksExtractor.d.ts +2 -0
  12. package/dist/aab-converter/apksExtractor.d.ts.map +1 -0
  13. package/dist/aab-converter/apksExtractor.js +108 -0
  14. package/dist/aab-converter/apksExtractor.js.map +1 -0
  15. package/dist/aab-converter/bundletoolRunner.d.ts +15 -0
  16. package/dist/aab-converter/bundletoolRunner.d.ts.map +1 -0
  17. package/dist/aab-converter/bundletoolRunner.js +46 -0
  18. package/dist/aab-converter/bundletoolRunner.js.map +1 -0
  19. package/dist/aab-converter/cliArgs.d.ts +27 -0
  20. package/dist/aab-converter/cliArgs.d.ts.map +1 -0
  21. package/dist/aab-converter/cliArgs.js +170 -0
  22. package/dist/aab-converter/cliArgs.js.map +1 -0
  23. package/dist/aab-converter/convertAabToApk.d.ts +7 -0
  24. package/dist/aab-converter/convertAabToApk.d.ts.map +1 -0
  25. package/dist/aab-converter/convertAabToApk.js +69 -0
  26. package/dist/aab-converter/convertAabToApk.js.map +1 -0
  27. package/dist/aab-converter/resourcePaths.d.ts +4 -0
  28. package/dist/aab-converter/resourcePaths.d.ts.map +1 -0
  29. package/dist/aab-converter/resourcePaths.js +42 -0
  30. package/dist/aab-converter/resourcePaths.js.map +1 -0
  31. package/dist/aab-converter/signingOptions.d.ts +9 -0
  32. package/dist/aab-converter/signingOptions.d.ts.map +1 -0
  33. package/dist/aab-converter/signingOptions.js +21 -0
  34. package/dist/aab-converter/signingOptions.js.map +1 -0
  35. package/dist/aab-converter/types.d.ts +24 -0
  36. package/dist/aab-converter/types.d.ts.map +1 -0
  37. package/dist/aab-converter/types.js +2 -0
  38. package/dist/aab-converter/types.js.map +1 -0
  39. package/dist/android/adapter.d.ts.map +1 -1
  40. package/dist/android/adapter.js +2 -2
  41. package/dist/android/adapter.js.map +1 -1
  42. package/dist/android/detect.d.ts +2 -2
  43. package/dist/android/detect.d.ts.map +1 -1
  44. package/dist/android/detect.js +36 -8
  45. package/dist/android/detect.js.map +1 -1
  46. package/dist/android/meetSdkRemoteGradle.d.ts +0 -3
  47. package/dist/android/meetSdkRemoteGradle.d.ts.map +1 -1
  48. package/dist/android/meetSdkRemoteGradle.js +13 -20
  49. package/dist/android/meetSdkRemoteGradle.js.map +1 -1
  50. package/dist/cli.d.ts.map +1 -1
  51. package/dist/cli.js +157 -31
  52. package/dist/cli.js.map +1 -1
  53. package/dist/config/meetSdkDefaultConfig.d.ts +19 -2
  54. package/dist/config/meetSdkDefaultConfig.d.ts.map +1 -1
  55. package/dist/config/meetSdkDefaultConfig.js +67 -5
  56. package/dist/config/meetSdkDefaultConfig.js.map +1 -1
  57. package/dist/config/meetSdkIosConfig.d.ts +21 -0
  58. package/dist/config/meetSdkIosConfig.d.ts.map +1 -0
  59. package/dist/config/meetSdkIosConfig.js +66 -0
  60. package/dist/config/meetSdkIosConfig.js.map +1 -0
  61. package/dist/config/meetSdkRemoteConfig.d.ts +19 -11
  62. package/dist/config/meetSdkRemoteConfig.d.ts.map +1 -1
  63. package/dist/config/meetSdkRemoteConfig.js +89 -69
  64. package/dist/config/meetSdkRemoteConfig.js.map +1 -1
  65. package/dist/config/topsdkFeatureModules.d.ts +5 -0
  66. package/dist/config/topsdkFeatureModules.d.ts.map +1 -1
  67. package/dist/config/topsdkFeatureModules.js +26 -0
  68. package/dist/config/topsdkFeatureModules.js.map +1 -1
  69. package/dist/contracts/types.d.ts +19 -6
  70. package/dist/contracts/types.d.ts.map +1 -1
  71. package/dist/core/doctor.d.ts +17 -0
  72. package/dist/core/doctor.d.ts.map +1 -0
  73. package/dist/core/doctor.js +444 -0
  74. package/dist/core/doctor.js.map +1 -0
  75. package/dist/core/pipeline.d.ts.map +1 -1
  76. package/dist/core/pipeline.js +0 -15
  77. package/dist/core/pipeline.js.map +1 -1
  78. package/dist/core/platform.d.ts +12 -0
  79. package/dist/core/platform.d.ts.map +1 -0
  80. package/dist/core/platform.js +40 -0
  81. package/dist/core/platform.js.map +1 -0
  82. package/dist/core/reporter.js +1 -1
  83. package/dist/core/reporter.js.map +1 -1
  84. package/dist/core/workspace.d.ts +2 -2
  85. package/dist/core/workspace.d.ts.map +1 -1
  86. package/dist/core/workspace.js +4 -5
  87. package/dist/core/workspace.js.map +1 -1
  88. package/dist/index.d.ts +3 -1
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +3 -1
  91. package/dist/index.js.map +1 -1
  92. package/dist/ios/channelConfig.d.ts +1 -0
  93. package/dist/ios/channelConfig.d.ts.map +1 -1
  94. package/dist/ios/channelConfig.js +82 -0
  95. package/dist/ios/channelConfig.js.map +1 -1
  96. package/dist/ios/codeUtils.d.ts +1 -0
  97. package/dist/ios/codeUtils.d.ts.map +1 -1
  98. package/dist/ios/codeUtils.js +11 -2
  99. package/dist/ios/codeUtils.js.map +1 -1
  100. package/dist/ios/detect.d.ts +2 -2
  101. package/dist/ios/detect.d.ts.map +1 -1
  102. package/dist/ios/detect.js +49 -10
  103. package/dist/ios/detect.js.map +1 -1
  104. package/dist/ios/entitlements.d.ts +4 -0
  105. package/dist/ios/entitlements.d.ts.map +1 -0
  106. package/dist/ios/entitlements.js +53 -0
  107. package/dist/ios/entitlements.js.map +1 -0
  108. package/dist/ios/fileManager.d.ts.map +1 -1
  109. package/dist/ios/fileManager.js +3 -2
  110. package/dist/ios/fileManager.js.map +1 -1
  111. package/dist/ios/infoPlist.d.ts +1 -1
  112. package/dist/ios/infoPlist.d.ts.map +1 -1
  113. package/dist/ios/infoPlist.js.map +1 -1
  114. package/dist/ios/integrate.d.ts.map +1 -1
  115. package/dist/ios/integrate.js +211 -36
  116. package/dist/ios/integrate.js.map +1 -1
  117. package/dist/ios/pbxprojEditor.d.ts +2 -0
  118. package/dist/ios/pbxprojEditor.d.ts.map +1 -1
  119. package/dist/ios/pbxprojEditor.js +179 -1
  120. package/dist/ios/pbxprojEditor.js.map +1 -1
  121. package/dist/ios/pluginConfig.d.ts +1 -0
  122. package/dist/ios/pluginConfig.d.ts.map +1 -1
  123. package/dist/ios/pluginConfig.js +36 -4
  124. package/dist/ios/pluginConfig.js.map +1 -1
  125. package/dist/ios/sdkBundle.d.ts +1 -1
  126. package/dist/ios/sdkBundle.d.ts.map +1 -1
  127. package/dist/ios/sdkBundle.js +7 -5
  128. package/dist/ios/sdkBundle.js.map +1 -1
  129. package/dist/ios/template.d.ts +1 -0
  130. package/dist/ios/template.d.ts.map +1 -1
  131. package/dist/ios/template.js +14 -1
  132. package/dist/ios/template.js.map +1 -1
  133. package/dist/ios/types.d.ts +2 -2
  134. package/dist/ios/types.d.ts.map +1 -1
  135. package/dist/mcp/server.d.ts.map +1 -1
  136. package/dist/mcp/server.js +14 -13
  137. package/dist/mcp/server.js.map +1 -1
  138. package/dist/mcp/service.d.ts +8 -6
  139. package/dist/mcp/service.d.ts.map +1 -1
  140. package/dist/mcp/service.js +34 -14
  141. package/dist/mcp/service.js.map +1 -1
  142. package/dist/ops/handlers.d.ts.map +1 -1
  143. package/dist/ops/handlers.js +10 -4
  144. package/dist/ops/handlers.js.map +1 -1
  145. package/dist/remote/sdkHomeDownload.d.ts +65 -0
  146. package/dist/remote/sdkHomeDownload.d.ts.map +1 -0
  147. package/dist/remote/sdkHomeDownload.js +208 -0
  148. package/dist/remote/sdkHomeDownload.js.map +1 -0
  149. package/dist/remote/topsdkDownloadSdkConfig.d.ts.map +1 -1
  150. package/dist/remote/topsdkDownloadSdkConfig.js +11 -1
  151. package/dist/remote/topsdkDownloadSdkConfig.js.map +1 -1
  152. package/dist/shared/errors.d.ts +7 -0
  153. package/dist/shared/errors.d.ts.map +1 -0
  154. package/dist/shared/errors.js +16 -0
  155. package/dist/shared/errors.js.map +1 -0
  156. package/dist/shared/fileUtils.d.ts +5 -0
  157. package/dist/shared/fileUtils.d.ts.map +1 -0
  158. package/dist/shared/fileUtils.js +35 -0
  159. package/dist/shared/fileUtils.js.map +1 -0
  160. package/dist/shared/logger.d.ts +10 -0
  161. package/dist/shared/logger.d.ts.map +1 -0
  162. package/dist/shared/logger.js +37 -0
  163. package/dist/shared/logger.js.map +1 -0
  164. package/dist/shared/pathUtils.d.ts +4 -0
  165. package/dist/shared/pathUtils.d.ts.map +1 -0
  166. package/dist/shared/pathUtils.js +22 -0
  167. package/dist/shared/pathUtils.js.map +1 -0
  168. package/dist/shared/processRunner.d.ts +12 -0
  169. package/dist/shared/processRunner.d.ts.map +1 -0
  170. package/dist/shared/processRunner.js +31 -0
  171. package/dist/shared/processRunner.js.map +1 -0
  172. package/docs/AAB_CONVERTER_CLI_PLAN.md +392 -0
  173. package/docs/API.md +246 -32
  174. package/docs/CLI.md +292 -0
  175. package/docs/INTEGRATION.md +116 -0
  176. package/docs/MCP.md +86 -0
  177. package/docs/README.md +19 -10
  178. package/docs/{api → archive/api}/downloadSDKConfig.md +8 -6
  179. package/docs/{api → archive/api}/getChannelConfig-meetgames.md +1 -1
  180. package/docs/archive/ios-migration.md +2139 -0
  181. package/docs/{ → archive/product/}/346/212/200/346/234/257/346/226/271/346/241/210/350/260/203/347/240/224.md +7 -7
  182. package/docs/{ → archive/product/}/351/234/200/346/261/202/346/226/207/346/241/243.md +15 -14
  183. package/logs/convert-20260622-155037.log +5 -0
  184. package/logs/convert-20260622-155226.log +6 -0
  185. package/meetsdk-android.json +2 -1
  186. package/meetsdk-ios.json +15 -0
  187. package/package.json +10 -35
  188. package/scripts/package-aab-cli-win.mjs +193 -0
  189. package/src/aab-converter/aab-entry.ts +48 -0
  190. package/src/aab-converter/apksExtractor.ts +119 -0
  191. package/src/aab-converter/bundletoolRunner.ts +63 -0
  192. package/src/aab-converter/cliArgs.ts +194 -0
  193. package/src/aab-converter/convertAabToApk.ts +81 -0
  194. package/src/aab-converter/resourcePaths.ts +43 -0
  195. package/src/aab-converter/signingOptions.ts +29 -0
  196. package/src/aab-converter/types.ts +26 -0
  197. package/src/android/adapter.ts +9 -0
  198. package/src/android/assembleIntegrationJson.ts +33 -0
  199. package/src/android/detect.ts +132 -0
  200. package/src/android/downloadGoogleServicesJson.ts +56 -0
  201. package/src/android/gradle.ts +116 -0
  202. package/src/android/manifest.ts +50 -0
  203. package/src/android/meetSdkRemoteGradle.ts +837 -0
  204. package/src/cli.ts +488 -0
  205. package/src/config/fetchConfigWrite.ts +30 -0
  206. package/src/config/loadAndroidIntegration.ts +41 -0
  207. package/src/config/loadManifest.ts +40 -0
  208. package/src/config/meetSdkDefaultConfig.ts +99 -0
  209. package/src/config/meetSdkIosConfig.ts +87 -0
  210. package/src/config/meetSdkRemoteConfig.ts +1211 -0
  211. package/src/config/topsdkFeatureModules.ts +92 -0
  212. package/src/contracts/types.ts +121 -0
  213. package/src/core/doctor.ts +485 -0
  214. package/src/core/patch.ts +64 -0
  215. package/src/core/pipeline.ts +107 -0
  216. package/src/core/platform.ts +47 -0
  217. package/src/core/previewPatches.ts +23 -0
  218. package/src/core/reporter.ts +24 -0
  219. package/src/core/workspace.ts +29 -0
  220. package/src/entry.ts +7 -0
  221. package/src/index.ts +133 -0
  222. package/src/ios/channelConfig.ts +128 -0
  223. package/src/ios/codeUtils.ts +160 -0
  224. package/src/ios/detect.ts +105 -0
  225. package/src/ios/entitlements.ts +61 -0
  226. package/src/ios/fileManager.ts +48 -0
  227. package/src/ios/infoPlist.ts +55 -0
  228. package/src/ios/integrate.ts +516 -0
  229. package/src/ios/pbxprojEditor.ts +383 -0
  230. package/src/ios/pluginConfig.ts +97 -0
  231. package/src/ios/reserved.ts +8 -0
  232. package/src/ios/sdkBundle.ts +36 -0
  233. package/src/ios/template.ts +36 -0
  234. package/src/ios/types.ts +65 -0
  235. package/src/mcp/server.ts +170 -0
  236. package/src/mcp/service.ts +222 -0
  237. package/src/mcp-entry.ts +7 -0
  238. package/src/ops/fileStore.ts +56 -0
  239. package/src/ops/handlers.ts +304 -0
  240. package/src/remote/fetchJson.ts +22 -0
  241. package/src/remote/sdkHomeDownload.ts +274 -0
  242. package/src/remote/topsdkDownloadSdkConfig.ts +93 -0
  243. package/src/remote/topsdkGetSdkConfig.ts +122 -0
  244. package/src/remote/topsdkSign.ts +10 -0
  245. package/src/shared/errors.ts +16 -0
  246. package/src/shared/fileUtils.ts +41 -0
  247. package/src/shared/logger.ts +49 -0
  248. package/src/shared/pathUtils.ts +24 -0
  249. package/src/shared/processRunner.ts +43 -0
  250. package/test-projects/README.md +51 -0
  251. package/test-projects/_preview/pipeline.patch +281 -0
  252. package/tests/aab-converter.test.ts +213 -0
  253. package/tests/assemble.test.ts +12 -0
  254. package/tests/doctor.test.ts +89 -0
  255. package/tests/downloadGoogleServicesJson.test.ts +47 -0
  256. package/tests/fetch-remote.test.ts +23 -0
  257. package/tests/fetchConfigOverrides.test.ts +28 -0
  258. package/tests/fetchConfigWrite.test.ts +54 -0
  259. package/tests/gradle.test.ts +33 -0
  260. package/tests/integration-json.test.ts +29 -0
  261. package/tests/ios.codeUtils.test.ts +23 -0
  262. package/tests/ios.sdkBundle.test.ts +16 -0
  263. package/tests/loadManifest.test.ts +15 -0
  264. package/tests/manifest-xml.test.ts +30 -0
  265. package/tests/mcp.e2e.ts +217 -0
  266. package/tests/mcp.service.test.ts +53 -0
  267. package/tests/meetSdkRemoteConfig.test.ts +456 -0
  268. package/tests/meetSdkRemoteGradle.test.ts +414 -0
  269. package/tests/pipeline.android.test.ts +96 -0
  270. package/tests/pipeline.integration-json.test.ts +58 -0
  271. package/tests/pipeline.ios.test.ts +385 -0
  272. package/tests/pipeline.preview.patch.test.ts +85 -0
  273. package/tests/platformSelection.test.ts +77 -0
  274. package/tests/sdkHomeDownload.test.ts +124 -0
  275. package/tests/sdkVersionConfig.test.ts +130 -0
  276. package/tests/test-projects-hosts.test.ts +78 -0
  277. package/tests/topsdk.test.ts +53 -0
  278. package/tests/topsdkDownloadSdkConfig.test.ts +81 -0
  279. package/tests/topsdkFeatureModules.test.ts +116 -0
  280. package/tsconfig.json +19 -0
  281. package/vitest.config.ts +9 -0
  282. package/vitest.mcp.config.ts +11 -0
  283. package/bundled/android/sample.txt +0 -1
  284. package/docs/ANDROID.md +0 -133
  285. package/docs/CURSOR-MCP-SETUP.md +0 -72
  286. package/docs/MCP-SKILL.md +0 -63
  287. package/fixtures/api-samples/getChannelConfig-meetgames.sample.json +0 -123
  288. package/fixtures/meetsdk-remote-config.download-shape.json +0 -20
  289. package/fixtures/meetsdk-remote-config.mock.json +0 -69
  290. package/fixtures/recipes/android-default.fixture.yaml +0 -15
  291. package/fixtures/recipes/android-integration.fixture.json +0 -29
  292. package/fixtures/topsdk-config-reference.json +0 -39
  293. /package/docs/{api → archive/api}/getSDKConfig.md +0 -0
@@ -86,7 +86,7 @@ PRD「AST 替代正则」指 **后续 Groovy Gradle 编辑** 与 **游戏源码
86
86
  | 接口 | 路径 | 鉴权 | 返回 | 工具 |
87
87
  |------|------|------|------|------|
88
88
  | 老接口 | `GET /openSDK/getSDKConfig` | sign + timestamp | `channelAuthConfig` 片段 | 库内保留映射,CLI 不用 |
89
- | **新接口(采用)** | `GET /console/openSDK/downloadSDKConfig` | `appId` + `channelType` | 完整 `meetsdk-remote-config.json` | **`fetch-config`** |
89
+ | **新接口(采用)** | `GET /console/openSDK/downloadSDKConfig` | sign + timestamp | 完整 `meetsdk-remote-config.json` | **`fetch-config`** |
90
90
  | 老接口 | `GET /openSDK/getSDKConfig` | sign | `channelAuthConfig` 片段 | 不用于 CLI 拉参 |
91
91
 
92
92
  JSON 内渠道字段:**根级 `channel` only**(无 `channelType`、无 `topsdk.channel`)。
@@ -182,7 +182,7 @@ flowchart LR
182
182
 
183
183
  ### 5.3 Firebase
184
184
 
185
- - `firebase_file_url` 在 analytics.firebase 对象中;
185
+ - `firebaseUrl` 在 analytics.firebase 对象中;
186
186
  - 集成时拉取或引用 URL(具体拷贝逻辑见 recipe / firebase 相关 op)。
187
187
 
188
188
  ---
@@ -251,14 +251,14 @@ meetgames fetch-config --project-root <原生工程根> \
251
251
  --app-id <APP_ID> --channel-type GOOGLE --env test
252
252
 
253
253
  meetgames setup --project-root <原生工程根> \
254
- --app-id <APP_ID> --channel-type GOOGLE
254
+ --app-id <APP_ID> --app-secret <APP_SECRET> --channel-type GOOGLE
255
255
  ```
256
256
 
257
257
  | 说明 | |
258
258
  |------|--|
259
259
  | `channel-type` | 商店渠道枚举,对应 HTTP 参数 `channelType` |
260
- | `app-secret` | **不必**在 fetch-config 传入;由 `downloadSDKConfig` 响应写入 JSON |
261
- | CI | 设置 `TOPSDK_APP_ID`、`TOPSDK_CHANNEL_TYPE` 或依赖已提交的 JSON 缓存 |
260
+ | `app-secret` | **必须**在 fetch-config/setup 传入,或通过 `TOPSDK_APP_SECRET` 设置;仅用于计算 `sign` |
261
+ | CI | 设置 `TOPSDK_APP_ID`、`TOPSDK_APP_SECRET`、`TOPSDK_CHANNEL_TYPE` 或依赖已提交的 JSON 缓存 |
262
262
 
263
263
  ### 7.2 MCP(MVP)
264
264
 
@@ -276,7 +276,7 @@ meetgames setup --project-root <原生工程根> \
276
276
  | 项 | 技术要点 |
277
277
  |----|----------|
278
278
  | 渠道配置下载 | 与 `downloadSDKConfig` 字节流一致,避免前端再拼 JSON |
279
- | 三方文件 | 对象存储原文件 + 下载 URL 写入 JSON(如 `firebase_file_url`) |
279
+ | 三方文件 | 对象存储原文件 + 下载 URL 写入 JSON(如 `firebaseUrl`) |
280
280
  | SDK 版本 | 后台配置 → 下发或随工具 `meetsdk-android.json` 同步策略 **【待与发版流程对齐】** |
281
281
  | 后台改 JSON 字段 | 与工具约定根级 `channel`,避免回退 `topsdk.channel` |
282
282
 
@@ -300,7 +300,7 @@ meetgames setup --project-root <原生工程根> \
300
300
  | 层级 | 内容 |
301
301
  |------|------|
302
302
  | 单元 | Gradle upsert、JSON 解析、Manifest |
303
- | 集成 | `test-projects/android-sample`、`pipeline.*.test.ts` |
303
+ | 集成 | `fixtures/android-test-project/android-latest-project`、`fixtures/android-test-project/power-raid`、`pipeline.*.test.ts` |
304
304
  | CI 建议 | `npm ci && npm test && meetgames setup --dry-run`(需 secrets 与 API 可用) |
305
305
 
306
306
  **宿主限制**:流水线仅针对标准 Android **Groovy** Gradle / Xcode 原生工程;**不支持** `build.gradle.kts`;Unity / Unreal 不在支持矩阵内。
@@ -143,19 +143,20 @@ dependencies {
143
143
  |------|------|
144
144
  | `--project-root` | 游戏工程根路径 |
145
145
  | `--app-id` | MG / TOPSDK 应用 ID |
146
+ | `--app-secret` | 应用密钥,仅用于本地计算 `downloadSDKConfig` 的 `sign`,不会从本地缓存读取 |
146
147
  | `--channel-type` | 商店渠道:`GOOGLE` / `APPLE` 等(HTTP 查询参数,非 android/ios) |
147
148
  | `--env` | `prod` / `pre` / `test` |
148
149
 
149
- 说明:`downloadSDKConfig` **无需** CLI `appSecret`,密钥由接口写入 JSON `topsdk.appSecret`。旧文档中的 `--app-secret` 仅适用于已废弃的 `getSDKConfig` 签名流程。
150
+ 说明:`downloadSDKConfig` **必须**带 `sign` / `timestamp`。`sign = base64(md5(appId + appSecret + timestamp))`,`appSecret` 通过 `--app-secret` `TOPSDK_APP_SECRET` 传入,仅用于签名;工具不会从本地 `meetsdk-remote-config.json` 读取密钥再签名。
150
151
 
151
152
  ```bash
152
153
  # 仅拉配置
153
154
  meetgames fetch-config --project-root . \
154
- --app-id "$SDK_APP_ID" --channel-type GOOGLE --env test
155
+ --app-id "$SDK_APP_ID" --app-secret "$SDK_APP_SECRET" --channel-type GOOGLE --env test
155
156
 
156
157
  # 拉配置 + 集成(推荐流水线)
157
158
  meetgames setup --project-root . \
158
- --app-id "$SDK_APP_ID" --channel-type GOOGLE --env test
159
+ --app-id "$SDK_APP_ID" --app-secret "$SDK_APP_SECRET" --channel-type GOOGLE --env test
159
160
 
160
161
  # 仅集成(已有 meetsdk-remote-config.json)
161
162
  meetgames integrate --project-root . --dry-run
@@ -199,18 +200,18 @@ meetgames integrate --project-root . --dry-run
199
200
  "guest": {},
200
201
  "email": {},
201
202
  "facebook": {
202
- "facebookAppId": "0000000000000000",
203
- "fbLoginProtocolScheme": "fb0000000000000000",
204
- "facebookClientToken": "mockfacebookclienttoken00000000"
203
+ "clientId": "0000000000000000",
204
+ "scheme": "fb0000000000000000",
205
+ "secret": "mockfacebookclienttoken00000000"
205
206
  },
206
- "google": { "googleClientId": "mock-client-id.apps.googleusercontent.com" }
207
+ "google": { "clientId": "mock-client-id.apps.googleusercontent.com" }
207
208
  },
208
209
  "payment": { "googleIap": {} },
209
210
  "analytics": {
210
- "appsflyer": { "enableDebugLog": true, "devKey": "MOCK_APPSFLYER_DEV_KEY" },
211
+ "appsflyer": { "devKey": "MOCK_APPSFLYER_DEV_KEY" },
211
212
  "facebookdata": {},
212
- "firebase": { "firebase_file_url": "https://example.invalid/firebase/google-services.json" },
213
- "adjust": { "appId": "MOCK_ADJUST_APP_ID" }
213
+ "firebase": { "firebaseUrl": "https://example.invalid/firebase/google-services.json" },
214
+ "adjust": { "appCode": "MOCK_ADJUST_APP_ID" }
214
215
  }
215
216
  }
216
217
  }
@@ -241,15 +242,15 @@ meetgames integrate --project-root . --dry-run
241
242
  | 项 | 内容 |
242
243
  |----|------|
243
244
  | **新接口** | `GET {baseUrl}console/openSDK/downloadSDKConfig` |
244
- | **Query** | `appId`、`channelType`(商店渠道,如 `GOOGLE` / `APPLE`) |
245
- | **鉴权** | 无需 sign |
245
+ | **Query** | `appId`、`channelType`(商店渠道,如 `GOOGLE` / `APPLE`)、`sign`、`timestamp` |
246
+ | **鉴权** | 必须带 `sign + timestamp`,签名算法与 `getSDKConfig` 一致 |
246
247
  | **响应** | 响应体即为 `meetsdk-remote-config.json` 全文(附件下载) |
247
248
  | **CLI** | `meetgames fetch-config` / `meetgames setup` |
248
249
 
249
250
  示例:
250
251
 
251
252
  ```http
252
- GET https://{api-sdk-gameplus-host}/console/openSDK/downloadSDKConfig?appId=791251136341225472&channelType=GOOGLE
253
+ GET https://{api-sdk-gameplus-host}/console/openSDK/downloadSDKConfig?appId=791251136341225472&channelType=GOOGLE&sign=...&timestamp=...
253
254
  ```
254
255
 
255
256
  响应 JSON **必须**含根级 `channel`(与 `channelType` 查询值一致)、`devicePlatform`,且 **`topsdk` 内不含 `channel`**。
@@ -304,7 +305,7 @@ GET https://{api-sdk-gameplus-host}/console/openSDK/downloadSDKConfig?appId=7912
304
305
  |------|------|
305
306
  | 接口调用诊断 | 对约定 API 列表输出静态检查报告 |
306
307
  | Snapchat Manifest placeholders | 与 PRD 示例一致的 `addManifestPlaceholders` |
307
- | 三方配置文件原文件下载 | OSS 直链写入 JSON(如 `firebase_file_url`) |
308
+ | 三方配置文件原文件下载 | OSS 直链写入 JSON(如 `firebaseUrl`) |
308
309
  | 接入客服 | 可回答 FAQ 范围内接入问题 |
309
310
 
310
311
  ---
@@ -0,0 +1,5 @@
1
+ [2026-06-22T07:50:37.206Z] [INFO] AAB: /Users/work/Workspace/Projects/meet-sdk-tool/test-assets/aab-converter/sample.aab
2
+ [2026-06-22T07:50:37.206Z] [INFO] Output directory: /private/tmp/aab2apk-out
3
+ [2026-06-22T07:50:37.206Z] [INFO] APK set: /private/tmp/aab2apk-out/sample.apks
4
+ [2026-06-22T07:50:37.206Z] [INFO] Output APK: /private/tmp/aab2apk-out/sample_universal.apk
5
+ [2026-06-22T07:50:37.207Z] [INFO] Running bundletool: java -jar /Users/work/Workspace/Projects/meet-sdk-tool/tools/bundletool.jar build-apks --bundle=/Users/work/Workspace/Projects/meet-sdk-tool/test-assets/aab-converter/sample.aab --output=/private/tmp/aab2apk-out/sample.apks --mode=universal --ks=/Users/work/Workspace/Projects/meet-sdk-tool/test-assets/aab-converter/dragonstone.jks --ks-key-alias=dragonstone --ks-pass=pass:****** --key-pass=pass:****** --overwrite
@@ -0,0 +1,6 @@
1
+ [2026-06-22T07:52:26.222Z] [INFO] AAB: /Users/work/Workspace/Projects/meet-sdk-tool/test-assets/aab-converter/sample.aab
2
+ [2026-06-22T07:52:26.222Z] [INFO] Output directory: /private/tmp/aab2apk-out
3
+ [2026-06-22T07:52:26.222Z] [INFO] APK set: /private/tmp/aab2apk-out/sample.apks
4
+ [2026-06-22T07:52:26.222Z] [INFO] Output APK: /private/tmp/aab2apk-out/sample_universal.apk
5
+ [2026-06-22T07:52:26.222Z] [INFO] Running bundletool: java -jar /Users/work/Workspace/Projects/meet-sdk-tool/tools/bundletool.jar build-apks --bundle=/Users/work/Workspace/Projects/meet-sdk-tool/test-assets/aab-converter/sample.aab --output=/private/tmp/aab2apk-out/sample.apks --mode=universal --ks=/Users/work/Workspace/Projects/meet-sdk-tool/test-assets/aab-converter/dragonstone.jks --ks-key-alias=dragonstone --ks-pass=pass:****** --key-pass=pass:****** --overwrite
6
+ [2026-06-22T07:52:58.806Z] [INFO] Extracted universal APK: /private/tmp/aab2apk-out/sample_universal.apk
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "topsdk": {
3
- "version": "1.6.1.4",
3
+ "version": "1.6.1.3",
4
+ "date": "2026-05-25",
4
5
  "groupId": "com.sino.topsdk",
5
6
  "repositories": [
6
7
  "https://storage-sdk-gameplus.meetsocial.com/repository/TopSdk/"
@@ -0,0 +1,15 @@
1
+ {
2
+ "topsdk": {
3
+ "version": "1.6.0.5",
4
+ "date": "2026-06-09",
5
+ "packageType": "native",
6
+ "plugins": [
7
+ "guest",
8
+ "facebook",
9
+ "google",
10
+ "apple",
11
+ "apple_pay",
12
+ "dataAppsFlyer"
13
+ ]
14
+ }
15
+ }
package/package.json CHANGED
@@ -1,50 +1,25 @@
1
1
  {
2
2
  "name": "@leonxin/meetgames",
3
- "version": "0.1.7",
4
- "description": "Meet SDK integration CLI: fetch remote config, patch Android Gradle/Manifest and iOS Xcode projects (dry-run, patch output, MCP)",
5
- "type": "module",
3
+ "version": "0.1.11",
6
4
  "license": "MIT",
7
- "author": "MeetSocial",
8
- "keywords": [
9
- "meetgames",
10
- "meet-sdk",
11
- "topsdk",
12
- "android",
13
- "ios",
14
- "gradle",
15
- "xcode",
16
- "sdk-integration"
17
- ],
18
- "publishConfig": {
19
- "access": "public"
20
- },
5
+ "type": "module",
21
6
  "bin": {
22
- "meetgames": "./dist/entry.js",
23
- "meetgames-mcp": "./dist/mcp-entry.js"
7
+ "meetgames": "dist/entry.js",
8
+ "meetgames-mcp": "dist/mcp-entry.js",
9
+ "meetgames-aab": "dist/aab-converter/aab-entry.js"
24
10
  },
25
- "files": [
26
- "dist",
27
- "schema",
28
- "bundled/android",
29
- "docs",
30
- "recipes",
31
- "fixtures",
32
- "meetsdk-android.json"
33
- ],
34
11
  "scripts": {
35
12
  "build": "tsc -p tsconfig.json",
13
+ "build:aab": "tsc -p tsconfig.json",
14
+ "package:aab-win-zip": "node scripts/package-aab-cli-win.mjs",
36
15
  "test": "vitest run",
37
- "test:watch": "vitest",
38
- "prepack": "npm run build",
39
- "preview:integrate:android-sample": "npm run build -s && node dist/entry.js integrate --project-root test-projects/android-sample --dry-run --verbose --patch-file test-projects/_preview/cli-preview.patch",
40
- "mcp:stdio": "npm run build -s && node dist/mcp-entry.js"
16
+ "test:mcp": "npm run build && npm run test:mcp:run",
17
+ "test:mcp:run": "vitest run --config vitest.mcp.config.ts",
18
+ "test:regression": "npm run build && npm test && npm run test:mcp:run"
41
19
  },
42
20
  "engines": {
43
21
  "node": ">=18"
44
22
  },
45
- "overrides": {
46
- "uuid": "^14.0.0"
47
- },
48
23
  "dependencies": {
49
24
  "@modelcontextprotocol/sdk": "^1.29.0",
50
25
  "ajv": "^8.17.1",
@@ -0,0 +1,193 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import zlib from "node:zlib";
4
+
5
+ const root = process.cwd();
6
+ const productName = "MeetSDKTool-AABConverter-win-x64";
7
+ const releaseDir = path.join(root, "release");
8
+ const stagingDir = path.join(releaseDir, productName);
9
+ const zipPath = path.join(releaseDir, `${productName}.zip`);
10
+
11
+ function fail(message) {
12
+ console.error(`[package:aab-win-zip] ERROR: ${message}`);
13
+ process.exit(1);
14
+ }
15
+
16
+ function assertExists(p, label) {
17
+ if (!fs.existsSync(p)) fail(`${label} not found: ${p}`);
18
+ }
19
+
20
+ function copyDir(src, dest) {
21
+ assertExists(src, "Source directory");
22
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
23
+ fs.cpSync(src, dest, { recursive: true });
24
+ }
25
+
26
+ function copyFileIfExists(src, dest) {
27
+ if (!fs.existsSync(src)) return;
28
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
29
+ fs.copyFileSync(src, dest);
30
+ }
31
+
32
+ function copyNodeRuntime(src, dest) {
33
+ assertExists(path.join(src, "node.exe"), "Windows node.exe");
34
+ fs.mkdirSync(dest, { recursive: true });
35
+ fs.copyFileSync(path.join(src, "node.exe"), path.join(dest, "node.exe"));
36
+ copyFileIfExists(path.join(src, "LICENSE"), path.join(dest, "LICENSE"));
37
+ copyFileIfExists(path.join(src, "README.md"), path.join(dest, "README.md"));
38
+ }
39
+
40
+ function writeCmd() {
41
+ const cmd = [
42
+ "@echo off",
43
+ "setlocal",
44
+ 'set "TOOL_DIR=%~dp0"',
45
+ '"%TOOL_DIR%runtime\\node\\node.exe" "%TOOL_DIR%app\\aab-converter\\aab-entry.js" %*',
46
+ "exit /b %ERRORLEVEL%",
47
+ "",
48
+ ].join("\r\n");
49
+ fs.writeFileSync(path.join(stagingDir, "aab2apk.cmd"), cmd, "utf8");
50
+ }
51
+
52
+ function crc32(buf) {
53
+ let crc = 0xffffffff;
54
+ for (const b of buf) {
55
+ crc ^= b;
56
+ for (let i = 0; i < 8; i += 1) {
57
+ crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
58
+ }
59
+ }
60
+ return (crc ^ 0xffffffff) >>> 0;
61
+ }
62
+
63
+ function u16(value) {
64
+ const buf = Buffer.alloc(2);
65
+ buf.writeUInt16LE(value);
66
+ return buf;
67
+ }
68
+
69
+ function u32(value) {
70
+ const buf = Buffer.alloc(4);
71
+ buf.writeUInt32LE(value);
72
+ return buf;
73
+ }
74
+
75
+ function listEntries(baseDir) {
76
+ const out = [];
77
+ function walk(abs, rel) {
78
+ const stat = fs.statSync(abs);
79
+ if (stat.isDirectory()) {
80
+ if (rel) out.push({ rel: `${rel}/`, dir: true, content: Buffer.alloc(0) });
81
+ for (const name of fs.readdirSync(abs).sort()) {
82
+ if (name === ".DS_Store" || name === "__MACOSX") continue;
83
+ walk(path.join(abs, name), rel ? `${rel}/${name}` : name);
84
+ }
85
+ return;
86
+ }
87
+ if (stat.isFile()) {
88
+ out.push({ rel, dir: false, content: fs.readFileSync(abs) });
89
+ }
90
+ }
91
+ walk(baseDir, productName);
92
+ return out;
93
+ }
94
+
95
+ function writeZip(baseDir, outPath) {
96
+ const entries = listEntries(baseDir);
97
+ const localParts = [];
98
+ const centralParts = [];
99
+ let offset = 0;
100
+ for (const entry of entries) {
101
+ const name = Buffer.from(entry.rel, "utf8");
102
+ const compressed = entry.dir ? Buffer.alloc(0) : zlib.deflateRawSync(entry.content);
103
+ const method = entry.dir ? 0 : 8;
104
+ const crc = entry.dir ? 0 : crc32(entry.content);
105
+ const local = Buffer.concat([
106
+ u32(0x04034b50),
107
+ u16(20),
108
+ u16(0),
109
+ u16(method),
110
+ u16(0),
111
+ u16(0),
112
+ u32(crc),
113
+ u32(compressed.length),
114
+ u32(entry.content.length),
115
+ u16(name.length),
116
+ u16(0),
117
+ name,
118
+ compressed,
119
+ ]);
120
+ localParts.push(local);
121
+ centralParts.push(
122
+ Buffer.concat([
123
+ u32(0x02014b50),
124
+ u16(20),
125
+ u16(20),
126
+ u16(0),
127
+ u16(method),
128
+ u16(0),
129
+ u16(0),
130
+ u32(crc),
131
+ u32(compressed.length),
132
+ u32(entry.content.length),
133
+ u16(name.length),
134
+ u16(0),
135
+ u16(0),
136
+ u16(0),
137
+ u16(0),
138
+ u32(entry.dir ? 0x10 : 0),
139
+ u32(offset),
140
+ name,
141
+ ])
142
+ );
143
+ offset += local.length;
144
+ }
145
+ const centralDir = Buffer.concat(centralParts);
146
+ const eocd = Buffer.concat([
147
+ u32(0x06054b50),
148
+ u16(0),
149
+ u16(0),
150
+ u16(entries.length),
151
+ u16(entries.length),
152
+ u32(centralDir.length),
153
+ u32(offset),
154
+ u16(0),
155
+ ]);
156
+ fs.writeFileSync(outPath, Buffer.concat([...localParts, centralDir, eocd]));
157
+ }
158
+
159
+ assertExists(path.join(root, "dist", "aab-converter", "aab-entry.js"), "Compiled AAB CLI entry");
160
+ assertExists(path.join(root, "dist", "shared"), "Compiled shared modules");
161
+ assertExists(path.join(root, "tools", "bundletool.jar"), "bundletool.jar");
162
+ assertExists(path.join(root, "runtime", "win-x64", "node", "node.exe"), "Windows node.exe");
163
+ assertExists(path.join(root, "runtime", "win-x64", "jre", "bin", "java.exe"), "Windows JRE java.exe");
164
+
165
+ fs.rmSync(stagingDir, { recursive: true, force: true });
166
+ fs.rmSync(zipPath, { force: true });
167
+ fs.mkdirSync(stagingDir, { recursive: true });
168
+
169
+ copyDir(path.join(root, "dist", "aab-converter"), path.join(stagingDir, "app", "aab-converter"));
170
+ copyDir(path.join(root, "dist", "shared"), path.join(stagingDir, "app", "shared"));
171
+ copyNodeRuntime(path.join(root, "runtime", "win-x64", "node"), path.join(stagingDir, "runtime", "node"));
172
+ copyDir(path.join(root, "runtime", "win-x64", "jre"), path.join(stagingDir, "runtime", "jre"));
173
+ fs.mkdirSync(path.join(stagingDir, "tools"), { recursive: true });
174
+ fs.copyFileSync(path.join(root, "tools", "bundletool.jar"), path.join(stagingDir, "tools", "bundletool.jar"));
175
+ fs.mkdirSync(path.join(stagingDir, "logs"), { recursive: true });
176
+ fs.mkdirSync(path.join(stagingDir, "output"), { recursive: true });
177
+ writeCmd();
178
+ fs.writeFileSync(
179
+ path.join(stagingDir, "README.txt"),
180
+ [
181
+ "Meet SDK AAB Converter",
182
+ "",
183
+ "Example:",
184
+ " set KS_PASS=your_keystore_password",
185
+ " set KEY_PASS=your_key_password",
186
+ " aab2apk.cmd convert --aab D:\\packages\\game.aab --out-dir D:\\output --ks D:\\keys\\release.jks --ks-key-alias release --ks-pass-env KS_PASS --key-pass-env KEY_PASS",
187
+ "",
188
+ ].join("\r\n"),
189
+ "utf8"
190
+ );
191
+
192
+ writeZip(stagingDir, zipPath);
193
+ console.log(`[package:aab-win-zip] wrote ${zipPath}`);
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import { UserFacingError, errorMessage } from "../shared/errors.js";
4
+ import { createFileLogger } from "../shared/logger.js";
5
+ import { convertAabToApk } from "./convertAabToApk.js";
6
+ import { parseAabCliArgv, printAabHelp, validateConvertArgs } from "./cliArgs.js";
7
+ import { resolveBundletoolPath, resolveJavaPath, resolveToolRoot } from "./resourcePaths.js";
8
+
9
+ const EXIT = {
10
+ OK: 0,
11
+ INVALID_ARGS: 2,
12
+ CONVERT_FAILED: 3,
13
+ } as const;
14
+
15
+ export async function main(): Promise<void> {
16
+ const parsed = parseAabCliArgv(process.argv);
17
+ if (parsed.help) {
18
+ printAabHelp();
19
+ process.exit(EXIT.OK);
20
+ }
21
+
22
+ const toolRoot = resolveToolRoot(import.meta.url);
23
+ const convertArgs = validateConvertArgs(parsed);
24
+ const logger = createFileLogger(path.join(toolRoot, "logs"));
25
+ try {
26
+ const result = await convertAabToApk({
27
+ ...convertArgs,
28
+ javaPath: resolveJavaPath(toolRoot),
29
+ bundletoolPath: resolveBundletoolPath(toolRoot),
30
+ logger,
31
+ });
32
+ console.log(`[aab2apk] APK: ${result.apkPath}`);
33
+ if (result.apksPath) console.log(`[aab2apk] APKS: ${result.apksPath}`);
34
+ if (result.logPath) console.log(`[aab2apk] Log: ${result.logPath}`);
35
+ } finally {
36
+ logger.close();
37
+ }
38
+ }
39
+
40
+ main().catch((err: unknown) => {
41
+ if (err instanceof UserFacingError) {
42
+ console.error(`[aab2apk] ERROR (${err.code}): ${err.message}`);
43
+ if (err.details) console.error(err.details);
44
+ process.exit(err.code === "INVALID_ARGS" || err.code === "INVALID_COMMAND" ? EXIT.INVALID_ARGS : EXIT.CONVERT_FAILED);
45
+ }
46
+ console.error(`[aab2apk] ERROR: ${errorMessage(err)}`);
47
+ process.exit(EXIT.CONVERT_FAILED);
48
+ });
@@ -0,0 +1,119 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import zlib from "node:zlib";
4
+ import { UserFacingError } from "../shared/errors.js";
5
+ import { assertCanWriteFile } from "../shared/fileUtils.js";
6
+
7
+ interface ZipEntry {
8
+ name: string;
9
+ method: number;
10
+ compressedSize: number;
11
+ localHeaderOffset: number;
12
+ }
13
+
14
+ function readAt(fd: number, position: number, length: number): Buffer {
15
+ const buf = Buffer.alloc(length);
16
+ const bytesRead = fs.readSync(fd, buf, 0, length, position);
17
+ return bytesRead === length ? buf : buf.subarray(0, bytesRead);
18
+ }
19
+
20
+ function findEndOfCentralDirectory(buf: Buffer): number {
21
+ const signature = 0x06054b50;
22
+ for (let i = buf.length - 22; i >= 0; i -= 1) {
23
+ if (buf.readUInt32LE(i) === signature) return i;
24
+ }
25
+ throw new UserFacingError("APKS_INVALID", "Invalid .apks file: end of central directory not found");
26
+ }
27
+
28
+ function readZipEntries(apksPath: string): ZipEntry[] {
29
+ const fd = fs.openSync(apksPath, "r");
30
+ try {
31
+ const fileSize = fs.fstatSync(fd).size;
32
+ const tailSize = Math.min(fileSize, 0xffff + 22);
33
+ const tailStart = fileSize - tailSize;
34
+ const tail = readAt(fd, tailStart, tailSize);
35
+ const eocd = findEndOfCentralDirectory(tail);
36
+ const totalEntries = tail.readUInt16LE(eocd + 10);
37
+ const centralDirSize = tail.readUInt32LE(eocd + 12);
38
+ const centralDirOffset = tail.readUInt32LE(eocd + 16);
39
+ if (centralDirSize === 0xffffffff || centralDirOffset === 0xffffffff || totalEntries === 0xffff) {
40
+ throw new UserFacingError("APKS_ZIP64_UNSUPPORTED", "ZIP64 .apks files are not supported yet");
41
+ }
42
+ const buf = readAt(fd, centralDirOffset, centralDirSize);
43
+ return parseCentralDirectory(buf, totalEntries);
44
+ } finally {
45
+ fs.closeSync(fd);
46
+ }
47
+ }
48
+
49
+ function parseCentralDirectory(buf: Buffer, totalEntries: number): ZipEntry[] {
50
+ const entries: ZipEntry[] = [];
51
+ let pos = 0;
52
+ for (let i = 0; i < totalEntries; i += 1) {
53
+ if (buf.readUInt32LE(pos) !== 0x02014b50) {
54
+ throw new UserFacingError("APKS_INVALID", "Invalid .apks file: central directory is corrupt");
55
+ }
56
+ const method = buf.readUInt16LE(pos + 10);
57
+ const compressedSize = buf.readUInt32LE(pos + 20);
58
+ const nameLength = buf.readUInt16LE(pos + 28);
59
+ const extraLength = buf.readUInt16LE(pos + 30);
60
+ const commentLength = buf.readUInt16LE(pos + 32);
61
+ const localHeaderOffset = buf.readUInt32LE(pos + 42);
62
+ const name = buf.subarray(pos + 46, pos + 46 + nameLength).toString("utf8");
63
+ entries.push({ name, method, compressedSize, localHeaderOffset });
64
+ pos += 46 + nameLength + extraLength + commentLength;
65
+ }
66
+ return entries;
67
+ }
68
+
69
+ async function extractEntryToFile(apksPath: string, entry: ZipEntry, outputApkPath: string): Promise<void> {
70
+ const fd = fs.openSync(apksPath, "r");
71
+ let dataStart: number;
72
+ try {
73
+ const localHeader = readAt(fd, entry.localHeaderOffset, 30);
74
+ if (localHeader.readUInt32LE(0) !== 0x04034b50) {
75
+ throw new UserFacingError("APKS_INVALID", `Invalid .apks file: local header not found for ${entry.name}`);
76
+ }
77
+ const nameLength = localHeader.readUInt16LE(26);
78
+ const extraLength = localHeader.readUInt16LE(28);
79
+ dataStart = entry.localHeaderOffset + 30 + nameLength + extraLength;
80
+ } finally {
81
+ fs.closeSync(fd);
82
+ }
83
+
84
+ await new Promise<void>((resolve, reject) => {
85
+ const input = fs.createReadStream(apksPath, {
86
+ start: dataStart,
87
+ end: dataStart + entry.compressedSize - 1,
88
+ });
89
+ const output = fs.createWriteStream(outputApkPath);
90
+ const source =
91
+ entry.method === 0
92
+ ? input
93
+ : entry.method === 8
94
+ ? input.pipe(zlib.createInflateRaw())
95
+ : null;
96
+ if (!source) {
97
+ input.destroy();
98
+ output.destroy();
99
+ reject(new UserFacingError("APKS_UNSUPPORTED_ZIP_METHOD", `Unsupported zip compression method ${entry.method}`));
100
+ return;
101
+ }
102
+ input.on("error", reject);
103
+ source.on("error", reject);
104
+ output.on("error", reject);
105
+ output.on("finish", resolve);
106
+ source.pipe(output);
107
+ });
108
+ }
109
+
110
+ export async function extractUniversalApk(apksPath: string, outputApkPath: string, overwrite: boolean): Promise<void> {
111
+ assertCanWriteFile(outputApkPath, overwrite, "Output APK");
112
+ const entries = readZipEntries(apksPath);
113
+ const universal = entries.find((entry) => entry.name === "universal.apk" || entry.name.endsWith("/universal.apk"));
114
+ if (!universal) {
115
+ throw new UserFacingError("UNIVERSAL_APK_NOT_FOUND", "universal.apk not found in .apks output");
116
+ }
117
+ fs.mkdirSync(path.dirname(outputApkPath), { recursive: true });
118
+ await extractEntryToFile(apksPath, universal, outputApkPath);
119
+ }
@@ -0,0 +1,63 @@
1
+ import path from "node:path";
2
+ import { UserFacingError } from "../shared/errors.js";
3
+ import { runProcess } from "../shared/processRunner.js";
4
+ import type { Logger } from "../shared/logger.js";
5
+ import type { SigningOptions } from "./types.js";
6
+
7
+ export interface BuildApksOptions {
8
+ javaPath: string;
9
+ bundletoolPath: string;
10
+ aabPath: string;
11
+ apksPath: string;
12
+ signing: SigningOptions;
13
+ overwrite: boolean;
14
+ logger?: Logger;
15
+ }
16
+
17
+ export function buildBundletoolArgs(options: Omit<BuildApksOptions, "javaPath" | "logger">): string[] {
18
+ const args = [
19
+ "-jar",
20
+ options.bundletoolPath,
21
+ "build-apks",
22
+ `--bundle=${options.aabPath}`,
23
+ `--output=${options.apksPath}`,
24
+ "--mode=universal",
25
+ `--ks=${options.signing.keyStorePath}`,
26
+ `--ks-key-alias=${options.signing.keyAlias}`,
27
+ `--ks-pass=pass:${options.signing.keyStorePassword}`,
28
+ ];
29
+ if (options.signing.keyPassword) {
30
+ args.push(`--key-pass=pass:${options.signing.keyPassword}`);
31
+ }
32
+ if (options.overwrite) {
33
+ args.push("--overwrite");
34
+ }
35
+ return args;
36
+ }
37
+
38
+ function redactArg(arg: string): string {
39
+ if (arg.startsWith("--ks-pass=pass:")) return "--ks-pass=pass:******";
40
+ if (arg.startsWith("--key-pass=pass:")) return "--key-pass=pass:******";
41
+ return arg;
42
+ }
43
+
44
+ export function redactedBundletoolCommand(javaPath: string, args: string[]): string {
45
+ return [javaPath, ...args.map(redactArg)].map((part) => (part.includes(" ") ? `"${part}"` : part)).join(" ");
46
+ }
47
+
48
+ export async function buildUniversalApks(options: BuildApksOptions): Promise<void> {
49
+ const args = buildBundletoolArgs(options);
50
+ options.logger?.info(`Running bundletool: ${redactedBundletoolCommand(options.javaPath, args)}`);
51
+ const result = await runProcess(options.javaPath, args, {
52
+ cwd: path.dirname(options.apksPath),
53
+ onStdout: (chunk) => options.logger?.info(chunk.trimEnd()),
54
+ onStderr: (chunk) => options.logger?.warn(chunk.trimEnd()),
55
+ });
56
+ if (result.exitCode !== 0) {
57
+ throw new UserFacingError(
58
+ "BUNDLETOOL_FAILED",
59
+ "bundletool failed to build APK set",
60
+ (result.stderr || result.stdout).trim()
61
+ );
62
+ }
63
+ }