@leonxin/meetgames 0.1.18 → 0.1.20

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 (109) hide show
  1. package/README.md +16 -6
  2. package/dist/android/detect.d.ts +6 -1
  3. package/dist/android/detect.d.ts.map +1 -1
  4. package/dist/android/detect.js +41 -17
  5. package/dist/android/detect.js.map +1 -1
  6. package/dist/android/gradle.d.ts.map +1 -1
  7. package/dist/android/gradle.js +8 -1
  8. package/dist/android/gradle.js.map +1 -1
  9. package/dist/android/meetSdkRemoteGradle.d.ts +1 -0
  10. package/dist/android/meetSdkRemoteGradle.d.ts.map +1 -1
  11. package/dist/android/meetSdkRemoteGradle.js +44 -21
  12. package/dist/android/meetSdkRemoteGradle.js.map +1 -1
  13. package/dist/cache.d.ts +16 -0
  14. package/dist/cache.d.ts.map +1 -1
  15. package/dist/cache.js +21 -0
  16. package/dist/cache.js.map +1 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +52 -23
  19. package/dist/cli.js.map +1 -1
  20. package/dist/config/meetSdkDefaultConfig.d.ts +7 -11
  21. package/dist/config/meetSdkDefaultConfig.d.ts.map +1 -1
  22. package/dist/config/meetSdkDefaultConfig.js +216 -53
  23. package/dist/config/meetSdkDefaultConfig.js.map +1 -1
  24. package/dist/config/meetSdkIosConfig.d.ts +4 -5
  25. package/dist/config/meetSdkIosConfig.d.ts.map +1 -1
  26. package/dist/config/meetSdkIosConfig.js +19 -19
  27. package/dist/config/meetSdkIosConfig.js.map +1 -1
  28. package/dist/contracts/types.d.ts +18 -0
  29. package/dist/contracts/types.d.ts.map +1 -1
  30. package/dist/core/doctor.d.ts.map +1 -1
  31. package/dist/core/doctor.js +18 -1
  32. package/dist/core/doctor.js.map +1 -1
  33. package/dist/core/platform.js +1 -1
  34. package/dist/core/platform.js.map +1 -1
  35. package/dist/core/previewPatches.d.ts +2 -2
  36. package/dist/core/previewPatches.d.ts.map +1 -1
  37. package/dist/core/previewPatches.js +3 -3
  38. package/dist/core/previewPatches.js.map +1 -1
  39. package/dist/core/workspace.d.ts.map +1 -1
  40. package/dist/core/workspace.js +3 -1
  41. package/dist/core/workspace.js.map +1 -1
  42. package/dist/index.d.ts +4 -4
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +4 -4
  45. package/dist/index.js.map +1 -1
  46. package/dist/mcp/server.d.ts.map +1 -1
  47. package/dist/mcp/server.js +12 -7
  48. package/dist/mcp/server.js.map +1 -1
  49. package/dist/mcp/service.d.ts +6 -0
  50. package/dist/mcp/service.d.ts.map +1 -1
  51. package/dist/mcp/service.js +17 -9
  52. package/dist/mcp/service.js.map +1 -1
  53. package/dist/ops/handlers.d.ts.map +1 -1
  54. package/dist/ops/handlers.js +25 -3
  55. package/dist/ops/handlers.js.map +1 -1
  56. package/dist/remote/sdkHomeDownload.d.ts +4 -5
  57. package/dist/remote/sdkHomeDownload.d.ts.map +1 -1
  58. package/dist/remote/sdkHomeDownload.js +38 -12
  59. package/dist/remote/sdkHomeDownload.js.map +1 -1
  60. package/docs/API.md +13 -13
  61. package/docs/CLI.md +27 -13
  62. package/docs/INTEGRATION.md +7 -6
  63. package/package.json +1 -1
  64. package/src/android/detect.ts +47 -17
  65. package/src/android/gradle.ts +16 -1
  66. package/src/android/meetSdkRemoteGradle.ts +50 -22
  67. package/src/cache.ts +37 -0
  68. package/src/cli.ts +51 -21
  69. package/src/config/meetSdkDefaultConfig.ts +243 -59
  70. package/src/config/meetSdkIosConfig.ts +17 -21
  71. package/src/contracts/types.ts +21 -0
  72. package/src/core/doctor.ts +17 -1
  73. package/src/core/platform.ts +1 -1
  74. package/src/core/previewPatches.ts +3 -3
  75. package/src/core/workspace.ts +3 -1
  76. package/src/index.ts +5 -3
  77. package/src/mcp/server.ts +12 -7
  78. package/src/mcp/service.ts +30 -10
  79. package/src/ops/handlers.ts +27 -3
  80. package/src/remote/sdkHomeDownload.ts +48 -16
  81. package/config/meetsdk-android.json +0 -171
  82. package/config/meetsdk-ios.json +0 -15
  83. package/tests/assemble.test.ts +0 -12
  84. package/tests/doctor.test.ts +0 -131
  85. package/tests/downloadGoogleServicesJson.test.ts +0 -47
  86. package/tests/fetch-remote.test.ts +0 -23
  87. package/tests/fetchConfigOverrides.test.ts +0 -28
  88. package/tests/fetchConfigWrite.test.ts +0 -54
  89. package/tests/fixtures-hosts.test.ts +0 -78
  90. package/tests/gradle.test.ts +0 -33
  91. package/tests/integration-json.test.ts +0 -29
  92. package/tests/ios.codeUtils.test.ts +0 -23
  93. package/tests/ios.sdkBundle.test.ts +0 -21
  94. package/tests/loadManifest.test.ts +0 -15
  95. package/tests/manifest-xml.test.ts +0 -30
  96. package/tests/mcp.e2e.ts +0 -214
  97. package/tests/mcp.service.test.ts +0 -53
  98. package/tests/meetSdkRemoteConfig.test.ts +0 -482
  99. package/tests/meetSdkRemoteGradle.test.ts +0 -414
  100. package/tests/pipeline.android.test.ts +0 -149
  101. package/tests/pipeline.integration-json.test.ts +0 -58
  102. package/tests/pipeline.ios.test.ts +0 -609
  103. package/tests/pipeline.preview.patch.test.ts +0 -85
  104. package/tests/platformSelection.test.ts +0 -77
  105. package/tests/sdkHomeDownload.test.ts +0 -275
  106. package/tests/sdkVersionConfig.test.ts +0 -131
  107. package/tests/topsdk.test.ts +0 -53
  108. package/tests/topsdkDownloadSdkConfig.test.ts +0 -81
  109. package/tests/topsdkFeatureModules.test.ts +0 -116
