@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.
Files changed (285) 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/cli.d.ts.map +1 -1
  47. package/dist/cli.js +157 -31
  48. package/dist/cli.js.map +1 -1
  49. package/dist/config/meetSdkDefaultConfig.d.ts +19 -2
  50. package/dist/config/meetSdkDefaultConfig.d.ts.map +1 -1
  51. package/dist/config/meetSdkDefaultConfig.js +67 -5
  52. package/dist/config/meetSdkDefaultConfig.js.map +1 -1
  53. package/dist/config/meetSdkIosConfig.d.ts +21 -0
  54. package/dist/config/meetSdkIosConfig.d.ts.map +1 -0
  55. package/dist/config/meetSdkIosConfig.js +66 -0
  56. package/dist/config/meetSdkIosConfig.js.map +1 -0
  57. package/dist/config/meetSdkRemoteConfig.d.ts +18 -1
  58. package/dist/config/meetSdkRemoteConfig.d.ts.map +1 -1
  59. package/dist/config/meetSdkRemoteConfig.js +61 -25
  60. package/dist/config/meetSdkRemoteConfig.js.map +1 -1
  61. package/dist/contracts/types.d.ts +19 -6
  62. package/dist/contracts/types.d.ts.map +1 -1
  63. package/dist/core/doctor.d.ts +17 -0
  64. package/dist/core/doctor.d.ts.map +1 -0
  65. package/dist/core/doctor.js +444 -0
  66. package/dist/core/doctor.js.map +1 -0
  67. package/dist/core/pipeline.d.ts.map +1 -1
  68. package/dist/core/pipeline.js +0 -15
  69. package/dist/core/pipeline.js.map +1 -1
  70. package/dist/core/platform.d.ts +12 -0
  71. package/dist/core/platform.d.ts.map +1 -0
  72. package/dist/core/platform.js +40 -0
  73. package/dist/core/platform.js.map +1 -0
  74. package/dist/core/reporter.js +1 -1
  75. package/dist/core/reporter.js.map +1 -1
  76. package/dist/core/workspace.d.ts +2 -2
  77. package/dist/core/workspace.d.ts.map +1 -1
  78. package/dist/core/workspace.js +4 -5
  79. package/dist/core/workspace.js.map +1 -1
  80. package/dist/index.d.ts +3 -1
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.js +3 -1
  83. package/dist/index.js.map +1 -1
  84. package/dist/ios/channelConfig.d.ts +1 -0
  85. package/dist/ios/channelConfig.d.ts.map +1 -1
  86. package/dist/ios/channelConfig.js +82 -0
  87. package/dist/ios/channelConfig.js.map +1 -1
  88. package/dist/ios/codeUtils.d.ts +1 -0
  89. package/dist/ios/codeUtils.d.ts.map +1 -1
  90. package/dist/ios/codeUtils.js +11 -2
  91. package/dist/ios/codeUtils.js.map +1 -1
  92. package/dist/ios/detect.d.ts +2 -2
  93. package/dist/ios/detect.d.ts.map +1 -1
  94. package/dist/ios/detect.js +49 -10
  95. package/dist/ios/detect.js.map +1 -1
  96. package/dist/ios/entitlements.d.ts +4 -0
  97. package/dist/ios/entitlements.d.ts.map +1 -0
  98. package/dist/ios/entitlements.js +53 -0
  99. package/dist/ios/entitlements.js.map +1 -0
  100. package/dist/ios/fileManager.d.ts.map +1 -1
  101. package/dist/ios/fileManager.js +3 -2
  102. package/dist/ios/fileManager.js.map +1 -1
  103. package/dist/ios/infoPlist.d.ts +1 -1
  104. package/dist/ios/infoPlist.d.ts.map +1 -1
  105. package/dist/ios/infoPlist.js.map +1 -1
  106. package/dist/ios/integrate.d.ts.map +1 -1
  107. package/dist/ios/integrate.js +211 -36
  108. package/dist/ios/integrate.js.map +1 -1
  109. package/dist/ios/pbxprojEditor.d.ts +2 -0
  110. package/dist/ios/pbxprojEditor.d.ts.map +1 -1
  111. package/dist/ios/pbxprojEditor.js +179 -1
  112. package/dist/ios/pbxprojEditor.js.map +1 -1
  113. package/dist/ios/pluginConfig.d.ts +1 -0
  114. package/dist/ios/pluginConfig.d.ts.map +1 -1
  115. package/dist/ios/pluginConfig.js +36 -4
  116. package/dist/ios/pluginConfig.js.map +1 -1
  117. package/dist/ios/sdkBundle.d.ts +1 -1
  118. package/dist/ios/sdkBundle.d.ts.map +1 -1
  119. package/dist/ios/sdkBundle.js +7 -5
  120. package/dist/ios/sdkBundle.js.map +1 -1
  121. package/dist/ios/template.d.ts +1 -0
  122. package/dist/ios/template.d.ts.map +1 -1
  123. package/dist/ios/template.js +14 -1
  124. package/dist/ios/template.js.map +1 -1
  125. package/dist/ios/types.d.ts +2 -2
  126. package/dist/ios/types.d.ts.map +1 -1
  127. package/dist/mcp/server.d.ts.map +1 -1
  128. package/dist/mcp/server.js +14 -13
  129. package/dist/mcp/server.js.map +1 -1
  130. package/dist/mcp/service.d.ts +8 -6
  131. package/dist/mcp/service.d.ts.map +1 -1
  132. package/dist/mcp/service.js +34 -14
  133. package/dist/mcp/service.js.map +1 -1
  134. package/dist/ops/handlers.d.ts.map +1 -1
  135. package/dist/ops/handlers.js +10 -4
  136. package/dist/ops/handlers.js.map +1 -1
  137. package/dist/remote/sdkHomeDownload.d.ts +65 -0
  138. package/dist/remote/sdkHomeDownload.d.ts.map +1 -0
  139. package/dist/remote/sdkHomeDownload.js +208 -0
  140. package/dist/remote/sdkHomeDownload.js.map +1 -0
  141. package/dist/remote/topsdkDownloadSdkConfig.d.ts.map +1 -1
  142. package/dist/remote/topsdkDownloadSdkConfig.js +11 -1
  143. package/dist/remote/topsdkDownloadSdkConfig.js.map +1 -1
  144. package/dist/shared/errors.d.ts +7 -0
  145. package/dist/shared/errors.d.ts.map +1 -0
  146. package/dist/shared/errors.js +16 -0
  147. package/dist/shared/errors.js.map +1 -0
  148. package/dist/shared/fileUtils.d.ts +5 -0
  149. package/dist/shared/fileUtils.d.ts.map +1 -0
  150. package/dist/shared/fileUtils.js +35 -0
  151. package/dist/shared/fileUtils.js.map +1 -0
  152. package/dist/shared/logger.d.ts +10 -0
  153. package/dist/shared/logger.d.ts.map +1 -0
  154. package/dist/shared/logger.js +37 -0
  155. package/dist/shared/logger.js.map +1 -0
  156. package/dist/shared/pathUtils.d.ts +4 -0
  157. package/dist/shared/pathUtils.d.ts.map +1 -0
  158. package/dist/shared/pathUtils.js +22 -0
  159. package/dist/shared/pathUtils.js.map +1 -0
  160. package/dist/shared/processRunner.d.ts +12 -0
  161. package/dist/shared/processRunner.d.ts.map +1 -0
  162. package/dist/shared/processRunner.js +31 -0
  163. package/dist/shared/processRunner.js.map +1 -0
  164. package/docs/AAB_CONVERTER_CLI_PLAN.md +392 -0
  165. package/docs/API.md +246 -32
  166. package/docs/CLI.md +292 -0
  167. package/docs/INTEGRATION.md +116 -0
  168. package/docs/MCP.md +86 -0
  169. package/docs/README.md +19 -10
  170. package/docs/{api → archive/api}/downloadSDKConfig.md +8 -6
  171. package/docs/{api → archive/api}/getChannelConfig-meetgames.md +1 -1
  172. package/docs/archive/ios-migration.md +2139 -0
  173. 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
  174. package/docs/{ → archive/product/}/351/234/200/346/261/202/346/226/207/346/241/243.md +15 -14
  175. package/logs/convert-20260622-155037.log +5 -0
  176. package/logs/convert-20260622-155226.log +6 -0
  177. package/meetsdk-android.json +2 -1
  178. package/meetsdk-ios.json +15 -0
  179. package/package.json +10 -36
  180. package/scripts/package-aab-cli-win.mjs +193 -0
  181. package/src/aab-converter/aab-entry.ts +48 -0
  182. package/src/aab-converter/apksExtractor.ts +119 -0
  183. package/src/aab-converter/bundletoolRunner.ts +63 -0
  184. package/src/aab-converter/cliArgs.ts +194 -0
  185. package/src/aab-converter/convertAabToApk.ts +81 -0
  186. package/src/aab-converter/resourcePaths.ts +43 -0
  187. package/src/aab-converter/signingOptions.ts +29 -0
  188. package/src/aab-converter/types.ts +26 -0
  189. package/src/android/adapter.ts +9 -0
  190. package/src/android/assembleIntegrationJson.ts +33 -0
  191. package/src/android/detect.ts +132 -0
  192. package/src/android/downloadGoogleServicesJson.ts +56 -0
  193. package/src/android/gradle.ts +116 -0
  194. package/src/android/manifest.ts +50 -0
  195. package/src/android/meetSdkRemoteGradle.ts +837 -0
  196. package/src/cli.ts +488 -0
  197. package/src/config/fetchConfigWrite.ts +30 -0
  198. package/src/config/loadAndroidIntegration.ts +41 -0
  199. package/src/config/loadManifest.ts +40 -0
  200. package/src/config/meetSdkDefaultConfig.ts +99 -0
  201. package/src/config/meetSdkIosConfig.ts +87 -0
  202. package/src/config/meetSdkRemoteConfig.ts +1211 -0
  203. package/src/config/topsdkFeatureModules.ts +92 -0
  204. package/src/contracts/types.ts +121 -0
  205. package/src/core/doctor.ts +485 -0
  206. package/src/core/patch.ts +64 -0
  207. package/src/core/pipeline.ts +107 -0
  208. package/src/core/platform.ts +47 -0
  209. package/src/core/previewPatches.ts +23 -0
  210. package/src/core/reporter.ts +24 -0
  211. package/src/core/workspace.ts +29 -0
  212. package/src/entry.ts +7 -0
  213. package/src/index.ts +133 -0
  214. package/src/ios/channelConfig.ts +128 -0
  215. package/src/ios/codeUtils.ts +160 -0
  216. package/src/ios/detect.ts +105 -0
  217. package/src/ios/entitlements.ts +61 -0
  218. package/src/ios/fileManager.ts +48 -0
  219. package/src/ios/infoPlist.ts +55 -0
  220. package/src/ios/integrate.ts +516 -0
  221. package/src/ios/pbxprojEditor.ts +383 -0
  222. package/src/ios/pluginConfig.ts +97 -0
  223. package/src/ios/reserved.ts +8 -0
  224. package/src/ios/sdkBundle.ts +36 -0
  225. package/src/ios/template.ts +36 -0
  226. package/src/ios/types.ts +65 -0
  227. package/src/mcp/server.ts +170 -0
  228. package/src/mcp/service.ts +222 -0
  229. package/src/mcp-entry.ts +7 -0
  230. package/src/ops/fileStore.ts +56 -0
  231. package/src/ops/handlers.ts +304 -0
  232. package/src/remote/fetchJson.ts +22 -0
  233. package/src/remote/sdkHomeDownload.ts +274 -0
  234. package/src/remote/topsdkDownloadSdkConfig.ts +93 -0
  235. package/src/remote/topsdkGetSdkConfig.ts +122 -0
  236. package/src/remote/topsdkSign.ts +10 -0
  237. package/src/shared/errors.ts +16 -0
  238. package/src/shared/fileUtils.ts +41 -0
  239. package/src/shared/logger.ts +49 -0
  240. package/src/shared/pathUtils.ts +24 -0
  241. package/src/shared/processRunner.ts +43 -0
  242. package/test-projects/README.md +51 -0
  243. package/test-projects/_preview/pipeline.patch +281 -0
  244. package/tests/aab-converter.test.ts +213 -0
  245. package/tests/assemble.test.ts +12 -0
  246. package/tests/doctor.test.ts +89 -0
  247. package/tests/downloadGoogleServicesJson.test.ts +47 -0
  248. package/tests/fetch-remote.test.ts +23 -0
  249. package/tests/fetchConfigOverrides.test.ts +28 -0
  250. package/tests/fetchConfigWrite.test.ts +54 -0
  251. package/tests/gradle.test.ts +33 -0
  252. package/tests/integration-json.test.ts +29 -0
  253. package/tests/ios.codeUtils.test.ts +23 -0
  254. package/tests/ios.sdkBundle.test.ts +16 -0
  255. package/tests/loadManifest.test.ts +15 -0
  256. package/tests/manifest-xml.test.ts +30 -0
  257. package/tests/mcp.e2e.ts +217 -0
  258. package/tests/mcp.service.test.ts +53 -0
  259. package/tests/meetSdkRemoteConfig.test.ts +456 -0
  260. package/tests/meetSdkRemoteGradle.test.ts +414 -0
  261. package/tests/pipeline.android.test.ts +96 -0
  262. package/tests/pipeline.integration-json.test.ts +58 -0
  263. package/tests/pipeline.ios.test.ts +385 -0
  264. package/tests/pipeline.preview.patch.test.ts +85 -0
  265. package/tests/platformSelection.test.ts +77 -0
  266. package/tests/sdkHomeDownload.test.ts +124 -0
  267. package/tests/sdkVersionConfig.test.ts +130 -0
  268. package/tests/test-projects-hosts.test.ts +78 -0
  269. package/tests/topsdk.test.ts +53 -0
  270. package/tests/topsdkDownloadSdkConfig.test.ts +81 -0
  271. package/tests/topsdkFeatureModules.test.ts +116 -0
  272. package/tsconfig.json +19 -0
  273. package/vitest.config.ts +9 -0
  274. package/vitest.mcp.config.ts +11 -0
  275. package/bundled/android/sample.txt +0 -1
  276. package/docs/ANDROID.md +0 -133
  277. package/docs/CURSOR-MCP-SETUP.md +0 -72
  278. package/docs/MCP-SKILL.md +0 -63
  279. package/fixtures/api-samples/getChannelConfig-meetgames.sample.json +0 -123
  280. package/fixtures/meetsdk-remote-config.download-shape.json +0 -20
  281. package/fixtures/meetsdk-remote-config.mock.json +0 -69
  282. package/fixtures/recipes/android-default.fixture.yaml +0 -15
  283. package/fixtures/recipes/android-integration.fixture.json +0 -29
  284. package/fixtures/topsdk-config-reference.json +0 -39
  285. /package/docs/{api → archive/api}/getSDKConfig.md +0 -0
