@leonxin/meetgames 0.1.17 → 0.1.19

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 (92) hide show
  1. package/README.md +5 -4
  2. package/dist/cache.d.ts +16 -0
  3. package/dist/cache.d.ts.map +1 -1
  4. package/dist/cache.js +21 -0
  5. package/dist/cache.js.map +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +24 -26
  8. package/dist/cli.js.map +1 -1
  9. package/dist/config/meetSdkDefaultConfig.d.ts +7 -11
  10. package/dist/config/meetSdkDefaultConfig.d.ts.map +1 -1
  11. package/dist/config/meetSdkDefaultConfig.js +216 -53
  12. package/dist/config/meetSdkDefaultConfig.js.map +1 -1
  13. package/dist/config/meetSdkIosConfig.d.ts +4 -5
  14. package/dist/config/meetSdkIosConfig.d.ts.map +1 -1
  15. package/dist/config/meetSdkIosConfig.js +19 -19
  16. package/dist/config/meetSdkIosConfig.js.map +1 -1
  17. package/dist/contracts/types.d.ts +4 -0
  18. package/dist/contracts/types.d.ts.map +1 -1
  19. package/dist/core/doctor.d.ts.map +1 -1
  20. package/dist/core/doctor.js +3 -1
  21. package/dist/core/doctor.js.map +1 -1
  22. package/dist/core/previewPatches.d.ts +2 -2
  23. package/dist/core/previewPatches.d.ts.map +1 -1
  24. package/dist/core/previewPatches.js +3 -3
  25. package/dist/core/previewPatches.js.map +1 -1
  26. package/dist/core/workspace.d.ts.map +1 -1
  27. package/dist/core/workspace.js +1 -0
  28. package/dist/core/workspace.js.map +1 -1
  29. package/dist/index.d.ts +4 -4
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +4 -4
  32. package/dist/index.js.map +1 -1
  33. package/dist/ios/integrate.d.ts.map +1 -1
  34. package/dist/ios/integrate.js +11 -9
  35. package/dist/ios/integrate.js.map +1 -1
  36. package/dist/mcp/service.d.ts +2 -0
  37. package/dist/mcp/service.d.ts.map +1 -1
  38. package/dist/mcp/service.js +8 -5
  39. package/dist/mcp/service.js.map +1 -1
  40. package/dist/ops/handlers.d.ts.map +1 -1
  41. package/dist/ops/handlers.js +4 -0
  42. package/dist/ops/handlers.js.map +1 -1
  43. package/dist/remote/sdkHomeDownload.d.ts +6 -5
  44. package/dist/remote/sdkHomeDownload.d.ts.map +1 -1
  45. package/dist/remote/sdkHomeDownload.js +73 -11
  46. package/dist/remote/sdkHomeDownload.js.map +1 -1
  47. package/docs/API.md +13 -13
  48. package/docs/CLI.md +28 -16
  49. package/docs/INTEGRATION.md +7 -6
  50. package/package.json +1 -1
  51. package/src/cache.ts +37 -0
  52. package/src/cli.ts +28 -27
  53. package/src/config/meetSdkDefaultConfig.ts +243 -59
  54. package/src/config/meetSdkIosConfig.ts +17 -21
  55. package/src/contracts/types.ts +4 -0
  56. package/src/core/doctor.ts +3 -1
  57. package/src/core/previewPatches.ts +3 -3
  58. package/src/core/workspace.ts +1 -0
  59. package/src/index.ts +5 -3
  60. package/src/ios/integrate.ts +12 -10
  61. package/src/mcp/service.ts +15 -5
  62. package/src/ops/handlers.ts +4 -0
  63. package/src/remote/sdkHomeDownload.ts +82 -15
  64. package/config/meetsdk-android.json +0 -171
  65. package/config/meetsdk-ios.json +0 -15
  66. package/tests/assemble.test.ts +0 -12
  67. package/tests/doctor.test.ts +0 -131
  68. package/tests/downloadGoogleServicesJson.test.ts +0 -47
  69. package/tests/fetch-remote.test.ts +0 -23
  70. package/tests/fetchConfigOverrides.test.ts +0 -28
  71. package/tests/fetchConfigWrite.test.ts +0 -54
  72. package/tests/fixtures-hosts.test.ts +0 -78
  73. package/tests/gradle.test.ts +0 -33
  74. package/tests/integration-json.test.ts +0 -29
  75. package/tests/ios.codeUtils.test.ts +0 -23
  76. package/tests/ios.sdkBundle.test.ts +0 -21
  77. package/tests/loadManifest.test.ts +0 -15
  78. package/tests/manifest-xml.test.ts +0 -30
  79. package/tests/mcp.e2e.ts +0 -214
  80. package/tests/mcp.service.test.ts +0 -53
  81. package/tests/meetSdkRemoteConfig.test.ts +0 -482
  82. package/tests/meetSdkRemoteGradle.test.ts +0 -414
  83. package/tests/pipeline.android.test.ts +0 -149
  84. package/tests/pipeline.integration-json.test.ts +0 -58
  85. package/tests/pipeline.ios.test.ts +0 -608
  86. package/tests/pipeline.preview.patch.test.ts +0 -85
  87. package/tests/platformSelection.test.ts +0 -77
  88. package/tests/sdkHomeDownload.test.ts +0 -224
  89. package/tests/sdkVersionConfig.test.ts +0 -131
  90. package/tests/topsdk.test.ts +0 -53
  91. package/tests/topsdkDownloadSdkConfig.test.ts +0 -81
  92. package/tests/topsdkFeatureModules.test.ts +0 -116
