@leonxin/meetgames 0.1.8 → 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 (228) hide show
  1. package/.agents/skills/meet-sdk-regression/SKILL.md +93 -0
  2. package/.cursor/mcp.example.json +16 -0
  3. package/.cursor/mcp.json +11 -0
  4. package/.cursor/skills/meetgames-mcp/SKILL.md +48 -0
  5. package/.vite/vitest/results.json +1 -0
  6. package/README.md +36 -13
  7. package/{meetsdk-android.json → config/meetsdk-android.json} +2 -1
  8. package/config/meetsdk-ios.json +15 -0
  9. package/dist/android/adapter.d.ts.map +1 -1
  10. package/dist/android/adapter.js +2 -2
  11. package/dist/android/adapter.js.map +1 -1
  12. package/dist/android/detect.d.ts +2 -2
  13. package/dist/android/detect.d.ts.map +1 -1
  14. package/dist/android/detect.js +36 -8
  15. package/dist/android/detect.js.map +1 -1
  16. package/dist/cache.d.ts +44 -0
  17. package/dist/cache.d.ts.map +1 -0
  18. package/dist/cache.js +101 -0
  19. package/dist/cache.js.map +1 -0
  20. package/dist/cli.d.ts.map +1 -1
  21. package/dist/cli.js +181 -49
  22. package/dist/cli.js.map +1 -1
  23. package/dist/config/meetSdkDefaultConfig.d.ts +19 -2
  24. package/dist/config/meetSdkDefaultConfig.d.ts.map +1 -1
  25. package/dist/config/meetSdkDefaultConfig.js +69 -6
  26. package/dist/config/meetSdkDefaultConfig.js.map +1 -1
  27. package/dist/config/meetSdkIosConfig.d.ts +21 -0
  28. package/dist/config/meetSdkIosConfig.d.ts.map +1 -0
  29. package/dist/config/meetSdkIosConfig.js +68 -0
  30. package/dist/config/meetSdkIosConfig.js.map +1 -0
  31. package/dist/config/meetSdkRemoteConfig.d.ts +19 -1
  32. package/dist/config/meetSdkRemoteConfig.d.ts.map +1 -1
  33. package/dist/config/meetSdkRemoteConfig.js +64 -25
  34. package/dist/config/meetSdkRemoteConfig.js.map +1 -1
  35. package/dist/contracts/types.d.ts +27 -6
  36. package/dist/contracts/types.d.ts.map +1 -1
  37. package/dist/core/doctor.d.ts +17 -0
  38. package/dist/core/doctor.d.ts.map +1 -0
  39. package/dist/core/doctor.js +444 -0
  40. package/dist/core/doctor.js.map +1 -0
  41. package/dist/core/pipeline.d.ts.map +1 -1
  42. package/dist/core/pipeline.js +0 -15
  43. package/dist/core/pipeline.js.map +1 -1
  44. package/dist/core/platform.d.ts +12 -0
  45. package/dist/core/platform.d.ts.map +1 -0
  46. package/dist/core/platform.js +40 -0
  47. package/dist/core/platform.js.map +1 -0
  48. package/dist/core/previewPatches.d.ts +1 -1
  49. package/dist/core/previewPatches.js +2 -2
  50. package/dist/core/previewPatches.js.map +1 -1
  51. package/dist/core/reporter.js +1 -1
  52. package/dist/core/reporter.js.map +1 -1
  53. package/dist/core/workspace.d.ts +2 -2
  54. package/dist/core/workspace.d.ts.map +1 -1
  55. package/dist/core/workspace.js +6 -5
  56. package/dist/core/workspace.js.map +1 -1
  57. package/dist/index.d.ts +4 -1
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +4 -1
  60. package/dist/index.js.map +1 -1
  61. package/dist/ios/channelConfig.d.ts +1 -0
  62. package/dist/ios/channelConfig.d.ts.map +1 -1
  63. package/dist/ios/channelConfig.js +82 -0
  64. package/dist/ios/channelConfig.js.map +1 -1
  65. package/dist/ios/codeUtils.d.ts +1 -0
  66. package/dist/ios/codeUtils.d.ts.map +1 -1
  67. package/dist/ios/codeUtils.js +11 -2
  68. package/dist/ios/codeUtils.js.map +1 -1
  69. package/dist/ios/detect.d.ts +2 -2
  70. package/dist/ios/detect.d.ts.map +1 -1
  71. package/dist/ios/detect.js +49 -10
  72. package/dist/ios/detect.js.map +1 -1
  73. package/dist/ios/entitlements.d.ts +4 -0
  74. package/dist/ios/entitlements.d.ts.map +1 -0
  75. package/dist/ios/entitlements.js +53 -0
  76. package/dist/ios/entitlements.js.map +1 -0
  77. package/dist/ios/fileManager.d.ts.map +1 -1
  78. package/dist/ios/fileManager.js +3 -2
  79. package/dist/ios/fileManager.js.map +1 -1
  80. package/dist/ios/infoPlist.d.ts +1 -1
  81. package/dist/ios/infoPlist.d.ts.map +1 -1
  82. package/dist/ios/infoPlist.js.map +1 -1
  83. package/dist/ios/integrate.d.ts.map +1 -1
  84. package/dist/ios/integrate.js +214 -39
  85. package/dist/ios/integrate.js.map +1 -1
  86. package/dist/ios/pbxprojEditor.d.ts +2 -0
  87. package/dist/ios/pbxprojEditor.d.ts.map +1 -1
  88. package/dist/ios/pbxprojEditor.js +250 -6
  89. package/dist/ios/pbxprojEditor.js.map +1 -1
  90. package/dist/ios/pluginConfig.d.ts +1 -0
  91. package/dist/ios/pluginConfig.d.ts.map +1 -1
  92. package/dist/ios/pluginConfig.js +36 -4
  93. package/dist/ios/pluginConfig.js.map +1 -1
  94. package/dist/ios/sdkBundle.d.ts +1 -6
  95. package/dist/ios/sdkBundle.d.ts.map +1 -1
  96. package/dist/ios/sdkBundle.js +47 -17
  97. package/dist/ios/sdkBundle.js.map +1 -1
  98. package/dist/ios/template.d.ts +1 -0
  99. package/dist/ios/template.d.ts.map +1 -1
  100. package/dist/ios/template.js +14 -1
  101. package/dist/ios/template.js.map +1 -1
  102. package/dist/ios/types.d.ts +2 -2
  103. package/dist/ios/types.d.ts.map +1 -1
  104. package/dist/mcp/server.d.ts.map +1 -1
  105. package/dist/mcp/server.js +22 -15
  106. package/dist/mcp/server.js.map +1 -1
  107. package/dist/mcp/service.d.ts +11 -6
  108. package/dist/mcp/service.d.ts.map +1 -1
  109. package/dist/mcp/service.js +61 -18
  110. package/dist/mcp/service.js.map +1 -1
  111. package/dist/ops/handlers.d.ts.map +1 -1
  112. package/dist/ops/handlers.js +34 -23
  113. package/dist/ops/handlers.js.map +1 -1
  114. package/dist/remote/sdkHomeDownload.d.ts +65 -0
  115. package/dist/remote/sdkHomeDownload.d.ts.map +1 -0
  116. package/dist/remote/sdkHomeDownload.js +229 -0
  117. package/dist/remote/sdkHomeDownload.js.map +1 -0
  118. package/dist/remote/topsdkDownloadSdkConfig.d.ts.map +1 -1
  119. package/dist/remote/topsdkDownloadSdkConfig.js +11 -1
  120. package/dist/remote/topsdkDownloadSdkConfig.js.map +1 -1
  121. package/dist/shared/errors.d.ts +7 -0
  122. package/dist/shared/errors.d.ts.map +1 -0
  123. package/dist/shared/errors.js +16 -0
  124. package/dist/shared/errors.js.map +1 -0
  125. package/docs/API.md +246 -32
  126. package/docs/CLI.md +291 -0
  127. package/docs/INTEGRATION.md +116 -0
  128. package/docs/MCP.md +86 -0
  129. package/docs/README.md +18 -10
  130. package/docs/{api → archive/api}/downloadSDKConfig.md +9 -7
  131. package/docs/{api → archive/api}/getChannelConfig-meetgames.md +2 -2
  132. package/docs/archive/ios-migration.md +2139 -0
  133. package/docs/{ → archive/product/}/346/212/200/346/234/257/346/226/271/346/241/210/350/260/203/347/240/224.md +7 -7
  134. package/docs/{ → archive/product/}/351/234/200/346/261/202/346/226/207/346/241/243.md +16 -15
  135. package/package.json +7 -36
  136. package/recipes/android-default.yaml +0 -5
  137. package/recipes/integrate-default.yaml +0 -5
  138. package/src/android/adapter.ts +9 -0
  139. package/src/android/assembleIntegrationJson.ts +33 -0
  140. package/src/android/detect.ts +132 -0
  141. package/src/android/downloadGoogleServicesJson.ts +56 -0
  142. package/src/android/gradle.ts +116 -0
  143. package/src/android/manifest.ts +50 -0
  144. package/src/android/meetSdkRemoteGradle.ts +837 -0
  145. package/src/cache.ts +164 -0
  146. package/src/cli.ts +496 -0
  147. package/src/config/fetchConfigWrite.ts +30 -0
  148. package/src/config/loadAndroidIntegration.ts +41 -0
  149. package/src/config/loadManifest.ts +40 -0
  150. package/src/config/meetSdkDefaultConfig.ts +100 -0
  151. package/src/config/meetSdkIosConfig.ts +89 -0
  152. package/src/config/meetSdkRemoteConfig.ts +1215 -0
  153. package/src/config/topsdkFeatureModules.ts +92 -0
  154. package/src/contracts/types.ts +129 -0
  155. package/src/core/doctor.ts +485 -0
  156. package/src/core/patch.ts +64 -0
  157. package/src/core/pipeline.ts +107 -0
  158. package/src/core/platform.ts +47 -0
  159. package/src/core/previewPatches.ts +23 -0
  160. package/src/core/reporter.ts +24 -0
  161. package/src/core/workspace.ts +31 -0
  162. package/src/entry.ts +7 -0
  163. package/src/index.ts +140 -0
  164. package/src/ios/channelConfig.ts +128 -0
  165. package/src/ios/codeUtils.ts +160 -0
  166. package/src/ios/detect.ts +105 -0
  167. package/src/ios/entitlements.ts +61 -0
  168. package/src/ios/fileManager.ts +48 -0
  169. package/src/ios/infoPlist.ts +55 -0
  170. package/src/ios/integrate.ts +517 -0
  171. package/src/ios/pbxprojEditor.ts +450 -0
  172. package/src/ios/pluginConfig.ts +97 -0
  173. package/src/ios/reserved.ts +8 -0
  174. package/src/ios/sdkBundle.ts +59 -0
  175. package/src/ios/template.ts +36 -0
  176. package/src/ios/types.ts +65 -0
  177. package/src/mcp/server.ts +176 -0
  178. package/src/mcp/service.ts +253 -0
  179. package/src/mcp-entry.ts +7 -0
  180. package/src/ops/fileStore.ts +56 -0
  181. package/src/ops/handlers.ts +308 -0
  182. package/src/remote/fetchJson.ts +22 -0
  183. package/src/remote/sdkHomeDownload.ts +295 -0
  184. package/src/remote/topsdkDownloadSdkConfig.ts +93 -0
  185. package/src/remote/topsdkGetSdkConfig.ts +122 -0
  186. package/src/remote/topsdkSign.ts +10 -0
  187. package/src/shared/errors.ts +16 -0
  188. package/tests/assemble.test.ts +12 -0
  189. package/tests/doctor.test.ts +91 -0
  190. package/tests/downloadGoogleServicesJson.test.ts +47 -0
  191. package/tests/fetch-remote.test.ts +23 -0
  192. package/tests/fetchConfigOverrides.test.ts +28 -0
  193. package/tests/fetchConfigWrite.test.ts +54 -0
  194. package/tests/fixtures-hosts.test.ts +78 -0
  195. package/tests/gradle.test.ts +33 -0
  196. package/tests/integration-json.test.ts +29 -0
  197. package/tests/ios.codeUtils.test.ts +23 -0
  198. package/tests/ios.sdkBundle.test.ts +21 -0
  199. package/tests/loadManifest.test.ts +15 -0
  200. package/tests/manifest-xml.test.ts +30 -0
  201. package/tests/mcp.e2e.ts +214 -0
  202. package/tests/mcp.service.test.ts +53 -0
  203. package/tests/meetSdkRemoteConfig.test.ts +481 -0
  204. package/tests/meetSdkRemoteGradle.test.ts +414 -0
  205. package/tests/pipeline.android.test.ts +95 -0
  206. package/tests/pipeline.integration-json.test.ts +58 -0
  207. package/tests/pipeline.ios.test.ts +392 -0
  208. package/tests/pipeline.preview.patch.test.ts +85 -0
  209. package/tests/platformSelection.test.ts +77 -0
  210. package/tests/sdkHomeDownload.test.ts +124 -0
  211. package/tests/sdkVersionConfig.test.ts +131 -0
  212. package/tests/topsdk.test.ts +53 -0
  213. package/tests/topsdkDownloadSdkConfig.test.ts +81 -0
  214. package/tests/topsdkFeatureModules.test.ts +116 -0
  215. package/tsconfig.json +19 -0
  216. package/vitest.config.ts +9 -0
  217. package/vitest.mcp.config.ts +11 -0
  218. package/bundled/android/sample.txt +0 -1
  219. package/docs/ANDROID.md +0 -133
  220. package/docs/CURSOR-MCP-SETUP.md +0 -72
  221. package/docs/MCP-SKILL.md +0 -63
  222. package/fixtures/api-samples/getChannelConfig-meetgames.sample.json +0 -123
  223. package/fixtures/meetsdk-remote-config.download-shape.json +0 -20
  224. package/fixtures/meetsdk-remote-config.mock.json +0 -69
  225. package/fixtures/recipes/android-default.fixture.yaml +0 -15
  226. package/fixtures/recipes/android-integration.fixture.json +0 -29
  227. package/fixtures/topsdk-config-reference.json +0 -39
  228. /package/docs/{api → archive/api}/getSDKConfig.md +0 -0