@@ -0,0 +1,122 @@
1
+ import { fetchJsonGet } from "./fetchJson.js";
2
+ import { topsdkMd5Base64Sign } from "./topsdkSign.js";
3
+
4
+ /**
5
+ * Console API host by environment.
6
+ * - `prod`: 正式服(与文档 `api-sdk-gameplus.meetsocial.com` 一致)
7
+ * - `pre`: 预发
8
+ * - `test`: 测试服(开发调试使用)
9
+ */
10
+ export type TopSdkApiEnv = "prod" | "pre" | "test";
11
+
12
+ export function defaultTopSdkBaseUrl(env: TopSdkApiEnv): string {
13
+ if (env === "test") return "https://test-api-sdk-gameplus.meetsocial.com/";
14
+ if (env === "pre") return "https://pre-api-sdk-gameplus.meetsocial.com/";
15
+ return "https://api-sdk-gameplus.meetsocial.com/";
16
+ }
17
+
18
+ const SDK_CONFIG_PATH = "console/openSDK/getSDKConfig";
19
+
20
+ function joinBaseAndPath(baseUrl: string, path: string): URL {
21
+ const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
22
+ return new URL(path, base);
23
+ }
24
+
25
+ /**
26
+ * Same query semantics as topsdk-tool-ios `ChannelConfigManager.getSdkConfig` /
27
+ * topsdk-tool `config_utils.getSdkConfig`: GET `console/openSDK/getSDKConfig` with
28
+ * appId, authType (multi), channelType, sign, timestamp.
29
+ */
30
+ export function buildTopSdkGetSdkConfigUrl(params: {
31
+ baseUrl: string;
32
+ appId: string;
33
+ appSecret: string;
34
+ authTypes: readonly string[];
35
+ channelType: string;
36
+ timestampMillis?: number;
37
+ }): { url: string; timestampMillis: number } {
38
+ const timestampMillis = params.timestampMillis ?? Date.now();
39
+ const sign = topsdkMd5Base64Sign(params.appId, params.appSecret, timestampMillis);
40
+ const u = joinBaseAndPath(params.baseUrl, SDK_CONFIG_PATH);
41
+ u.searchParams.set("appId", params.appId);
42
+ for (const t of params.authTypes) {
43
+ u.searchParams.append("authType", t);
44
+ }
45
+ u.searchParams.set("channelType", params.channelType);
46
+ u.searchParams.set("sign", sign);
47
+ u.searchParams.set("timestamp", String(timestampMillis));
48
+ return { url: u.toString(), timestampMillis };
49
+ }
50
+
51
+ /** Strips auth types not sent to the API (same list as topsdk-tool-ios ChannelConfigManager). */
52
+ export function filterTopSdkAuthTypesForRequest(authTypes: readonly string[]): string[] {
53
+ const skip = new Set(["GUEST", "UI", "IAPPAY"]);
54
+ return authTypes.map((t) => t.trim()).filter(Boolean).filter((t) => !skip.has(t.toUpperCase()));
55
+ }
56
+
57
+ /**
58
+ * Upper-case names aligned with gp-sdk `ThirdPlatformType#getType`(不含 `MOCK`)。
59
+ * 当调用方未传任何 `authType` 时用于 `getSDKConfig`:服务端只返回该渠道已配置的子集。
60
+ */
61
+ export const TOPSDK_GET_SDK_CONFIG_DISCOVERY_AUTH_TYPES: readonly string[] = [
62
+ "NORMAL",
63
+ "GUEST",
64
+ "FACEBOOK",
65
+ "GOOGLE",
66
+ "TOKEN",
67
+ "SNAPCHAT",
68
+ "TWITTER",
69
+ "INSTAGRAM",
70
+ "AMAZON",
71
+ "APPLE",
72
+ "GAMEPLUS",
73
+ "NAVER",
74
+ "KAKAO",
75
+ "LINE",
76
+ "XSOLLA",
77
+ "ONESTORE",
78
+ "SSO",
79
+ "APPSFLYER",
80
+ "ADJUST",
81
+ "FIREBASE",
82
+ "MEETGAMES",
83
+ "EMAIL",
84
+ "HUAWEI",
85
+ "XIAOMI",
86
+ "CATAPPULT",
87
+ "SAMSUNG",
88
+ "TIKTOK",
89
+ ];
90
+
91
+ /** 显式列表为空时,用发现列表再按 `filterTopSdkAuthTypesForRequest` 过滤。 */
92
+ export function resolveAuthTypesForGetSdkConfigRequest(explicit: readonly string[]): string[] {
93
+ const merged = explicit.length > 0 ? explicit : [...TOPSDK_GET_SDK_CONFIG_DISCOVERY_AUTH_TYPES];
94
+ return filterTopSdkAuthTypesForRequest(merged);
95
+ }
96
+
97
+ export async function fetchTopSdkGetSdkConfig(params: {
98
+ baseUrl: string;
99
+ appId: string;
100
+ appSecret: string;
101
+ authTypes: readonly string[];
102
+ channelType: string;
103
+ signal?: AbortSignal;
104
+ }): Promise<{ url: string; timestampMillis: number; body: unknown }> {
105
+ const filtered = resolveAuthTypesForGetSdkConfigRequest(params.authTypes);
106
+ if (filtered.length === 0) {
107
+ return {
108
+ url: joinBaseAndPath(params.baseUrl, SDK_CONFIG_PATH).toString(),
109
+ timestampMillis: Date.now(),
110
+ body: { code: 200, data: { channelAuthConfig: [] }, message: "skipped_no_auth_types_after_filter" },
111
+ };
112
+ }
113
+ const { url, timestampMillis } = buildTopSdkGetSdkConfigUrl({
114
+ baseUrl: params.baseUrl,
115
+ appId: params.appId,
116
+ appSecret: params.appSecret,
117
+ authTypes: filtered,
118
+ channelType: params.channelType,
119
+ });
120
+ const body = await fetchJsonGet(url, { signal: params.signal });
121
+ return { url, timestampMillis, body };
122
+ }
@@ -0,0 +1,10 @@
1
+ import crypto from "node:crypto";
2
+
3
+ /**
4
+ * Same algorithm as topsdk-tool-ios `TOPSDKUtils/ChannelConfigManager.md5_base64_byte`:
5
+ * base64( md5_binary( appId + appSecret + timestampMillis ) )
6
+ */
7
+ export function topsdkMd5Base64Sign(appId: string, appSecret: string, timestampMillis: number): string {
8
+ const plain = `${appId}${appSecret}${String(timestampMillis)}`;
9
+ return crypto.createHash("md5").update(plain, "utf8").digest("base64");
10
+ }
@@ -0,0 +1,16 @@
1
+ export class UserFacingError extends Error {
2
+ readonly code: string;
3
+ readonly details?: string;
4
+
5
+ constructor(code: string, message: string, details?: string) {
6
+ super(message);
7
+ this.name = "UserFacingError";
8
+ this.code = code;
9
+ this.details = details;
10
+ }
11
+ }
12
+
13
+ export function errorMessage(error: unknown): string {
14
+ if (error instanceof Error) return error.message;
15
+ return String(error);
16
+ }
@@ -0,0 +1,41 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { UserFacingError } from "./errors.js";
4
+
5
+ export function assertFileExists(filePath: string, code: string, label: string): void {
6
+ if (!fs.existsSync(filePath)) {
7
+ throw new UserFacingError(code, `${label} does not exist: ${filePath}`);
8
+ }
9
+ if (!fs.statSync(filePath).isFile()) {
10
+ throw new UserFacingError(code, `${label} is not a file: ${filePath}`);
11
+ }
12
+ }
13
+
14
+ export function ensureDirectory(dirPath: string): void {
15
+ fs.mkdirSync(dirPath, { recursive: true });
16
+ if (!fs.statSync(dirPath).isDirectory()) {
17
+ throw new UserFacingError("OUTPUT_DIR_INVALID", `Output path is not a directory: ${dirPath}`);
18
+ }
19
+ }
20
+
21
+ export function assertWritableDirectory(dirPath: string): void {
22
+ ensureDirectory(dirPath);
23
+ try {
24
+ const probe = path.join(dirPath, `.write-test-${Date.now()}-${Math.random().toString(16).slice(2)}`);
25
+ fs.writeFileSync(probe, "");
26
+ fs.unlinkSync(probe);
27
+ } catch (e) {
28
+ throw new UserFacingError(
29
+ "OUTPUT_DIR_NOT_WRITABLE",
30
+ `Output directory is not writable: ${dirPath}`,
31
+ e instanceof Error ? e.message : String(e)
32
+ );
33
+ }
34
+ }
35
+
36
+ export function assertCanWriteFile(filePath: string, overwrite: boolean, label: string): void {
37
+ if (!overwrite && fs.existsSync(filePath)) {
38
+ throw new UserFacingError("OUTPUT_EXISTS", `${label} already exists: ${filePath}`);
39
+ }
40
+ ensureDirectory(path.dirname(filePath));
41
+ }
@@ -0,0 +1,49 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export interface Logger {
5
+ readonly logPath?: string;
6
+ info(message: string): void;
7
+ warn(message: string): void;
8
+ error(message: string): void;
9
+ close(): void;
10
+ }
11
+
12
+ function timestamp(): string {
13
+ return new Date().toISOString();
14
+ }
15
+
16
+ function logFileName(): string {
17
+ const d = new Date();
18
+ const pad = (n: number) => String(n).padStart(2, "0");
19
+ return `convert-${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(
20
+ d.getMinutes()
21
+ )}${pad(d.getSeconds())}.log`;
22
+ }
23
+
24
+ export function createFileLogger(logDir: string): Logger {
25
+ fs.mkdirSync(logDir, { recursive: true });
26
+ const logPath = path.join(logDir, logFileName());
27
+ const stream = fs.createWriteStream(logPath, { flags: "a", encoding: "utf8" });
28
+ const write = (level: string, message: string) => {
29
+ stream.write(`[${timestamp()}] [${level}] ${message}\n`);
30
+ };
31
+ return {
32
+ logPath,
33
+ info: (message) => write("INFO", message),
34
+ warn: (message) => write("WARN", message),
35
+ error: (message) => write("ERROR", message),
36
+ close: () => stream.end(),
37
+ };
38
+ }
39
+
40
+ export function createConsoleLogger(verbose: boolean): Logger {
41
+ return {
42
+ info: (message) => {
43
+ if (verbose) console.log(message);
44
+ },
45
+ warn: (message) => console.warn(message),
46
+ error: (message) => console.error(message),
47
+ close: () => undefined,
48
+ };
49
+ }
@@ -0,0 +1,24 @@
1
+ import path from "node:path";
2
+ import { UserFacingError } from "./errors.js";
3
+
4
+ export function toAbsolutePath(value: string, baseDir = process.cwd()): string {
5
+ return path.resolve(baseDir, value);
6
+ }
7
+
8
+ export function baseNameWithoutExt(filePath: string, expectedExt: string): string {
9
+ const parsed = path.parse(filePath);
10
+ if (parsed.ext.toLowerCase() !== expectedExt.toLowerCase()) {
11
+ throw new UserFacingError("INVALID_EXTENSION", `Expected ${expectedExt} file: ${filePath}`);
12
+ }
13
+ return parsed.name;
14
+ }
15
+
16
+ export function validateOutputFileName(fileName: string): string {
17
+ if (!fileName.trim()) {
18
+ throw new UserFacingError("INVALID_APK_NAME", "--apk-name cannot be empty");
19
+ }
20
+ if (fileName.includes("/") || fileName.includes("\\") || path.basename(fileName) !== fileName) {
21
+ throw new UserFacingError("INVALID_APK_NAME", "--apk-name must be a file name, not a path");
22
+ }
23
+ return fileName.toLowerCase().endsWith(".apk") ? fileName : `${fileName}.apk`;
24
+ }
@@ -0,0 +1,43 @@
1
+ import { spawn } from "node:child_process";
2
+
3
+ export interface RunProcessOptions {
4
+ cwd?: string;
5
+ onStdout?: (chunk: string) => void;
6
+ onStderr?: (chunk: string) => void;
7
+ }
8
+
9
+ export interface RunProcessResult {
10
+ exitCode: number;
11
+ stdout: string;
12
+ stderr: string;
13
+ }
14
+
15
+ export function runProcess(file: string, args: string[], options: RunProcessOptions = {}): Promise<RunProcessResult> {
16
+ return new Promise((resolve, reject) => {
17
+ const child = spawn(file, args, {
18
+ cwd: options.cwd,
19
+ windowsHide: true,
20
+ shell: false,
21
+ });
22
+ let stdout = "";
23
+ let stderr = "";
24
+ child.stdout?.on("data", (buf: Buffer) => {
25
+ const text = buf.toString("utf8");
26
+ stdout += text;
27
+ options.onStdout?.(text);
28
+ });
29
+ child.stderr?.on("data", (buf: Buffer) => {
30
+ const text = buf.toString("utf8");
31
+ stderr += text;
32
+ options.onStderr?.(text);
33
+ });
34
+ child.on("error", reject);
35
+ child.on("close", (exitCode) => {
36
+ resolve({
37
+ exitCode: exitCode ?? 1,
38
+ stdout,
39
+ stderr,
40
+ });
41
+ });
42
+ });
43
+ }
@@ -0,0 +1,51 @@
1
+ # Test preview output
2
+
3
+ `test-projects/` no longer stores host project fixtures. Runtime test projects live under `fixtures/`:
4
+
5
+ - `fixtures/android-test-project/android-latest-project/`
6
+ - `fixtures/android-test-project/power-raid/`
7
+ - `fixtures/ios-test-project/tooltest/`
8
+
9
+ `test-projects/_preview/` is kept only for generated dry-run patch output. Running **`npm test`** refreshes **`test-projects/_preview/pipeline.patch`**; fixture source trees are not modified by tests.
10
+
11
+ ## Preview changes (`--dry-run`)
12
+
13
+ **`integrate` writes to disk by default.** Use **`--dry-run`** to preview diffs without touching host projects:
14
+
15
+ 1. **`npm test`** — writes **`test-projects/_preview/pipeline.patch`** (yaml + integration-json fixture dry-runs on `fixtures/android-test-project/android-latest-project`).
16
+
17
+ 2. **Manual dry-run**:
18
+ ```bash
19
+ npm run build -s
20
+ node dist/entry.js integrate --project-root fixtures/android-test-project/android-latest-project \
21
+ --dry-run --verbose --patch-file ./meetgames-preview.patch
22
+ ```
23
+
24
+ `test-projects/_preview/` is gitignored. Writing **`--patch-file`** into that directory clears existing files there first.
25
+
26
+ ## Primary test hosts
27
+
28
+ ```bash
29
+ cd /path/to/meet-sdk-tool
30
+ node dist/entry.js doctor --project-root fixtures/android-test-project/android-latest-project
31
+ node dist/entry.js doctor --project-root fixtures/android-test-project/power-raid
32
+ node dist/entry.js integrate --project-root fixtures/android-test-project/android-latest-project --dry-run --verbose
33
+ node dist/entry.js integrate --project-root fixtures/android-test-project/power-raid --dry-run --verbose
34
+ ```
35
+
36
+ ### `android-latest-project/`
37
+
38
+ - Modern template: `plugins { alias(libs.plugins…) }`, `dependencyResolutionManagement` in `settings.gradle`.
39
+ - Single **`app`** module (`com.android.application`).
40
+ - `meetsdk-remote-config.json` — test/联调配置(`fetch-config` 可覆盖)。
41
+
42
+ ### `power-raid/`
43
+
44
+ - Unity Android export: **`launcher`** + `unityLibrary`, `engine=unity`.
45
+ - Legacy `apply plugin` + existing TOPSDK marker blocks (re-integrate / upsert).
46
+ - `meetsdk-remote-config.json` — 与线上一致的插件 key 集合。
47
+
48
+ ### `fixtures/ios-test-project/tooltest/`
49
+
50
+ - Xcode test host for iOS pipeline and platform-detection tests.
51
+ - Uses `fixtures/meetsdk-remote-config.ios-tooltest.json` as its offline remote-config fixture.
@@ -0,0 +1,281 @@
1
+ # dry-run — fixtures/recipes/android-default.fixture.yaml
2
+ diff --git a/app/build.gradle b/app/build.gradle
3
+ index ba437dd..b97390c 100644
4
+ --- a/app/build.gradle
5
+ +++ b/app/build.gradle
6
+ @@ -1,6 +1,9 @@
7
+ plugins {
8
+ alias(libs.plugins.android.application)
9
+ alias(libs.plugins.kotlin.compose)
10
+ + // >>> TOPSDK PLUGIN AUTO START
11
+ + id 'com.google.gms.google-services'
12
+ + // >>> TOPSDK PLUGIN AUTO END
13
+ }
14
+
15
+ android {
16
+ @@ -10,14 +13,42 @@ android {
17
+ }
18
+
19
+ defaultConfig {
20
+ - applicationId "com.example.myapplication"
21
+ + applicationId "com.meet.integrate.androidsample"
22
+ minSdk 24
23
+ targetSdk 36
24
+ versionCode 1
25
+ versionName "1.0"
26
+
27
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
28
+ - }
29
+ + // >>> TOPSDK AUTO START
30
+ + resValue('string', 'top_channel_id', 'GOOGLE')
31
+ + resValue('string', 'top_app_id', 'mock-topsdk-app-id')
32
+ + resValue('string', 'facebook_app_id', '0000000000000000')
33
+ + resValue('string', 'fb_login_protocol_scheme', 'fb0000000000000000')
34
+ + resValue('string', 'facebook_client_token', 'mockfacebookclienttoken00000000')
35
+ + resValue('string', 'google_client_id', 'mock-client-id.apps.googleusercontent.com')
36
+ + resValue('string', 'twitter_client_id', 'MOCK_TWITTER_OAUTH2_CLIENT_ID')
37
+ + resValue('string', 'twitter_client_secret', 'MOCK_TWITTER_OAUTH2_CLIENT_SECRET')
38
+ + resValue('string', 'twitter_redirect_url', 'com.feishu.topsdk://callback')
39
+ + resValue('string', 'snapchat_client_id', 'MOCK_SNAPCHAT_CLIENT_ID_UUID')
40
+ + resValue('string', 'snapchat_redirect_uri', 'https://com.feishu.topsdk/snapchat/callback')
41
+ + resValue('string', 'tiktok_client_id', 'MOCK_TIKTOK_CLIENT_KEY')
42
+ + resValue('string', 'tiktok_client_secret', 'MOCK_TIKTOK_CLIENT_SECRET')
43
+ + resValue('string', 'tiktok_redirect_uri', 'https://example.invalid/tiktok/oauth/callback')
44
+ + resValue('string', 'line_channel_id', 'MOCK_LINE_CHANNEL_ID')
45
+ + resValue('string', 'kakao_app_id', 'MOCK_KAKAO_NATIVE_APP_KEY')
46
+ + resValue('string', 'kakao_scheme', 'kakaoMOCK_KAKAO_NATIVE_APP_KEY')
47
+ + resValue('string', 'naver_client_id', 'MOCK_NAVER_CLIENT_ID')
48
+ + resValue('string', 'naver_client_secret', 'MOCK_NAVER_CLIENT_SECRET')
49
+ + resValue('string', 'naver_client_name', 'Android Sample')
50
+ + resValue('bool', 'appsflyer_enable_debug_log', "true")
51
+ + resValue('string', 'af_dev_key', 'MOCK_APPSFLYER_DEV_KEY')
52
+ + resValue('bool', 'adjust_enable_sandbox', "false")
53
+ + resValue('string', 'adjust_app_token', 'MOCK_ADJUST_APP_ID')
54
+ + resValue('string', 'facebook_data_app_id', '0000000000000000')
55
+ + resValue('string', 'facebook_data_client_token', 'mockfacebookclienttoken00000000')
56
+ +// >>> TOPSDK AUTO END
57
+ +}
58
+
59
+ buildTypes {
60
+ release {
61
+ @@ -50,4 +81,28 @@ dependencies {
62
+ androidTestImplementation libs.androidx.compose.ui.test.junit4
63
+ debugImplementation libs.androidx.compose.ui.tooling
64
+ debugImplementation libs.androidx.compose.ui.test.manifest
65
+ +// >>> TOPSDK AUTO START
66
+ + def topsdk_version = "1.6.1.3"
67
+ + def groupId = "com.sino.topsdk"
68
+ +
69
+ + implementation "$groupId:ui:$topsdk_version"
70
+ + implementation "$groupId:guest:$topsdk_version"
71
+ + implementation "$groupId:email:$topsdk_version"
72
+ + implementation "$groupId:facebook:$topsdk_version"
73
+ + implementation "$groupId:google:$topsdk_version"
74
+ + implementation "$groupId:twitter:$topsdk_version"
75
+ + implementation "$groupId:snapchat:$topsdk_version"
76
+ + implementation "$groupId:line:$topsdk_version"
77
+ + implementation "$groupId:naver:$topsdk_version"
78
+ + implementation "$groupId:kakao:$topsdk_version"
79
+ + implementation "$groupId:tiktok:$topsdk_version"
80
+ + implementation "$groupId:appsflyer:$topsdk_version"
81
+ + implementation "$groupId:firebase:$topsdk_version"
82
+ + implementation "$groupId:adjust:$topsdk_version"
83
+ + implementation "$groupId:facebook-data:$topsdk_version"
84
+ + implementation "$groupId:google-iap:$topsdk_version"
85
+ + implementation "$groupId:onestore-iap:$topsdk_version"
86
+ + implementation "$groupId:huawei-iap:$topsdk_version"
87
+ + implementation "$groupId:xiaomi-iap:$topsdk_version"
88
+ +// >>> TOPSDK AUTO END
89
+ }
90
+
91
+ diff --git a/settings.gradle b/settings.gradle
92
+ index 437187c..a7deb33 100644
93
+ --- a/settings.gradle
94
+ +++ b/settings.gradle
95
+ @@ -14,9 +14,16 @@ pluginManagement {
96
+ dependencyResolutionManagement {
97
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
98
+ repositories {
99
+ - google()
100
+ + // >>> TOPSDK REPO AUTO START
101
+ + maven { url 'https://devrepo.kakao.com/nexus/content/groups/public/' }
102
+ + maven { url 'https://artifact.bytedance.com/repository/AwemeOpenSDK' }
103
+ mavenCentral()
104
+ - }
105
+ + google()
106
+ + maven { url 'https://repo.onestore.co.kr/repository/onestore-sdk-public/' }
107
+ + maven { url 'https://developer.huawei.com/repo/' }
108
+ + maven { url 'https://storage-sdk-gameplus.meetsocial.com/repository/TopSdk/' }
109
+ +// >>> TOPSDK REPO AUTO END
110
+ +}
111
+ }
112
+
113
+ rootProject.name = "My Application"
114
+
115
+ diff --git a/build.gradle b/build.gradle
116
+ index b546c74..aee9a1f 100644
117
+ --- a/build.gradle
118
+ +++ b/build.gradle
119
+ @@ -2,4 +2,7 @@
120
+ plugins {
121
+ alias(libs.plugins.android.application) apply false
122
+ alias(libs.plugins.kotlin.compose) apply false
123
+ + // >>> TOPSDK PLUGIN AUTO START
124
+ + id 'com.google.gms.google-services' version '4.4.4' apply false
125
+ + // >>> TOPSDK PLUGIN AUTO END
126
+ }
127
+
128
+ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
129
+ index 5927aac..615935a 100644
130
+ --- a/app/src/main/AndroidManifest.xml
131
+ +++ b/app/src/main/AndroidManifest.xml
132
+ @@ -24,4 +24,8 @@
133
+ </activity>
134
+ </application>
135
+
136
+ +
137
+ +<!-- MEET_INTEGRATE PERMISSIONS START -->
138
+ + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
139
+ +<!-- MEET_INTEGRATE PERMISSIONS END -->
140
+ </manifest>
141
+
142
+ # dry-run — android-integration.fixture.json
143
+ diff --git a/app/build.gradle b/app/build.gradle
144
+ index ba437dd..b97390c 100644
145
+ --- a/app/build.gradle
146
+ +++ b/app/build.gradle
147
+ @@ -1,6 +1,9 @@
148
+ plugins {
149
+ alias(libs.plugins.android.application)
150
+ alias(libs.plugins.kotlin.compose)
151
+ + // >>> TOPSDK PLUGIN AUTO START
152
+ + id 'com.google.gms.google-services'
153
+ + // >>> TOPSDK PLUGIN AUTO END
154
+ }
155
+
156
+ android {
157
+ @@ -10,14 +13,42 @@ android {
158
+ }
159
+
160
+ defaultConfig {
161
+ - applicationId "com.example.myapplication"
162
+ + applicationId "com.meet.integrate.androidsample"
163
+ minSdk 24
164
+ targetSdk 36
165
+ versionCode 1
166
+ versionName "1.0"
167
+
168
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
169
+ - }
170
+ + // >>> TOPSDK AUTO START
171
+ + resValue('string', 'top_channel_id', 'GOOGLE')
172
+ + resValue('string', 'top_app_id', 'mock-topsdk-app-id')
173
+ + resValue('string', 'facebook_app_id', '0000000000000000')
174
+ + resValue('string', 'fb_login_protocol_scheme', 'fb0000000000000000')
175
+ + resValue('string', 'facebook_client_token', 'mockfacebookclienttoken00000000')
176
+ + resValue('string', 'google_client_id', 'mock-client-id.apps.googleusercontent.com')
177
+ + resValue('string', 'twitter_client_id', 'MOCK_TWITTER_OAUTH2_CLIENT_ID')
178
+ + resValue('string', 'twitter_client_secret', 'MOCK_TWITTER_OAUTH2_CLIENT_SECRET')
179
+ + resValue('string', 'twitter_redirect_url', 'com.feishu.topsdk://callback')
180
+ + resValue('string', 'snapchat_client_id', 'MOCK_SNAPCHAT_CLIENT_ID_UUID')
181
+ + resValue('string', 'snapchat_redirect_uri', 'https://com.feishu.topsdk/snapchat/callback')
182
+ + resValue('string', 'tiktok_client_id', 'MOCK_TIKTOK_CLIENT_KEY')
183
+ + resValue('string', 'tiktok_client_secret', 'MOCK_TIKTOK_CLIENT_SECRET')
184
+ + resValue('string', 'tiktok_redirect_uri', 'https://example.invalid/tiktok/oauth/callback')
185
+ + resValue('string', 'line_channel_id', 'MOCK_LINE_CHANNEL_ID')
186
+ + resValue('string', 'kakao_app_id', 'MOCK_KAKAO_NATIVE_APP_KEY')
187
+ + resValue('string', 'kakao_scheme', 'kakaoMOCK_KAKAO_NATIVE_APP_KEY')
188
+ + resValue('string', 'naver_client_id', 'MOCK_NAVER_CLIENT_ID')
189
+ + resValue('string', 'naver_client_secret', 'MOCK_NAVER_CLIENT_SECRET')
190
+ + resValue('string', 'naver_client_name', 'Android Sample')
191
+ + resValue('bool', 'appsflyer_enable_debug_log', "true")
192
+ + resValue('string', 'af_dev_key', 'MOCK_APPSFLYER_DEV_KEY')
193
+ + resValue('bool', 'adjust_enable_sandbox', "false")
194
+ + resValue('string', 'adjust_app_token', 'MOCK_ADJUST_APP_ID')
195
+ + resValue('string', 'facebook_data_app_id', '0000000000000000')
196
+ + resValue('string', 'facebook_data_client_token', 'mockfacebookclienttoken00000000')
197
+ +// >>> TOPSDK AUTO END
198
+ +}
199
+
200
+ buildTypes {
201
+ release {
202
+ @@ -50,4 +81,28 @@ dependencies {
203
+ androidTestImplementation libs.androidx.compose.ui.test.junit4
204
+ debugImplementation libs.androidx.compose.ui.tooling
205
+ debugImplementation libs.androidx.compose.ui.test.manifest
206
+ +// >>> TOPSDK AUTO START
207
+ + def topsdk_version = "1.6.1.3"
208
+ + def groupId = "com.sino.topsdk"
209
+ +
210
+ + implementation "$groupId:ui:$topsdk_version"
211
+ + implementation "$groupId:guest:$topsdk_version"
212
+ + implementation "$groupId:email:$topsdk_version"
213
+ + implementation "$groupId:facebook:$topsdk_version"
214
+ + implementation "$groupId:google:$topsdk_version"
215
+ + implementation "$groupId:twitter:$topsdk_version"
216
+ + implementation "$groupId:snapchat:$topsdk_version"
217
+ + implementation "$groupId:line:$topsdk_version"
218
+ + implementation "$groupId:naver:$topsdk_version"
219
+ + implementation "$groupId:kakao:$topsdk_version"
220
+ + implementation "$groupId:tiktok:$topsdk_version"
221
+ + implementation "$groupId:appsflyer:$topsdk_version"
222
+ + implementation "$groupId:firebase:$topsdk_version"
223
+ + implementation "$groupId:adjust:$topsdk_version"
224
+ + implementation "$groupId:facebook-data:$topsdk_version"
225
+ + implementation "$groupId:google-iap:$topsdk_version"
226
+ + implementation "$groupId:onestore-iap:$topsdk_version"
227
+ + implementation "$groupId:huawei-iap:$topsdk_version"
228
+ + implementation "$groupId:xiaomi-iap:$topsdk_version"
229
+ +// >>> TOPSDK AUTO END
230
+ }
231
+
232
+ diff --git a/settings.gradle b/settings.gradle
233
+ index 437187c..a7deb33 100644
234
+ --- a/settings.gradle
235
+ +++ b/settings.gradle
236
+ @@ -14,9 +14,16 @@ pluginManagement {
237
+ dependencyResolutionManagement {
238
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
239
+ repositories {
240
+ - google()
241
+ + // >>> TOPSDK REPO AUTO START
242
+ + maven { url 'https://devrepo.kakao.com/nexus/content/groups/public/' }
243
+ + maven { url 'https://artifact.bytedance.com/repository/AwemeOpenSDK' }
244
+ mavenCentral()
245
+ - }
246
+ + google()
247
+ + maven { url 'https://repo.onestore.co.kr/repository/onestore-sdk-public/' }
248
+ + maven { url 'https://developer.huawei.com/repo/' }
249
+ + maven { url 'https://storage-sdk-gameplus.meetsocial.com/repository/TopSdk/' }
250
+ +// >>> TOPSDK REPO AUTO END
251
+ +}
252
+ }
253
+
254
+ rootProject.name = "My Application"
255
+
256
+ diff --git a/build.gradle b/build.gradle
257
+ index b546c74..aee9a1f 100644
258
+ --- a/build.gradle
259
+ +++ b/build.gradle
260
+ @@ -2,4 +2,7 @@
261
+ plugins {
262
+ alias(libs.plugins.android.application) apply false
263
+ alias(libs.plugins.kotlin.compose) apply false
264
+ + // >>> TOPSDK PLUGIN AUTO START
265
+ + id 'com.google.gms.google-services' version '4.4.4' apply false
266
+ + // >>> TOPSDK PLUGIN AUTO END
267
+ }
268
+
269
+ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
270
+ index 5927aac..615935a 100644
271
+ --- a/app/src/main/AndroidManifest.xml
272
+ +++ b/app/src/main/AndroidManifest.xml
273
+ @@ -24,4 +24,8 @@
274
+ </activity>
275
+ </application>
276
+
277
+ +
278
+ +<!-- MEET_INTEGRATE PERMISSIONS START -->
279
+ + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
280
+ +<!-- MEET_INTEGRATE PERMISSIONS END -->
281
+ </manifest>