package/src/cli.ts CHANGED
@@ -63,7 +63,6 @@ interface Parsed {
63
63
  topsdkPackageName: string;
64
64
  appTarget: string;
65
65
  sdkHomeApiBaseUrl: string;
66
- iosSdkPlugins: string;
67
66
  iosSdkPackageType: string;
68
67
  }
69
68
 
@@ -74,7 +73,7 @@ meetgames — Android-first integration CLI (TypeScript)
74
73
  Usage:
75
74
  meetgames integrate [--project-root <path>] [--app-id ID] [--channel-type TYPE] [--env prod|pre|test] [--app-target <target>] [--dry-run] [--verbose] [--patch-file <path>]
76
75
  meetgames fetch-config [--app-id ID] [--app-secret SECRET] [--channel-type TYPE] [--env prod|pre|test] [--project-root <path>] [--verbose]
77
- meetgames download-ios-sdk [--plugins csv] [--package-type native|unity|cocos] [--sdk-api-base-url URL] [--verbose]
76
+ meetgames download-ios-sdk [--project-root <path>] [--app-id ID] [--channel-type TYPE] [--env prod|pre|test] [--package-type native|unity|cocos] [--sdk-api-base-url URL] [--verbose]
78
77
  meetgames setup [--app-id ID] [--app-secret SECRET] [--channel-type TYPE] [--env prod|pre|test] [--project-root <path>] [--app-target <target>] [--dry-run] [--verbose] [--patch-file <path>]
79
78
  meetgames doctor [--app-id ID] [--app-secret SECRET] [--channel-type TYPE] [--env prod|pre|test] --project-root <path> [--app-target <target>] [--verbose]
80
79
 
@@ -87,8 +86,8 @@ Notes:
87
86
  - doctor first fetches downloadSDKConfig to the cache, then validates the detected platform integration.
88
87
  - integrate uses recipes/integrate-default.yaml, filtered to the detected platform before execution.
89
88
  - iOS SDK is read from ${MEET_SDK_TOOL_CACHE_ROOT}/sdk/ios/.
90
- - Android integrate/doctor reads the latest SDK version from sdk-home and stores it in config/meetsdk-android.json.
91
- - download-ios-sdk calls sdk-home getDownLoadUrl, saves and extracts the iOS SDK under ${MEET_SDK_TOOL_CACHE_ROOT}/sdk/ios/.
89
+ - Android integrate/doctor reads latest SDK config from sdk-home and stores it under ${MEET_SDK_TOOL_CACHE_ROOT}/configs/sdk-home/.
90
+ - download-ios-sdk reads an iOS meetsdk-remote-config.json, derives sdk-home plugins from it, then calls sdk-home getDownLoadUrl and extracts the iOS SDK under ${MEET_SDK_TOOL_CACHE_ROOT}/sdk/ios/.
92
91
  - fetch-config requires --app-secret (or TOPSDK_APP_SECRET) to sign downloadSDKConfig; appSecret is never read from meetsdk-remote-config.json.
93
92
  - When analytics.firebase is enabled, Android downloads google-services.json to the detected/selected app module; iOS downloads GoogleService-Info.plist next to Info.plist.
94
93
  - fetch-config writes to ${MEET_SDK_TOOL_CACHE_ROOT}/configs/<env>/<appId>/<channelType>/meetsdk-remote-config.json.