@@ -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
  }
@@ -12,6 +12,19 @@ export interface AndroidDetectionError {
12
12
 
13
13
  export type AndroidDetectResult = AndroidDetection | AndroidDetectionError;
14
14
 
15
+ export interface AndroidSdkModule {
16
+ ok: true;
17
+ moduleName: string;
18
+ moduleDir: string;
19
+ }
20
+
21
+ export interface AndroidSdkModuleError {
22
+ ok: false;
23
+ error: string;
24
+ }
25
+
26
+ export type AndroidSdkModuleResult = AndroidSdkModule | AndroidSdkModuleError;
27
+
15
28
  export interface AndroidDetectOptions {
16
29
  /** Unified app target selector; for Android this is the application Gradle module, e.g. `:app` or `launcher`. */
17
30
  appTarget?: string;
@@ -57,7 +70,11 @@ export interface WorkspaceContext {
57
70
  remoteConfigPath?: string;
58
71
  /** Cached iOS SDK root used as the source for integration resources. */
59
72
  iosSdkRoot?: string;
73
+ /** Optional cache root override, primarily used by tests. */
74
+ cacheRoot?: string;
60
75
  android?: AndroidDetectResult;
76
+ /** Android Gradle module where SDK resValue/dependencies are written. Defaults to `:unityLibrary`. */
77
+ androidSdkModule?: AndroidSdkModuleResult;
61
78
  /** Reserved: populated via placeholder until iOS tooling ships. */
62
79
  ios?: IOSDetectResult;
63
80
  iosReserved?: IosToolingReserved;
@@ -66,12 +83,16 @@ export interface WorkspaceContext {
66
83
  export interface WorkspaceContextOptions {
67
84
  /** Unified app target selector: Android application module or iOS application target. */
68
85
  appTarget?: string;
86
+ /** Android Gradle module where SDK resValue/dependencies are written. Defaults to `unityLibrary`. */
87
+ sdkModuleName?: string;
69
88
  /** sdk-home API root; defaults to the production MeetGames business API. */
70
89
  sdkHomeApiBaseUrl?: string;
71
90
  /** Override the cached remote config path. */
72
91
  remoteConfigPath?: string;
73
92
  /** Override the iOS SDK source root. */
74
93
  iosSdkRoot?: string;
94
+ /** Override meet-sdk-tool cache root. */
95
+ cacheRoot?: string;
75
96
  }
76
97
 
77
98
  export interface ManifestStep {
@@ -84,6 +84,14 @@ function fileContainsRepository(text: string, repo: string): boolean {
84
84
  }
85
85
 
86
86
  function moduleBuildGradlePath(ctx: WorkspaceContext): string | null {
87
+ if (!ctx.androidSdkModule?.ok) return null;
88
+ const groovy = path.join(ctx.androidSdkModule.moduleDir, "build.gradle");
89
+ if (fs.existsSync(groovy)) return groovy;
90
+ const kts = path.join(ctx.androidSdkModule.moduleDir, "build.gradle.kts");
91
+ return fs.existsSync(kts) ? kts : null;
92
+ }
93
+
94
+ function applicationBuildGradlePath(ctx: WorkspaceContext): string | null {
87
95
  if (!ctx.android?.ok) return null;
88
96
  const groovy = path.join(ctx.android.moduleDir, "build.gradle");
89
97
  if (fs.existsSync(groovy)) return groovy;
@@ -193,22 +201,30 @@ async function checkAndroid(ctx: WorkspaceContext, report: DoctorReport, config:
193
201
  addCheck(report, "android.detect", false, "android application module not detected");
194
202
  return;
195
203
  }
204
+ if (!ctx.androidSdkModule?.ok) {
205
+ addCheck(report, "android.sdkModule", false, ctx.androidSdkModule?.error ?? "android SDK module not resolved");
206
+ return;
207
+ }
196
208
  const resolved = applyMeetSdkDefaultConfig(
197
209
  config,
198
210
  await loadMeetSdkDefaultConfigWithLatestAndroidVersion({
199
211
  sdkHomeApiBaseUrl: ctx.sdkHomeApiBaseUrl,
200
212
  packageRoot: ctx.packageRoot,
213
+ cacheRoot: ctx.cacheRoot,
214
+ channelType: config.channel,
201
215
  })
202
216
  );
203
217
  const moduleGradleAbs = moduleBuildGradlePath(ctx);
218
+ const applicationGradleAbs = applicationBuildGradlePath(ctx);
204
219
  const rootGradleAbs = rootBuildGradlePath(ctx.projectRoot);
205
220
  const settingsGradleAbs = settingsGradlePath(ctx.projectRoot);
206
221
  const moduleGradle = moduleGradleAbs ? readFileIfExists(moduleGradleAbs) : "";
222
+ const applicationGradle = applicationGradleAbs ? readFileIfExists(applicationGradleAbs) : "";
207
223
  const rootGradle = rootGradleAbs ? readFileIfExists(rootGradleAbs) : "";
208
224
  const settingsGradle = settingsGradleAbs ? readFileIfExists(settingsGradleAbs) : "";
209
225
 
210
226
  addCheck(report, "android.moduleGradle.exists", Boolean(moduleGradleAbs), moduleGradleAbs ?? "module build.gradle not found");
211
- addCheck(report, "android.applicationId", moduleGradle.includes(`applicationId "${resolved.packageName}"`) || moduleGradle.includes(`applicationId '${resolved.packageName}'`), resolved.packageName);
227
+ addCheck(report, "android.applicationId", applicationGradle.includes(`applicationId "${resolved.packageName}"`) || applicationGradle.includes(`applicationId '${resolved.packageName}'`), resolved.packageName);
212
228
 
213
229
  const repoText = `${settingsGradle}\n${rootGradle}`;
214
230
  const missingRepos = collectMeetSdkRemoteRepositories(resolved).filter((repo) => !fileContainsRepository(repoText, repo));
@@ -35,7 +35,7 @@ export function platformContext(ctx: WorkspaceContext, platform: DetectedPlatfor
35
35
  if (platform === "android") {
36
36
  return { ...ctx, ios: undefined, iosReserved: undefined };
37
37
  }
38
- return { ...ctx, android: undefined };
38
+ return { ...ctx, android: undefined, androidSdkModule: undefined };
39
39
  }
40
40
 
41
41
  export function manifestForPlatform(manifest: Manifest, platform: DetectedPlatform): Manifest {
@@ -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 {
@@ -1,7 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import type { WorkspaceContext, WorkspaceContextOptions } from "../contracts/types.js";
4
- import { detectAndroid } from "../android/detect.js";
4
+ import { detectAndroid, resolveAndroidSdkModule } from "../android/detect.js";
5
5
  import { detectIOS } from "../ios/detect.js";
6
6
  import { iosToolingReserved } from "../ios/reserved.js";
7
7
 
@@ -24,7 +24,9 @@ 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 }),
29
+ androidSdkModule: resolveAndroidSdkModule(root, options.sdkModuleName),
28
30
  ios: detectIOS(root, { appTarget: options.appTarget }),
29
31
  iosReserved: iosToolingReserved,
30
32
  };
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,