@leonxin/meetgames 0.1.8 → 0.1.12
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 +36 -13
- package/{meetsdk-android.json → config/meetsdk-android.json} +2 -1
- package/config/meetsdk-ios.json +15 -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/cache.d.ts +44 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +101 -0
- package/dist/cache.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +181 -49
- 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 +69 -6
- 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 +68 -0
- package/dist/config/meetSdkIosConfig.js.map +1 -0
- package/dist/config/meetSdkRemoteConfig.d.ts +19 -1
- package/dist/config/meetSdkRemoteConfig.d.ts.map +1 -1
- package/dist/config/meetSdkRemoteConfig.js +64 -25
- package/dist/config/meetSdkRemoteConfig.js.map +1 -1
- package/dist/contracts/types.d.ts +27 -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/previewPatches.d.ts +1 -1
- package/dist/core/previewPatches.js +2 -2
- package/dist/core/previewPatches.js.map +1 -1
- 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 +6 -5
- package/dist/core/workspace.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -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 +214 -39
- 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 +250 -6
- 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 -6
- package/dist/ios/sdkBundle.d.ts.map +1 -1
- package/dist/ios/sdkBundle.js +47 -17
- 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 +22 -15
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/service.d.ts +11 -6
- package/dist/mcp/service.d.ts.map +1 -1
- package/dist/mcp/service.js +61 -18
- package/dist/mcp/service.js.map +1 -1
- package/dist/ops/handlers.d.ts.map +1 -1
- package/dist/ops/handlers.js +34 -23
- 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 +229 -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/docs/API.md +246 -32
- package/docs/CLI.md +291 -0
- package/docs/INTEGRATION.md +116 -0
- package/docs/MCP.md +86 -0
- package/docs/README.md +18 -10
- package/docs/{api → archive/api}/downloadSDKConfig.md +9 -7
- package/docs/{api → archive/api}/getChannelConfig-meetgames.md +2 -2
- 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 +16 -15
- package/package.json +7 -36
- package/recipes/android-default.yaml +0 -5
- package/recipes/integrate-default.yaml +0 -5
- 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/cache.ts +164 -0
- package/src/cli.ts +496 -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 +100 -0
- package/src/config/meetSdkIosConfig.ts +89 -0
- package/src/config/meetSdkRemoteConfig.ts +1215 -0
- package/src/config/topsdkFeatureModules.ts +92 -0
- package/src/contracts/types.ts +129 -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 +31 -0
- package/src/entry.ts +7 -0
- package/src/index.ts +140 -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 +517 -0
- package/src/ios/pbxprojEditor.ts +450 -0
- package/src/ios/pluginConfig.ts +97 -0
- package/src/ios/reserved.ts +8 -0
- package/src/ios/sdkBundle.ts +59 -0
- package/src/ios/template.ts +36 -0
- package/src/ios/types.ts +65 -0
- package/src/mcp/server.ts +176 -0
- package/src/mcp/service.ts +253 -0
- package/src/mcp-entry.ts +7 -0
- package/src/ops/fileStore.ts +56 -0
- package/src/ops/handlers.ts +308 -0
- package/src/remote/fetchJson.ts +22 -0
- package/src/remote/sdkHomeDownload.ts +295 -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/tests/assemble.test.ts +12 -0
- package/tests/doctor.test.ts +91 -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/fixtures-hosts.test.ts +78 -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 +21 -0
- package/tests/loadManifest.test.ts +15 -0
- package/tests/manifest-xml.test.ts +30 -0
- package/tests/mcp.e2e.ts +214 -0
- package/tests/mcp.service.test.ts +53 -0
- package/tests/meetSdkRemoteConfig.test.ts +481 -0
- package/tests/meetSdkRemoteGradle.test.ts +414 -0
- package/tests/pipeline.android.test.ts +95 -0
- package/tests/pipeline.integration-json.test.ts +58 -0
- package/tests/pipeline.ios.test.ts +392 -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 +131 -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 不在支持矩阵内。
|
|
@@ -77,7 +77,7 @@ addManifestPlaceholders([
|
|
|
77
77
|
#### 新方式
|
|
78
78
|
|
|
79
79
|
- 远程 JSON 决定**接入哪些可选模块**(guest、email、facebook、google-iap、appsflyer 等)。
|
|
80
|
-
- 工具内置 [`meetsdk-android.json`](
|
|
80
|
+
- 工具内置 [`config/meetsdk-android.json`](../../config/meetsdk-android.json) 提供**固定** SDK 版本、仓库 URL、`classpath`、Firebase plugin、各模块 `dependencies`。
|
|
81
81
|
- `integrate` 合并后写入根工程与 App Module 的 Gradle(标记块 upsert,避免重复插入)。
|
|
82
82
|
|
|
83
83
|
**示例(由工具生成,非人工维护)**
|
|
@@ -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
|
---
|
package/package.json
CHANGED
|
@@ -1,51 +1,22 @@
|
|
|
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.12",
|
|
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"
|
|
24
9
|
},
|
|
25
|
-
"files": [
|
|
26
|
-
"dist",
|
|
27
|
-
"schema",
|
|
28
|
-
"bundled/android",
|
|
29
|
-
"docs",
|
|
30
|
-
"recipes",
|
|
31
|
-
"fixtures",
|
|
32
|
-
"meetsdk-android.json"
|
|
33
|
-
],
|
|
34
10
|
"scripts": {
|
|
35
11
|
"build": "tsc -p tsconfig.json",
|
|
36
12
|
"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"
|
|
13
|
+
"test:mcp": "npm run build && npm run test:mcp:run",
|
|
14
|
+
"test:mcp:run": "vitest run --config vitest.mcp.config.ts",
|
|
15
|
+
"test:regression": "npm run build && npm test && npm run test:mcp:run"
|
|
42
16
|
},
|
|
43
17
|
"engines": {
|
|
44
18
|
"node": ">=18"
|
|
45
19
|
},
|
|
46
|
-
"overrides": {
|
|
47
|
-
"uuid": "^14.0.0"
|
|
48
|
-
},
|
|
49
20
|
"dependencies": {
|
|
50
21
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
51
22
|
"ajv": "^8.17.1",
|
|
@@ -6,11 +6,6 @@ steps:
|
|
|
6
6
|
- op: files.downloadGoogleServicesJson
|
|
7
7
|
platform: android
|
|
8
8
|
args: {}
|
|
9
|
-
- op: files.copyBundled
|
|
10
|
-
platform: android
|
|
11
|
-
args:
|
|
12
|
-
from: android/sample.txt
|
|
13
|
-
to: vendor/meet-integrate/sample.txt
|
|
14
9
|
- op: ios.integrateTopSdk
|
|
15
10
|
platform: ios
|
|
16
11
|
args: {}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AndroidDetectOptions, AndroidDetectResult, AndroidPlatformAdapter } from "../contracts/types.js";
|
|
2
|
+
import { detectAndroid } from "./detect.js";
|
|
3
|
+
|
|
4
|
+
export const androidPlatformAdapter: AndroidPlatformAdapter = {
|
|
5
|
+
id: "android",
|
|
6
|
+
detect(projectRoot: string, options?: AndroidDetectOptions): AndroidDetectResult {
|
|
7
|
+
return detectAndroid(projectRoot, options);
|
|
8
|
+
},
|
|
9
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AndroidIntegrationDocument } from "../contracts/types.js";
|
|
2
|
+
|
|
3
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
4
|
+
return typeof v === "object" && v !== null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Normalizes common API envelopes into the integration document shape.
|
|
9
|
+
* Supported roots: top-level `{ version, steps }`, `{ data: ... }`, `{ config: ... }`, `{ android: ... }` (when it contains steps).
|
|
10
|
+
*/
|
|
11
|
+
export function unwrapRemoteIntegrationPayload(body: unknown): unknown {
|
|
12
|
+
if (!isRecord(body)) return body;
|
|
13
|
+
if (Array.isArray(body.steps)) return body;
|
|
14
|
+
if (isRecord(body.data) && Array.isArray(body.data.steps)) return body.data;
|
|
15
|
+
if (isRecord(body.config) && Array.isArray(body.config.steps)) return body.config;
|
|
16
|
+
if (isRecord(body.android) && Array.isArray(body.android.steps)) return body.android;
|
|
17
|
+
return body;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function attachIntegrationMeta(
|
|
21
|
+
doc: AndroidIntegrationDocument,
|
|
22
|
+
sourceUrl: string,
|
|
23
|
+
fetchedAt: string
|
|
24
|
+
): AndroidIntegrationDocument {
|
|
25
|
+
return {
|
|
26
|
+
...doc,
|
|
27
|
+
meta: {
|
|
28
|
+
...(doc.meta ?? {}),
|
|
29
|
+
sourceUrl,
|
|
30
|
+
fetchedAt,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { AndroidDetectOptions, AndroidDetectResult } from "../contracts/types.js";
|
|
4
|
+
|
|
5
|
+
function parseIncludes(settingsContent: string): string[] {
|
|
6
|
+
const includes: string[] = [];
|
|
7
|
+
const includeRegex = /include\s+([^\n]+)/g;
|
|
8
|
+
let match = includeRegex.exec(settingsContent);
|
|
9
|
+
while (match) {
|
|
10
|
+
const raw = match[1];
|
|
11
|
+
const parts = raw
|
|
12
|
+
.split(",")
|
|
13
|
+
.map((x) => x.trim())
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.map((x) => x.replace(/^['"]|['"]$/g, ""));
|
|
16
|
+
includes.push(...parts);
|
|
17
|
+
match = includeRegex.exec(settingsContent);
|
|
18
|
+
}
|
|
19
|
+
return includes;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isApplicationModule(buildGradleContent: string): boolean {
|
|
23
|
+
const applyPlugin = /apply\s+plugin:\s*['"]com\.android\.application['"]/m.test(buildGradleContent);
|
|
24
|
+
const pluginsBlock = /id\s*\(?\s*['"]com\.android\.application['"]\s*\)?/m.test(buildGradleContent);
|
|
25
|
+
const pluginAlias = /\balias\s*\(\s*libs\.plugins\.[A-Za-z0-9_.-]*application[A-Za-z0-9_.-]*\s*\)/m.test(buildGradleContent);
|
|
26
|
+
const hasAndroidBlock = /\bandroid\s*\{/m.test(buildGradleContent);
|
|
27
|
+
const hasApplicationId = /\bapplicationId\s+(?:=+\s*)?["'][^"']+["']/m.test(buildGradleContent);
|
|
28
|
+
return applyPlugin || pluginsBlock || pluginAlias || (hasAndroidBlock && hasApplicationId);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function moduleNameToDir(projectRoot: string, moduleName: string): string {
|
|
32
|
+
const normalized = moduleName.startsWith(":") ? moduleName.slice(1) : moduleName;
|
|
33
|
+
const relative = normalized.replace(/:/g, "/");
|
|
34
|
+
return path.join(projectRoot, relative);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function defaultManifestPath(moduleDir: string): string {
|
|
38
|
+
return path.join(moduleDir, "src", "main", "AndroidManifest.xml");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeModuleSelector(value: string): string {
|
|
42
|
+
const trimmed = value.trim().replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
43
|
+
if (!trimmed) return "";
|
|
44
|
+
const asGradlePath = trimmed.replace(/\//g, ":");
|
|
45
|
+
return asGradlePath.startsWith(":") ? asGradlePath : `:${asGradlePath}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function applicationModuleList(modules: { moduleName: string }[]): string {
|
|
49
|
+
return modules.map((x) => x.moduleName).join(", ");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function successResult(projectRoot: string, moduleName: string, moduleDir: string): AndroidDetectResult {
|
|
53
|
+
const manifestPath = defaultManifestPath(moduleDir);
|
|
54
|
+
if (!fs.existsSync(manifestPath)) {
|
|
55
|
+
return { ok: false, error: `AndroidManifest.xml not found at ${manifestPath}` };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { ok: true, moduleName, moduleDir, manifestPath };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function detectAndroid(projectRoot: string, options: AndroidDetectOptions = {}): AndroidDetectResult {
|
|
62
|
+
const settingsGradle = path.join(projectRoot, "settings.gradle");
|
|
63
|
+
const settingsGradleKts = path.join(projectRoot, "settings.gradle.kts");
|
|
64
|
+
let settingsContent = "";
|
|
65
|
+
if (fs.existsSync(settingsGradle)) {
|
|
66
|
+
settingsContent = fs.readFileSync(settingsGradle, "utf8");
|
|
67
|
+
} else if (fs.existsSync(settingsGradleKts)) {
|
|
68
|
+
settingsContent = fs.readFileSync(settingsGradleKts, "utf8");
|
|
69
|
+
} else {
|
|
70
|
+
return { ok: false, error: "settings.gradle / settings.gradle.kts not found in project root" };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const modules = parseIncludes(settingsContent);
|
|
74
|
+
if (!modules.length) {
|
|
75
|
+
return { ok: false, error: "no modules found in settings.gradle" };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const applicationModules: { moduleName: string; moduleDir: string }[] = [];
|
|
79
|
+
for (const moduleName of modules) {
|
|
80
|
+
const moduleDir = moduleNameToDir(projectRoot, moduleName);
|
|
81
|
+
const candidates = [path.join(moduleDir, "build.gradle"), path.join(moduleDir, "build.gradle.kts")];
|
|
82
|
+
const moduleGradle = candidates.find((p) => fs.existsSync(p));
|
|
83
|
+
if (!moduleGradle) continue;
|
|
84
|
+
const content = fs.readFileSync(moduleGradle, "utf8");
|
|
85
|
+
if (isApplicationModule(content)) {
|
|
86
|
+
applicationModules.push({ moduleName, moduleDir });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const requestedModule = options.appTarget ? normalizeModuleSelector(options.appTarget) : "";
|
|
91
|
+
if (applicationModules.length === 0) {
|
|
92
|
+
return { ok: false, error: "NO_APPLICATION_MODULE_FOUND" };
|
|
93
|
+
}
|
|
94
|
+
if (requestedModule) {
|
|
95
|
+
const selected = applicationModules.find((x) => normalizeModuleSelector(x.moduleName) === requestedModule);
|
|
96
|
+
if (!selected) {
|
|
97
|
+
const list = applicationModuleList(applicationModules);
|
|
98
|
+
return {
|
|
99
|
+
ok: false,
|
|
100
|
+
error: `ANDROID_APPLICATION_MODULE_NOT_FOUND: ${requestedModule}; available application modules: ${list}`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return successResult(projectRoot, selected.moduleName, selected.moduleDir);
|
|
104
|
+
}
|
|
105
|
+
if (applicationModules.length > 1) {
|
|
106
|
+
const list = applicationModuleList(applicationModules);
|
|
107
|
+
return {
|
|
108
|
+
ok: false,
|
|
109
|
+
error: `MULTIPLE_APPLICATION_MODULES_FOUND: ${list}; pass --app-target <module> to select one`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const { moduleName, moduleDir } = applicationModules[0];
|
|
114
|
+
return successResult(projectRoot, moduleName, moduleDir);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Relative path from project root to the detected application module directory (e.g. `app`, `launcher`). */
|
|
118
|
+
export function applicationModuleRelPath(
|
|
119
|
+
projectRoot: string,
|
|
120
|
+
android: Extract<AndroidDetectResult, { ok: true }>
|
|
121
|
+
): string {
|
|
122
|
+
return path.relative(projectRoot, android.moduleDir).split(path.sep).join("/");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Default `google-services.json` location: `{applicationModule}/google-services.json`. */
|
|
126
|
+
export function defaultGoogleServicesJsonRelPath(
|
|
127
|
+
projectRoot: string,
|
|
128
|
+
android: Extract<AndroidDetectResult, { ok: true }>
|
|
129
|
+
): string {
|
|
130
|
+
const moduleRel = applicationModuleRelPath(projectRoot, android);
|
|
131
|
+
return moduleRel ? `${moduleRel}/google-services.json` : "google-services.json";
|
|
132
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { MeetSdkAnalyticsFirebaseCredentials } from "../config/meetSdkRemoteConfig.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolve target file under the detected Android application module:
|
|
7
|
+
* `{projectRoot}/{applicationModuleRel}/{fileName}`.
|
|
8
|
+
* `fileName` comes from remote `firebase_file_name` (DB `firebase_name`, upload original name + suffix).
|
|
9
|
+
* Relative path is not configured in MG; meet-sdk-tool detects the app module from `projectRoot`.
|
|
10
|
+
*/
|
|
11
|
+
export function resolveGoogleServicesJsonTarget(
|
|
12
|
+
projectRoot: string,
|
|
13
|
+
firebase: MeetSdkAnalyticsFirebaseCredentials,
|
|
14
|
+
applicationModuleRelPath?: string
|
|
15
|
+
): string {
|
|
16
|
+
const fileName = firebase.firebase_file_name?.trim() || "google-services.json";
|
|
17
|
+
const moduleRel = applicationModuleRelPath?.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
18
|
+
if (moduleRel) {
|
|
19
|
+
return path.join(projectRoot, moduleRel, fileName);
|
|
20
|
+
}
|
|
21
|
+
return path.join(projectRoot, fileName);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function downloadGoogleServicesJson(
|
|
25
|
+
projectRoot: string,
|
|
26
|
+
firebase: MeetSdkAnalyticsFirebaseCredentials,
|
|
27
|
+
options?: { applicationModuleRelPath?: string; dryRun?: boolean }
|
|
28
|
+
): Promise<{ ok: true; relPath: string; absPath: string } | { ok: false; error: string }> {
|
|
29
|
+
const url = firebase.firebase_file_url?.trim();
|
|
30
|
+
if (!url) {
|
|
31
|
+
return { ok: false, error: "firebase_file_url is empty" };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const absPath = resolveGoogleServicesJsonTarget(projectRoot, firebase, options?.applicationModuleRelPath);
|
|
35
|
+
const relPath = path.relative(projectRoot, absPath).split(path.sep).join("/");
|
|
36
|
+
|
|
37
|
+
if (options?.dryRun) {
|
|
38
|
+
return { ok: true, relPath, absPath };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let res: Response;
|
|
42
|
+
try {
|
|
43
|
+
res = await fetch(url);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
46
|
+
return { ok: false, error: `fetch failed: ${msg}` };
|
|
47
|
+
}
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
return { ok: false, error: `HTTP ${res.status} ${res.statusText}` };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
53
|
+
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
|
54
|
+
fs.writeFileSync(absPath, buf);
|
|
55
|
+
return { ok: true, relPath, absPath };
|
|
56
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export const START_REPO_MARKER = "// >>> MEET_INTEGRATE REPOS START";
|
|
2
|
+
export const END_REPO_MARKER = "// >>> MEET_INTEGRATE REPOS END";
|
|
3
|
+
export const START_BLOCK_MARKER = "// >>> MEET_INTEGRATE BLOCK START";
|
|
4
|
+
export const END_BLOCK_MARKER = "// >>> MEET_INTEGRATE BLOCK END";
|
|
5
|
+
|
|
6
|
+
export interface BlockRange {
|
|
7
|
+
start: number;
|
|
8
|
+
openBrace: number;
|
|
9
|
+
end: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function findBlockRange(content: string, keyword: string, searchFrom = 0): BlockRange | null {
|
|
13
|
+
// Require `keyword {` so `com.android.application` / `androidx` are not matched.
|
|
14
|
+
const re = new RegExp(`\\b${keyword}\\s*\\{`);
|
|
15
|
+
const slice = content.slice(searchFrom);
|
|
16
|
+
const match = re.exec(slice);
|
|
17
|
+
if (!match || match.index === undefined) return null;
|
|
18
|
+
const keywordIndex = searchFrom + match.index;
|
|
19
|
+
const openBrace = content.indexOf("{", keywordIndex + match[0].length - 1);
|
|
20
|
+
if (openBrace < 0) return null;
|
|
21
|
+
let depth = 0;
|
|
22
|
+
for (let i = openBrace; i < content.length; i += 1) {
|
|
23
|
+
const ch = content[i];
|
|
24
|
+
if (ch === "{") depth += 1;
|
|
25
|
+
if (ch === "}") {
|
|
26
|
+
depth -= 1;
|
|
27
|
+
if (depth === 0) return { start: keywordIndex, openBrace, end: i };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function replaceOrInsertManaged(
|
|
34
|
+
content: string,
|
|
35
|
+
blockRange: BlockRange,
|
|
36
|
+
startMarker: string,
|
|
37
|
+
endMarker: string,
|
|
38
|
+
snippet: string
|
|
39
|
+
): string {
|
|
40
|
+
const blockText = content.slice(blockRange.openBrace + 1, blockRange.end);
|
|
41
|
+
const startInBlock = blockText.indexOf(startMarker);
|
|
42
|
+
const endInBlock = blockText.indexOf(endMarker);
|
|
43
|
+
const insertion = `${snippet}\n`;
|
|
44
|
+
|
|
45
|
+
if (startInBlock >= 0 && endInBlock > startInBlock) {
|
|
46
|
+
const before = blockText.slice(0, startInBlock).replace(/\s*$/, "\n");
|
|
47
|
+
const after = blockText.slice(endInBlock + endMarker.length).replace(/^\s*/, "\n");
|
|
48
|
+
const replaced = `${before}${snippet}${after}`;
|
|
49
|
+
return content.slice(0, blockRange.openBrace + 1) + replaced + content.slice(blockRange.end);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const prefix = content.slice(0, blockRange.end);
|
|
53
|
+
return prefix + insertion.trimEnd() + "\n" + content.slice(blockRange.end);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildRepoSnippet(urls: string[]): string {
|
|
57
|
+
const lines = [START_REPO_MARKER];
|
|
58
|
+
for (const url of urls) {
|
|
59
|
+
if (url === "google") lines.push(" google()");
|
|
60
|
+
else if (url === "mavenCentral") lines.push(" mavenCentral()");
|
|
61
|
+
else lines.push(` maven { url '${url}' }`);
|
|
62
|
+
}
|
|
63
|
+
lines.push(END_REPO_MARKER);
|
|
64
|
+
return lines.join("\n");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function updateRootBuildGradleRepositories(
|
|
68
|
+
content: string,
|
|
69
|
+
urls: string[]
|
|
70
|
+
): { ok: true; content: string; changed: boolean; warnings: string[] } | { ok: false; error: string } {
|
|
71
|
+
const warnings: string[] = [];
|
|
72
|
+
const allprojects = findBlockRange(content, "allprojects");
|
|
73
|
+
if (!allprojects) {
|
|
74
|
+
return { ok: false, error: "allprojects block not found in root build.gradle" };
|
|
75
|
+
}
|
|
76
|
+
const repositoriesBlock = findBlockRange(content, "repositories", allprojects.start);
|
|
77
|
+
if (!repositoriesBlock || repositoriesBlock.end > allprojects.end) {
|
|
78
|
+
return { ok: false, error: "repositories block under allprojects not found" };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const snippet = buildRepoSnippet(urls || []);
|
|
82
|
+
const updated = replaceOrInsertManaged(
|
|
83
|
+
content,
|
|
84
|
+
repositoriesBlock,
|
|
85
|
+
START_REPO_MARKER,
|
|
86
|
+
END_REPO_MARKER,
|
|
87
|
+
snippet
|
|
88
|
+
);
|
|
89
|
+
if (updated === content) warnings.push("root build.gradle repositories unchanged");
|
|
90
|
+
return { ok: true, content: updated, changed: updated !== content, warnings };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function buildDependenciesSnippet(lines: string[]): string {
|
|
94
|
+
return [START_BLOCK_MARKER, ...lines, END_BLOCK_MARKER].join("\n");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function updateModuleBuildGradleDependencies(
|
|
98
|
+
content: string,
|
|
99
|
+
dependencyLines: string[]
|
|
100
|
+
): { ok: true; content: string; changed: boolean; warnings: string[] } | { ok: false; error: string } {
|
|
101
|
+
const warnings: string[] = [];
|
|
102
|
+
const dependenciesBlock = findBlockRange(content, "dependencies");
|
|
103
|
+
if (!dependenciesBlock) {
|
|
104
|
+
return { ok: false, error: "dependencies block not found in module build.gradle" };
|
|
105
|
+
}
|
|
106
|
+
const snippet = buildDependenciesSnippet(dependencyLines);
|
|
107
|
+
const updated = replaceOrInsertManaged(
|
|
108
|
+
content,
|
|
109
|
+
dependenciesBlock,
|
|
110
|
+
START_BLOCK_MARKER,
|
|
111
|
+
END_BLOCK_MARKER,
|
|
112
|
+
snippet
|
|
113
|
+
);
|
|
114
|
+
if (updated === content) warnings.push("module build.gradle dependencies unchanged");
|
|
115
|
+
return { ok: true, content: updated, changed: updated !== content, warnings };
|
|
116
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export const MANIFEST_PERM_START = "<!-- MEET_INTEGRATE PERMISSIONS START -->";
|
|
2
|
+
export const MANIFEST_PERM_END = "<!-- MEET_INTEGRATE PERMISSIONS END -->";
|
|
3
|
+
|
|
4
|
+
const PERMISSION_NAME_RE = /android:name="([^"]+)"/;
|
|
5
|
+
|
|
6
|
+
export function extractPermissionName(line: string): string | null {
|
|
7
|
+
if (!line.includes("uses-permission")) return null;
|
|
8
|
+
const m = line.match(PERMISSION_NAME_RE);
|
|
9
|
+
return m?.[1] ?? null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Remove <uses-permission> lines for the same android:name (inside or outside managed block). */
|
|
13
|
+
export function removePermissionLinesByNames(content: string, names: ReadonlySet<string>): string {
|
|
14
|
+
return content
|
|
15
|
+
.split("\n")
|
|
16
|
+
.filter((line) => {
|
|
17
|
+
const name = extractPermissionName(line);
|
|
18
|
+
return !name || !names.has(name);
|
|
19
|
+
})
|
|
20
|
+
.join("\n");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function stripPermissionManagedBlock(content: string): string {
|
|
24
|
+
const start = content.indexOf(MANIFEST_PERM_START);
|
|
25
|
+
const end = content.indexOf(MANIFEST_PERM_END);
|
|
26
|
+
if (start >= 0 && end > start) {
|
|
27
|
+
return content.slice(0, start) + content.slice(end + MANIFEST_PERM_END.length);
|
|
28
|
+
}
|
|
29
|
+
return content;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function insertPermissions(content: string, permissions: string[]): { content: string; changed: boolean } {
|
|
33
|
+
const names = new Set(permissions);
|
|
34
|
+
let cleaned = removePermissionLinesByNames(content, names);
|
|
35
|
+
cleaned = stripPermissionManagedBlock(cleaned);
|
|
36
|
+
|
|
37
|
+
const lines = [
|
|
38
|
+
MANIFEST_PERM_START,
|
|
39
|
+
...permissions.map((p) => ` <uses-permission android:name="${p}" />`),
|
|
40
|
+
MANIFEST_PERM_END,
|
|
41
|
+
].join("\n");
|
|
42
|
+
|
|
43
|
+
const closeManifest = cleaned.lastIndexOf("</manifest>");
|
|
44
|
+
if (closeManifest < 0) {
|
|
45
|
+
return { content, changed: false };
|
|
46
|
+
}
|
|
47
|
+
const insert = `\n${lines}\n`;
|
|
48
|
+
const updated = cleaned.slice(0, closeManifest) + insert + cleaned.slice(closeManifest);
|
|
49
|
+
return { content: updated, changed: updated !== content };
|
|
50
|
+
}
|