@@ -105,11 +104,10 @@ Options (global where noted):
105
104
  TOPSDK_API_BASE_URL fetch-config: 覆盖 API 根地址(如 http://localhost:18080/ 联调本机 console)
106
105
  MEETGAMES_SDK_HOME_API_BASE_URL 覆盖 sdk-home API 根地址(默认 https://business-api.meetgames.com);用于 Android 最新版本查询和 iOS SDK 下载
107
106
  --sdk-api-base-url sdk-home API 根地址;用于 Android 最新版本查询和 iOS SDK 下载
108
- --plugins download-ios-sdk: 逗号分隔插件列表;默认与下载页 iOS 默认选择一致
109
107
  --package-type download-ios-sdk: native(默认)| unity | cocos
110
108
  --dry-run integrate: compute changes and patch preview without writing host project files (default is write to disk)
111
109
  --report-file Write JSON report to path
112
- --patch-file integrate: write unified diff to this path (works with --dry-run). If under <pkg>/preview-patches/, all existing files in that dir are removed first so only the latest patch remains.
110
+ --patch-file integrate: write unified diff to this path (works with --dry-run). If under <pkg>/fixtures/expected-patches/, all existing files in that dir are removed first so only the latest patch remains.
113
111
  --verbose Verbose logs and full patch preview
114
112
 
115
113
  Subcommand notes:
@@ -136,7 +134,6 @@ function parseArgv(argv: string[]): Parsed {
136
134
  topsdkPackageName: "",
137
135
  appTarget: "",
138
136
  sdkHomeApiBaseUrl: "",
139
- iosSdkPlugins: "",
140
137
  iosSdkPackageType: "",
141
138
  };
142
139
  const rest = argv.slice(2);
@@ -179,12 +176,6 @@ function parseArgv(argv: string[]): Parsed {
179
176
  i += 1;
180
177
  continue;
181
178
  }
182
- if (t === "--plugins") {
183
- out.iosSdkPlugins = rest[i + 1] ?? "";
184
- if (!out.iosSdkPlugins || out.iosSdkPlugins.startsWith("-")) fail("--plugins requires a comma-separated list", EXIT.INVALID_ARGS);
185
- i += 1;
186
- continue;
187
- }
188
179
  if (t === "--package-type") {
189
180
  out.iosSdkPackageType = rest[i + 1] ?? "";
190
181
  if (!out.iosSdkPackageType || out.iosSdkPackageType.startsWith("-")) {
@@ -285,7 +276,7 @@ function readLocalMeetSdkRemoteConfig(configPath: string): MeetSdkRemoteConfig |
285
276
  }
286
277
  }
287
278
 
288
- async function cmdFetchConfig(parsed: Parsed): Promise<void> {
279
+ async function cmdFetchConfig(parsed: Parsed): Promise<string> {
289
280
  const selectedConfigPath = cachedRemoteConfigPathFromArgs(parsed);
290
281
  const localConfig = selectedConfigPath ? readLocalMeetSdkRemoteConfig(selectedConfigPath) : null;
291
282
 
@@ -348,6 +339,7 @@ async function cmdFetchConfig(parsed: Parsed): Promise<void> {
348
339
  console.log(`[meetgames] GET ${requestUrl}`);
349
340
  console.log(`[meetgames] metadata: ${written.metadataPath}`);
350
341
  }
342
+ return written.configPath;
351
343
  }
352
344
 
353
345
  async function cmdIntegrate(parsed: Parsed): Promise<void> {
@@ -394,38 +386,47 @@ async function cmdIntegrate(parsed: Parsed): Promise<void> {
394
386
  async function cmdSetup(parsed: Parsed): Promise<void> {
395
387
  const { platform } = selectedPlatformContext(parsed);
396
388
  console.log("[meetgames] setup: fetching remote config…");
397
- await cmdFetchConfig(parsed);
389
+ const configPath = await cmdFetchConfig(parsed);
398
390
  if (platform === "ios") {
399
391
  console.log("[meetgames] setup: ensuring iOS SDK cache…");
400
- await cmdDownloadIosSdk(parsed);
392
+ await cmdDownloadIosSdk(parsed, configPath);
401
393
  }
402
394
  console.log("[meetgames] setup: integrating project…");
403
395
  await cmdIntegrate(parsed);
404
396
  }
405
397
 
406
- async function cmdDownloadIosSdk(parsed: Parsed): Promise<void> {
398
+ function resolveIosDownloadRemoteConfigPath(parsed: Parsed, explicitPath?: string): string {
399
+ const candidates = [
400
+ explicitPath,
401
+ cachedRemoteConfigPathFromArgs(parsed),
402
+ path.join(parsed.projectRoot, MEETSDK_REMOTE_CONFIG_FILENAME),
403
+ ]
404
+ .filter((p): p is string => Boolean(p))
405
+ .filter((p, i, arr) => arr.indexOf(p) === i);
406
+ const found = candidates.find((p) => fs.existsSync(p));
407
+ if (found) return found;
408
+ fail(
409
+ `download-ios-sdk requires an iOS ${MEETSDK_REMOTE_CONFIG_FILENAME}; checked: ${candidates.join(", ")}`,
410
+ EXIT.CONFIG_ERROR
411
+ );
412
+ }
413
+
414
+ async function cmdDownloadIosSdk(parsed: Parsed, remoteConfigPath?: string): Promise<void> {
407
415
  const baseUrl =
408
416
  parsed.sdkHomeApiBaseUrl || process.env.MEETGAMES_SDK_HOME_API_BASE_URL?.trim() || defaultSdkHomeApiBaseUrl;
409
417
  const packageType = parsed.iosSdkPackageType || DEFAULT_IOS_SDK_PACKAGE_TYPE;
410
418
  if (!["native", "unity", "cocos"].includes(packageType)) {
411
419
  fail(`invalid --package-type (use native, unity, or cocos): ${packageType}`, EXIT.INVALID_ARGS);
412
420
  }
413
- const plugins = parsed.iosSdkPlugins
414
- ? parsed.iosSdkPlugins
415
- .split(",")
416
- .map((s) => s.trim())
417
- .filter(Boolean)
418
- : undefined;
419
- if (parsed.iosSdkPlugins && !plugins?.length) {
420
- fail("--plugins must contain at least one plugin", EXIT.INVALID_ARGS);
421
- }
421
+ const configPath = resolveIosDownloadRemoteConfigPath(parsed, remoteConfigPath);
422
422
 
423
+ console.log(`[meetgames] download-ios-sdk: using remote config ${configPath}`);
423
424
  console.log("[meetgames] download-ios-sdk: resolving sdk-home iOS SDK…");
424
425
  let result;
425
426
  try {
426
427
  result = await downloadIosSdkToBundled(resolvePackageRoot(), {
427
428
  baseUrl,
428
- plugins,
429
+ remoteConfigPath: configPath,
429
430
  packageType,
430
431
  });
431
432
  } catch (e) {
@@ -1,42 +1,159 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { tryParseAsMeetSdkDefaultConfig, type MeetSdkDefaultConfig } from "./meetSdkRemoteConfig.js";
5
- import { defaultSdkHomeApiBaseUrl, fetchSdkHomeVersions, type SdkHomePlatformVersion } from "../remote/sdkHomeDownload.js";
3
+ import {
4
+ MEETSDK_ANDROID_CONFIG_FILENAME,
5
+ resolveAndroidSdkCacheLayout,
6
+ resolveMeetSdkAndroidConfigCachePath,
7
+ withCacheLock,
8
+ } from "../cache.js";
9
+ import {
10
+ buildSdkHomeDownloadUrl,
11
+ defaultSdkHomeApiBaseUrl,
12
+ downloadBinaryFile,
13
+ extractZip,
14
+ fetchSdkHomeVersions,
15
+ type SdkHomePlatformVersion,
16
+ } from "../remote/sdkHomeDownload.js";
17
+ import {
18
+ DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES,
19
+ tryParseAsMeetSdkDefaultConfig,
20
+ type MeetSdkDefaultConfig,
21
+ } from "./meetSdkRemoteConfig.js";
6
22
 
7
- /** Built-in Android SDK defaults (repos and per-plugin dependencies). */
8
- export const MEETSDK_ANDROID_DEFAULTS_FILENAME = "meetsdk-android.json";
9
- const MEETSDK_CONFIG_DIR = "config";
23
+ const DEFAULT_GROUP_ID = "com.sino.topsdk";
24
+ const DEFAULT_ANDROID_SDK_PACKAGE_TYPE = "native";
10
25
 
11
- const defaultConfigPath = fileURLToPath(
12
- new URL(`../../${MEETSDK_CONFIG_DIR}/${MEETSDK_ANDROID_DEFAULTS_FILENAME}`, import.meta.url)
13
- );
26
+ type JsonRecord = Record<string, unknown>;
14
27
 
15
- export function resolveMeetSdkAndroidConfigPath(packageRoot?: string): string {
16
- return packageRoot ? path.join(packageRoot, MEETSDK_CONFIG_DIR, MEETSDK_ANDROID_DEFAULTS_FILENAME) : defaultConfigPath;
28
+ function isRecord(value: unknown): value is JsonRecord {
29
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17
30
  }
18
31
 
19
- export function loadBuiltInMeetSdkDefaultConfig(options: { packageRoot?: string } = {}): MeetSdkDefaultConfig {
20
- const configPath = resolveMeetSdkAndroidConfigPath(options.packageRoot);
21
- if (!fs.existsSync(configPath)) {
22
- throw new Error(
23
- `built-in ${MEETSDK_ANDROID_DEFAULTS_FILENAME} not found at ${configPath}; reinstall @leonxin/meetgames or run from a complete package install`
24
- );
32
+ function cloneDefaultConfig(value: MeetSdkDefaultConfig): MeetSdkDefaultConfig {
33
+ return JSON.parse(JSON.stringify(value)) as MeetSdkDefaultConfig;
34
+ }
35
+
36
+ function resolveSdkHomeApiBaseUrl(baseUrl?: string): string {
37
+ return baseUrl || process.env.MEETGAMES_SDK_HOME_API_BASE_URL?.trim() || defaultSdkHomeApiBaseUrl;
38
+ }
39
+
40
+ function parseJsonResponseEnvelope(body: unknown, label: string): unknown {
41
+ if (!isRecord(body)) throw new Error(`${label} response is not an object`);
42
+ const code = body.code;
43
+ if (code !== undefined && Number(code) !== 200) {
44
+ const message = typeof body.message === "string" ? body.message : JSON.stringify(body);
45
+ throw new Error(`${label} API error (code=${String(code)}): ${message}`);
25
46
  }
26
- const raw = JSON.parse(fs.readFileSync(configPath, "utf8")) as unknown;
27
- const parsed = tryParseAsMeetSdkDefaultConfig(raw);
47
+ return "data" in body ? body.data : body;
48
+ }
49
+
50
+ async function fetchJson(url: string, signal?: AbortSignal): Promise<unknown> {
51
+ let res: Response;
52
+ try {
53
+ res = await fetch(url, { redirect: "follow", signal, headers: { Accept: "application/json" } });
54
+ } catch (e) {
55
+ const cause = e instanceof Error && "cause" in e && e.cause instanceof Error ? e.cause.message : null;
56
+ const detail = cause && cause !== (e instanceof Error ? e.message : String(e)) ? ` (${cause})` : "";
57
+ throw new Error(`GET ${url} failed: ${e instanceof Error ? e.message : String(e)}${detail}`);
58
+ }
59
+ if (!res.ok) {
60
+ const text = await res.text().catch(() => "");
61
+ const detail = text.trim() ? `: ${text.trim().slice(0, 1000)}` : "";
62
+ throw new Error(`GET ${url} failed: HTTP ${res.status}${detail}`);
63
+ }
64
+ const text = await res.text();
65
+ try {
66
+ return JSON.parse(text) as unknown;
67
+ } catch {
68
+ throw new Error(`GET ${url} did not return valid JSON`);
69
+ }
70
+ }
71
+
72
+ export function resolveMeetSdkAndroidConfigPath(cacheRoot?: string): string {
73
+ return resolveMeetSdkAndroidConfigCachePath(cacheRoot);
74
+ }
75
+
76
+ function readMeetSdkDefaultConfigFromPath(configPath: string): MeetSdkDefaultConfig {
77
+ const parsed = tryParseAsMeetSdkDefaultConfig(JSON.parse(fs.readFileSync(configPath, "utf8")) as unknown);
28
78
  if (!parsed) {
29
- throw new Error(`built-in ${MEETSDK_ANDROID_DEFAULTS_FILENAME} is not a valid MeetSdk default config`);
79
+ throw new Error(`${MEETSDK_ANDROID_CONFIG_FILENAME} is not a valid MeetSdk default config: ${configPath}`);
30
80
  }
31
81
  return parsed;
32
82
  }
33
83
 
34
- function cloneDefaultConfig(value: MeetSdkDefaultConfig): MeetSdkDefaultConfig {
35
- return JSON.parse(JSON.stringify(value)) as MeetSdkDefaultConfig;
84
+ export function loadBuiltInMeetSdkDefaultConfig(options: { cacheRoot?: string; packageRoot?: string } = {}): MeetSdkDefaultConfig {
85
+ const cachePath = resolveMeetSdkAndroidConfigPath(options.cacheRoot);
86
+ if (fs.existsSync(cachePath)) return readMeetSdkDefaultConfigFromPath(cachePath);
87
+ return compatibilityDefaultConfig();
36
88
  }
37
89
 
38
- function resolveSdkHomeApiBaseUrl(baseUrl?: string): string {
39
- return baseUrl || process.env.MEETGAMES_SDK_HOME_API_BASE_URL?.trim() || defaultSdkHomeApiBaseUrl;
90
+ function compatibilityDefaultConfig(): MeetSdkDefaultConfig {
91
+ const raw = {
92
+ topsdk: {
93
+ version: "",
94
+ groupId: DEFAULT_GROUP_ID,
95
+ repositories: ["https://storage-sdk-gameplus.meetsocial.com/repository/TopSdk/"],
96
+ },
97
+ sdkModules: {
98
+ login: {
99
+ guest: { dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.login.guest] },
100
+ email: { dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.login.email] },
101
+ apple: { dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.login.apple] },
102
+ facebook: { dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.login.facebook] },
103
+ google: { dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.login.google] },
104
+ twitter: { dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.login.twitter] },
105
+ snapchat: { dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.login.snapchat] },
106
+ line: { dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.login.line] },
107
+ naver: { dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.login.naver] },
108
+ kakao: {
109
+ repositories: ["https://devrepo.kakao.com/nexus/content/groups/public/"],
110
+ dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.login.kakao],
111
+ },
112
+ tiktok: {
113
+ repositories: ["https://artifact.bytedance.com/repository/AwemeOpenSDK"],
114
+ dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.login.tiktok],
115
+ },
116
+ discord: { dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.login.discord] },
117
+ },
118
+ payment: {
119
+ googleIap: { dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.payment.googleIap] },
120
+ appleIap: { dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.payment.appleIap] },
121
+ onestoreIap: {
122
+ repositories: ["https://repo.onestore.co.kr/repository/onestore-sdk-public/"],
123
+ dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.payment.onestoreIap],
124
+ },
125
+ huaweiIap: {
126
+ repositories: ["https://developer.huawei.com/repo/"],
127
+ dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.payment.huaweiIap],
128
+ },
129
+ xiaomiIap: { dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.payment.xiaomiIap] },
130
+ },
131
+ analytics: {
132
+ appsflyer: {
133
+ repositories: ["mavenCentral"],
134
+ dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.analytics.appsflyer],
135
+ },
136
+ facebookdata: { dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.analytics.facebookdata] },
137
+ firebase: {
138
+ repositories: ["google"],
139
+ classpath: "com.google.gms:google-services:4.4.4",
140
+ applyplugin: "com.google.gms.google-services",
141
+ dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.analytics.firebase],
142
+ },
143
+ adjust: { dependencies: [...DEFAULT_TOPSDK_PLUGIN_DEPENDENCIES.analytics.adjust] },
144
+ },
145
+ },
146
+ };
147
+ const parsed = tryParseAsMeetSdkDefaultConfig(raw);
148
+ if (!parsed) throw new Error("embedded compatibility meetsdk-android defaults are invalid");
149
+ return parsed;
150
+ }
151
+
152
+ function writeMeetSdkAndroidConfigCache(config: MeetSdkDefaultConfig, cacheRoot?: string): string {
153
+ const configPath = resolveMeetSdkAndroidConfigPath(cacheRoot);
154
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
155
+ fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
156
+ return configPath;
40
157
  }
