@leonxin/meetgames 0.1.11 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/README.md +9 -9
  2. package/dist/cache.d.ts +44 -0
  3. package/dist/cache.d.ts.map +1 -0
  4. package/dist/cache.js +101 -0
  5. package/dist/cache.js.map +1 -0
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +43 -37
  8. package/dist/cli.js.map +1 -1
  9. package/dist/config/meetSdkDefaultConfig.d.ts +1 -1
  10. package/dist/config/meetSdkDefaultConfig.d.ts.map +1 -1
  11. package/dist/config/meetSdkDefaultConfig.js +4 -3
  12. package/dist/config/meetSdkDefaultConfig.js.map +1 -1
  13. package/dist/config/meetSdkIosConfig.d.ts.map +1 -1
  14. package/dist/config/meetSdkIosConfig.js +3 -1
  15. package/dist/config/meetSdkIosConfig.js.map +1 -1
  16. package/dist/config/meetSdkRemoteConfig.d.ts +1 -0
  17. package/dist/config/meetSdkRemoteConfig.d.ts.map +1 -1
  18. package/dist/config/meetSdkRemoteConfig.js +4 -1
  19. package/dist/config/meetSdkRemoteConfig.js.map +1 -1
  20. package/dist/contracts/types.d.ts +8 -0
  21. package/dist/contracts/types.d.ts.map +1 -1
  22. package/dist/core/doctor.js +7 -7
  23. package/dist/core/doctor.js.map +1 -1
  24. package/dist/core/previewPatches.d.ts +1 -1
  25. package/dist/core/previewPatches.js +2 -2
  26. package/dist/core/previewPatches.js.map +1 -1
  27. package/dist/core/workspace.d.ts.map +1 -1
  28. package/dist/core/workspace.js +2 -0
  29. package/dist/core/workspace.js.map +1 -1
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +1 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/ios/integrate.d.ts.map +1 -1
  35. package/dist/ios/integrate.js +3 -3
  36. package/dist/ios/integrate.js.map +1 -1
  37. package/dist/ios/pbxprojEditor.d.ts.map +1 -1
  38. package/dist/ios/pbxprojEditor.js +71 -5
  39. package/dist/ios/pbxprojEditor.js.map +1 -1
  40. package/dist/ios/sdkBundle.d.ts +1 -6
  41. package/dist/ios/sdkBundle.d.ts.map +1 -1
  42. package/dist/ios/sdkBundle.js +44 -16
  43. package/dist/ios/sdkBundle.js.map +1 -1
  44. package/dist/mcp/server.d.ts.map +1 -1
  45. package/dist/mcp/server.js +12 -6
  46. package/dist/mcp/server.js.map +1 -1
  47. package/dist/mcp/service.d.ts +3 -0
  48. package/dist/mcp/service.d.ts.map +1 -1
  49. package/dist/mcp/service.js +31 -8
  50. package/dist/mcp/service.js.map +1 -1
  51. package/dist/ops/handlers.d.ts.map +1 -1
  52. package/dist/ops/handlers.js +24 -19
  53. package/dist/ops/handlers.js.map +1 -1
  54. package/dist/remote/sdkHomeDownload.d.ts +1 -1
  55. package/dist/remote/sdkHomeDownload.d.ts.map +1 -1
  56. package/dist/remote/sdkHomeDownload.js +27 -6
  57. package/dist/remote/sdkHomeDownload.js.map +1 -1
  58. package/docs/API.md +16 -16
  59. package/docs/CLI.md +21 -22
  60. package/docs/INTEGRATION.md +11 -11
  61. package/docs/MCP.md +1 -1
  62. package/docs/README.md +0 -1
  63. package/docs/archive/api/downloadSDKConfig.md +2 -2
  64. package/docs/archive/api/getChannelConfig-meetgames.md +1 -1
  65. package/docs/archive/product//351/234/200/346/261/202/346/226/207/346/241/243.md +1 -1
  66. package/package.json +2 -5
  67. package/recipes/android-default.yaml +0 -5
  68. package/recipes/integrate-default.yaml +0 -5
  69. package/src/cache.ts +164 -0
  70. package/src/cli.ts +46 -38
  71. package/src/config/meetSdkDefaultConfig.ts +4 -3
  72. package/src/config/meetSdkIosConfig.ts +3 -1
  73. package/src/config/meetSdkRemoteConfig.ts +5 -1
  74. package/src/contracts/types.ts +8 -0
  75. package/src/core/doctor.ts +7 -7
  76. package/src/core/previewPatches.ts +2 -2
  77. package/src/core/workspace.ts +2 -0
  78. package/src/index.ts +7 -0
  79. package/src/ios/integrate.ts +4 -3
  80. package/src/ios/pbxprojEditor.ts +70 -3
  81. package/src/ios/sdkBundle.ts +41 -18
  82. package/src/mcp/server.ts +12 -6
  83. package/src/mcp/service.ts +39 -8
  84. package/src/ops/handlers.ts +23 -19
  85. package/src/remote/sdkHomeDownload.ts +28 -7
  86. package/tests/doctor.test.ts +4 -2
  87. package/tests/{test-projects-hosts.test.ts → fixtures-hosts.test.ts} +2 -2
  88. package/tests/ios.sdkBundle.test.ts +10 -5
  89. package/tests/mcp.e2e.ts +2 -5
  90. package/tests/meetSdkRemoteConfig.test.ts +25 -0
  91. package/tests/pipeline.android.test.ts +1 -2
  92. package/tests/pipeline.ios.test.ts +26 -19
  93. package/tests/pipeline.preview.patch.test.ts +2 -2
  94. package/tests/sdkVersionConfig.test.ts +3 -2
  95. package/dist/aab-converter/aab-entry.d.ts +0 -3
  96. package/dist/aab-converter/aab-entry.d.ts.map +0 -1
  97. package/dist/aab-converter/aab-entry.js +0 -49
  98. package/dist/aab-converter/aab-entry.js.map +0 -1
  99. package/dist/aab-converter/apksExtractor.d.ts +0 -2
  100. package/dist/aab-converter/apksExtractor.d.ts.map +0 -1
  101. package/dist/aab-converter/apksExtractor.js +0 -108
  102. package/dist/aab-converter/apksExtractor.js.map +0 -1
  103. package/dist/aab-converter/bundletoolRunner.d.ts +0 -15
  104. package/dist/aab-converter/bundletoolRunner.d.ts.map +0 -1
  105. package/dist/aab-converter/bundletoolRunner.js +0 -46
  106. package/dist/aab-converter/bundletoolRunner.js.map +0 -1
  107. package/dist/aab-converter/cliArgs.d.ts +0 -27
  108. package/dist/aab-converter/cliArgs.d.ts.map +0 -1
  109. package/dist/aab-converter/cliArgs.js +0 -170
  110. package/dist/aab-converter/cliArgs.js.map +0 -1
  111. package/dist/aab-converter/convertAabToApk.d.ts +0 -7
  112. package/dist/aab-converter/convertAabToApk.d.ts.map +0 -1
  113. package/dist/aab-converter/convertAabToApk.js +0 -69
  114. package/dist/aab-converter/convertAabToApk.js.map +0 -1
  115. package/dist/aab-converter/resourcePaths.d.ts +0 -4
  116. package/dist/aab-converter/resourcePaths.d.ts.map +0 -1
  117. package/dist/aab-converter/resourcePaths.js +0 -42
  118. package/dist/aab-converter/resourcePaths.js.map +0 -1
  119. package/dist/aab-converter/signingOptions.d.ts +0 -9
  120. package/dist/aab-converter/signingOptions.d.ts.map +0 -1
  121. package/dist/aab-converter/signingOptions.js +0 -21
  122. package/dist/aab-converter/signingOptions.js.map +0 -1
  123. package/dist/aab-converter/types.d.ts +0 -24
  124. package/dist/aab-converter/types.d.ts.map +0 -1
  125. package/dist/aab-converter/types.js +0 -2
  126. package/dist/aab-converter/types.js.map +0 -1
  127. package/dist/shared/fileUtils.d.ts +0 -5
  128. package/dist/shared/fileUtils.d.ts.map +0 -1
  129. package/dist/shared/fileUtils.js +0 -35
  130. package/dist/shared/fileUtils.js.map +0 -1
  131. package/dist/shared/logger.d.ts +0 -10
  132. package/dist/shared/logger.d.ts.map +0 -1
  133. package/dist/shared/logger.js +0 -37
  134. package/dist/shared/logger.js.map +0 -1
  135. package/dist/shared/pathUtils.d.ts +0 -4
  136. package/dist/shared/pathUtils.d.ts.map +0 -1
  137. package/dist/shared/pathUtils.js +0 -22
  138. package/dist/shared/pathUtils.js.map +0 -1
  139. package/dist/shared/processRunner.d.ts +0 -12
  140. package/dist/shared/processRunner.d.ts.map +0 -1
  141. package/dist/shared/processRunner.js +0 -31
  142. package/dist/shared/processRunner.js.map +0 -1
  143. package/docs/AAB_CONVERTER_CLI_PLAN.md +0 -392
  144. package/logs/convert-20260622-155037.log +0 -5
  145. package/logs/convert-20260622-155226.log +0 -6
  146. package/scripts/package-aab-cli-win.mjs +0 -193
  147. package/src/aab-converter/aab-entry.ts +0 -48
  148. package/src/aab-converter/apksExtractor.ts +0 -119
  149. package/src/aab-converter/bundletoolRunner.ts +0 -63
  150. package/src/aab-converter/cliArgs.ts +0 -194
  151. package/src/aab-converter/convertAabToApk.ts +0 -81
  152. package/src/aab-converter/resourcePaths.ts +0 -43
  153. package/src/aab-converter/signingOptions.ts +0 -29
  154. package/src/aab-converter/types.ts +0 -26
  155. package/src/shared/fileUtils.ts +0 -41
  156. package/src/shared/logger.ts +0 -49
  157. package/src/shared/pathUtils.ts +0 -24
  158. package/src/shared/processRunner.ts +0 -43
  159. package/test-projects/README.md +0 -51
  160. package/test-projects/_preview/pipeline.patch +0 -281
  161. package/tests/aab-converter.test.ts +0 -213
  162. /package/{meetsdk-android.json → config/meetsdk-android.json} +0 -0
  163. /package/{meetsdk-ios.json → config/meetsdk-ios.json} +0 -0