@@ -0,0 +1,392 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { describe, expect, it } from "vitest";
6
+ import { loadManifestFile } from "../src/config/loadManifest.js";
7
+ import { buildWorkspaceContext } from "../src/core/workspace.js";
8
+ import { runPipeline } from "../src/core/pipeline.js";
9
+ import { detectIOS } from "../src/ios/detect.js";
10
+ import { resolveIosSdkRootFromDirectory } from "../src/remote/sdkHomeDownload.js";
11
+
12
+ const here = path.dirname(fileURLToPath(import.meta.url));
13
+ const pkgRoot = path.resolve(here, "..");
14
+ const iosRoot = path.join(pkgRoot, "fixtures", "ios-test-project", "tooltest");
15
+ const iosRemoteConfigFixture = path.join(pkgRoot, "fixtures", "meetsdk-remote-config.ios-tooltest.json");
16
+ const hasIosProjectFixture = fs.existsSync(iosRoot);
17
+ const manifest = () => loadManifestFile(path.join(pkgRoot, "recipes", "ios-default.yaml"));
18
+ const fixtureIosSdkRoot = resolveIosSdkRootFromDirectory(path.join(pkgRoot, "fixtures", "ios-sdk", "topSDK-ios--V1.6.0.5"));
19
+
20
+ function writeOfflineRemoteConfig(projectRoot: string): void {
21
+ fs.copyFileSync(iosRemoteConfigFixture, path.join(projectRoot, "meetsdk-remote-config.json"));
22
+ }
23
+
24
+ function copyProjectToTemp(): string {
25
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "meet-ios-integrate-"));
26
+ fs.cpSync(iosRoot, tmp, { recursive: true });
27
+ writeOfflineRemoteConfig(tmp);
28
+ return tmp;
29
+ }
30
+
31
+ function pbxBuildFileRefErrors(pbx: string): string[] {
32
+ const fileRefs = new Set<string>();
33
+ for (const match of pbx.matchAll(/^\s*([A-F0-9]{24}) \/\* .* \*\/ = \{isa = PBXFileReference;/gm)) {
34
+ fileRefs.add(match[1]);
35
+ }
36
+
37
+ const errors: string[] = [];
38
+ for (const match of pbx.matchAll(
39
+ /^\s*([A-F0-9]{24}) \/\* (.*?) \*\/ = \{isa = PBXBuildFile; fileRef = ([A-F0-9]{24}) /gm
40
+ )) {
41
+ const [, buildFileId, label, fileRefId] = match;
42
+ if (!fileRefs.has(fileRefId)) errors.push(`${label}: ${buildFileId} -> ${fileRefId}`);
43
+ }
44
+ return errors;
45
+ }
46
+
47
+ function plannedIosAsset(patch: string, binaryCopies: Array<{ relTo: string }>, name: string): boolean {
48
+ return patch.includes(name) || binaryCopies.some((c) => c.relTo.includes(name));
49
+ }
50
+
51
+ function testIosContext(projectRoot: string) {
52
+ return buildWorkspaceContext(projectRoot, pkgRoot, { iosSdkRoot: fixtureIosSdkRoot });
53
+ }
54
+
55
+ describe("pipeline ios fixture SDK", () => {
56
+ it("resolves TopSDK iOS fixture package", () => {
57
+ expect(fs.existsSync(path.join(fixtureIosSdkRoot, "sdk", "topsdkConfig.t.json"))).toBe(true);
58
+ expect(fs.existsSync(path.join(fixtureIosSdkRoot, "plugins", "GuestSignin", "config.t.json"))).toBe(true);
59
+ });
60
+ });
61
+
62
+ describe.skipIf(!hasIosProjectFixture)("pipeline ios fixture", () => {
63
+ it("detects tooltest xcodeproj with AppDelegate target", () => {
64
+ const d = detectIOS(iosRoot);
65
+ expect(d.ok).toBe(true);
66
+ if (d.ok) {
67
+ expect(d.targetName).toBe("tooltest");
68
+ expect(fs.existsSync(path.join(iosRoot, "tooltest", "AppDelegate.m"))).toBe(true);
69
+ }
70
+ });
71
+
72
+ it("detects when project root is the xcodeproj directory itself", () => {
73
+ const xcodeprojRoot = path.join(iosRoot, "tooltest.xcodeproj");
74
+ const d = detectIOS(xcodeprojRoot);
75
+ expect(d.ok).toBe(true);
76
+ if (d.ok) {
77
+ expect(d.xcodeprojPath).toBe(xcodeprojRoot);
78
+ expect(d.targetName).toBe("tooltest");
79
+ }
80
+ });
81
+
82
+ it("builds workspace context when project root is the xcodeproj directory", () => {
83
+ const ctx = testIosContext(path.join(iosRoot, "tooltest.xcodeproj"));
84
+ expect(ctx.ios?.ok).toBe(true);
85
+ expect(ctx.ios?.ok && ctx.ios.xcodeprojPath).toBe(path.join(iosRoot, "tooltest.xcodeproj"));
86
+ });
87
+
88
+ it("dry-run: plans TOPCore + GuestSignin frameworks and pbxproj edits", async () => {
89
+ const tmp = copyProjectToTemp();
90
+ try {
91
+ const ctx = testIosContext(tmp);
92
+ expect(ctx.ios?.ok).toBe(true);
93
+ const { report, patch, binaryCopies } = await runPipeline(ctx, manifest(), { dryRun: true });
94
+ expect(report.errors).toEqual([]);
95
+ expect(plannedIosAsset(patch, binaryCopies, "TOPCore.framework")).toBe(true);
96
+ expect(plannedIosAsset(patch, binaryCopies, "TOPGuestSigninPlugin.framework")).toBe(true);
97
+ expect(binaryCopies.some((c) => c.relTo.includes("topSDK/TOPCore.framework"))).toBe(true);
98
+ expect(binaryCopies.some((c) => c.relTo.includes("topSDK/TOPGuestSigninPlugin.framework"))).toBe(true);
99
+
100
+ } finally {
101
+ fs.rmSync(tmp, { recursive: true, force: true });
102
+ }
103
+ });
104
+
105
+ it("apply: copies SDK, updates pbxproj, injects AppDelegate", async () => {
106
+ const tmp = copyProjectToTemp();
107
+ try {
108
+ const ctx = testIosContext(tmp);
109
+ const { report } = await runPipeline(ctx, manifest(), { dryRun: false });
110
+ expect(report.errors).toEqual([]);
111
+
112
+ expect(fs.existsSync(path.join(tmp, "topSDK", "TOPCore.framework"))).toBe(true);
113
+ expect(fs.existsSync(path.join(tmp, "topSDK", "TOPSDK.framework"))).toBe(true);
114
+ expect(fs.existsSync(path.join(tmp, "topSDK", "TOPGuestSigninPlugin.framework"))).toBe(true);
115
+ expect(fs.existsSync(path.join(tmp, "topSDK", "TopSDKSource.bundle"))).toBe(true);
116
+
117
+ const pbx = fs.readFileSync(path.join(tmp, "tooltest.xcodeproj", "project.pbxproj"), "utf8");
118
+ expect(pbx).toContain("TOPCore.framework");
119
+ expect(pbx).toContain("TOPGuestSigninPlugin.framework");
120
+ expect(pbx).toContain('"-ObjC"');
121
+ expect(pbx).toContain('"-lc++"');
122
+ expect(pbx).not.toMatch(/\n\s+-ObjC,/);
123
+ expect(pbx).not.toMatch(/\n\s+-lc\+\+,/);
124
+ expect(pbxBuildFileRefErrors(pbx)).toEqual([]);
125
+ expect(pbx).not.toMatch(/\bundefined;/);
126
+
127
+ const delegate = fs.readFileSync(path.join(tmp, "tooltest", "AppDelegate.m"), "utf8");
128
+ expect(delegate).toContain("#import <TOPSDK/TopSDK.h>");
129
+ expect(delegate).toContain("TopSDK.sharedInstance");
130
+ expect(delegate).toContain("TOPDataSDK");
131
+
132
+ const plist = fs.readFileSync(path.join(tmp, "tooltest", "Info.plist"), "utf8");
133
+ expect(plist).toContain("<key>TOPSDK</key>");
134
+ expect(plist).toContain("<key>APP_ID</key>");
135
+ expect(plist).toContain("mock-ios-tooltest-app-id");
136
+ expect(plist).toContain("GuestSignin");
137
+ } finally {
138
+ fs.rmSync(tmp, { recursive: true, force: true });
139
+ }
140
+ });
141
+
142
+ it("apply: enables Apple Sign In entitlement when AppleSignin plugin is enabled", async () => {
143
+ const tmp = copyProjectToTemp();
144
+ try {
145
+ const configPath = path.join(tmp, "meetsdk-remote-config.json");
146
+ const remote = JSON.parse(fs.readFileSync(configPath, "utf8")) as Record<string, unknown>;
147
+ remote.sdkModules = {
148
+ login: { apple: {} },
149
+ payment: {},
150
+ analytics: {},
151
+ };
152
+ fs.writeFileSync(configPath, JSON.stringify(remote, null, 2), "utf8");
153
+
154
+ const ctx = testIosContext(tmp);
155
+ const { report } = await runPipeline(ctx, manifest(), { dryRun: false });
156
+ expect(report.errors).toEqual([]);
157
+
158
+ const entitlementsPath = path.join(tmp, "tooltest", "tooltest.entitlements");
159
+ expect(fs.existsSync(entitlementsPath)).toBe(true);
160
+ const entitlements = fs.readFileSync(entitlementsPath, "utf8");
161
+ expect(entitlements).toContain("com.apple.developer.applesignin");
162
+ expect(entitlements).toContain("Default");
163
+
164
+ const pbx = fs.readFileSync(path.join(tmp, "tooltest.xcodeproj", "project.pbxproj"), "utf8");
165
+ expect(pbx).toContain("CODE_SIGN_ENTITLEMENTS");
166
+ expect(pbx).toContain("tooltest/tooltest.entitlements");
167
+ expect(pbx).toContain("TOPAppleSigninPlugin.framework");
168
+ } finally {
169
+ fs.rmSync(tmp, { recursive: true, force: true });
170
+ }
171
+ });
172
+
173
+ it("dry-run: maps supported iOS plugin params from meetsdk remote config", async () => {
174
+ const tmp = copyProjectToTemp();
175
+ try {
176
+ const configPath = path.join(tmp, "meetsdk-remote-config.json");
177
+ const remote = JSON.parse(fs.readFileSync(configPath, "utf8")) as Record<string, unknown>;
178
+ remote.sdkModules = {
179
+ login: {
180
+ guest: {},
181
+ email: {},
182
+ apple: {},
183
+ facebook: {
184
+ clientId: "fb-app",
185
+ scheme: "fb-scheme",
186
+ secret: "fb-token",
187
+ name: "Facebook Display",
188
+ },
189
+ google: {
190
+ clientId: "google-client",
191
+ scheme: "google-reversed",
192
+ },
193
+ snapchat: {
194
+ clientId: "snap-client",
195
+ redirect: "snap-redirect",
196
+ scheme: "snap-scheme",
197
+ },
198
+ line: {
199
+ clientId: "line-client",
200
+ scheme: "line-scheme",
201
+ },
202
+ naver: {
203
+ clientId: "naver-client",
204
+ secret: "naver-secret",
205
+ name: "Naver Name",
206
+ scheme: "naver-scheme",
207
+ },
208
+ kakao: {
209
+ clientId: "kakao-client",
210
+ scheme: "kakao-scheme",
211
+ },
212
+ tiktok: {
213
+ clientId: "tiktok-client",
214
+ secret: "tiktok-secret",
215
+ redirect: "tiktok-scheme://callback",
216
+ },
217
+ discord: {
218
+ clientId: "discord-client",
219
+ secret: "discord-secret",
220
+ redirect: "https://example.invalid/oauth-redirect.html",
221
+ },
222
+ },
223
+ payment: {
224
+ googleIap: {},
225
+ },
226
+ analytics: {
227
+ appsflyer: {
228
+ devKey: "af-dev-key",
229
+ appleAppId: "123456789",
230
+ enableDebugLog: true,
231
+ },
232
+ adjust: {
233
+ appCode: "adjust-token",
234
+ enableSandbox: true,
235
+ },
236
+ facebookdata: {
237
+ clientId: "fb-app",
238
+ scheme: "fb-scheme",
239
+ secret: "fb-token",
240
+ name: "Facebook Display",
241
+ },
242
+ },
243
+ };
244
+ fs.writeFileSync(configPath, JSON.stringify(remote, null, 2), "utf8");
245
+
246
+ const ctx = testIosContext(tmp);
247
+ const { report, patch, binaryCopies } = await runPipeline(ctx, manifest(), { dryRun: true });
248
+ expect(report.errors).toEqual([]);
249
+ expect(report.warnings.join("\n")).not.toContain("Discord");
250
+ expect(plannedIosAsset(patch, binaryCopies, "TOPGoogleSigninPlugin.framework")).toBe(true);
251
+ expect(plannedIosAsset(patch, binaryCopies, "TOPFacebookSigninPlugin.framework")).toBe(true);
252
+ expect(plannedIosAsset(patch, binaryCopies, "TOPSnapchatSigninPlugin.framework")).toBe(true);
253
+ expect(plannedIosAsset(patch, binaryCopies, "TOPLineSigninPlugin.framework")).toBe(true);
254
+ expect(plannedIosAsset(patch, binaryCopies, "TOPNaverSigninPlugin.framework")).toBe(true);
255
+ expect(plannedIosAsset(patch, binaryCopies, "TOPKakaoSigninPlugin.framework")).toBe(true);
256
+ expect(plannedIosAsset(patch, binaryCopies, "TOPTiktokSigninPlugin.framework")).toBe(true);
257
+ expect(plannedIosAsset(patch, binaryCopies, "TOPDiscordSigninPlugin.framework")).toBe(true);
258
+ expect(plannedIosAsset(patch, binaryCopies, "TOPIAPPayPlugin.framework")).toBe(true);
259
+ expect(plannedIosAsset(patch, binaryCopies, "TOPDataAppsFlyerPlugin.framework")).toBe(true);
260
+ expect(plannedIosAsset(patch, binaryCopies, "TOPDataAdjustPlugin.framework")).toBe(true);
261
+ expect(plannedIosAsset(patch, binaryCopies, "TOPDataFacebookPlugin.framework")).toBe(true);
262
+ expect(patch).toContain("FacebookDisplayName");
263
+ expect(patch).toContain("Facebook Display");
264
+ expect(patch).toContain("REVERSED_CLIENT_ID");
265
+ expect(patch).toContain("google-reversed");
266
+ expect(patch).toContain("APPLE_APP_ID");
267
+ expect(patch).toContain("123456789");
268
+ expect(patch).toContain("APP_TOKEN");
269
+ expect(patch).toContain("adjust-token");
270
+ expect(patch).toContain("SCSDKRedirectUrl");
271
+ expect(patch).toContain("snap-redirect");
272
+ expect(binaryCopies.some((c) => c.relTo.includes("topSDK/TOPDataFacebookPlugin.framework"))).toBe(true);
273
+ } finally {
274
+ fs.rmSync(tmp, { recursive: true, force: true });
275
+ }
276
+ });
277
+
278
+ it("dry-run: uses remote GoogleService-Info.plist for iOS Firebase instead of bundled source", async () => {
279
+ const tmp = copyProjectToTemp();
280
+ try {
281
+ const configPath = path.join(tmp, "meetsdk-remote-config.json");
282
+ const remote = JSON.parse(fs.readFileSync(configPath, "utf8")) as Record<string, unknown>;
283
+ remote.sdkModules = {
284
+ login: {},
285
+ payment: {},
286
+ analytics: {
287
+ firebase: {
288
+ firebaseUrl: "https://cdn.example.invalid/GoogleService-Info.plist",
289
+ firebaseName: "GoogleService-Info.plist",
290
+ },
291
+ },
292
+ };
293
+ fs.writeFileSync(configPath, JSON.stringify(remote, null, 2), "utf8");
294
+
295
+ const ctx = testIosContext(tmp);
296
+ const { report, patch, binaryCopies } = await runPipeline(ctx, manifest(), { dryRun: true });
297
+
298
+ expect(report.errors).toEqual([]);
299
+ expect(report.warnings.join("\n")).toContain("would download GoogleService-Info.plist to tooltest/GoogleService-Info.plist");
300
+ expect(plannedIosAsset(patch, binaryCopies, "TOPDataFirebasePlugin.framework")).toBe(true);
301
+ } finally {
302
+ fs.rmSync(tmp, { recursive: true, force: true });
303
+ }
304
+ });
305
+
306
+ it("apply: keeps launch initialization in AppDelegate and routes URL/lifecycle callbacks to SceneDelegate when present", async () => {
307
+ const tmp = copyProjectToTemp();
308
+ try {
309
+ fs.writeFileSync(
310
+ path.join(tmp, "tooltest", "AppDelegate.m"),
311
+ `#import "AppDelegate.h"
312
+
313
+ @implementation AppDelegate
314
+
315
+ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
316
+ return YES;
317
+ }
318
+
319
+ @end
320
+ `,
321
+ "utf8"
322
+ );
323
+ fs.writeFileSync(
324
+ path.join(tmp, "tooltest", "SceneDelegate.m"),
325
+ `#import "SceneDelegate.h"
326
+
327
+ @implementation SceneDelegate
328
+
329
+ @end
330
+ `,
331
+ "utf8"
332
+ );
333
+
334
+ const ctx = testIosContext(tmp);
335
+ const { report } = await runPipeline(ctx, manifest(), { dryRun: false });
336
+ expect(report.errors).toEqual([]);
337
+
338
+ const appDelegate = fs.readFileSync(path.join(tmp, "tooltest", "AppDelegate.m"), "utf8");
339
+ expect(appDelegate).toContain("[TopSDK.sharedInstance application:application didFinishLaunchingWithOptions:launchOptions]");
340
+ expect(appDelegate).toContain("[TOPDataSDK application:application didFinishLaunchingWithOptions:launchOptions]");
341
+ expect(appDelegate).not.toContain("openURL:url options");
342
+ expect(appDelegate).not.toContain("continueUserActivity:userActivity restorationHandler");
343
+ expect(appDelegate).not.toContain("applicationDidEnterBackground");
344
+ expect(appDelegate).not.toContain("applicationWillEnterForeground");
345
+
346
+ const sceneDelegate = fs.readFileSync(path.join(tmp, "tooltest", "SceneDelegate.m"), "utf8");
347
+ expect(sceneDelegate).toContain("[TopSDK.sharedInstance scene:scene openURLContexts:URLContexts]");
348
+ expect(sceneDelegate).toContain("[TopSDK.sharedInstance scene:scene continueUserActivity:userActivity]");
349
+ expect(sceneDelegate).toContain("[TopSDK.sharedInstance sceneWillEnterForeground:scene]");
350
+ expect(sceneDelegate).toContain("[TopSDK.sharedInstance sceneDidEnterBackground:scene]");
351
+ } finally {
352
+ fs.rmSync(tmp, { recursive: true, force: true });
353
+ }
354
+ });
355
+
356
+ it("fails when the configured Info.plist file is missing", async () => {
357
+ const tmp = copyProjectToTemp();
358
+ try {
359
+ fs.rmSync(path.join(tmp, "tooltest", "Info.plist"));
360
+
361
+ const ctx = testIosContext(tmp);
362
+ const { report } = await runPipeline(ctx, manifest(), { dryRun: true });
363
+
364
+ expect(report.errors.join("\n")).toContain("Info.plist not found for iOS target tooltest");
365
+ } finally {
366
+ fs.rmSync(tmp, { recursive: true, force: true });
367
+ }
368
+ });
369
+
370
+ it("fails clearly when enabled iOS plugin is missing required remote params", async () => {
371
+ const tmp = copyProjectToTemp();
372
+ try {
373
+ const configPath = path.join(tmp, "meetsdk-remote-config.json");
374
+ const remote = JSON.parse(fs.readFileSync(configPath, "utf8")) as Record<string, unknown>;
375
+ remote.sdkModules = {
376
+ login: { google: {} },
377
+ payment: {},
378
+ analytics: {},
379
+ };
380
+ fs.writeFileSync(configPath, JSON.stringify(remote, null, 2), "utf8");
381
+
382
+ const ctx = testIosContext(tmp);
383
+ const { report } = await runPipeline(ctx, manifest(), { dryRun: false });
384
+ expect(report.errors.join("\n")).toContain("iOS remote config missing required channel params");
385
+ expect(report.errors.join("\n")).toContain("GoogleSignin: GOOGLE.scheme");
386
+ expect(report.errors.join("\n")).toContain("GoogleSignin: GOOGLE.clientId");
387
+ expect(report.results[0]?.changedFiles).toEqual([]);
388
+ } finally {
389
+ fs.rmSync(tmp, { recursive: true, force: true });
390
+ }
391
+ });
392
+ });
@@ -0,0 +1,85 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { loadAndroidIntegrationFile } from "../src/config/loadAndroidIntegration.js";
6
+ import { loadManifestFile } from "../src/config/loadManifest.js";
7
+ import type { AndroidIntegrationDocument, Manifest } from "../src/contracts/types.js";
8
+ import { clearPreviewPatchFilesIfTargetInside } from "../src/core/previewPatches.js";
9
+ import { buildWorkspaceContext } from "../src/core/workspace.js";
10
+ import { runPipeline } from "../src/core/pipeline.js";
11
+
12
+ const here = path.dirname(fileURLToPath(import.meta.url));
13
+ const pkgRoot = path.resolve(here, "..");
14
+ const fixtureRoot = path.join(pkgRoot, "fixtures", "android-test-project", "android-latest-project");
15
+ const recipeFixtureRoot = path.join(pkgRoot, "fixtures", "recipes");
16
+ const previewPatchPath = path.join(pkgRoot, "preview-patches", "pipeline.patch");
17
+
18
+ function stubSdkHomeVersion(): void {
19
+ vi.stubGlobal(
20
+ "fetch",
21
+ vi.fn(async () => ({
22
+ ok: true,
23
+ status: 200,
24
+ text: async () =>
25
+ JSON.stringify({
26
+ code: 200,
27
+ data: {
28
+ result: {
29
+ android: { ver: "1.6.1.3", date: "2026-05-25" },
30
+ ios: { ver: "1.6.0.5", date: "2026-06-09" },
31
+ },
32
+ },
33
+ }),
34
+ })) as unknown as typeof fetch
35
+ );
36
+ }
37
+
38
+ function toManifest(doc: AndroidIntegrationDocument): Manifest {
39
+ const { meta: _m, remote: _r, ...rest } = doc;
40
+ void _m;
41
+ void _r;
42
+ return rest;
43
+ }
44
+
45
+ /**
46
+ * Single on-disk preview for humans / CI artifacts: both fixture manifests (dry-run only).
47
+ */
48
+ describe("pipeline preview patch", () => {
49
+ beforeEach(() => {
50
+ stubSdkHomeVersion();
51
+ });
52
+
53
+ afterEach(() => {
54
+ vi.unstubAllGlobals();
55
+ });
56
+
57
+ it("writes preview-patches/pipeline.patch (yaml + integration-json)", async () => {
58
+ const ctx = buildWorkspaceContext(fixtureRoot, pkgRoot);
59
+ expect(ctx.android?.ok).toBe(true);
60
+
61
+ const yamlManifest = loadManifestFile(path.join(recipeFixtureRoot, "android-default.fixture.yaml"));
62
+ const rYaml = await runPipeline(ctx, yamlManifest, { dryRun: true });
63
+ expect(rYaml.report.errors).toEqual([]);
64
+
65
+ const doc = loadAndroidIntegrationFile(path.join(recipeFixtureRoot, "android-integration.fixture.json"));
66
+ const rJson = await runPipeline(ctx, toManifest(doc), { dryRun: true });
67
+ expect(rJson.report.errors).toEqual([]);
68
+
69
+ const body = [
70
+ "# dry-run — fixtures/recipes/android-default.fixture.yaml",
71
+ rYaml.patch.trimEnd(),
72
+ "",
73
+ "# dry-run — android-integration.fixture.json",
74
+ rJson.patch.trimEnd(),
75
+ "",
76
+ ].join("\n");
77
+
78
+ clearPreviewPatchFilesIfTargetInside(pkgRoot, previewPatchPath);
79
+ fs.mkdirSync(path.dirname(previewPatchPath), { recursive: true });
80
+ fs.writeFileSync(previewPatchPath, body, "utf8");
81
+
82
+ expect(rYaml.patch).toContain("TOPSDK REPO AUTO");
83
+ expect(rJson.patch).toContain("TOPSDK AUTO");
84
+ });
85
+ });
@@ -0,0 +1,77 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { describe, expect, it } from "vitest";
4
+ import {
5
+ manifestForPlatform,
6
+ detectSinglePlatform,
7
+ platformContext,
8
+ } from "../src/core/platform.js";
9
+ import { buildWorkspaceContext } from "../src/core/workspace.js";
10
+ import type { Manifest } from "../src/contracts/types.js";
11
+
12
+ const pkgRoot = path.resolve(__dirname, "..");
13
+ const androidLatestRoot = path.join(pkgRoot, "fixtures", "android-test-project", "android-latest-project");
14
+ const iosProjectRoot = path.join(pkgRoot, "fixtures", "ios-test-project", "tooltest");
15
+ const hasIosProjectFixture = fs.existsSync(iosProjectRoot);
16
+
17
+ describe("platform selection", () => {
18
+ it("detects Android project roots as Android only", () => {
19
+ const ctx = buildWorkspaceContext(androidLatestRoot, pkgRoot);
20
+ const detected = detectSinglePlatform(ctx);
21
+
22
+ expect(detected).toEqual({ ok: true, platform: "android" });
23
+ expect(platformContext(ctx, "android").android?.ok).toBe(true);
24
+ expect(platformContext(ctx, "android").ios).toBeUndefined();
25
+ });
26
+
27
+ it.skipIf(!hasIosProjectFixture)("detects iOS project roots as iOS only", () => {
28
+ const ctx = buildWorkspaceContext(iosProjectRoot, pkgRoot);
29
+ const detected = detectSinglePlatform(ctx);
30
+
31
+ expect(detected).toEqual({ ok: true, platform: "ios" });
32
+ expect(platformContext(ctx, "ios").ios?.ok).toBe(true);
33
+ expect(platformContext(ctx, "ios").android).toBeUndefined();
34
+ });
35
+
36
+ it("filters mixed manifests to the selected platform before running", () => {
37
+ const manifest: Manifest = {
38
+ version: 1,
39
+ steps: [
40
+ { op: "android.one", platform: "android" },
41
+ { op: "ios.one", platform: "ios" },
42
+ { op: "shared.one", platform: "all" },
43
+ { op: "shared.two" },
44
+ ],
45
+ };
46
+
47
+ expect(manifestForPlatform(manifest, "android").steps.map((s) => s.op)).toEqual([
48
+ "android.one",
49
+ "shared.one",
50
+ "shared.two",
51
+ ]);
52
+ expect(manifestForPlatform(manifest, "ios").steps.map((s) => s.op)).toEqual([
53
+ "ios.one",
54
+ "shared.one",
55
+ "shared.two",
56
+ ]);
57
+ });
58
+
59
+ it.skipIf(!hasIosProjectFixture)("overrides detected iOS target name when explicitly provided", () => {
60
+ const ctx = buildWorkspaceContext(iosProjectRoot, pkgRoot, {
61
+ appTarget: "tooltest",
62
+ });
63
+
64
+ expect(ctx.ios?.ok && ctx.ios.targetName).toBe("tooltest");
65
+ expect(ctx.ios?.ok && ctx.ios.targetNames).toContain("tooltest");
66
+ });
67
+
68
+ it.skipIf(!hasIosProjectFixture)("rejects missing iOS app target names during detection", () => {
69
+ const ctx = buildWorkspaceContext(iosProjectRoot, pkgRoot, {
70
+ appTarget: "GameRelease",
71
+ });
72
+
73
+ expect(ctx.ios?.ok).toBe(false);
74
+ expect(ctx.ios?.ok ? "" : ctx.ios?.error).toContain("IOS_APP_TARGET_NOT_FOUND: GameRelease");
75
+ });
76
+
77
+ });
@@ -0,0 +1,124 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import {
6
+ DEFAULT_IOS_SDK_PLUGINS,
7
+ buildSdkHomeDownloadUrl,
8
+ buildSdkHomeVersionUrl,
9
+ fetchSdkHomeIosDownloadUrl,
10
+ fetchSdkHomeIosVersion,
11
+ fetchSdkHomeVersions,
12
+ resolveIosSdkRootFromDirectory,
13
+ resolveIosSdkZipFileName,
14
+ } from "../src/remote/sdkHomeDownload.js";
15
+
16
+ describe("sdk-home iOS SDK download client", () => {
17
+ afterEach(() => {
18
+ vi.unstubAllGlobals();
19
+ });
20
+
21
+ it("builds version and getDownLoadUrl URLs", () => {
22
+ expect(buildSdkHomeVersionUrl("https://business-api.meetgames.com")).toBe(
23
+ "https://business-api.meetgames.com/sdk/home/version"
24
+ );
25
+ const url = buildSdkHomeDownloadUrl({
26
+ baseUrl: "https://business-api.meetgames.com/",
27
+ version: "1.6.0.5",
28
+ plugins: DEFAULT_IOS_SDK_PLUGINS,
29
+ packageType: "native",
30
+ });
31
+ const u = new URL(url);
32
+ expect(u.pathname).toBe("/sdk/home/sdk-download/getDownLoadUrl");
33
+ expect(u.searchParams.get("version")).toBe("1.6.0.5");
34
+ expect(u.searchParams.get("platform")).toBe("ios");
35
+ expect(u.searchParams.get("plugins")).toBe(DEFAULT_IOS_SDK_PLUGINS.join(","));
36
+ expect(u.searchParams.get("packageType")).toBe("native");
37
+ });
38
+
39
+ it("parses the iOS version envelope", async () => {
40
+ vi.stubGlobal(
41
+ "fetch",
42
+ vi.fn(async () => ({
43
+ ok: true,
44
+ status: 200,
45
+ text: async () =>
46
+ JSON.stringify({
47
+ code: 200,
48
+ data: {
49
+ result: {
50
+ android: { ver: "1.6.1.3", date: "2026-05-25" },
51
+ ios: { ver: "1.6.0.5", date: "2026-06-09" },
52
+ },
53
+ },
54
+ }),
55
+ })) as unknown as typeof fetch
56
+ );
57
+
58
+ const { ios } = await fetchSdkHomeIosVersion({ baseUrl: "https://example.com" });
59
+ expect(ios).toEqual({ ver: "1.6.0.5", date: "2026-06-09" });
60
+ });
61
+
62
+ it("parses Android and iOS versions from the version envelope", async () => {
63
+ vi.stubGlobal(
64
+ "fetch",
65
+ vi.fn(async () => ({
66
+ ok: true,
67
+ status: 200,
68
+ text: async () =>
69
+ JSON.stringify({
70
+ code: 200,
71
+ data: {
72
+ result: {
73
+ android: { ver: "1.6.1.3", date: "2026-05-25" },
74
+ ios: { ver: "1.6.0.5", date: "2026-06-09" },
75
+ },
76
+ },
77
+ }),
78
+ })) as unknown as typeof fetch
79
+ );
80
+
81
+ const { versions } = await fetchSdkHomeVersions({ baseUrl: "https://example.com" });
82
+ expect(versions.android).toEqual({ ver: "1.6.1.3", date: "2026-05-25" });
83
+ expect(versions.ios).toEqual({ ver: "1.6.0.5", date: "2026-06-09" });
84
+ });
85
+
86
+ it("parses the download URL envelope", async () => {
87
+ vi.stubGlobal(
88
+ "fetch",
89
+ vi.fn(async () => ({
90
+ ok: true,
91
+ status: 200,
92
+ text: async () => JSON.stringify({ code: 200, data: "https://cdn.example.com/topSDK-ios.zip" }),
93
+ })) as unknown as typeof fetch
94
+ );
95
+
96
+ const { sdkZipUrl } = await fetchSdkHomeIosDownloadUrl({
97
+ baseUrl: "https://example.com",
98
+ version: "1.6.0.5",
99
+ });
100
+ expect(sdkZipUrl).toBe("https://cdn.example.com/topSDK-ios.zip");
101
+ });
102
+
103
+ it("uses the sdk-home generated iOS archive name", () => {
104
+ expect(resolveIosSdkZipFileName("1.6.0.5")).toBe("topSDK-ios--V1.6.0.5.zip");
105
+ });
106
+
107
+ it("resolves top-level and nested extracted SDK layouts", () => {
108
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "meet-sdk-tool-ios-"));
109
+ try {
110
+ fs.mkdirSync(path.join(root, "sdk"), { recursive: true });
111
+ fs.mkdirSync(path.join(root, "plugins"), { recursive: true });
112
+ expect(resolveIosSdkRootFromDirectory(root)).toBe(root);
113
+
114
+ const nestedRoot = path.join(root, "nested");
115
+ fs.rmSync(path.join(root, "sdk"), { recursive: true, force: true });
116
+ fs.rmSync(path.join(root, "plugins"), { recursive: true, force: true });
117
+ fs.mkdirSync(path.join(nestedRoot, "sdk"), { recursive: true });
118
+ fs.mkdirSync(path.join(nestedRoot, "plugins"), { recursive: true });
119
+ expect(resolveIosSdkRootFromDirectory(root)).toBe(nestedRoot);
120
+ } finally {
121
+ fs.rmSync(root, { recursive: true, force: true });
122
+ }
123
+ });
124
+ });