41
158
 
42
159
  const latestAndroidVersionCache = new Map<string, Promise<SdkHomePlatformVersion>>();
@@ -51,50 +168,117 @@ async function fetchLatestAndroidSdkVersion(baseUrl?: string): Promise<SdkHomePl
51
168
  return cached;
52
169
  }
53
170
 
54
- export function syncMeetSdkAndroidVersionToConfig(params: {
55
- version: string;
56
- date?: string;
57
- packageRoot?: string;
58
- }): string {
59
- const version = params.version.trim();
60
- if (!version) throw new Error("Android SDK version is empty");
171
+ function androidChannelPlugin(channelType?: string): string {
172
+ const normalized = (channelType ?? "").trim().toUpperCase();
173
+ const map: Record<string, string> = {
174
+ GOOGLE: "google_channel",
175
+ ONESTORE: "onestore_channel",
176
+ XIAOMI: "xiaomi_channel",
177
+ HUAWEI: "huawei_channel",
178
+ };
179
+ return map[normalized] ?? "google_channel";
180
+ }
61
181
 
62
- const configPath = resolveMeetSdkAndroidConfigPath(params.packageRoot);
63
- if (!fs.existsSync(configPath)) {
64
- throw new Error(`built-in ${MEETSDK_ANDROID_DEFAULTS_FILENAME} not found at ${configPath}`);
65
- }
66
- const raw = JSON.parse(fs.readFileSync(configPath, "utf8")) as unknown;
67
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
68
- throw new Error(`built-in ${MEETSDK_ANDROID_DEFAULTS_FILENAME} is not a JSON object`);
182
+ function findMeetSdkAndroidConfigFile(root: string): string | null {
183
+ if (!fs.existsSync(root)) return null;
184
+ const stack = [root];
185
+ while (stack.length) {
186
+ const dir = stack.pop()!;
187
+ for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
188
+ if (ent.name.startsWith("__MACOSX") || ent.name.startsWith("._")) continue;
189
+ const abs = path.join(dir, ent.name);
190
+ if (ent.isDirectory()) {
191
+ stack.push(abs);
192
+ } else if (ent.isFile() && ent.name === MEETSDK_ANDROID_CONFIG_FILENAME) {
193
+ return abs;
194
+ }
195
+ }
69
196
  }
70
- const record = raw as Record<string, unknown>;
71
- if (typeof record.topsdk !== "object" || record.topsdk === null || Array.isArray(record.topsdk)) {
72
- throw new Error(`built-in ${MEETSDK_ANDROID_DEFAULTS_FILENAME} does not contain topsdk object`);
73
- }
74
- const topsdk = record.topsdk as Record<string, unknown>;
75
- topsdk.version = version;
76
- if (params.date?.trim()) topsdk.date = params.date.trim();
77
- fs.writeFileSync(configPath, `${JSON.stringify(record, null, 2)}\n`, "utf8");
78
- return configPath;
197
+ return null;
79
198
  }
