@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,13 +1,15 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import type { AndroidDetectOptions, AndroidDetectResult } from "../contracts/types.js";
3
+ import type { AndroidDetectOptions, AndroidDetectResult, AndroidSdkModuleResult } from "../contracts/types.js";
4
4
 
5
- function parseIncludes(settingsContent: string): string[] {
5
+ export const DEFAULT_ANDROID_SDK_MODULE_NAME = "unityLibrary";
6
+
7
+ export function parseIncludes(settingsContent: string): string[] {
6
8
  const includes: string[] = [];
7
- const includeRegex = /include\s+([^\n]+)/g;
9
+ const includeRegex = /\binclude\b\s*(?:\(([^)\n]+)\)|([^\n]+))/g;
8
10
  let match = includeRegex.exec(settingsContent);
9
11
  while (match) {
10
- const raw = match[1];
12
+ const raw = match[1] ?? match[2] ?? "";
11
13
  const parts = raw
12
14
  .split(",")
13
15
  .map((x) => x.trim())
@@ -28,7 +30,7 @@ function isApplicationModule(buildGradleContent: string): boolean {
28
30
  return applyPlugin || pluginsBlock || pluginAlias || (hasAndroidBlock && hasApplicationId);
29
31
  }
30
32
 
31
- function moduleNameToDir(projectRoot: string, moduleName: string): string {
33
+ export function moduleNameToDir(projectRoot: string, moduleName: string): string {
32
34
  const normalized = moduleName.startsWith(":") ? moduleName.slice(1) : moduleName;
33
35
  const relative = normalized.replace(/:/g, "/");
34
36
  return path.join(projectRoot, relative);
@@ -38,7 +40,7 @@ function defaultManifestPath(moduleDir: string): string {
38
40
  return path.join(moduleDir, "src", "main", "AndroidManifest.xml");
39
41
  }
40
42
 
41
- function normalizeModuleSelector(value: string): string {
43
+ export function normalizeModuleSelector(value: string): string {
42
44
  const trimmed = value.trim().replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
43
45
  if (!trimmed) return "";
44
46
  const asGradlePath = trimmed.replace(/\//g, ":");
@@ -59,18 +61,10 @@ function successResult(projectRoot: string, moduleName: string, moduleDir: strin
59
61
  }
60
62
 
61
63
  export function detectAndroid(projectRoot: string, options: AndroidDetectOptions = {}): AndroidDetectResult {
62
- const settingsGradle = path.join(projectRoot, "settings.gradle");
63
- const settingsGradleKts = path.join(projectRoot, "settings.gradle.kts");
64
- let settingsContent = "";
65
- if (fs.existsSync(settingsGradle)) {
66
- settingsContent = fs.readFileSync(settingsGradle, "utf8");
67
- } else if (fs.existsSync(settingsGradleKts)) {
68
- settingsContent = fs.readFileSync(settingsGradleKts, "utf8");
69
- } else {
70
- return { ok: false, error: "settings.gradle / settings.gradle.kts not found in project root" };
71
- }
64
+ const settings = readAndroidSettings(projectRoot);
65
+ if (!settings) return { ok: false, error: "settings.gradle / settings.gradle.kts not found in project root" };
72
66
 
73
- const modules = parseIncludes(settingsContent);
67
+ const modules = parseIncludes(settings.content);
74
68
  if (!modules.length) {
75
69
  return { ok: false, error: "no modules found in settings.gradle" };
76
70
  }
@@ -114,6 +108,42 @@ export function detectAndroid(projectRoot: string, options: AndroidDetectOptions
114
108
  return successResult(projectRoot, moduleName, moduleDir);
115
109
  }
116
110
 
111
+ function readAndroidSettings(projectRoot: string): { path: string; content: string } | null {
112
+ const settingsGradle = path.join(projectRoot, "settings.gradle");
113
+ const settingsGradleKts = path.join(projectRoot, "settings.gradle.kts");
114
+ if (fs.existsSync(settingsGradle)) {
115
+ return { path: settingsGradle, content: fs.readFileSync(settingsGradle, "utf8") };
116
+ }
117
+ if (fs.existsSync(settingsGradleKts)) {
118
+ return { path: settingsGradleKts, content: fs.readFileSync(settingsGradleKts, "utf8") };
119
+ }
120
+ return null;
121
+ }
122
+
123
+ export function resolveAndroidSdkModule(
124
+ projectRoot: string,
125
+ moduleName = DEFAULT_ANDROID_SDK_MODULE_NAME
126
+ ): AndroidSdkModuleResult {
127
+ const settings = readAndroidSettings(projectRoot);
128
+ if (!settings) return { ok: false, error: "settings.gradle / settings.gradle.kts not found in project root" };
129
+ const modules = parseIncludes(settings.content);
130
+ if (!modules.length) return { ok: false, error: "no modules found in settings.gradle" };
131
+
132
+ const requested = normalizeModuleSelector(moduleName);
133
+ const selected = modules.find((x) => normalizeModuleSelector(x) === requested);
134
+ if (!selected) {
135
+ return {
136
+ ok: false,
137
+ error: `ANDROID_SDK_MODULE_NOT_FOUND: ${requested}; available modules: ${modules.join(", ")}`,
138
+ };
139
+ }
140
+ return {
141
+ ok: true,
142
+ moduleName: normalizeModuleSelector(selected),
143
+ moduleDir: moduleNameToDir(projectRoot, selected),
144
+ };
145
+ }
146
+
117
147
  /** Relative path from project root to the detected application module directory (e.g. `app`, `launcher`). */
118
148
  export function applicationModuleRelPath(
119
149
  projectRoot: string,
@@ -79,13 +79,28 @@ export function updateRootBuildGradleRepositories(
79
79
  }
80
80
 
81
81
  const snippet = buildRepoSnippet(urls || []);
82
- const updated = replaceOrInsertManaged(
82
+ let updated = replaceOrInsertManaged(
83
83
  content,
84
84
  repositoriesBlock,
85
85
  START_REPO_MARKER,
86
86
  END_REPO_MARKER,
87
87
  snippet
88
88
  );
89
+
90
+ const buildscript = findBlockRange(updated, "buildscript");
91
+ if (buildscript) {
92
+ const buildscriptRepositoriesBlock = findBlockRange(updated, "repositories", buildscript.start);
93
+ if (buildscriptRepositoriesBlock && buildscriptRepositoriesBlock.end <= buildscript.end) {
94
+ updated = replaceOrInsertManaged(
95
+ updated,
96
+ buildscriptRepositoriesBlock,
97
+ START_REPO_MARKER,
98
+ END_REPO_MARKER,
99
+ snippet
100
+ );
101
+ }
102
+ }
103
+
89
104
  if (updated === content) warnings.push("root build.gradle repositories unchanged");
90
105
  return { ok: true, content: updated, changed: updated !== content, warnings };
91
106
  }
@@ -180,7 +180,7 @@ export type ModuleGradlePluginStyle = "plugins-dsl" | "apply-plugin";
180
180
  * Module uses `plugins { }` for the Android application plugin (no `apply plugin: com.android.application`).
181
181
  */
182
182
  export function detectModuleGradlePluginStyle(content: string): ModuleGradlePluginStyle {
183
- if (/apply\s+plugin:\s*['"]com\.android\.application['"]/m.test(content)) {
183
+ if (/apply\s+plugin:\s*['"]com\.android\.(?:application|library)['"]/m.test(content)) {
184
184
  return "apply-plugin";
185
185
  }
186
186
  const pluginsBlock = findBlockRange(content, "plugins");
@@ -191,8 +191,9 @@ export function detectModuleGradlePluginStyle(content: string): ModuleGradlePlug
191
191
  const blockText = content.slice(pluginsBlock.openBrace + 1, pluginsBlock.end);
192
192
  if (
193
193
  /alias\s*\(\s*libs\.plugins\.android\.application\s*\)/.test(blockText) ||
194
- /\bid\s+['"]com\.android\.application['"]/.test(blockText) ||
195
- /\bid\s*\(\s*['"]com\.android\.application['"]\s*\)/.test(blockText)
194
+ /alias\s*\(\s*libs\.plugins\.android\.library\s*\)/.test(blockText) ||
195
+ /\bid\s+['"]com\.android\.(?:application|library)['"]/.test(blockText) ||
196
+ /\bid\s*\(\s*['"]com\.android\.(?:application|library)['"]\s*\)/.test(blockText)
196
197
  ) {
197
198
  return "plugins-dsl";
198
199
  }
@@ -525,6 +526,14 @@ function ensureBuildscriptBlock(content: string): string {
525
526
  return `buildscript {\n repositories {\n }\n dependencies {\n }\n}\n\n${content}`;
526
527
  }
527
528
 
529
+ function uniqueStrings(values: readonly string[]): string[] {
530
+ const out: string[] = [];
531
+ for (const value of values) {
532
+ if (!out.includes(value)) out.push(value);
533
+ }
534
+ return out;
535
+ }
536
+
528
537
  export function updateRootBuildGradleMeetSdkRemote(
529
538
  content: string,
530
539
  params:
@@ -574,7 +583,13 @@ export function updateRootBuildGradleMeetSdkRemote(
574
583
  current = removePluginsDslLinesByIds(current, new Set(stripPluginsDslIds));
575
584
  current = stripManagedBlocks(current, TOPSDK_PLUGIN_AUTO_START, TOPSDK_PLUGIN_AUTO_END);
576
585
  }
577
- if (buildscriptRepositories.length > 0 || buildscriptClasspaths.length > 0) {
586
+ const hasExistingBuildscript = Boolean(findBlockRange(current, "buildscript"));
587
+ const mergedBuildscriptRepositoriesToApply = uniqueStrings([
588
+ ...buildscriptRepositories,
589
+ ...(hasExistingBuildscript || buildscriptRepositories.length > 0 || buildscriptClasspaths.length > 0 ? repositories : []),
590
+ ]);
591
+
592
+ if (mergedBuildscriptRepositoriesToApply.length > 0 || buildscriptClasspaths.length > 0) {
578
593
  current = ensureBuildscriptBlock(current);
579
594
  const buildscript = findBlockRange(current, "buildscript");
580
595
  if (!buildscript) return { ok: false, error: "buildscript block not found in root build.gradle" };
@@ -583,7 +598,7 @@ export function updateRootBuildGradleMeetSdkRemote(
583
598
  return { ok: false, error: "repositories block under buildscript not found" };
584
599
  }
585
600
  const buildscriptReposText = current.slice(repositoriesBlock.openBrace + 1, repositoriesBlock.end);
586
- const mergedBuildscriptRepos = mergeRepositoriesInBlock(buildscriptReposText, buildscriptRepositories);
601
+ const mergedBuildscriptRepos = mergeRepositoriesInBlock(buildscriptReposText, mergedBuildscriptRepositoriesToApply);
587
602
  current =
588
603
  current.slice(0, repositoriesBlock.openBrace + 1) +
589
604
  mergedBuildscriptRepos +
@@ -880,8 +895,25 @@ export type MeetSdkModulePluginConfig = {
880
895
  style: ModuleGradlePluginStyle;
881
896
  applyPlugins?: readonly string[];
882
897
  pluginsDsl?: readonly MeetSdkGradlePluginDslSpec[];
898
+ writeApplicationId?: boolean;
883
899
  };
884
900
 
901
+ function findAndroidDefaultConfigBlocks(
902
+ content: string
903
+ ): { androidBlock: { start: number; openBrace: number; end: number }; defaultConfigBlock: { start: number; openBrace: number; end: number } } | null {
904
+ let searchFrom = 0;
905
+ while (searchFrom < content.length) {
906
+ const androidBlock = findBlockRange(content, "android", searchFrom);
907
+ if (!androidBlock) return null;
908
+ const defaultConfigBlock = findBlockRange(content, "defaultConfig", androidBlock.start);
909
+ if (defaultConfigBlock && defaultConfigBlock.end <= androidBlock.end) {
910
+ return { androidBlock, defaultConfigBlock };
911
+ }
912
+ searchFrom = androidBlock.end + 1;
913
+ }
914
+ return null;
915
+ }
916
+
885
917
  export function updateModuleBuildGradleMeetSdkRemote(
886
918
  content: string,
887
919
  config: MeetSdkRemoteConfig,
@@ -896,6 +928,7 @@ export function updateModuleBuildGradleMeetSdkRemote(
896
928
  style: detectModuleGradlePluginStyle(content),
897
929
  applyPlugins: Array.isArray(plugins) ? plugins : [],
898
930
  pluginsDsl: [],
931
+ writeApplicationId: true,
899
932
  };
900
933
  }
901
934
  const withPlugins =
@@ -905,35 +938,30 @@ export function updateModuleBuildGradleMeetSdkRemote(
905
938
  ? mergeApplyPluginsInContent(content, pluginConfig.applyPlugins!)
906
939
  : content;
907
940
 
908
- const androidBlock = findBlockRange(withPlugins, "android");
909
- if (!androidBlock) {
910
- return { ok: false, error: "android block not found in module build.gradle" };
911
- }
912
- const defaultConfigBlock = findBlockRange(withPlugins, "defaultConfig", androidBlock.start);
913
- if (!defaultConfigBlock || defaultConfigBlock.end > androidBlock.end) {
941
+ const blocks = findAndroidDefaultConfigBlocks(withPlugins);
942
+ if (!blocks) {
914
943
  return { ok: false, error: "defaultConfig block not found under android" };
915
944
  }
916
- const withApplicationId = updateDefaultConfigApplicationId(withPlugins, defaultConfigBlock, config.packageName);
917
- const refreshedAndroidBlock = findBlockRange(withApplicationId, "android");
918
- if (!refreshedAndroidBlock) {
919
- return { ok: false, error: "android block not found in module build.gradle" };
920
- }
921
- const refreshedDefaultConfigBlock = findBlockRange(withApplicationId, "defaultConfig", refreshedAndroidBlock.start);
922
- if (!refreshedDefaultConfigBlock || refreshedDefaultConfigBlock.end > refreshedAndroidBlock.end) {
945
+ const withApplicationId =
946
+ pluginConfig.writeApplicationId === false
947
+ ? withPlugins
948
+ : updateDefaultConfigApplicationId(withPlugins, blocks.defaultConfigBlock, config.packageName);
949
+ const refreshedBlocks = findAndroidDefaultConfigBlocks(withApplicationId);
950
+ if (!refreshedBlocks) {
923
951
  return { ok: false, error: "defaultConfig block not found under android" };
924
952
  }
925
953
  const defaultConfigText = withApplicationId.slice(
926
- refreshedDefaultConfigBlock.openBrace + 1,
927
- refreshedDefaultConfigBlock.end
954
+ refreshedBlocks.defaultConfigBlock.openBrace + 1,
955
+ refreshedBlocks.defaultConfigBlock.end
928
956
  );
929
957
  const mergedDefaultConfigText = mergeResValuesInDefaultConfigBlock(
930
958
  defaultConfigText,
931
959
  buildResValuesSnippet(config)
932
960
  );
933
961
  const withResValues =
934
- withApplicationId.slice(0, refreshedDefaultConfigBlock.openBrace + 1) +
962
+ withApplicationId.slice(0, refreshedBlocks.defaultConfigBlock.openBrace + 1) +
935
963
  mergedDefaultConfigText +
936
- withApplicationId.slice(refreshedDefaultConfigBlock.end);
964
+ withApplicationId.slice(refreshedBlocks.defaultConfigBlock.end);
937
965
 
938
966
  const dependenciesBlock = findBlockRange(withResValues, "dependencies");
939
967
  if (!dependenciesBlock) {
package/src/cache.ts CHANGED
@@ -5,11 +5,17 @@ import path from "node:path";
5
5
  import { MEETSDK_REMOTE_CONFIG_FILENAME } from "./config/meetSdkRemoteConfig.js";
6
6
 
7
7
  export const MEET_SDK_TOOL_CACHE_ROOT = path.join(os.homedir(), ".cache", "meet-sdk-tool");
8
+ export const MEETSDK_ANDROID_CONFIG_FILENAME = "meetsdk-android.json";
9
+ export const MEETSDK_IOS_CONFIG_FILENAME = "meetsdk-ios.json";
8
10
 
9
11
  function iosSdkZipFileName(version: string): string {
10
12
  return `topSDK-ios--V${version}.zip`;
11
13
  }
12
14
 
15
+ function androidSdkZipFileName(version: string): string {
16
+ return `topSDK-android-V${version}.zip`;
17
+ }
18
+
13
19
  function safeSegment(value: string): string {
14
20
  const cleaned = value.trim().replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
15
21
  return cleaned || "unknown";
@@ -52,6 +58,14 @@ export function resolveRemoteConfigCachePath(params: {
52
58
  );
53
59
  }
54
60
 
61
+ export function resolveMeetSdkAndroidConfigCachePath(cacheRoot = MEET_SDK_TOOL_CACHE_ROOT): string {
62
+ return path.join(cacheRoot, "configs", "sdk-home", MEETSDK_ANDROID_CONFIG_FILENAME);
63
+ }
64
+
65
+ export function resolveMeetSdkIosConfigCachePath(cacheRoot = MEET_SDK_TOOL_CACHE_ROOT): string {
66
+ return path.join(cacheRoot, "configs", "sdk-home", MEETSDK_IOS_CONFIG_FILENAME);
67
+ }
68
+
55
69
  export function writeRemoteConfigCache(params: {
56
70
  env: string;
57
71
  appId: string;
@@ -110,6 +124,29 @@ export function resolveIosSdkCacheLayout(params: {
110
124
  };
111
125
  }
112
126
 
127
+ export function resolveAndroidSdkCacheLayout(params: {
128
+ version: string;
129
+ packageType: string;
130
+ plugins: readonly string[];
131
+ cacheRoot?: string;
132
+ }): { baseDir: string; zipPath: string; extractDir: string; metadataPath: string; lockDir: string } {
133
+ const baseDir = path.join(
134
+ params.cacheRoot ?? MEET_SDK_TOOL_CACHE_ROOT,
135
+ "sdk",
136
+ "android",
137
+ safeSegment(params.packageType),
138
+ safeSegment(params.version),
139
+ pluginsHash(params.plugins)
140
+ );
141
+ return {
142
+ baseDir,
143
+ zipPath: path.join(baseDir, androidSdkZipFileName(params.version)),
144
+ extractDir: path.join(baseDir, "extracted"),
145
+ metadataPath: path.join(baseDir, "metadata.json"),
146
+ lockDir: path.join(baseDir, ".lock"),
147
+ };
148
+ }
149
+
113
150
  export function writeIosSdkCacheMetadata(params: {
114
151
  metadataPath: string;
115
152
  version: string;
package/src/cli.ts CHANGED
@@ -16,7 +16,6 @@ import {
16
16
  defaultSdkHomeApiBaseUrl,
17
17
  downloadIosSdkToBundled,
18
18
  DEFAULT_IOS_SDK_PACKAGE_TYPE,
19
- resolveIosSdkDownloadPluginsFromRemoteConfig,
20
19
  } from "./remote/sdkHomeDownload.js";
21
20
  import type { Manifest } from "./contracts/types.js";
22
21
  import {
@@ -63,6 +62,7 @@ interface Parsed {
63
62
  topsdkChannelType: string;
64
63
  topsdkPackageName: string;
65
64
  appTarget: string;
65
+ sdkModuleName: string;
66
66
  sdkHomeApiBaseUrl: string;
67
67
  iosSdkPackageType: string;
68
68
  }
@@ -72,11 +72,11 @@ function printHelp(): void {
72
72
  meetgames — Android-first integration CLI (TypeScript)
73
73
 
74
74
  Usage:
75
- meetgames integrate [--project-root <path>] [--app-id ID] [--channel-type TYPE] [--env prod|pre|test] [--app-target <target>] [--dry-run] [--verbose] [--patch-file <path>]
75
+ meetgames integrate [--project-root <path>] [--app-id ID] [--channel-type TYPE] [--env prod|pre|test] [--app-target <target>] [--sdk-module <module>] [--dry-run] [--verbose] [--patch-file <path>]
76
76
  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 [--package-type native|unity|cocos] [--sdk-api-base-url URL] [--verbose]
78
- 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
- meetgames doctor [--app-id ID] [--app-secret SECRET] [--channel-type TYPE] [--env prod|pre|test] --project-root <path> [--app-target <target>] [--verbose]
77
+ 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
+ meetgames setup [--app-id ID] [--app-secret SECRET] [--channel-type TYPE] [--env prod|pre|test] [--project-root <path>] [--app-target <target>] [--sdk-module <module>] [--dry-run] [--verbose]
79
+ meetgames doctor [--app-id ID] [--app-secret SECRET] [--channel-type TYPE] [--env prod|pre|test] --project-root <path> [--app-target <target>] [--sdk-module <module>] [--verbose]
80
80
 
81
81
  Notes:
82
82
  - Source code is TypeScript only; published bin runs compiled output under dist/.
@@ -87,8 +87,8 @@ Notes:
87
87
  - doctor first fetches downloadSDKConfig to the cache, then validates the detected platform integration.
88
88
  - integrate uses recipes/integrate-default.yaml, filtered to the detected platform before execution.
89
89
  - 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/.
90
+ - Android integrate/doctor reads latest SDK config from sdk-home and stores it under ${MEET_SDK_TOOL_CACHE_ROOT}/configs/sdk-home/.
91
+ - 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
92
  - fetch-config requires --app-secret (or TOPSDK_APP_SECRET) to sign downloadSDKConfig; appSecret is never read from meetsdk-remote-config.json.
93
93
  - 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
94
  - fetch-config writes to ${MEET_SDK_TOOL_CACHE_ROOT}/configs/<env>/<appId>/<channelType>/meetsdk-remote-config.json.
@@ -98,22 +98,23 @@ Notes:
98
98
  Options (global where noted):
99
99
  --project-root Host project directory (default: cwd); for iOS it may point to the project root or directly to a .xcodeproj directory
100
100
  --app-target App target to integrate/check: Android application Gradle module (e.g. :app or launcher) or iOS App target name
101
+ --sdk-module Android SDK integration Gradle module for resValue/dependencies (default: unityLibrary); must be included by settings.gradle/settings.gradle.kts
101
102
  --app-id fetch-config/integrate/doctor cache selector; default TOPSDK_APP_ID
102
103
  --app-secret fetch-config: required appSecret for downloadSDKConfig sign (or env TOPSDK_APP_SECRET)
103
104
  --channel-type fetch-config/integrate/doctor cache selector; default TOPSDK_CHANNEL_TYPE
104
105
  --env fetch-config: prod(正式服,默认)| pre | test(开发调试)
105
106
  TOPSDK_API_BASE_URL fetch-config: 覆盖 API 根地址(如 http://localhost:18080/ 联调本机 console)
106
107
  MEETGAMES_SDK_HOME_API_BASE_URL 覆盖 sdk-home API 根地址(默认 https://business-api.meetgames.com);用于 Android 最新版本查询和 iOS SDK 下载
107
- --sdk-api-base-url sdk-home API 根地址;用于 Android 最新版本查询和 iOS SDK 下载
108
+ --sdk-api-base-url sdk-home API 根地址;用于 Android 最新版本查询和 iOS SDK 下载(setup 不支持)
108
109
  --package-type download-ios-sdk: native(默认)| unity | cocos
109
- --dry-run integrate: compute changes and patch preview without writing host project files (default is write to disk)
110
- --report-file Write JSON report to path
111
- --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
+ --dry-run integrate/setup: compute changes without writing host project files (setup still writes fetched config cache)
111
+ --report-file integrate: write JSON report to path
112
+ --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.
112
113
  --verbose Verbose logs and full patch preview
113
114
 
114
115
  Subcommand notes:
115
116
  - integrate defaults to writing files; use --dry-run for CI preview / diff-only.
116
- - setup: step 1 always writes meetsdk-remote-config.json; step 2 honors --dry-run / --patch-file / --report-file.
117
+ - setup: step 1 always writes meetsdk-remote-config.json; step 2 honors --dry-run.
117
118
  - fetch-config ignores --dry-run, --report-file, --patch-file (shared parser only).
118
119
  `);
119
120
  }
@@ -134,6 +135,7 @@ function parseArgv(argv: string[]): Parsed {
134
135
  topsdkChannelType: "",
135
136
  topsdkPackageName: "",
136
137
  appTarget: "",
138
+ sdkModuleName: "",
137
139
  sdkHomeApiBaseUrl: "",
138
140
  iosSdkPackageType: "",
139
141
  };
@@ -169,6 +171,12 @@ function parseArgv(argv: string[]): Parsed {
169
171
  i += 1;
170
172
  continue;
171
173
  }
174
+ if (t === "--sdk-module") {
175
+ out.sdkModuleName = rest[i + 1] ?? "";
176
+ if (!out.sdkModuleName || out.sdkModuleName.startsWith("-")) fail("--sdk-module requires a module name", EXIT.INVALID_ARGS);
177
+ i += 1;
178
+ continue;
179
+ }
172
180
  if (t === "--sdk-api-base-url") {
173
181
  out.sdkHomeApiBaseUrl = rest[i + 1] ?? "";
174
182
  if (!out.sdkHomeApiBaseUrl || out.sdkHomeApiBaseUrl.startsWith("-")) {
@@ -232,6 +240,10 @@ function resolvedAppTarget(parsed: Parsed): string {
232
240
  return parsed.appTarget;
233
241
  }
234
242
 
243
+ function resolvedSdkModuleName(parsed: Parsed): string | undefined {
244
+ return parsed.sdkModuleName.trim() || undefined;
245
+ }
246
+
235
247
  function cachedRemoteConfigPathFromArgs(parsed: Parsed): string {
236
248
  const appId = parsed.topsdkAppId || process.env.TOPSDK_APP_ID || "";
237
249
  const channelType = parsed.topsdkChannelType || process.env.TOPSDK_CHANNEL_TYPE || "";
@@ -246,6 +258,7 @@ function selectedPlatformContext(parsed: Parsed): {
246
258
  } {
247
259
  const ctx = buildWorkspaceContext(parsed.projectRoot, resolvePackageRoot(), {
248
260
  appTarget: resolvedAppTarget(parsed),
261
+ sdkModuleName: resolvedSdkModuleName(parsed),
249
262
  sdkHomeApiBaseUrl: parsed.sdkHomeApiBaseUrl || undefined,
250
263
  remoteConfigPath: cachedRemoteConfigPathFromArgs(parsed) || undefined,
251
264
  });
@@ -396,12 +409,20 @@ async function cmdSetup(parsed: Parsed): Promise<void> {
396
409
  await cmdIntegrate(parsed);
397
410
  }
398
411
 
399
- function readIosDownloadPluginsFromRemoteConfig(configPath: string): string[] | undefined {
400
- if (!configPath || !fs.existsSync(configPath)) return undefined;
401
- const remote = tryParseAsMeetSdkRemoteConfig(JSON.parse(fs.readFileSync(configPath, "utf8")) as unknown);
402
- if (!remote || remote.devicePlatform !== "ios") return undefined;
403
- const plugins = resolveIosSdkDownloadPluginsFromRemoteConfig(remote);
404
- return plugins.length ? plugins : undefined;
412
+ function resolveIosDownloadRemoteConfigPath(parsed: Parsed, explicitPath?: string): string {
413
+ const candidates = [
414
+ explicitPath,
415
+ cachedRemoteConfigPathFromArgs(parsed),
416
+ path.join(parsed.projectRoot, MEETSDK_REMOTE_CONFIG_FILENAME),
417
+ ]
418
+ .filter((p): p is string => Boolean(p))
419
+ .filter((p, i, arr) => arr.indexOf(p) === i);
420
+ const found = candidates.find((p) => fs.existsSync(p));
421
+ if (found) return found;
422
+ fail(
423
+ `download-ios-sdk requires an iOS ${MEETSDK_REMOTE_CONFIG_FILENAME}; checked: ${candidates.join(", ")}`,
424
+ EXIT.CONFIG_ERROR
425
+ );
405
426
  }
406
427
 
407
428
  async function cmdDownloadIosSdk(parsed: Parsed, remoteConfigPath?: string): Promise<void> {
@@ -411,15 +432,15 @@ async function cmdDownloadIosSdk(parsed: Parsed, remoteConfigPath?: string): Pro
411
432
  if (!["native", "unity", "cocos"].includes(packageType)) {
412
433
  fail(`invalid --package-type (use native, unity, or cocos): ${packageType}`, EXIT.INVALID_ARGS);
413
434
  }
414
- const configuredPlugins = remoteConfigPath ? readIosDownloadPluginsFromRemoteConfig(remoteConfigPath) : undefined;
415
- const plugins = configuredPlugins;
435
+ const configPath = resolveIosDownloadRemoteConfigPath(parsed, remoteConfigPath);
416
436
 
437
+ console.log(`[meetgames] download-ios-sdk: using remote config ${configPath}`);
417
438
  console.log("[meetgames] download-ios-sdk: resolving sdk-home iOS SDK…");
418
439
  let result;
419
440
  try {
420
441
  result = await downloadIosSdkToBundled(resolvePackageRoot(), {
421
442
  baseUrl,
422
- plugins,
443
+ remoteConfigPath: configPath,
423
444
  packageType,
424
445
  });
425
446
  } catch (e) {
@@ -462,7 +483,16 @@ export async function main(): Promise<void> {
462
483
  if (parsed.appTarget && !["doctor", "setup", "integrate"].includes(parsed.command)) {
463
484
  fail("--app-target is only supported by doctor, setup, and integrate", EXIT.INVALID_ARGS);
464
485
  }
486
+ if (parsed.sdkModuleName && !["doctor", "setup", "integrate"].includes(parsed.command)) {
487
+ fail("--sdk-module is only supported by doctor, setup, and integrate", EXIT.INVALID_ARGS);
488
+ }
489
+ if (parsed.command === "setup") {
490
+ if (parsed.patchFile) fail("--patch-file is not supported by setup", EXIT.INVALID_ARGS);
491
+ if (parsed.reportFile) fail("--report-file is not supported by setup", EXIT.INVALID_ARGS);
492
+ if (parsed.sdkHomeApiBaseUrl) fail("--sdk-api-base-url is not supported by setup", EXIT.INVALID_ARGS);
493
+ }
465
494
  resolvedAppTarget(parsed);
495
+ resolvedSdkModuleName(parsed);
466
496
 
467
497
  switch (parsed.command) {
468
498
  case "doctor":