@leonxin/meetgames 0.1.8 → 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.
- package/.agents/skills/meet-sdk-regression/SKILL.md +93 -0
- package/.cursor/mcp.example.json +16 -0
- package/.cursor/mcp.json +11 -0
- package/.cursor/skills/meetgames-mcp/SKILL.md +48 -0
- package/.vite/vitest/results.json +1 -0
- package/README.md +31 -8
- package/dist/aab-converter/aab-entry.d.ts +3 -0
- package/dist/aab-converter/aab-entry.d.ts.map +1 -0
- package/dist/aab-converter/aab-entry.js +49 -0
- package/dist/aab-converter/aab-entry.js.map +1 -0
- package/dist/aab-converter/apksExtractor.d.ts +2 -0
- package/dist/aab-converter/apksExtractor.d.ts.map +1 -0
- package/dist/aab-converter/apksExtractor.js +108 -0
- package/dist/aab-converter/apksExtractor.js.map +1 -0
- package/dist/aab-converter/bundletoolRunner.d.ts +15 -0
- package/dist/aab-converter/bundletoolRunner.d.ts.map +1 -0
- package/dist/aab-converter/bundletoolRunner.js +46 -0
- package/dist/aab-converter/bundletoolRunner.js.map +1 -0
- package/dist/aab-converter/cliArgs.d.ts +27 -0
- package/dist/aab-converter/cliArgs.d.ts.map +1 -0
- package/dist/aab-converter/cliArgs.js +170 -0
- package/dist/aab-converter/cliArgs.js.map +1 -0
- package/dist/aab-converter/convertAabToApk.d.ts +7 -0
- package/dist/aab-converter/convertAabToApk.d.ts.map +1 -0
- package/dist/aab-converter/convertAabToApk.js +69 -0
- package/dist/aab-converter/convertAabToApk.js.map +1 -0
- package/dist/aab-converter/resourcePaths.d.ts +4 -0
- package/dist/aab-converter/resourcePaths.d.ts.map +1 -0
- package/dist/aab-converter/resourcePaths.js +42 -0
- package/dist/aab-converter/resourcePaths.js.map +1 -0
- package/dist/aab-converter/signingOptions.d.ts +9 -0
- package/dist/aab-converter/signingOptions.d.ts.map +1 -0
- package/dist/aab-converter/signingOptions.js +21 -0
- package/dist/aab-converter/signingOptions.js.map +1 -0
- package/dist/aab-converter/types.d.ts +24 -0
- package/dist/aab-converter/types.d.ts.map +1 -0
- package/dist/aab-converter/types.js +2 -0
- package/dist/aab-converter/types.js.map +1 -0
- package/dist/android/adapter.d.ts.map +1 -1
- package/dist/android/adapter.js +2 -2
- package/dist/android/adapter.js.map +1 -1
- package/dist/android/detect.d.ts +2 -2
- package/dist/android/detect.d.ts.map +1 -1
- package/dist/android/detect.js +36 -8
- package/dist/android/detect.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +157 -31
- package/dist/cli.js.map +1 -1
- package/dist/config/meetSdkDefaultConfig.d.ts +19 -2
- package/dist/config/meetSdkDefaultConfig.d.ts.map +1 -1
- package/dist/config/meetSdkDefaultConfig.js +67 -5
- package/dist/config/meetSdkDefaultConfig.js.map +1 -1
- package/dist/config/meetSdkIosConfig.d.ts +21 -0
- package/dist/config/meetSdkIosConfig.d.ts.map +1 -0
- package/dist/config/meetSdkIosConfig.js +66 -0
- package/dist/config/meetSdkIosConfig.js.map +1 -0
- package/dist/config/meetSdkRemoteConfig.d.ts +18 -1
- package/dist/config/meetSdkRemoteConfig.d.ts.map +1 -1
- package/dist/config/meetSdkRemoteConfig.js +61 -25
- package/dist/config/meetSdkRemoteConfig.js.map +1 -1
- package/dist/contracts/types.d.ts +19 -6
- package/dist/contracts/types.d.ts.map +1 -1
- package/dist/core/doctor.d.ts +17 -0
- package/dist/core/doctor.d.ts.map +1 -0
- package/dist/core/doctor.js +444 -0
- package/dist/core/doctor.js.map +1 -0
- package/dist/core/pipeline.d.ts.map +1 -1
- package/dist/core/pipeline.js +0 -15
- package/dist/core/pipeline.js.map +1 -1
- package/dist/core/platform.d.ts +12 -0
- package/dist/core/platform.d.ts.map +1 -0
- package/dist/core/platform.js +40 -0
- package/dist/core/platform.js.map +1 -0
- package/dist/core/reporter.js +1 -1
- package/dist/core/reporter.js.map +1 -1
- package/dist/core/workspace.d.ts +2 -2
- package/dist/core/workspace.d.ts.map +1 -1
- package/dist/core/workspace.js +4 -5
- package/dist/core/workspace.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/ios/channelConfig.d.ts +1 -0
- package/dist/ios/channelConfig.d.ts.map +1 -1
- package/dist/ios/channelConfig.js +82 -0
- package/dist/ios/channelConfig.js.map +1 -1
- package/dist/ios/codeUtils.d.ts +1 -0
- package/dist/ios/codeUtils.d.ts.map +1 -1
- package/dist/ios/codeUtils.js +11 -2
- package/dist/ios/codeUtils.js.map +1 -1
- package/dist/ios/detect.d.ts +2 -2
- package/dist/ios/detect.d.ts.map +1 -1
- package/dist/ios/detect.js +49 -10
- package/dist/ios/detect.js.map +1 -1
- package/dist/ios/entitlements.d.ts +4 -0
- package/dist/ios/entitlements.d.ts.map +1 -0
- package/dist/ios/entitlements.js +53 -0
- package/dist/ios/entitlements.js.map +1 -0
- package/dist/ios/fileManager.d.ts.map +1 -1
- package/dist/ios/fileManager.js +3 -2
- package/dist/ios/fileManager.js.map +1 -1
- package/dist/ios/infoPlist.d.ts +1 -1
- package/dist/ios/infoPlist.d.ts.map +1 -1
- package/dist/ios/infoPlist.js.map +1 -1
- package/dist/ios/integrate.d.ts.map +1 -1
- package/dist/ios/integrate.js +211 -36
- package/dist/ios/integrate.js.map +1 -1
- package/dist/ios/pbxprojEditor.d.ts +2 -0
- package/dist/ios/pbxprojEditor.d.ts.map +1 -1
- package/dist/ios/pbxprojEditor.js +179 -1
- package/dist/ios/pbxprojEditor.js.map +1 -1
- package/dist/ios/pluginConfig.d.ts +1 -0
- package/dist/ios/pluginConfig.d.ts.map +1 -1
- package/dist/ios/pluginConfig.js +36 -4
- package/dist/ios/pluginConfig.js.map +1 -1
- package/dist/ios/sdkBundle.d.ts +1 -1
- package/dist/ios/sdkBundle.d.ts.map +1 -1
- package/dist/ios/sdkBundle.js +7 -5
- package/dist/ios/sdkBundle.js.map +1 -1
- package/dist/ios/template.d.ts +1 -0
- package/dist/ios/template.d.ts.map +1 -1
- package/dist/ios/template.js +14 -1
- package/dist/ios/template.js.map +1 -1
- package/dist/ios/types.d.ts +2 -2
- package/dist/ios/types.d.ts.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +14 -13
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/service.d.ts +8 -6
- package/dist/mcp/service.d.ts.map +1 -1
- package/dist/mcp/service.js +34 -14
- package/dist/mcp/service.js.map +1 -1
- package/dist/ops/handlers.d.ts.map +1 -1
- package/dist/ops/handlers.js +10 -4
- package/dist/ops/handlers.js.map +1 -1
- package/dist/remote/sdkHomeDownload.d.ts +65 -0
- package/dist/remote/sdkHomeDownload.d.ts.map +1 -0
- package/dist/remote/sdkHomeDownload.js +208 -0
- package/dist/remote/sdkHomeDownload.js.map +1 -0
- package/dist/remote/topsdkDownloadSdkConfig.d.ts.map +1 -1
- package/dist/remote/topsdkDownloadSdkConfig.js +11 -1
- package/dist/remote/topsdkDownloadSdkConfig.js.map +1 -1
- package/dist/shared/errors.d.ts +7 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/errors.js +16 -0
- package/dist/shared/errors.js.map +1 -0
- package/dist/shared/fileUtils.d.ts +5 -0
- package/dist/shared/fileUtils.d.ts.map +1 -0
- package/dist/shared/fileUtils.js +35 -0
- package/dist/shared/fileUtils.js.map +1 -0
- package/dist/shared/logger.d.ts +10 -0
- package/dist/shared/logger.d.ts.map +1 -0
- package/dist/shared/logger.js +37 -0
- package/dist/shared/logger.js.map +1 -0
- package/dist/shared/pathUtils.d.ts +4 -0
- package/dist/shared/pathUtils.d.ts.map +1 -0
- package/dist/shared/pathUtils.js +22 -0
- package/dist/shared/pathUtils.js.map +1 -0
- package/dist/shared/processRunner.d.ts +12 -0
- package/dist/shared/processRunner.d.ts.map +1 -0
- package/dist/shared/processRunner.js +31 -0
- package/dist/shared/processRunner.js.map +1 -0
- package/docs/AAB_CONVERTER_CLI_PLAN.md +392 -0
- package/docs/API.md +246 -32
- package/docs/CLI.md +292 -0
- package/docs/INTEGRATION.md +116 -0
- package/docs/MCP.md +86 -0
- package/docs/README.md +19 -10
- package/docs/{api → archive/api}/downloadSDKConfig.md +8 -6
- package/docs/{api → archive/api}/getChannelConfig-meetgames.md +1 -1
- package/docs/archive/ios-migration.md +2139 -0
- 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
- package/docs/{ → archive/product/}/351/234/200/346/261/202/346/226/207/346/241/243.md +15 -14
- package/logs/convert-20260622-155037.log +5 -0
- package/logs/convert-20260622-155226.log +6 -0
- package/meetsdk-android.json +2 -1
- package/meetsdk-ios.json +15 -0
- package/package.json +10 -36
- package/scripts/package-aab-cli-win.mjs +193 -0
- package/src/aab-converter/aab-entry.ts +48 -0
- package/src/aab-converter/apksExtractor.ts +119 -0
- package/src/aab-converter/bundletoolRunner.ts +63 -0
- package/src/aab-converter/cliArgs.ts +194 -0
- package/src/aab-converter/convertAabToApk.ts +81 -0
- package/src/aab-converter/resourcePaths.ts +43 -0
- package/src/aab-converter/signingOptions.ts +29 -0
- package/src/aab-converter/types.ts +26 -0
- package/src/android/adapter.ts +9 -0
- package/src/android/assembleIntegrationJson.ts +33 -0
- package/src/android/detect.ts +132 -0
- package/src/android/downloadGoogleServicesJson.ts +56 -0
- package/src/android/gradle.ts +116 -0
- package/src/android/manifest.ts +50 -0
- package/src/android/meetSdkRemoteGradle.ts +837 -0
- package/src/cli.ts +488 -0
- package/src/config/fetchConfigWrite.ts +30 -0
- package/src/config/loadAndroidIntegration.ts +41 -0
- package/src/config/loadManifest.ts +40 -0
- package/src/config/meetSdkDefaultConfig.ts +99 -0
- package/src/config/meetSdkIosConfig.ts +87 -0
- package/src/config/meetSdkRemoteConfig.ts +1211 -0
- package/src/config/topsdkFeatureModules.ts +92 -0
- package/src/contracts/types.ts +121 -0
- package/src/core/doctor.ts +485 -0
- package/src/core/patch.ts +64 -0
- package/src/core/pipeline.ts +107 -0
- package/src/core/platform.ts +47 -0
- package/src/core/previewPatches.ts +23 -0
- package/src/core/reporter.ts +24 -0
- package/src/core/workspace.ts +29 -0
- package/src/entry.ts +7 -0
- package/src/index.ts +133 -0
- package/src/ios/channelConfig.ts +128 -0
- package/src/ios/codeUtils.ts +160 -0
- package/src/ios/detect.ts +105 -0
- package/src/ios/entitlements.ts +61 -0
- package/src/ios/fileManager.ts +48 -0
- package/src/ios/infoPlist.ts +55 -0
- package/src/ios/integrate.ts +516 -0
- package/src/ios/pbxprojEditor.ts +383 -0
- package/src/ios/pluginConfig.ts +97 -0
- package/src/ios/reserved.ts +8 -0
- package/src/ios/sdkBundle.ts +36 -0
- package/src/ios/template.ts +36 -0
- package/src/ios/types.ts +65 -0
- package/src/mcp/server.ts +170 -0
- package/src/mcp/service.ts +222 -0
- package/src/mcp-entry.ts +7 -0
- package/src/ops/fileStore.ts +56 -0
- package/src/ops/handlers.ts +304 -0
- package/src/remote/fetchJson.ts +22 -0
- package/src/remote/sdkHomeDownload.ts +274 -0
- package/src/remote/topsdkDownloadSdkConfig.ts +93 -0
- package/src/remote/topsdkGetSdkConfig.ts +122 -0
- package/src/remote/topsdkSign.ts +10 -0
- package/src/shared/errors.ts +16 -0
- package/src/shared/fileUtils.ts +41 -0
- package/src/shared/logger.ts +49 -0
- package/src/shared/pathUtils.ts +24 -0
- package/src/shared/processRunner.ts +43 -0
- package/test-projects/README.md +51 -0
- package/test-projects/_preview/pipeline.patch +281 -0
- package/tests/aab-converter.test.ts +213 -0
- package/tests/assemble.test.ts +12 -0
- package/tests/doctor.test.ts +89 -0
- package/tests/downloadGoogleServicesJson.test.ts +47 -0
- package/tests/fetch-remote.test.ts +23 -0
- package/tests/fetchConfigOverrides.test.ts +28 -0
- package/tests/fetchConfigWrite.test.ts +54 -0
- package/tests/gradle.test.ts +33 -0
- package/tests/integration-json.test.ts +29 -0
- package/tests/ios.codeUtils.test.ts +23 -0
- package/tests/ios.sdkBundle.test.ts +16 -0
- package/tests/loadManifest.test.ts +15 -0
- package/tests/manifest-xml.test.ts +30 -0
- package/tests/mcp.e2e.ts +217 -0
- package/tests/mcp.service.test.ts +53 -0
- package/tests/meetSdkRemoteConfig.test.ts +456 -0
- package/tests/meetSdkRemoteGradle.test.ts +414 -0
- package/tests/pipeline.android.test.ts +96 -0
- package/tests/pipeline.integration-json.test.ts +58 -0
- package/tests/pipeline.ios.test.ts +385 -0
- package/tests/pipeline.preview.patch.test.ts +85 -0
- package/tests/platformSelection.test.ts +77 -0
- package/tests/sdkHomeDownload.test.ts +124 -0
- package/tests/sdkVersionConfig.test.ts +130 -0
- package/tests/test-projects-hosts.test.ts +78 -0
- package/tests/topsdk.test.ts +53 -0
- package/tests/topsdkDownloadSdkConfig.test.ts +81 -0
- package/tests/topsdkFeatureModules.test.ts +116 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +9 -0
- package/vitest.mcp.config.ts +11 -0
- package/bundled/android/sample.txt +0 -1
- package/docs/ANDROID.md +0 -133
- package/docs/CURSOR-MCP-SETUP.md +0 -72
- package/docs/MCP-SKILL.md +0 -63
- package/fixtures/api-samples/getChannelConfig-meetgames.sample.json +0 -123
- package/fixtures/meetsdk-remote-config.download-shape.json +0 -20
- package/fixtures/meetsdk-remote-config.mock.json +0 -69
- package/fixtures/recipes/android-default.fixture.yaml +0 -15
- package/fixtures/recipes/android-integration.fixture.json +0 -29
- package/fixtures/topsdk-config-reference.json +0 -39
- /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` |
|
|
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
|
-
- `
|
|
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` |
|
|
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(如 `
|
|
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-
|
|
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`
|
|
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
|
-
"
|
|
203
|
-
"
|
|
204
|
-
"
|
|
203
|
+
"clientId": "0000000000000000",
|
|
204
|
+
"scheme": "fb0000000000000000",
|
|
205
|
+
"secret": "mockfacebookclienttoken00000000"
|
|
205
206
|
},
|
|
206
|
-
"google": { "
|
|
207
|
+
"google": { "clientId": "mock-client-id.apps.googleusercontent.com" }
|
|
207
208
|
},
|
|
208
209
|
"payment": { "googleIap": {} },
|
|
209
210
|
"analytics": {
|
|
210
|
-
"appsflyer": { "
|
|
211
|
+
"appsflyer": { "devKey": "MOCK_APPSFLYER_DEV_KEY" },
|
|
211
212
|
"facebookdata": {},
|
|
212
|
-
"firebase": { "
|
|
213
|
-
"adjust": { "
|
|
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
|
-
| **鉴权** |
|
|
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=...×tamp=...
|
|
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(如 `
|
|
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
|
package/meetsdk-android.json
CHANGED
package/meetsdk-ios.json
ADDED
package/package.json
CHANGED
|
@@ -1,51 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leonxin/meetgames",
|
|
3
|
-
"version": "0.1.
|
|
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
|
-
"
|
|
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": "
|
|
23
|
-
"meetgames-mcp": "
|
|
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:
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"preview:integrate:power-raid": "npm run build -s && node dist/entry.js integrate --project-root test-projects/power-raid --dry-run --verbose --patch-file test-projects/_preview/power-raid-preview.patch",
|
|
41
|
-
"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"
|
|
42
19
|
},
|
|
43
20
|
"engines": {
|
|
44
21
|
"node": ">=18"
|
|
45
22
|
},
|
|
46
|
-
"overrides": {
|
|
47
|
-
"uuid": "^14.0.0"
|
|
48
|
-
},
|
|
49
23
|
"dependencies": {
|
|
50
24
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
51
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
|
+
}
|