80
199
 
200
+ function loadMeetSdkAndroidConfigFromExtracted(root: string, version: string, date?: string): MeetSdkDefaultConfig {
201
+ const configPath = findMeetSdkAndroidConfigFile(root);
202
+ if (!configPath) throw new Error(`${MEETSDK_ANDROID_CONFIG_FILENAME} not found under downloaded Android SDK: ${root}`);
203
+ const config = readMeetSdkDefaultConfigFromPath(configPath);
204
+ config.topsdk.version = config.topsdk.version || version;
205
+ config.topsdk.date = config.topsdk.date || date;
206
+ return config;
207
+ }
208
+
209
+ async function fetchSdkHomeAndroidConfig(params: {
210
+ baseUrl?: string;
211
+ version: string;
212
+ date?: string;
213
+ channelType?: string;
214
+ packageType?: string;
215
+ cacheRoot?: string;
216
+ signal?: AbortSignal;
217
+ }): Promise<{ url?: string; config: MeetSdkDefaultConfig }> {
218
+ const baseUrl = resolveSdkHomeApiBaseUrl(params.baseUrl);
219
+ const packageType = params.packageType || DEFAULT_ANDROID_SDK_PACKAGE_TYPE;
220
+ const plugins = [androidChannelPlugin(params.channelType)];
221
+ const layout = resolveAndroidSdkCacheLayout({
222
+ version: params.version,
223
+ packageType,
224
+ plugins,
225
+ cacheRoot: params.cacheRoot,
226
+ });
227
+ let downloadApiUrl: string | undefined;
228
+ const downloadAndExtract = async (): Promise<void> => {
229
+ if (findMeetSdkAndroidConfigFile(layout.extractDir)) return;
230
+ downloadApiUrl = buildSdkHomeDownloadUrl({
231
+ baseUrl,
232
+ version: params.version,
233
+ platform: "android",
234
+ packageType,
235
+ plugins,
236
+ });
237
+ const body = parseJsonResponseEnvelope(await fetchJson(downloadApiUrl, params.signal), "sdk-home Android download");
238
+ if (typeof body !== "string" || !body.trim()) {
239
+ throw new Error("sdk-home Android getDownLoadUrl response does not contain a download URL in data");
240
+ }
241
+ await downloadBinaryFile(body, layout.zipPath, params.signal);
242
+ fs.rmSync(layout.extractDir, { recursive: true, force: true });
243
+ await extractZip(layout.zipPath, layout.extractDir);
244
+ };
245
+ await withCacheLock(layout.lockDir, downloadAndExtract);
246
+ return {
247
+ url: downloadApiUrl,
248
+ config: loadMeetSdkAndroidConfigFromExtracted(layout.extractDir, params.version, params.date),
249
+ };
250
+ }
251
+
252
+ const androidConfigCache = new Map<string, Promise<MeetSdkDefaultConfig>>();
253
+
81
254
  /**
82
- * Built-in Android SDK defaults with the SDK version resolved at runtime from sdk-home.
83
- * The version is also persisted to `config/meetsdk-android.json` so the package config
84
- * reflects the latest SDK version discovered from the download center.
255
+ * Android SDK defaults resolved from sdk-home and persisted under ~/.cache/meet-sdk-tool.
256
+ * The package config/ directory is no longer written at runtime.
85
257
  */