@@ -333,9 +333,9 @@ export async function runIosIntegrateTopSdk(
333
333
  const warnings: string[] = [];
334
334
  const changed = new Set<string>();
335
335
 
336
- const configPath = path.join(ctx.projectRoot, MEETSDK_REMOTE_CONFIG_FILENAME);
336
+ const configPath = ctx.remoteConfigPath ?? path.join(ctx.projectRoot, MEETSDK_REMOTE_CONFIG_FILENAME);
337
337
  if (!fs.existsSync(configPath)) {
338
- return fail([`${MEETSDK_REMOTE_CONFIG_FILENAME} not found; run fetch-config or setup first`]);
338
+ return fail([`${MEETSDK_REMOTE_CONFIG_FILENAME} not found at ${configPath}; run fetch-config or setup first`]);
339
339
  }
340
340
  let remote: ReturnType<typeof tryParseAsMeetSdkRemoteConfig>;
341
341
  try {
@@ -353,7 +353,7 @@ export async function runIosIntegrateTopSdk(
353
353
 
354
354
  let sdkRoot: string;
355
355
  try {
356
- sdkRoot = resolveIosSdkRoot(ctx.packageRoot);
356
+ sdkRoot = ctx.iosSdkRoot ?? resolveIosSdkRoot(ctx.packageRoot);
357
357
  } catch (e) {
358
358
  return fail([e instanceof Error ? e.message : String(e)]);
359
359
  }
@@ -375,6 +375,7 @@ export async function runIosIntegrateTopSdk(
375
375
  if (p) pluginConfigs.push(p);
376
376
  else warnings.push(`iOS plugin folder not in SDK package: plugins/${folder}`);
377
377
  }
378
+
378
379
  const xcodeprojPath = ctx.ios.xcodeprojPath!;
379
380
  const pbx = await loadPbxFromStore(store, ctx.projectRoot, xcodeprojPath, targetName);
380
381
  const fm = new TopSdkFileManager(pbx.srcRoot);
@@ -69,9 +69,70 @@ export async function loadPbxFromStore(
69
69
  }
70
70
 
71
71
  export function savePbxToStore(store: TextFileStore, ctx: PbxContext): void {
72
+ stripUndefinedValues(ctx.proj.hash?.project?.objects);
73
+ sanitizePbxBuildSettings(ctx.proj);
72
74
  store.write(ctx.rel, ctx.proj.writeSync());
73
75
  }
74
76
 
77
+ function stripUndefinedValues(value: unknown): void {
78
+ if (Array.isArray(value)) {
79
+ for (const item of value) stripUndefinedValues(item);
80
+ return;
81
+ }
82
+ if (!value || typeof value !== "object") return;
83
+
84
+ const record = value as Record<string, unknown>;
85
+ for (const [key, nested] of Object.entries(record)) {
86
+ if (nested === undefined || nested === "undefined") {
87
+ delete record[key];
88
+ continue;
89
+ }
90
+ stripUndefinedValues(nested);
91
+ }
92
+ }
93
+
94
+ function sanitizePbxBuildSettings(proj: XcodeProject): void {
95
+ const buildConfigs = proj.pbxXCBuildConfigurationSection?.() ?? {};
96
+ for (const [uuid, cfg] of Object.entries(buildConfigs)) {
97
+ if (uuid.endsWith("_comment") || !cfg || typeof cfg !== "object") continue;
98
+ const settings = (cfg as { buildSettings?: Record<string, unknown> }).buildSettings;
99
+ if (!settings) continue;
100
+ for (const [key, raw] of Object.entries(settings)) {
101
+ if (typeof raw === "string") {
102
+ settings[key] = quotePbxStringIfNeeded(raw);
103
+ continue;
104
+ }
105
+ if (Array.isArray(raw)) {
106
+ settings[key] = raw.map((value) => (typeof value === "string" ? quotePbxStringIfNeeded(value) : value));
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ function isPbxQuotedString(value: string): boolean {
113
+ return value.length >= 2 && value.startsWith('"') && value.endsWith('"');
114
+ }
115
+
116
+ function unquotePbxString(value: string): string {
117
+ if (!isPbxQuotedString(value)) return value;
118
+ return value.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
119
+ }
120
+
121
+ function quotePbxStringIfNeeded(value: string): string {
122
+ if (isPbxQuotedString(value)) return value;
123
+ if (!pbxStringNeedsQuotes(value)) return value;
124
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
125
+ }
126
+
127
+ function pbxStringNeedsQuotes(value: string): boolean {
128
+ if (value.length === 0 || value.startsWith("-")) return true;
129
+ return /[^A-Za-z0-9_.$/{}()+-]/.test(value);
130
+ }
131
+
132
+ function samePbxStringValue(a: unknown, b: string): boolean {
133
+ return typeof a === "string" && unquotePbxString(a) === unquotePbxString(b);
134
+ }
135
+
75
136
  function targetUuid(proj: XcodeProject, targetName: string): string | undefined {
76
137
  const targets = proj.pbxNativeTargetSection?.() ?? {};
77
138
  for (const [uuid, t] of Object.entries(targets)) {
@@ -317,12 +378,18 @@ export function addOtherLinkerFlag(ctx: PbxContext, flag: string): void {
317
378
  if (!cfg || typeof cfg !== "object") continue;
318
379
  const settings = (cfg as { buildSettings?: Record<string, unknown> }).buildSettings ?? {};
319
380
  const cur = settings.OTHER_LDFLAGS;
381
+ const normalizedFlag = unquotePbxString(flag);
382
+ const nextFlag = quotePbxStringIfNeeded(normalizedFlag);
320
383
  if (typeof cur === "string") {
321
- if (!cur.includes(flag)) settings.OTHER_LDFLAGS = `${cur} ${flag}`.trim();
384
+ if (!unquotePbxString(cur).split(/\s+/).includes(normalizedFlag)) {
385
+ settings.OTHER_LDFLAGS = [quotePbxStringIfNeeded(cur), nextFlag];
386
+ }
322
387
  } else if (Array.isArray(cur)) {
323
- if (!cur.includes(flag)) settings.OTHER_LDFLAGS = [...cur, flag];
388
+ const values = cur.map((value) => (typeof value === "string" ? quotePbxStringIfNeeded(value) : value));
389
+ if (!values.some((value) => samePbxStringValue(value, normalizedFlag))) values.push(nextFlag);
390
+ settings.OTHER_LDFLAGS = values;
324
391
  } else {
325
- settings.OTHER_LDFLAGS = [flag];
392
+ settings.OTHER_LDFLAGS = [nextFlag];
326
393
  }
327
394
  (cfg as { buildSettings: Record<string, unknown> }).buildSettings = settings;
328
395
  }
@@ -1,25 +1,17 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
-
4
- const BUNDLED_IOS_REL = path.join("bundled", "ios-sdk");
5
-
6
- export function defaultBundledIosSdkRoot(packageRoot: string): string {
7
- return path.join(packageRoot, BUNDLED_IOS_REL);
8
- }
3
+ import { MEET_SDK_TOOL_CACHE_ROOT } from "../cache.js";
9
4
 
10
5
  /**
11
- * TopSDK iOS package root: `bundled/ios-sdk` or a child version dir (e.g. `ios--V1.6.0.4/`).
12
- * Native iOS only; SDK is vendored in the tool package.
6
+ * TopSDK iOS package root from cache.
7
+ * Native iOS only.
13
8
  */
14
- export function resolveIosSdkRoot(packageRoot: string): string {
15
- const bundled = defaultBundledIosSdkRoot(packageRoot);
16
- if (!fs.existsSync(bundled)) {
17
- throw new Error(`iOS SDK not found at ${BUNDLED_IOS_REL} (expected sdk/ and plugins/ inside).`);
18
- }
9
+ function findIosSdkRoot(container: string): string | null {
10
+ if (!fs.existsSync(container)) return null;
19
11
  const children = fs
20
- .readdirSync(bundled, { withFileTypes: true })
12
+ .readdirSync(container, { withFileTypes: true })
21
13
  .filter((d) => d.isDirectory() && !d.name.startsWith("."))
22
- .map((d) => path.join(bundled, d.name))
14
+ .map((d) => path.join(container, d.name))
23
15
  .sort()
24
16
  .reverse();
25
17
  for (const dir of children) {
@@ -27,10 +19,41 @@ export function resolveIosSdkRoot(packageRoot: string): string {
27
19
  return dir;
28
20
  }
29
21
  }
30
- if (fs.existsSync(path.join(bundled, "sdk")) && fs.existsSync(path.join(bundled, "plugins"))) {
31
- return bundled;
22
+ if (fs.existsSync(path.join(container, "sdk")) && fs.existsSync(path.join(container, "plugins"))) {
23
+ return container;
32
24
  }
25
+ return null;
26
+ }
27
+
28
+ function findLatestCachedIosSdkRoot(): string | null {
29
+ const iosCache = path.join(MEET_SDK_TOOL_CACHE_ROOT, "sdk", "ios");
30
+ if (!fs.existsSync(iosCache)) return null;
31
+ const candidates: string[] = [];
32
+ const walk = (dir: string): void => {
33
+ for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
34
+ if (!ent.isDirectory() || ent.name.startsWith(".")) continue;
35
+ const abs = path.join(dir, ent.name);
36
+ if (ent.name === "extracted") {
37
+ candidates.push(abs);
38
+ } else {
39
+ walk(abs);
40
+ }
41
+ }
42
+ };
43
+ walk(iosCache);
44
+ candidates.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
45
+ for (const candidate of candidates) {
46
+ const found = findIosSdkRoot(candidate);
47
+ if (found) return found;
48
+ }
49
+ return null;
50
+ }
51
+
52
+ export function resolveIosSdkRoot(_packageRoot: string): string {
53
+ const cached = findLatestCachedIosSdkRoot();
54
+ if (cached) return cached;
55
+
33
56
  throw new Error(
34
- `iOS SDK layout invalid under ${BUNDLED_IOS_REL}: need sdk/ + plugins/ at top level or under one version directory.`
57
+ `iOS SDK not found in ${path.join(MEET_SDK_TOOL_CACHE_ROOT, "sdk", "ios")}; run download-ios-sdk or setup first.`
35
58
  );
36
59
  }
package/src/mcp/server.ts CHANGED
@@ -46,7 +46,7 @@ export function createMeetgamesMcpServer(): McpServer {
46
46
  "meetgames_fetch_config",
47
47
  {
48
48
  description:
49
- "Download TOPSDK meetsdk-remote-config.json via downloadSDKConfig and write it into project root.",
49
+ "Download TOPSDK meetsdk-remote-config.json via downloadSDKConfig and write it into ~/.cache/meet-sdk-tool.",
50
50
  inputSchema: {
51
51
  projectRoot: z.string().optional().describe("Absolute or relative project root; default is current working directory."),
52
52
  env: z.enum(["prod", "pre", "test"]).optional().describe("TOPSDK API environment."),
@@ -78,11 +78,14 @@ export function createMeetgamesMcpServer(): McpServer {
78
78
  inputSchema: {
79
79
  projectRoot: z.string().optional().describe("Absolute or relative project root; default is current working directory."),
80
80
  appTarget: z.string().optional().describe("App target to integrate/check: Android application Gradle module or iOS App target name."),
81
+ env: z.enum(["prod", "pre", "test"]).optional().describe("TOPSDK API environment for selecting cached config."),
82
+ appId: z.string().optional().describe("Application id for selecting cached config."),
83
+ channelType: z.string().optional().describe("Channel type for selecting cached config."),
81
84
  },
82
85
  },
83
- async ({ projectRoot, appTarget }) => {
86
+ async ({ projectRoot, appTarget, env, appId, channelType }) => {
84
87
  try {
85
- const result = await meetgamesIntegrate({ projectRoot, appTarget, dryRun: true });
88
+ const result = await meetgamesIntegrate({ projectRoot, appTarget, env, appId, channelType, dryRun: true });
86
89
  if (result.hasErrors) {
87
90
  return {
88
91
  ...toSuccessResult(result),
@@ -103,11 +106,14 @@ export function createMeetgamesMcpServer(): McpServer {
103
106
  inputSchema: {
104
107
  projectRoot: z.string().optional().describe("Absolute or relative project root; default is current working directory."),
105
108
  appTarget: z.string().optional().describe("App target to integrate/check: Android application Gradle module or iOS App target name."),
109
+ env: z.enum(["prod", "pre", "test"]).optional().describe("TOPSDK API environment for selecting cached config."),
110
+ appId: z.string().optional().describe("Application id for selecting cached config."),
111
+ channelType: z.string().optional().describe("Channel type for selecting cached config."),
106
112
  },
107
113
  },
108
- async ({ projectRoot, appTarget }) => {
114
+ async ({ projectRoot, appTarget, env, appId, channelType }) => {
109
115
  try {
110
- const result = await meetgamesIntegrate({ projectRoot, appTarget, dryRun: false });
116
+ const result = await meetgamesIntegrate({ projectRoot, appTarget, env, appId, channelType, dryRun: false });
111
117
  if (result.hasErrors) {
112
118
  return {
113
119
  ...toSuccessResult(result),
@@ -125,7 +131,7 @@ export function createMeetgamesMcpServer(): McpServer {
125
131
  "meetgames_setup",
126
132
  {
127
133
  description:
128
- "Download meetsdk-remote-config.json then run default integrate recipe (fetch-config + integrate).",
134
+ "Download meetsdk-remote-config.json into ~/.cache/meet-sdk-tool, ensure iOS SDK cache when needed, then run the default integrate recipe.",
129
135
  inputSchema: {
130
136
  projectRoot: z.string().optional().describe("Absolute or relative project root; default is current working directory."),
131
137
  env: z.enum(["prod", "pre", "test"]).optional().describe("TOPSDK API environment."),
@@ -9,7 +9,6 @@ import {
9
9
  } from "../config/meetSdkRemoteConfig.js";
10
10
  import {
11
11
  validateDownloadSdkConfigForWrite,
12
- writeDownloadSdkConfigRaw,
13
12
  } from "../config/fetchConfigWrite.js";
14
13
  import type { PipelineReport } from "../contracts/types.js";
15
14
  import type { BinaryCopy } from "../ops/handlers.js";
@@ -22,6 +21,8 @@ import {
22
21
  } from "../core/platform.js";
23
22
  import { buildWorkspaceContext, resolvePackageRoot } from "../core/workspace.js";
24
23
  import { defaultTopSdkBaseUrl, fetchTopSdkDownloadSdkConfig, type TopSdkApiEnv } from "../remote/topsdkDownloadSdkConfig.js";
24
+ import { downloadIosSdkToBundled, defaultSdkHomeApiBaseUrl } from "../remote/sdkHomeDownload.js";
25
+ import { resolveRemoteConfigCachePath, writeRemoteConfigCache } from "../cache.js";
25
26
 
26
27
  const DEFAULT_MANIFEST_REL = "recipes/integrate-default.yaml";
27
28
 
@@ -43,6 +44,17 @@ function readLocalMeetSdkRemoteConfig(projectRoot: string): MeetSdkRemoteConfig
43
44
  }
44
45
  }
45
46
 
47
+ function resolveCachedRemoteConfigPath(params: {
48
+ env?: TopSdkApiEnv;
49
+ appId?: string;
50
+ channelType?: string;
51
+ }): string | undefined {
52
+ const appId = params.appId || process.env.TOPSDK_APP_ID || "";
53
+ const channelType = params.channelType || process.env.TOPSDK_CHANNEL_TYPE || "";
54
+ if (!appId || !channelType) return undefined;
55
+ return resolveRemoteConfigCachePath({ env: params.env ?? "prod", appId, channelType });
56
+ }
57
+
46
58
  function ensureDefaultManifestPath(packageRoot: string): string {
47
59
  const manifestPath = path.join(packageRoot, DEFAULT_MANIFEST_REL);
48
60
  if (!fs.existsSync(manifestPath)) {
@@ -54,12 +66,13 @@ function ensureDefaultManifestPath(packageRoot: string): string {
54
66
  function ensureSinglePlatformContext(
55
67
  projectRoot: string,
56
68
  packageRoot: string,
57
- appTarget?: string
69
+ appTarget?: string,
70
+ remoteConfigPath?: string
58
71
  ): {
59
72
  platform: DetectedPlatform;
60
73
  ctx: ReturnType<typeof buildWorkspaceContext>;
61
74
  } {
62
- const detectedCtx = buildWorkspaceContext(projectRoot, packageRoot, { appTarget });
75
+ const detectedCtx = buildWorkspaceContext(projectRoot, packageRoot, { appTarget, remoteConfigPath });
63
76
  const detected = detectSinglePlatform(detectedCtx);
64
77
  if (!detected.ok) throw new Error(detected.error);
65
78
  const platformCtx = platformContext(detectedCtx, detected.platform);
@@ -121,7 +134,6 @@ export async function meetgamesFetchConfig(params: {
121
134
  warnings: string[];
122
135
  }> {
123
136
  const projectRoot = ensureProjectRoot(params.projectRoot);
124
- const out = path.join(projectRoot, MEETSDK_REMOTE_CONFIG_FILENAME);
125
137
  const localConfig = readLocalMeetSdkRemoteConfig(projectRoot);
126
138
 
127
139
  const appId = params.appId || localConfig?.topsdk.appId || process.env.TOPSDK_APP_ID || "";
@@ -153,13 +165,23 @@ export async function meetgamesFetchConfig(params: {
153
165
  }
154
166
  warnings.push(...validation.warnings);
155
167
 
156
- writeDownloadSdkConfigRaw(out, res.text);
157
- return { outputPath: out, requestUrl: res.url, env, warnings };
168
+ const written = writeRemoteConfigCache({
169
+ env,
170
+ appId,
171
+ channelType,
172
+ requestUrl: res.url,
173
+ rawText: res.text,
174
+ warnings,
175
+ });
176
+ return { outputPath: written.configPath, requestUrl: res.url, env, warnings };
158
177
  }
159
178
 
160
179
  export async function meetgamesIntegrate(params: {
161
180
  projectRoot?: string;
162
181
  appTarget?: string;
182
+ env?: TopSdkApiEnv;
183
+ appId?: string;
184
+ channelType?: string;
163
185
  dryRun: boolean;
164
186
  }): Promise<{
165
187
  dryRun: boolean;
@@ -176,7 +198,8 @@ export async function meetgamesIntegrate(params: {
176
198
  const { platform, ctx } = ensureSinglePlatformContext(
177
199
  projectRoot,
178
200
  packageRoot,
179
- resolveAppTarget(params)
201
+ resolveAppTarget(params),
202
+ resolveCachedRemoteConfigPath(params)
180
203
  );
181
204
  const { report, patch, binaryCopies } = await runPipeline(ctx, manifestForPlatform(manifest, platform), { dryRun: params.dryRun });
182
205
  return {
@@ -205,7 +228,7 @@ export async function meetgamesSetup(params: {
205
228
  const projectRoot = ensureProjectRoot(params.projectRoot);
206
229
  const packageRoot = resolvePackageRoot();
207
230
  const appTarget = resolveAppTarget(params);
208
- ensureSinglePlatformContext(projectRoot, packageRoot, appTarget);
231
+ const { platform } = ensureSinglePlatformContext(projectRoot, packageRoot, appTarget);
209
232
  const fetch = await meetgamesFetchConfig({
210
233
  projectRoot,
211
234
  env: params.env,
@@ -213,9 +236,17 @@ export async function meetgamesSetup(params: {
213
236
  appSecret: params.appSecret,
214
237
  channelType: params.channelType,
215
238
  });
239
+ if (platform === "ios") {
240
+ await downloadIosSdkToBundled(packageRoot, {
241
+ baseUrl: process.env.MEETGAMES_SDK_HOME_API_BASE_URL?.trim() || defaultSdkHomeApiBaseUrl,
242
+ });
243
+ }
216
244
  const integrate = await meetgamesIntegrate({
217
245
  projectRoot,
218
246
  appTarget,
247
+ env: params.env,
248
+ appId: params.appId,
249
+ channelType: params.channelType,
219
250
  dryRun: params.dryRun,
220
251
  });
221
252
  return { fetch, integrate };
@@ -59,6 +59,18 @@ function moduleGradleRel(ctx: WorkspaceContext): string | null {
59
59
  return null;
60
60
  }
61
61
 
62
+ function resolveRemoteConfigInput(ctx: WorkspaceContext, configFile: unknown): { abs: string; label: string } {
63
+ if (typeof configFile === "string" && configFile.trim()) {
64
+ const normalized = configFile.trim().replace(/\\/g, "/");
65
+ return {
66
+ abs: path.isAbsolute(normalized) ? normalized : path.join(ctx.projectRoot, normalized),
67
+ label: normalized,
68
+ };
69
+ }
70
+ const abs = ctx.remoteConfigPath ?? path.join(ctx.projectRoot, MEETSDK_REMOTE_CONFIG_FILENAME);
71
+ return { abs, label: abs };
72
+ }
73
+
62
74
  export interface BinaryCopy {
63
75
  fromAbs: string;
64
76
  relTo: string;
@@ -80,23 +92,19 @@ export const opHandlers: Record<string, OpHandler> = {
80
92
  */
81
93
  "gradle.applyMeetSdkRemoteConfig": async (ctx, store, args, _dry, _bc) => {
82
94
  if (!ctx.android?.ok) return fail(["android application module not detected"]);
83
- const configRel =
84
- typeof args.configFile === "string" && args.configFile.trim()
85
- ? args.configFile.trim().replace(/\\/g, "/")
86
- : MEETSDK_REMOTE_CONFIG_FILENAME;
87
- const abs = path.join(ctx.projectRoot, configRel);
88
- if (!fs.existsSync(abs)) return fail([`meet sdk remote config not found: ${configRel}`]);
95
+ const config = resolveRemoteConfigInput(ctx, args.configFile);
96
+ if (!fs.existsSync(config.abs)) return fail([`meet sdk remote config not found: ${config.label}`]);
89
97
  let raw: unknown;
90
98
  try {
91
- raw = JSON.parse(fs.readFileSync(abs, "utf8")) as unknown;
99
+ raw = JSON.parse(fs.readFileSync(config.abs, "utf8")) as unknown;
92
100
  } catch {
93
- return fail([`failed to parse JSON: ${configRel}`]);
101
+ return fail([`failed to parse JSON: ${config.label}`]);
94
102
  }
95
103
  const parsed = tryParseAsMeetSdkRemoteConfig(raw);
96
- if (!parsed) return fail([`not a meetsdk-remote-config document: ${configRel}`]);
104
+ if (!parsed) return fail([`not a meetsdk-remote-config document: ${config.label}`]);
97
105
  const v = validateMeetSdkRemoteConfig(parsed);
98
106
  if (!v.ok) {
99
- return fail([`meetsdk-remote-config validation failed (${configRel}): ${v.missing.join(", ")}`]);
107
+ return fail([`meetsdk-remote-config validation failed (${config.label}): ${v.missing.join(", ")}`]);
100
108
  }
101
109
  const resolved = applyMeetSdkDefaultConfig(
102
110
  parsed,
@@ -244,20 +252,16 @@ export const opHandlers: Record<string, OpHandler> = {
244
252
 
245
253
  "files.downloadGoogleServicesJson": async (ctx, _store, args, dryRun, _bc) => {
246
254
  if (!ctx.android?.ok) return fail(["android application module not detected"]);
247
- const configRel =
248
- typeof args.configFile === "string" && args.configFile.trim()
249
- ? args.configFile.trim().replace(/\\/g, "/")
250
- : MEETSDK_REMOTE_CONFIG_FILENAME;
251
- const abs = path.join(ctx.projectRoot, configRel);
252
- if (!fs.existsSync(abs)) return fail([`meet sdk remote config not found: ${configRel}`]);
255
+ const config = resolveRemoteConfigInput(ctx, args.configFile);
256
+ if (!fs.existsSync(config.abs)) return fail([`meet sdk remote config not found: ${config.label}`]);
253
257
  let raw: unknown;
254
258
  try {
255
- raw = JSON.parse(fs.readFileSync(abs, "utf8")) as unknown;
259
+ raw = JSON.parse(fs.readFileSync(config.abs, "utf8")) as unknown;
256
260
  } catch {
257
- return fail([`failed to parse JSON: ${configRel}`]);
261
+ return fail([`failed to parse JSON: ${config.label}`]);
258
262
  }
259
263
  const parsed = tryParseAsMeetSdkRemoteConfig(raw);
260
- if (!parsed) return fail([`not a meetsdk-remote-config document: ${configRel}`]);
264
+ if (!parsed) return fail([`not a meetsdk-remote-config document: ${config.label}`]);
261
265
  const resolved = applyMeetSdkDefaultConfig(
262
266
  parsed,
263
267
  await loadMeetSdkDefaultConfigWithLatestAndroidVersion({
@@ -1,8 +1,8 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
- import { defaultBundledIosSdkRoot } from "../ios/sdkBundle.js";
5
4
  import { syncMeetSdkIosVersionToConfig } from "../config/meetSdkIosConfig.js";
5
+ import { resolveIosSdkCacheLayout, withCacheLock, writeIosSdkCacheMetadata } from "../cache.js";
6
6
 
7
7
  export const defaultSdkHomeApiBaseUrl = "https://business-api.meetgames.com";
8
8
  export const DEFAULT_IOS_SDK_PLUGINS = ["guest", "facebook", "google", "apple", "apple_pay", "dataAppsFlyer"] as const;
@@ -36,7 +36,6 @@ export interface DownloadIosSdkOptions {
36
36
  version?: string;
37
37
  plugins?: readonly string[] | string;
38
38
  packageType?: string;
39
- outputDir?: string;
40
39
  signal?: AbortSignal;
41
40
  }
42
41
 
@@ -50,6 +49,7 @@ export interface DownloadIosSdkResult {
50
49
  extractDir: string;
51
50
  resolvedSdkRoot: string;
52
51
  iosConfigPath: string;
52
+ cacheMetadataPath: string;
53
53
  }
54
54
 
55
55
  function isRecord(value: unknown): value is JsonRecord {
@@ -247,11 +247,16 @@ export async function downloadIosSdkToBundled(
247
247
  packageType,
248
248
  signal: options.signal,
249
249
  });
250
- const extractDir = path.resolve(options.outputDir || defaultBundledIosSdkRoot(packageRoot));
251
- const zipPath = path.join(extractDir, resolveIosSdkZipFileName(version));
250
+ const cacheLayout = resolveIosSdkCacheLayout({ version, packageType, plugins });
251
+ const extractDir = cacheLayout.extractDir;
252
+ const zipPath = cacheLayout.zipPath;
252
253
 
253
- await downloadBinaryFile(downloadResult.sdkZipUrl, zipPath, options.signal);
254
- await extractZip(zipPath, extractDir);
254
+ const downloadAndExtract = async (): Promise<void> => {
255
+ await downloadBinaryFile(downloadResult.sdkZipUrl, zipPath, options.signal);
256
+ fs.rmSync(extractDir, { recursive: true, force: true });
257
+ await extractZip(zipPath, extractDir);
258
+ };
259
+ await withCacheLock(cacheLayout.lockDir, downloadAndExtract);
255
260
  const iosConfigPath = syncMeetSdkIosVersionToConfig({
256
261
  packageRoot,
257
262
  version,
@@ -259,6 +264,21 @@ export async function downloadIosSdkToBundled(
259
264
  packageType,
260
265
  plugins,
261
266
  });
267
+ const resolvedSdkRoot = resolveIosSdkRootFromDirectory(extractDir);
268
+ const cacheMetadataPath = cacheLayout.metadataPath;
269
+ writeIosSdkCacheMetadata({
270
+ metadataPath: cacheMetadataPath,
271
+ version,
272
+ date: versionResult.ios.date,
273
+ packageType,
274
+ plugins,
275
+ versionUrl: versionResult.url,
276
+ downloadApiUrl: downloadResult.url,
277
+ sdkZipUrl: downloadResult.sdkZipUrl,
278
+ zipPath,
279
+ extractDir,
280
+ resolvedSdkRoot,
281
+ });
262
282
 
263
283
  return {
264
284
  version,
@@ -268,7 +288,8 @@ export async function downloadIosSdkToBundled(
268
288
  sdkZipUrl: downloadResult.sdkZipUrl,
269
289
  zipPath,
270
290
  extractDir,
271
- resolvedSdkRoot: resolveIosSdkRootFromDirectory(extractDir),
291
+ resolvedSdkRoot,
272
292
  iosConfigPath,
293
+ cacheMetadataPath,
273
294
  };
274
295
  }
@@ -7,12 +7,14 @@ import { runDoctor } from "../src/core/doctor.js";
7
7
  import { detectSinglePlatform, platformContext } from "../src/core/platform.js";
8
8
  import { runPipeline } from "../src/core/pipeline.js";
9
9
  import { buildWorkspaceContext } from "../src/core/workspace.js";
10
+ import { resolveIosSdkRootFromDirectory } from "../src/remote/sdkHomeDownload.js";
10
11
 
11
12
  const pkgRoot = path.resolve(__dirname, "..");
12
13
  const androidLatestRoot = path.join(pkgRoot, "fixtures", "android-test-project", "android-latest-project");
13
14
  const iosProjectRoot = path.join(pkgRoot, "fixtures", "ios-test-project", "tooltest");
14
15
  const iosRemoteConfigFixture = path.join(pkgRoot, "fixtures", "meetsdk-remote-config.ios-tooltest.json");
15
16
  const hasIosProjectFixture = fs.existsSync(iosProjectRoot);
17
+ const fixtureIosSdkRoot = resolveIosSdkRootFromDirectory(path.join(pkgRoot, "fixtures", "ios-sdk", "topSDK-ios--V1.6.0.5"));
16
18
 
17
19
  function copyIosProjectToTemp(): string {
18
20
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "meet-ios-doctor-"));
@@ -67,11 +69,11 @@ describe("doctor", () => {
67
69
  const tmp = copyIosProjectToTemp();
68
70
  try {
69
71
  const manifest = loadManifestFile(path.join(pkgRoot, "recipes", "ios-default.yaml"));
70
- const applyCtx = buildWorkspaceContext(tmp, pkgRoot);
72
+ const applyCtx = buildWorkspaceContext(tmp, pkgRoot, { iosSdkRoot: fixtureIosSdkRoot });
71
73
  const { report: applyReport } = await runPipeline(applyCtx, manifest, { dryRun: false });
72
74
  expect(applyReport.errors).toEqual([]);
73
75
 
74
- const ctx = buildWorkspaceContext(tmp, pkgRoot);
76
+ const ctx = buildWorkspaceContext(tmp, pkgRoot, { iosSdkRoot: fixtureIosSdkRoot });
75
77
  const detected = detectSinglePlatform(ctx);
76
78
  expect(detected.ok && detected.platform).toBe("ios");
77
79
 
@@ -9,7 +9,7 @@ const here = path.dirname(fileURLToPath(import.meta.url));
9
9
  const pkgRoot = path.resolve(here, "..");
10
10
  const androidFixtureRoot = path.join(pkgRoot, "fixtures", "android-test-project");
11
11
 
12
- function testProjectRoot(...names: string[]): string {
12
+ function fixtureProjectRoot(...names: string[]): string {
13
13
  for (const name of names) {
14
14
  const candidate = path.join(androidFixtureRoot, name);
15
15
  if (fs.existsSync(candidate)) return candidate;
@@ -39,7 +39,7 @@ function createAndroidAppModule(root: string, name: string, applicationId: strin
39
39
  }
40
40
 
41
41
  describe("fixture host trees", () => {
42
- const powerRaidRoot = testProjectRoot("power-raid");
42
+ const powerRaidRoot = fixtureProjectRoot("power-raid");
43
43
 
44
44
  it("android-latest-project (plugins DSL) resolves application module :app", () => {
45
45
  const root = path.join(androidFixtureRoot, "android-latest-project");
@@ -3,14 +3,19 @@ import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { describe, expect, it } from "vitest";
5
5
  import { resolveIosSdkRoot } from "../src/ios/sdkBundle.js";
6
+ import { MEET_SDK_TOOL_CACHE_ROOT } from "../src/cache.js";
6
7
 
7
8
  const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8
9
 
9
10
  describe("resolveIosSdkRoot", () => {
10
- it("finds nested TopSDK package under bundled/ios-sdk", () => {
11
- const root = resolveIosSdkRoot(pkgRoot);
12
- expect(root).toContain(`${path.sep}bundled${path.sep}ios-sdk`);
13
- expect(fs.existsSync(path.join(root, "sdk", "topsdkConfig.t.json"))).toBe(true);
14
- expect(fs.existsSync(path.join(root, "plugins", "GuestSignin", "config.t.json"))).toBe(true);
11
+ it("uses only the iOS SDK cache", () => {
12
+ try {
13
+ const root = resolveIosSdkRoot(pkgRoot);
14
+ expect(root).toContain(`${path.sep}.cache${path.sep}meet-sdk-tool${path.sep}sdk${path.sep}ios`);
15
+ expect(fs.existsSync(path.join(root, "sdk", "topsdkConfig.t.json"))).toBe(true);
16
+ expect(fs.existsSync(path.join(root, "plugins"))).toBe(true);
17
+ } catch (e) {
18
+ expect(e instanceof Error ? e.message : String(e)).toContain(path.join(MEET_SDK_TOOL_CACHE_ROOT, "sdk", "ios"));
19
+ }
15
20
  });
16
21
  });
package/tests/mcp.e2e.ts CHANGED
@@ -153,7 +153,6 @@ describe("meetgames MCP stdio server", () => {
153
153
  expect(dryRun.dryRun).toBe(true);
154
154
  expect(dryRun.hasErrors).toBe(false);
155
155
  expect(String(dryRun.patch)).toContain("TOPSDK AUTO");
156
- expect(fs.existsSync(path.join(projectRoot, "vendor", "meet-integrate", "sample.txt"))).toBe(false);
157
156
  });
158
157
 
159
158
  it("runs fetch-config and setup dry-run through stdio", async () => {
@@ -175,9 +174,8 @@ describe("meetgames MCP stdio server", () => {
175
174
  );
176
175
  expect(fetched.env).toBe("test");
177
176
  expect(String(fetched.requestUrl)).toContain("/console/openSDK/downloadSDKConfig");
178
- expect(fs.readFileSync(path.join(projectRoot, "meetsdk-remote-config.json"), "utf8")).toBe(
179
- fs.readFileSync(remoteConfigFixture, "utf8")
180
- );
177
+ expect(fs.existsSync(path.join(projectRoot, "meetsdk-remote-config.json"))).toBe(false);
178
+ expect(fs.readFileSync(String(fetched.outputPath), "utf8")).toBe(fs.readFileSync(remoteConfigFixture, "utf8"));
181
179
 
182
180
  const setup = structured(
183
181
  await client.callTool({
@@ -211,7 +209,6 @@ describe("meetgames MCP stdio server", () => {
211
209
  expect(applied.dryRun).toBe(false);
212
210
  expect((applied.report as { errors?: string[] }).errors).toEqual([]);
213
211
  expect(applied.hasErrors).toBe(false);
214
- expect(fs.existsSync(path.join(projectRoot, "vendor", "meet-integrate", "sample.txt"))).toBe(true);
215
212
  expect(fs.readFileSync(path.join(projectRoot, "app", "build.gradle"), "utf8")).toContain("TOPSDK AUTO");
216
213
  });
217
214
  });
@@ -236,6 +236,31 @@ describe("meetSdkRemoteConfig", () => {
236
236
  });
237
237
  });
238
238
 
239
+ it("parses Adjust eventUrl under analytics.adjust", () => {
240
+ const parsed = tryParseAsMeetSdkRemoteConfig({
241
+ packageName: "com.example",
242
+ channel: "APPLE",
243
+ devicePlatform: "ios",
244
+ topsdk: { appId: "1", appSecret: "s" },
245
+ sdkModules: {
246
+ login: {},
247
+ payment: {},
248
+ analytics: {
249
+ adjust: {
250
+ appCode: "adjust-app-code",
251
+ eventUrl: "https://example.invalid/events.json",
252
+ },
253
+ },
254
+ },
255
+ });
256
+
257
+ expect(parsed?.sdkModules.analytics.adjust).toMatchObject({
258
+ appId: "adjust-app-code",
259
+ eventUrl: "https://example.invalid/events.json",
260
+ enableSandbox: false,
261
+ });
262
+ });
263
+
239
264
  it("maps TWITTER / LINE / KAKAO from channelAuthConfig", () => {
240
265
  const body = {
241
266
  code: 200,