86
258
  export async function loadMeetSdkDefaultConfigWithLatestAndroidVersion(options: {
87
259
  sdkHomeApiBaseUrl?: string;
260
+ cacheRoot?: string;
88
261
  packageRoot?: string;
262
+ channelType?: string;
263
+ packageType?: string;
89
264
  } = {}): Promise<MeetSdkDefaultConfig> {
90
- const defaults = cloneDefaultConfig(loadBuiltInMeetSdkDefaultConfig({ packageRoot: options.packageRoot }));
91
265
  const latest = await fetchLatestAndroidSdkVersion(options.sdkHomeApiBaseUrl);
92
- syncMeetSdkAndroidVersionToConfig({
93
- version: latest.ver,
94
- date: latest.date,
95
- packageRoot: options.packageRoot,
96
- });
97
- defaults.topsdk.version = latest.ver;
98
- defaults.topsdk.date = latest.date;
99
- return defaults;
266
+ const baseUrl = resolveSdkHomeApiBaseUrl(options.sdkHomeApiBaseUrl);
267
+ const key = `${baseUrl}\n${latest.ver}\n${options.cacheRoot ?? ""}\n${options.channelType ?? ""}\n${options.packageType ?? ""}`;
268
+ let cached = androidConfigCache.get(key);
269
+ if (!cached) {
270
+ cached = fetchSdkHomeAndroidConfig({
271
+ baseUrl,
272
+ version: latest.ver,
273
+ date: latest.date,
274
+ channelType: options.channelType,
275
+ packageType: options.packageType,
276
+ cacheRoot: options.cacheRoot,
277
+ }).then(({ config }) => {
278
+ writeMeetSdkAndroidConfigCache(config, options.cacheRoot);
279
+ return cloneDefaultConfig(config);
280
+ });
281
+ androidConfigCache.set(key, cached);
282
+ }
283
+ return cloneDefaultConfig(await cached);
100
284
  }
@@ -1,8 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
-
4
- export const MEETSDK_IOS_CONFIG_FILENAME = "meetsdk-ios.json";
5
- const MEETSDK_CONFIG_DIR = "config";
3
+ import { MEETSDK_IOS_CONFIG_FILENAME, resolveMeetSdkIosConfigCachePath } from "../cache.js";
6
4
 
7
5
  export interface MeetSdkIosConfig {
8
6
  topsdk: {
@@ -27,8 +25,8 @@ function normalizePlugins(value: unknown): string[] | undefined {
27
25
  return plugins.length ? plugins : undefined;
28
26
  }
29
27
 
30
- export function resolveMeetSdkIosConfigPath(packageRoot: string): string {
31
- return path.join(packageRoot, MEETSDK_CONFIG_DIR, MEETSDK_IOS_CONFIG_FILENAME);
28
+ export function resolveMeetSdkIosConfigPath(cacheRoot?: string): string {
29
+ return resolveMeetSdkIosConfigCachePath(cacheRoot);
32
30
  }
33
31
 
34
32
  export function tryParseAsMeetSdkIosConfig(raw: unknown): MeetSdkIosConfig | null {
@@ -44,8 +42,8 @@ export function tryParseAsMeetSdkIosConfig(raw: unknown): MeetSdkIosConfig | nul
44
42
  };
45
43
  }
46
44
 
47
- export function loadMeetSdkIosConfig(packageRoot: string): MeetSdkIosConfig {
48
- const configPath = resolveMeetSdkIosConfigPath(packageRoot);
45
+ export function loadMeetSdkIosConfig(cacheRoot?: string): MeetSdkIosConfig {
46
+ const configPath = resolveMeetSdkIosConfigPath(cacheRoot);
49
47
  if (!fs.existsSync(configPath)) {
50
48
  return { topsdk: { version: "" } };
51
49
  }
@@ -56,15 +54,15 @@ export function loadMeetSdkIosConfig(packageRoot: string): MeetSdkIosConfig {
56
54
  return parsed;
57
55
  }
58
56
 
59
- export function writeMeetSdkIosConfig(packageRoot: string, config: MeetSdkIosConfig): string {
60
- const configPath = resolveMeetSdkIosConfigPath(packageRoot);
57
+ export function writeMeetSdkIosConfig(config: MeetSdkIosConfig, cacheRoot?: string): string {
58
+ const configPath = resolveMeetSdkIosConfigPath(cacheRoot);
61
59
  fs.mkdirSync(path.dirname(configPath), { recursive: true });
62
60
  fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
63
61
  return configPath;
64
62
  }
65
63
 
66
64
  export function syncMeetSdkIosVersionToConfig(params: {
67
- packageRoot: string;
65
+ cacheRoot?: string;
68
66
  version: string;
69
67
  date?: string;
70
68
  packageType?: string;
@@ -73,17 +71,15 @@ export function syncMeetSdkIosVersionToConfig(params: {
73
71
  const version = params.version.trim();
74
72
  if (!version) throw new Error("iOS SDK version is empty");
75
73
 
76
- const current = loadMeetSdkIosConfig(params.packageRoot);
77
- const nextDate = params.date === undefined ? current.topsdk.date : params.date.trim() || undefined;
74
+ const topsdk: MeetSdkIosConfig["topsdk"] = { version };
75
+ const date = params.date?.trim();
76
+ const packageType = params.packageType?.trim();
77
+ const plugins = params.plugins?.map((x) => x.trim()).filter(Boolean);
78
+ if (date) topsdk.date = date;
79
+ if (packageType) topsdk.packageType = packageType;
80
+ if (plugins?.length) topsdk.plugins = plugins;
78
81
  const next: MeetSdkIosConfig = {
79
- ...current,
80
- topsdk: {
81
- ...current.topsdk,
82
- version,
83
- date: nextDate,
84
- packageType: params.packageType?.trim() || current.topsdk.packageType,
85
- plugins: params.plugins?.map((x) => x.trim()).filter(Boolean) || current.topsdk.plugins,
86
- },
82
+ topsdk,
87
83
  };
88
- return writeMeetSdkIosConfig(params.packageRoot, next);
84
+ return writeMeetSdkIosConfig(next, params.cacheRoot);
89
85
  }
@@ -57,6 +57,8 @@ export interface WorkspaceContext {
57
57
  remoteConfigPath?: string;
58
58
  /** Cached iOS SDK root used as the source for integration resources. */
59
59
  iosSdkRoot?: string;
60
+ /** Optional cache root override, primarily used by tests. */
61
+ cacheRoot?: string;
60
62
  android?: AndroidDetectResult;
61
63
  /** Reserved: populated via placeholder until iOS tooling ships. */
62
64
  ios?: IOSDetectResult;
@@ -72,6 +74,8 @@ export interface WorkspaceContextOptions {
72
74
  remoteConfigPath?: string;
73
75
  /** Override the iOS SDK source root. */
74
76
  iosSdkRoot?: string;
77
+ /** Override meet-sdk-tool cache root. */
78
+ cacheRoot?: string;
75
79
  }
76
80
 
77
81
  export interface ManifestStep {
@@ -198,6 +198,8 @@ async function checkAndroid(ctx: WorkspaceContext, report: DoctorReport, config:
198
198
  await loadMeetSdkDefaultConfigWithLatestAndroidVersion({
199
199
  sdkHomeApiBaseUrl: ctx.sdkHomeApiBaseUrl,
200
200
  packageRoot: ctx.packageRoot,
201
+ cacheRoot: ctx.cacheRoot,
202
+ channelType: config.channel,
201
203
  })
202
204
  );
203
205
  const moduleGradleAbs = moduleBuildGradlePath(ctx);
@@ -355,7 +357,7 @@ async function checkIos(ctx: WorkspaceContext, report: DoctorReport, config: Mee
355
357
  for (const folder of enabledIosPluginFolders(config)) {
356
358
  const plugin = findPluginByFolder(sdkRoot, folder);
357
359
  if (plugin) pluginConfigs.push(plugin);
358
- else report.warnings.push(`iOS plugin folder not in SDK package: plugins/${folder}`);
360
+ else report.warnings.push(`iOS config enables ${folder}, but current SDK package does not include plugins/${folder}; skipped`);
359
361
  }
360
362
  const loadedConfigs = [...coreConfigs, ...pluginConfigs];
361
363
  const resourceErrors = loadedConfigs.flatMap(validateLoadedPluginResourcesForDoctor);
@@ -1,13 +1,13 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
- /** Default directory for `pipeline.patch` / `cli-preview.patch` (gitignored). */
4
+ /** Default directory for expected dry-run patch artifacts. */
5
5
  export function resolveDefaultPreviewPatchDir(packageRoot: string): string {
6
- return path.join(packageRoot, "preview-patches");
6
+ return path.join(packageRoot, "fixtures", "expected-patches");
7
7
  }
8
8
 
9
9
  /**
10
- * Before writing a new patch under `preview-patches/`, remove all existing files there
10
+ * Before writing a new patch under `fixtures/expected-patches/`, remove all existing files there
11
11
  * so only the latest command output remains.
12
12
  */
13
13
  export function clearPreviewPatchFilesIfTargetInside(packageRoot: string, patchFileAbs: string): void {
@@ -24,6 +24,7 @@ export function buildWorkspaceContext(
24
24
  sdkHomeApiBaseUrl: options.sdkHomeApiBaseUrl,
25
25
  remoteConfigPath: options.remoteConfigPath,
26
26
  iosSdkRoot: options.iosSdkRoot,
27
+ cacheRoot: options.cacheRoot,
27
28
  android: detectAndroid(root, { appTarget: options.appTarget }),
28
29
  ios: detectIOS(root, { appTarget: options.appTarget }),
29
30
  iosReserved: iosToolingReserved,
package/src/index.ts CHANGED
@@ -61,10 +61,8 @@ export {
61
61
  loadBuiltInMeetSdkDefaultConfig,
62
62
  loadMeetSdkDefaultConfigWithLatestAndroidVersion,
63
63
  resolveMeetSdkAndroidConfigPath,
64
- syncMeetSdkAndroidVersionToConfig,
65
64
  } from "./config/meetSdkDefaultConfig.js";
66
65
  export {
67
- MEETSDK_IOS_CONFIG_FILENAME,
68
66
  loadMeetSdkIosConfig,
69
67
  resolveMeetSdkIosConfigPath,
70
68
  syncMeetSdkIosVersionToConfig,
@@ -81,9 +79,13 @@ export {
81
79
  } from "./config/topsdkFeatureModules.js";
82
80
  export { clearPreviewPatchFilesIfTargetInside, resolveDefaultPreviewPatchDir } from "./core/previewPatches.js";
83
81
  export {
82
+ MEETSDK_ANDROID_CONFIG_FILENAME,
83
+ MEETSDK_IOS_CONFIG_FILENAME,
84
84
  MEET_SDK_TOOL_CACHE_ROOT,
85
85
  ensureCacheRoot,
86
86
  resolveIosSdkCacheLayout,
87
+ resolveMeetSdkAndroidConfigCachePath,
88
+ resolveMeetSdkIosConfigCachePath,
87
89
  resolveRemoteConfigCachePath,
88
90
  writeRemoteConfigCache,
89
91
  } from "./cache.js";
@@ -108,7 +110,6 @@ export {
108
110
  } from "./remote/topsdkDownloadSdkConfig.js";
109
111
  export {
110
112
  DEFAULT_IOS_SDK_PACKAGE_TYPE,
111
- DEFAULT_IOS_SDK_PLUGINS,
112
113
  buildSdkHomeDownloadUrl,
113
114
  buildSdkHomeVersionUrl,
114
115
  defaultSdkHomeApiBaseUrl,
@@ -118,6 +119,7 @@ export {
118
119
  fetchSdkHomeIosDownloadUrl,
119
120
  fetchSdkHomeIosVersion,
120
121
  fetchSdkHomeVersions,
122
+ resolveIosSdkDownloadPluginsFromRemoteConfig,
121
123
  resolveIosSdkRootFromDirectory,
122
124
  resolveIosSdkZipFileName,
123
125
  type DownloadIosSdkOptions,