@leonxin/meetgames 0.1.7 → 0.1.11

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 (293) 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 +31 -8
  7. package/dist/aab-converter/aab-entry.d.ts +3 -0
  8. package/dist/aab-converter/aab-entry.d.ts.map +1 -0
  9. package/dist/aab-converter/aab-entry.js +49 -0
  10. package/dist/aab-converter/aab-entry.js.map +1 -0
  11. package/dist/aab-converter/apksExtractor.d.ts +2 -0
  12. package/dist/aab-converter/apksExtractor.d.ts.map +1 -0
  13. package/dist/aab-converter/apksExtractor.js +108 -0
  14. package/dist/aab-converter/apksExtractor.js.map +1 -0
  15. package/dist/aab-converter/bundletoolRunner.d.ts +15 -0
  16. package/dist/aab-converter/bundletoolRunner.d.ts.map +1 -0
  17. package/dist/aab-converter/bundletoolRunner.js +46 -0
  18. package/dist/aab-converter/bundletoolRunner.js.map +1 -0
  19. package/dist/aab-converter/cliArgs.d.ts +27 -0
  20. package/dist/aab-converter/cliArgs.d.ts.map +1 -0
  21. package/dist/aab-converter/cliArgs.js +170 -0
  22. package/dist/aab-converter/cliArgs.js.map +1 -0
  23. package/dist/aab-converter/convertAabToApk.d.ts +7 -0
  24. package/dist/aab-converter/convertAabToApk.d.ts.map +1 -0
  25. package/dist/aab-converter/convertAabToApk.js +69 -0
  26. package/dist/aab-converter/convertAabToApk.js.map +1 -0
  27. package/dist/aab-converter/resourcePaths.d.ts +4 -0
  28. package/dist/aab-converter/resourcePaths.d.ts.map +1 -0
  29. package/dist/aab-converter/resourcePaths.js +42 -0
  30. package/dist/aab-converter/resourcePaths.js.map +1 -0
  31. package/dist/aab-converter/signingOptions.d.ts +9 -0
  32. package/dist/aab-converter/signingOptions.d.ts.map +1 -0
  33. package/dist/aab-converter/signingOptions.js +21 -0
  34. package/dist/aab-converter/signingOptions.js.map +1 -0
  35. package/dist/aab-converter/types.d.ts +24 -0
  36. package/dist/aab-converter/types.d.ts.map +1 -0
  37. package/dist/aab-converter/types.js +2 -0
  38. package/dist/aab-converter/types.js.map +1 -0
  39. package/dist/android/adapter.d.ts.map +1 -1
  40. package/dist/android/adapter.js +2 -2
  41. package/dist/android/adapter.js.map +1 -1
  42. package/dist/android/detect.d.ts +2 -2
  43. package/dist/android/detect.d.ts.map +1 -1
  44. package/dist/android/detect.js +36 -8
  45. package/dist/android/detect.js.map +1 -1
  46. package/dist/android/meetSdkRemoteGradle.d.ts +0 -3
  47. package/dist/android/meetSdkRemoteGradle.d.ts.map +1 -1
  48. package/dist/android/meetSdkRemoteGradle.js +13 -20
  49. package/dist/android/meetSdkRemoteGradle.js.map +1 -1
  50. package/dist/cli.d.ts.map +1 -1
  51. package/dist/cli.js +157 -31
  52. package/dist/cli.js.map +1 -1
  53. package/dist/config/meetSdkDefaultConfig.d.ts +19 -2
  54. package/dist/config/meetSdkDefaultConfig.d.ts.map +1 -1
  55. package/dist/config/meetSdkDefaultConfig.js +67 -5
  56. package/dist/config/meetSdkDefaultConfig.js.map +1 -1
  57. package/dist/config/meetSdkIosConfig.d.ts +21 -0
  58. package/dist/config/meetSdkIosConfig.d.ts.map +1 -0
  59. package/dist/config/meetSdkIosConfig.js +66 -0
  60. package/dist/config/meetSdkIosConfig.js.map +1 -0
  61. package/dist/config/meetSdkRemoteConfig.d.ts +19 -11
  62. package/dist/config/meetSdkRemoteConfig.d.ts.map +1 -1
  63. package/dist/config/meetSdkRemoteConfig.js +89 -69
  64. package/dist/config/meetSdkRemoteConfig.js.map +1 -1
  65. package/dist/config/topsdkFeatureModules.d.ts +5 -0
  66. package/dist/config/topsdkFeatureModules.d.ts.map +1 -1
  67. package/dist/config/topsdkFeatureModules.js +26 -0
  68. package/dist/config/topsdkFeatureModules.js.map +1 -1
  69. package/dist/contracts/types.d.ts +19 -6
  70. package/dist/contracts/types.d.ts.map +1 -1
  71. package/dist/core/doctor.d.ts +17 -0
  72. package/dist/core/doctor.d.ts.map +1 -0
  73. package/dist/core/doctor.js +444 -0
  74. package/dist/core/doctor.js.map +1 -0
  75. package/dist/core/pipeline.d.ts.map +1 -1
  76. package/dist/core/pipeline.js +0 -15
  77. package/dist/core/pipeline.js.map +1 -1
  78. package/dist/core/platform.d.ts +12 -0
  79. package/dist/core/platform.d.ts.map +1 -0
  80. package/dist/core/platform.js +40 -0
  81. package/dist/core/platform.js.map +1 -0
  82. package/dist/core/reporter.js +1 -1
  83. package/dist/core/reporter.js.map +1 -1
  84. package/dist/core/workspace.d.ts +2 -2
  85. package/dist/core/workspace.d.ts.map +1 -1
  86. package/dist/core/workspace.js +4 -5
  87. package/dist/core/workspace.js.map +1 -1
  88. package/dist/index.d.ts +3 -1
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +3 -1
  91. package/dist/index.js.map +1 -1
  92. package/dist/ios/channelConfig.d.ts +1 -0
  93. package/dist/ios/channelConfig.d.ts.map +1 -1
  94. package/dist/ios/channelConfig.js +82 -0
  95. package/dist/ios/channelConfig.js.map +1 -1
  96. package/dist/ios/codeUtils.d.ts +1 -0
  97. package/dist/ios/codeUtils.d.ts.map +1 -1
  98. package/dist/ios/codeUtils.js +11 -2
  99. package/dist/ios/codeUtils.js.map +1 -1
  100. package/dist/ios/detect.d.ts +2 -2
  101. package/dist/ios/detect.d.ts.map +1 -1
  102. package/dist/ios/detect.js +49 -10
  103. package/dist/ios/detect.js.map +1 -1
  104. package/dist/ios/entitlements.d.ts +4 -0
  105. package/dist/ios/entitlements.d.ts.map +1 -0
  106. package/dist/ios/entitlements.js +53 -0
  107. package/dist/ios/entitlements.js.map +1 -0
  108. package/dist/ios/fileManager.d.ts.map +1 -1
  109. package/dist/ios/fileManager.js +3 -2
  110. package/dist/ios/fileManager.js.map +1 -1
  111. package/dist/ios/infoPlist.d.ts +1 -1
  112. package/dist/ios/infoPlist.d.ts.map +1 -1
  113. package/dist/ios/infoPlist.js.map +1 -1
  114. package/dist/ios/integrate.d.ts.map +1 -1
  115. package/dist/ios/integrate.js +211 -36
  116. package/dist/ios/integrate.js.map +1 -1
  117. package/dist/ios/pbxprojEditor.d.ts +2 -0
  118. package/dist/ios/pbxprojEditor.d.ts.map +1 -1
  119. package/dist/ios/pbxprojEditor.js +179 -1
  120. package/dist/ios/pbxprojEditor.js.map +1 -1
  121. package/dist/ios/pluginConfig.d.ts +1 -0
  122. package/dist/ios/pluginConfig.d.ts.map +1 -1
  123. package/dist/ios/pluginConfig.js +36 -4
  124. package/dist/ios/pluginConfig.js.map +1 -1
  125. package/dist/ios/sdkBundle.d.ts +1 -1
  126. package/dist/ios/sdkBundle.d.ts.map +1 -1
  127. package/dist/ios/sdkBundle.js +7 -5
  128. package/dist/ios/sdkBundle.js.map +1 -1
  129. package/dist/ios/template.d.ts +1 -0
  130. package/dist/ios/template.d.ts.map +1 -1
  131. package/dist/ios/template.js +14 -1
  132. package/dist/ios/template.js.map +1 -1
  133. package/dist/ios/types.d.ts +2 -2
  134. package/dist/ios/types.d.ts.map +1 -1
  135. package/dist/mcp/server.d.ts.map +1 -1
  136. package/dist/mcp/server.js +14 -13
  137. package/dist/mcp/server.js.map +1 -1
  138. package/dist/mcp/service.d.ts +8 -6
  139. package/dist/mcp/service.d.ts.map +1 -1
  140. package/dist/mcp/service.js +34 -14
  141. package/dist/mcp/service.js.map +1 -1
  142. package/dist/ops/handlers.d.ts.map +1 -1
  143. package/dist/ops/handlers.js +10 -4
  144. package/dist/ops/handlers.js.map +1 -1
  145. package/dist/remote/sdkHomeDownload.d.ts +65 -0
  146. package/dist/remote/sdkHomeDownload.d.ts.map +1 -0
  147. package/dist/remote/sdkHomeDownload.js +208 -0
  148. package/dist/remote/sdkHomeDownload.js.map +1 -0
  149. package/dist/remote/topsdkDownloadSdkConfig.d.ts.map +1 -1
  150. package/dist/remote/topsdkDownloadSdkConfig.js +11 -1
  151. package/dist/remote/topsdkDownloadSdkConfig.js.map +1 -1
  152. package/dist/shared/errors.d.ts +7 -0
  153. package/dist/shared/errors.d.ts.map +1 -0
  154. package/dist/shared/errors.js +16 -0
  155. package/dist/shared/errors.js.map +1 -0
  156. package/dist/shared/fileUtils.d.ts +5 -0
  157. package/dist/shared/fileUtils.d.ts.map +1 -0
  158. package/dist/shared/fileUtils.js +35 -0
  159. package/dist/shared/fileUtils.js.map +1 -0
  160. package/dist/shared/logger.d.ts +10 -0
  161. package/dist/shared/logger.d.ts.map +1 -0
  162. package/dist/shared/logger.js +37 -0
  163. package/dist/shared/logger.js.map +1 -0
  164. package/dist/shared/pathUtils.d.ts +4 -0
  165. package/dist/shared/pathUtils.d.ts.map +1 -0
  166. package/dist/shared/pathUtils.js +22 -0
  167. package/dist/shared/pathUtils.js.map +1 -0
  168. package/dist/shared/processRunner.d.ts +12 -0
  169. package/dist/shared/processRunner.d.ts.map +1 -0
  170. package/dist/shared/processRunner.js +31 -0
  171. package/dist/shared/processRunner.js.map +1 -0
  172. package/docs/AAB_CONVERTER_CLI_PLAN.md +392 -0
  173. package/docs/API.md +246 -32
  174. package/docs/CLI.md +292 -0
  175. package/docs/INTEGRATION.md +116 -0
  176. package/docs/MCP.md +86 -0
  177. package/docs/README.md +19 -10
  178. package/docs/{api → archive/api}/downloadSDKConfig.md +8 -6
  179. package/docs/{api → archive/api}/getChannelConfig-meetgames.md +1 -1
  180. package/docs/archive/ios-migration.md +2139 -0
  181. 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
  182. package/docs/{ → archive/product/}/351/234/200/346/261/202/346/226/207/346/241/243.md +15 -14
  183. package/logs/convert-20260622-155037.log +5 -0
  184. package/logs/convert-20260622-155226.log +6 -0
  185. package/meetsdk-android.json +2 -1
  186. package/meetsdk-ios.json +15 -0
  187. package/package.json +10 -35
  188. package/scripts/package-aab-cli-win.mjs +193 -0
  189. package/src/aab-converter/aab-entry.ts +48 -0
  190. package/src/aab-converter/apksExtractor.ts +119 -0
  191. package/src/aab-converter/bundletoolRunner.ts +63 -0
  192. package/src/aab-converter/cliArgs.ts +194 -0
  193. package/src/aab-converter/convertAabToApk.ts +81 -0
  194. package/src/aab-converter/resourcePaths.ts +43 -0
  195. package/src/aab-converter/signingOptions.ts +29 -0
  196. package/src/aab-converter/types.ts +26 -0
  197. package/src/android/adapter.ts +9 -0
  198. package/src/android/assembleIntegrationJson.ts +33 -0
  199. package/src/android/detect.ts +132 -0
  200. package/src/android/downloadGoogleServicesJson.ts +56 -0
  201. package/src/android/gradle.ts +116 -0
  202. package/src/android/manifest.ts +50 -0
  203. package/src/android/meetSdkRemoteGradle.ts +837 -0
  204. package/src/cli.ts +488 -0
  205. package/src/config/fetchConfigWrite.ts +30 -0
  206. package/src/config/loadAndroidIntegration.ts +41 -0
  207. package/src/config/loadManifest.ts +40 -0
  208. package/src/config/meetSdkDefaultConfig.ts +99 -0
  209. package/src/config/meetSdkIosConfig.ts +87 -0
  210. package/src/config/meetSdkRemoteConfig.ts +1211 -0
  211. package/src/config/topsdkFeatureModules.ts +92 -0
  212. package/src/contracts/types.ts +121 -0
  213. package/src/core/doctor.ts +485 -0
  214. package/src/core/patch.ts +64 -0
  215. package/src/core/pipeline.ts +107 -0
  216. package/src/core/platform.ts +47 -0
  217. package/src/core/previewPatches.ts +23 -0
  218. package/src/core/reporter.ts +24 -0
  219. package/src/core/workspace.ts +29 -0
  220. package/src/entry.ts +7 -0
  221. package/src/index.ts +133 -0
  222. package/src/ios/channelConfig.ts +128 -0
  223. package/src/ios/codeUtils.ts +160 -0
  224. package/src/ios/detect.ts +105 -0
  225. package/src/ios/entitlements.ts +61 -0
  226. package/src/ios/fileManager.ts +48 -0
  227. package/src/ios/infoPlist.ts +55 -0
  228. package/src/ios/integrate.ts +516 -0
  229. package/src/ios/pbxprojEditor.ts +383 -0
  230. package/src/ios/pluginConfig.ts +97 -0
  231. package/src/ios/reserved.ts +8 -0
  232. package/src/ios/sdkBundle.ts +36 -0
  233. package/src/ios/template.ts +36 -0
  234. package/src/ios/types.ts +65 -0
  235. package/src/mcp/server.ts +170 -0
  236. package/src/mcp/service.ts +222 -0
  237. package/src/mcp-entry.ts +7 -0
  238. package/src/ops/fileStore.ts +56 -0
  239. package/src/ops/handlers.ts +304 -0
  240. package/src/remote/fetchJson.ts +22 -0
  241. package/src/remote/sdkHomeDownload.ts +274 -0
  242. package/src/remote/topsdkDownloadSdkConfig.ts +93 -0
  243. package/src/remote/topsdkGetSdkConfig.ts +122 -0
  244. package/src/remote/topsdkSign.ts +10 -0
  245. package/src/shared/errors.ts +16 -0
  246. package/src/shared/fileUtils.ts +41 -0
  247. package/src/shared/logger.ts +49 -0
  248. package/src/shared/pathUtils.ts +24 -0
  249. package/src/shared/processRunner.ts +43 -0
  250. package/test-projects/README.md +51 -0
  251. package/test-projects/_preview/pipeline.patch +281 -0
  252. package/tests/aab-converter.test.ts +213 -0
  253. package/tests/assemble.test.ts +12 -0
  254. package/tests/doctor.test.ts +89 -0
  255. package/tests/downloadGoogleServicesJson.test.ts +47 -0
  256. package/tests/fetch-remote.test.ts +23 -0
  257. package/tests/fetchConfigOverrides.test.ts +28 -0
  258. package/tests/fetchConfigWrite.test.ts +54 -0
  259. package/tests/gradle.test.ts +33 -0
  260. package/tests/integration-json.test.ts +29 -0
  261. package/tests/ios.codeUtils.test.ts +23 -0
  262. package/tests/ios.sdkBundle.test.ts +16 -0
  263. package/tests/loadManifest.test.ts +15 -0
  264. package/tests/manifest-xml.test.ts +30 -0
  265. package/tests/mcp.e2e.ts +217 -0
  266. package/tests/mcp.service.test.ts +53 -0
  267. package/tests/meetSdkRemoteConfig.test.ts +456 -0
  268. package/tests/meetSdkRemoteGradle.test.ts +414 -0
  269. package/tests/pipeline.android.test.ts +96 -0
  270. package/tests/pipeline.integration-json.test.ts +58 -0
  271. package/tests/pipeline.ios.test.ts +385 -0
  272. package/tests/pipeline.preview.patch.test.ts +85 -0
  273. package/tests/platformSelection.test.ts +77 -0
  274. package/tests/sdkHomeDownload.test.ts +124 -0
  275. package/tests/sdkVersionConfig.test.ts +130 -0
  276. package/tests/test-projects-hosts.test.ts +78 -0
  277. package/tests/topsdk.test.ts +53 -0
  278. package/tests/topsdkDownloadSdkConfig.test.ts +81 -0
  279. package/tests/topsdkFeatureModules.test.ts +116 -0
  280. package/tsconfig.json +19 -0
  281. package/vitest.config.ts +9 -0
  282. package/vitest.mcp.config.ts +11 -0
  283. package/bundled/android/sample.txt +0 -1
  284. package/docs/ANDROID.md +0 -133
  285. package/docs/CURSOR-MCP-SETUP.md +0 -72
  286. package/docs/MCP-SKILL.md +0 -63
  287. package/fixtures/api-samples/getChannelConfig-meetgames.sample.json +0 -123
  288. package/fixtures/meetsdk-remote-config.download-shape.json +0 -20
  289. package/fixtures/meetsdk-remote-config.mock.json +0 -69
  290. package/fixtures/recipes/android-default.fixture.yaml +0 -15
  291. package/fixtures/recipes/android-integration.fixture.json +0 -29
  292. package/fixtures/topsdk-config-reference.json +0 -39
  293. /package/docs/{api → archive/api}/getSDKConfig.md +0 -0
@@ -0,0 +1,383 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { createRequire } from "node:module";
5
+ import type { TextFileStore } from "../ops/fileStore.js";
6
+
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ type XcodeProject = any;
9
+
10
+ const require = createRequire(import.meta.url);
11
+ const xcode = require("xcode") as {
12
+ project: (pbxprojPath: string) => XcodeProject;
13
+ };
14
+
15
+ export interface PbxContext {
16
+ rel: string;
17
+ proj: XcodeProject;
18
+ srcRoot: string;
19
+ targetName: string;
20
+ }
21
+
22
+ function parseProjectFromContent(content: string): Promise<XcodeProject> {
23
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "meet-pbx-"));
24
+ const pbxprojPath = path.join(dir, "project.pbxproj");
25
+ fs.writeFileSync(pbxprojPath, content, "utf8");
26
+ return new Promise((resolve, reject) => {
27
+ const proj = xcode.project(pbxprojPath);
28
+ proj.parse((err: Error | null) => {
29
+ if (err) reject(err);
30
+ else resolve(proj);
31
+ });
32
+ });
33
+ }
34
+
35
+ function ensureObjectSection(proj: XcodeProject, sectionName: string): void {
36
+ const objects = proj.hash?.project?.objects as Record<string, unknown> | undefined;
37
+ if (!objects) return;
38
+ if (!objects[sectionName]) objects[sectionName] = {};
39
+ }
40
+
41
+ function objectSection(proj: XcodeProject, sectionName: string): Record<string, unknown> {
42
+ ensureObjectSection(proj, sectionName);
43
+ return (proj.hash.project.objects as Record<string, Record<string, unknown>>)[sectionName];
44
+ }
45
+
46
+ export async function loadPbxFromStore(
47
+ store: TextFileStore,
48
+ projectRoot: string,
49
+ xcodeprojPath: string,
50
+ targetName: string
51
+ ): Promise<PbxContext> {
52
+ const rel = path.relative(projectRoot, path.join(xcodeprojPath, "project.pbxproj")).split(path.sep).join("/");
53
+ const content = store.read(rel);
54
+ const proj = await parseProjectFromContent(content);
55
+ // Newer Xcode projects can omit empty sections, while the `xcode` package assumes
56
+ // sections such as PBXBuildFile already exist before adding frameworks/resources.
57
+ ensureObjectSection(proj, "PBXBuildFile");
58
+ ensureObjectSection(proj, "PBXFileReference");
59
+ const xcodeprojDir = path.dirname(xcodeprojPath);
60
+ let srcRoot = xcodeprojDir;
61
+ const first = proj.getFirstProject?.()?.firstProject;
62
+ const projectDirPath = first?.projectDirPath
63
+ ? String(first.projectDirPath).replace(/^"|"$/g, "").trim()
64
+ : "";
65
+ if (projectDirPath.length > 0) {
66
+ srcRoot = path.resolve(xcodeprojDir, projectDirPath);
67
+ }
68
+ return { rel, proj, srcRoot, targetName };
69
+ }
70
+
71
+ export function savePbxToStore(store: TextFileStore, ctx: PbxContext): void {
72
+ store.write(ctx.rel, ctx.proj.writeSync());
73
+ }
74
+
75
+ function targetUuid(proj: XcodeProject, targetName: string): string | undefined {
76
+ const targets = proj.pbxNativeTargetSection?.() ?? {};
77
+ for (const [uuid, t] of Object.entries(targets)) {
78
+ if (uuid.endsWith("_comment")) continue;
79
+ const name = (t as { name?: string }).name?.replace(/"/g, "");
80
+ if (name === targetName) return uuid;
81
+ }
82
+ return undefined;
83
+ }
84
+
85
+ function targetKey(proj: XcodeProject, targetName: string): string {
86
+ const uuid = targetUuid(proj, targetName);
87
+ if (!uuid) throw new Error(`Xcode target not found: ${targetName}`);
88
+ return uuid;
89
+ }
90
+
91
+ function ensureResourcesBuildPhase(proj: XcodeProject, targetName: string): void {
92
+ const target = targetKey(proj, targetName);
93
+ try {
94
+ proj.pbxResourcesBuildPhaseObj(target);
95
+ return;
96
+ } catch {
97
+ proj.addBuildPhase([], "PBXResourcesBuildPhase", "Resources", target);
98
+ }
99
+ }
100
+
101
+ export function addThirdPartyFramework(ctx: PbxContext, relPathFromSrcRoot: string, embed: boolean): void {
102
+ const file = relPathFromSrcRoot.split(path.sep).join("/");
103
+ const target = targetKey(ctx.proj, ctx.targetName);
104
+ ctx.proj.addFramework(file, {
105
+ customFramework: true,
106
+ embed,
107
+ sign: true,
108
+ target,
109
+ });
110
+ }
111
+
112
+ export function addCopyFile(ctx: PbxContext, relPathFromSrcRoot: string): void {
113
+ const file = relPathFromSrcRoot.split(path.sep).join("/");
114
+ const target = targetKey(ctx.proj, ctx.targetName);
115
+ if (copyFilesPhaseJson(ctx, target).includes(path.basename(file))) return;
116
+ ensureCopyFilesBuildPhase(ctx, target);
117
+ addCopyFileManually(ctx, target, file);
118
+ }
119
+
120
+ function copyFilesPhaseJson(ctx: PbxContext, target: string): string {
121
+ try {
122
+ return JSON.stringify(ctx.proj.pbxCopyfilesBuildPhaseObj?.(target) ?? {});
123
+ } catch {
124
+ return "";
125
+ }
126
+ }
127
+
128
+ function findFileRefUuid(ctx: PbxContext, file: string): string | null {
129
+ const basename = path.basename(file);
130
+ const fileRefSection = objectSection(ctx.proj, "PBXFileReference");
131
+ for (const [uuid, raw] of Object.entries(fileRefSection)) {
132
+ if (uuid.endsWith("_comment") || !raw || typeof raw !== "object") continue;
133
+ const ref = raw as Record<string, unknown>;
134
+ const comment = fileRefSection[`${uuid}_comment`];
135
+ const refPath = String(ref.path ?? "").replace(/^"|"$/g, "");
136
+ const refName = String(ref.name ?? "").replace(/^"|"$/g, "");
137
+ if (comment === basename || refPath === file || refPath.endsWith(`/${basename}`) || refName === basename) {
138
+ return uuid;
139
+ }
140
+ }
141
+ return null;
142
+ }
143
+
144
+ function ensureCopyFilesBuildPhase(ctx: PbxContext, target: string): Record<string, unknown> {
145
+ const section = objectSection(ctx.proj, "PBXCopyFilesBuildPhase");
146
+ for (const [uuid, raw] of Object.entries(section)) {
147
+ if (uuid.endsWith("_comment") || !raw || typeof raw !== "object") continue;
148
+ const phase = raw as Record<string, unknown>;
149
+ const name = String(phase.name ?? "").replace(/^"|"$/g, "");
150
+ if (name === "Copy Files" || section[`${uuid}_comment`] === "Copy Files") {
151
+ phase.files = (phase.files as unknown[]) ?? [];
152
+ return phase;
153
+ }
154
+ }
155
+
156
+ const uuid = ctx.proj.generateUuid();
157
+ const phase: Record<string, unknown> = {
158
+ isa: "PBXCopyFilesBuildPhase",
159
+ buildActionMask: 2147483647,
160
+ dstPath: '""',
161
+ dstSubfolderSpec: 10,
162
+ files: [],
163
+ name: '"Copy Files"',
164
+ runOnlyForDeploymentPostprocessing: 0,
165
+ };
166
+ section[uuid] = phase;
167
+ section[`${uuid}_comment`] = "Copy Files";
168
+ const native = ctx.proj.pbxNativeTargetSection?.()[target] as { buildPhases?: Array<{ value: string; comment: string }> };
169
+ native.buildPhases ??= [];
170
+ if (!native.buildPhases.some((p) => p.value === uuid)) {
171
+ native.buildPhases.push({ value: uuid, comment: "Copy Files" });
172
+ }
173
+ return phase;
174
+ }
175
+
176
+ function addCopyFileManually(ctx: PbxContext, target: string, file: string): void {
177
+ const basename = path.basename(file);
178
+ const phase = ensureCopyFilesBuildPhase(ctx, target);
179
+ const files = (phase.files ??= []) as Array<{ value?: string; comment?: string }>;
180
+ if (JSON.stringify(files).includes(basename)) return;
181
+
182
+ let fileRefUuid: string = findFileRefUuid(ctx, file) ?? "";
183
+ if (!fileRefUuid) {
184
+ fileRefUuid = ctx.proj.generateUuid();
185
+ const fileRefSection = objectSection(ctx.proj, "PBXFileReference");
186
+ fileRefSection[fileRefUuid] = {
187
+ isa: "PBXFileReference",
188
+ name: `"${basename}"`,
189
+ path: `"${file}"`,
190
+ sourceTree: '"<group>"',
191
+ lastKnownFileType: "wrapper.framework",
192
+ includeInIndex: 0,
193
+ };
194
+ fileRefSection[`${fileRefUuid}_comment`] = basename;
195
+ }
196
+
197
+ const buildUuid = ctx.proj.generateUuid();
198
+ const buildFileSection = objectSection(ctx.proj, "PBXBuildFile");
199
+ buildFileSection[buildUuid] = {
200
+ isa: "PBXBuildFile",
201
+ fileRef: fileRefUuid,
202
+ fileRef_comment: basename,
203
+ };
204
+ buildFileSection[`${buildUuid}_comment`] = `${basename} in Copy Files`;
205
+ files.push({ value: buildUuid, comment: `${basename} in Copy Files` });
206
+ }
207
+
208
+ export function addSystemFramework(ctx: PbxContext, frameworkName: string): void {
209
+ const target = targetKey(ctx.proj, ctx.targetName);
210
+ ctx.proj.addFramework(`System/Library/Frameworks/${frameworkName}`, {
211
+ customFramework: false,
212
+ weak: false,
213
+ target,
214
+ });
215
+ }
216
+
217
+ export function addSystemLib(ctx: PbxContext, libName: string): void {
218
+ const target = targetKey(ctx.proj, ctx.targetName);
219
+ ctx.proj.addFramework(`usr/lib/${libName}`, {
220
+ customFramework: false,
221
+ weak: false,
222
+ target,
223
+ });
224
+ }
225
+
226
+ export function addSourceOrResourceFile(ctx: PbxContext, relPathFromSrcRoot: string): void {
227
+ const file = relPathFromSrcRoot.split(path.sep).join("/");
228
+ const target = targetKey(ctx.proj, ctx.targetName);
229
+ if (file.endsWith(".h")) {
230
+ ctx.proj.addHeaderFile(file, { target });
231
+ } else if (file.endsWith(".m") || file.endsWith(".mm") || file.endsWith(".swift")) {
232
+ ctx.proj.addSourceFile(file, { target });
233
+ } else {
234
+ ensureResourcesBuildPhase(ctx.proj, ctx.targetName);
235
+ const opt: Record<string, unknown> = { target };
236
+ if (file.endsWith(".xcdatamodeld")) {
237
+ opt.lastKnownFileType = "wrapper.xcdatamodel";
238
+ }
239
+ try {
240
+ ctx.proj.addResourceFile(file, opt);
241
+ } catch (e) {
242
+ if (!String(e instanceof Error ? e.message : e).includes("path")) throw e;
243
+ addResourceFileManually(ctx, file, typeof opt.lastKnownFileType === "string" ? opt.lastKnownFileType : undefined);
244
+ }
245
+ }
246
+ }
247
+
248
+ function resourceFileType(file: string, lastKnownFileType?: string): string {
249
+ if (lastKnownFileType) return lastKnownFileType;
250
+ if (file.endsWith(".bundle")) return "wrapper.plug-in";
251
+ if (file.endsWith(".xcdatamodeld")) return "wrapper.xcdatamodel";
252
+ if (file.endsWith(".xcassets")) return "folder.assetcatalog";
253
+ if (file.endsWith(".plist")) return "text.plist.xml";
254
+ return "unknown";
255
+ }
256
+
257
+ function addResourceFileManually(ctx: PbxContext, file: string, lastKnownFileType?: string): void {
258
+ const basename = path.basename(file);
259
+ const resources = ctx.proj.pbxResourcesBuildPhaseObj(targetKey(ctx.proj, ctx.targetName));
260
+ const files = (resources.files ??= []) as Array<{ value?: string; comment?: string }>;
261
+ if (JSON.stringify(files).includes(basename)) return;
262
+
263
+ const fileRefUuid = ctx.proj.generateUuid();
264
+ const buildUuid = ctx.proj.generateUuid();
265
+ const fileRefSection = objectSection(ctx.proj, "PBXFileReference");
266
+ const buildFileSection = objectSection(ctx.proj, "PBXBuildFile");
267
+
268
+ fileRefSection[fileRefUuid] = {
269
+ isa: "PBXFileReference",
270
+ name: `"${basename}"`,
271
+ path: `"${file}"`,
272
+ sourceTree: '"<group>"',
273
+ lastKnownFileType: resourceFileType(file, lastKnownFileType),
274
+ includeInIndex: 0,
275
+ };
276
+ fileRefSection[`${fileRefUuid}_comment`] = basename;
277
+ buildFileSection[buildUuid] = {
278
+ isa: "PBXBuildFile",
279
+ fileRef: fileRefUuid,
280
+ fileRef_comment: basename,
281
+ };
282
+ buildFileSection[`${buildUuid}_comment`] = `${basename} in Resources`;
283
+ files.push({ value: buildUuid, comment: `${basename} in Resources` });
284
+ }
285
+
286
+ export function setBuildSetting(ctx: PbxContext, key: string, value: string): void {
287
+ const uuid = targetUuid(ctx.proj, ctx.targetName);
288
+ if (!uuid) return;
289
+ const native = ctx.proj.pbxNativeTargetSection?.()[uuid] as { buildConfigurationList?: string };
290
+ const listId = native?.buildConfigurationList;
291
+ const lists = ctx.proj.pbxXCConfigurationList?.() ?? {};
292
+ if (!listId || !lists[listId]) return;
293
+ const list = lists[listId] as { buildConfigurations?: Array<string | { value: string }> };
294
+ const buildConfigs = ctx.proj.pbxXCBuildConfigurationSection?.() ?? {};
295
+ for (const entry of list.buildConfigurations ?? []) {
296
+ const cfgId = typeof entry === "string" ? entry : entry.value;
297
+ const cfg = buildConfigs[cfgId];
298
+ if (!cfg || typeof cfg !== "object") continue;
299
+ const settings = (cfg as { buildSettings?: Record<string, unknown> }).buildSettings ?? {};
300
+ settings[key] = value;
301
+ (cfg as { buildSettings: Record<string, unknown> }).buildSettings = settings;
302
+ }
303
+ }
304
+
305
+ export function addOtherLinkerFlag(ctx: PbxContext, flag: string): void {
306
+ const uuid = targetUuid(ctx.proj, ctx.targetName);
307
+ if (!uuid) return;
308
+ const native = ctx.proj.pbxNativeTargetSection?.()[uuid] as { buildConfigurationList?: string };
309
+ const listId = native?.buildConfigurationList;
310
+ const lists = ctx.proj.pbxXCConfigurationList?.() ?? {};
311
+ if (!listId || !lists[listId]) return;
312
+ const list = lists[listId] as { buildConfigurations?: Array<string | { value: string }> };
313
+ const buildConfigs = ctx.proj.pbxXCBuildConfigurationSection?.() ?? {};
314
+ for (const entry of list.buildConfigurations ?? []) {
315
+ const cfgId = typeof entry === "string" ? entry : entry.value;
316
+ const cfg = buildConfigs[cfgId];
317
+ if (!cfg || typeof cfg !== "object") continue;
318
+ const settings = (cfg as { buildSettings?: Record<string, unknown> }).buildSettings ?? {};
319
+ const cur = settings.OTHER_LDFLAGS;
320
+ if (typeof cur === "string") {
321
+ if (!cur.includes(flag)) settings.OTHER_LDFLAGS = `${cur} ${flag}`.trim();
322
+ } else if (Array.isArray(cur)) {
323
+ if (!cur.includes(flag)) settings.OTHER_LDFLAGS = [...cur, flag];
324
+ } else {
325
+ settings.OTHER_LDFLAGS = [flag];
326
+ }
327
+ (cfg as { buildSettings: Record<string, unknown> }).buildSettings = settings;
328
+ }
329
+ }
330
+
331
+ export function getTargetBuildSettings(ctx: PbxContext): Array<Record<string, unknown>> {
332
+ const uuid = targetUuid(ctx.proj, ctx.targetName);
333
+ if (!uuid) return [];
334
+ const native = ctx.proj.pbxNativeTargetSection?.()[uuid] as { buildConfigurationList?: string };
335
+ const listId = native?.buildConfigurationList;
336
+ const lists = ctx.proj.pbxXCConfigurationList?.() ?? {};
337
+ if (!listId || !lists[listId]) return [];
338
+ const list = lists[listId] as { buildConfigurations?: Array<string | { value: string }> };
339
+ const buildConfigs = ctx.proj.pbxXCBuildConfigurationSection?.() ?? {};
340
+ const out: Array<Record<string, unknown>> = [];
341
+ for (const entry of list.buildConfigurations ?? []) {
342
+ const cfgId = typeof entry === "string" ? entry : entry.value;
343
+ const cfg = buildConfigs[cfgId];
344
+ if (!cfg || typeof cfg !== "object") continue;
345
+ const settings = (cfg as { buildSettings?: Record<string, unknown> }).buildSettings ?? {};
346
+ (cfg as { buildSettings: Record<string, unknown> }).buildSettings = settings;
347
+ out.push(settings);
348
+ }
349
+ return out;
350
+ }
351
+
352
+ export function findInfoPlistPathsFromPbx(ctx: PbxContext): string[] {
353
+ const paths = new Set<string>();
354
+ const uuid = targetUuid(ctx.proj, ctx.targetName);
355
+ if (!uuid) return [];
356
+ const native = ctx.proj.pbxNativeTargetSection?.()[uuid] as { buildConfigurationList?: string };
357
+ const listId = native?.buildConfigurationList;
358
+ const lists = ctx.proj.pbxXCConfigurationList?.() ?? {};
359
+ if (!listId || !lists[listId]) return [];
360
+ const list = lists[listId] as { buildConfigurations?: Array<string | { value: string }> };
361
+ const buildConfigs = ctx.proj.pbxXCBuildConfigurationSection?.() ?? {};
362
+ for (const entry of list.buildConfigurations ?? []) {
363
+ const cfgId = typeof entry === "string" ? entry : entry.value;
364
+ const cfg = buildConfigs[cfgId];
365
+ const settings = (cfg as { buildSettings?: Record<string, string> })?.buildSettings;
366
+ const info = settings?.INFOPLIST_FILE;
367
+ if (!info) continue;
368
+ let resolved = info.replace(/"/g, "");
369
+ if (resolved.includes("$(SRCROOT)")) {
370
+ resolved = resolved.replace(/\$\(SRCROOT\)/g, ctx.srcRoot);
371
+ } else if (!path.isAbsolute(resolved)) {
372
+ resolved = path.join(ctx.srcRoot, resolved);
373
+ }
374
+ resolved = path.normalize(resolved);
375
+ paths.add(resolved);
376
+ }
377
+ return [...paths];
378
+ }
379
+
380
+ export function ensureInfoPlistInProject(ctx: PbxContext, _targetName: string, relPlist: string): void {
381
+ setBuildSetting(ctx, "GENERATE_INFOPLIST_FILE", "NO");
382
+ setBuildSetting(ctx, "INFOPLIST_FILE", relPlist);
383
+ }
@@ -0,0 +1,97 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import type { LoadedPluginConfig, PluginConfigJson } from "./types.js";
4
+
5
+ const CONFIG_FILENAMES = ["config.t.json", "config.json"] as const;
6
+
7
+ function findConfigFileInDir(dir: string): string | null {
8
+ for (const name of CONFIG_FILENAMES) {
9
+ const p = path.join(dir, name);
10
+ if (fs.existsSync(p)) return p;
11
+ }
12
+ return null;
13
+ }
14
+
15
+ function normalizePluginConfig(raw: Record<string, unknown>): PluginConfigJson {
16
+ const flags =
17
+ (raw.OtherLinkerFlags as string[] | undefined) ??
18
+ (raw.otherLinkerFlags as string[] | undefined) ??
19
+ [];
20
+ return {
21
+ ...(raw as unknown as PluginConfigJson),
22
+ OtherLinkerFlags: flags,
23
+ };
24
+ }
25
+
26
+ function parseConfigJson(filePath: string): PluginConfigJson {
27
+ const raw = JSON.parse(fs.readFileSync(filePath, "utf8")) as Record<string, unknown>;
28
+ const config = normalizePluginConfig(raw);
29
+ if (!config.name || typeof config.type !== "number") {
30
+ throw new Error(`invalid plugin config (missing name/type): ${filePath}`);
31
+ }
32
+ return config;
33
+ }
34
+
35
+ function resolveSourceDir(sdkRoot: string, config: PluginConfigJson, pluginDir: string): string {
36
+ const sdkDir = path.join(sdkRoot, "sdk");
37
+ if (config.type === 0) {
38
+ return path.join(sdkDir, "source");
39
+ }
40
+ return path.join(pluginDir, "source");
41
+ }
42
+
43
+ export function loadPluginConfig(configPath: string, sdkRoot: string): LoadedPluginConfig {
44
+ const abs = path.resolve(configPath);
45
+ const pluginDir = path.dirname(abs);
46
+ const config = parseConfigJson(abs);
47
+ const sourceDir = resolveSourceDir(sdkRoot, config, pluginDir);
48
+ return { configPath: abs, pluginDir, sourceDir, config };
49
+ }
50
+
51
+ /** Core SDK manifests: `sdk/*.json` (e.g. topsdkConfig.t.json). */
52
+ export function listSdkCoreConfigs(sdkRoot: string): LoadedPluginConfig[] {
53
+ const sdkDir = path.join(sdkRoot, "sdk");
54
+ if (!fs.existsSync(sdkDir)) return [];
55
+ const out: LoadedPluginConfig[] = [];
56
+ for (const name of fs.readdirSync(sdkDir)) {
57
+ if (!name.endsWith(".json") || name.startsWith(".")) continue;
58
+ out.push(loadPluginConfig(path.join(sdkDir, name), sdkRoot));
59
+ }
60
+ return out;
61
+ }
62
+
63
+ export function findPluginByFolder(sdkRoot: string, folderName: string): LoadedPluginConfig | null {
64
+ const pluginDir = path.join(sdkRoot, "plugins", folderName);
65
+ const cfg = findConfigFileInDir(pluginDir);
66
+ if (!cfg) return null;
67
+ return loadPluginConfig(cfg, sdkRoot);
68
+ }
69
+
70
+ export function validateLoadedPluginResources(loaded: LoadedPluginConfig): string[] {
71
+ const errors: string[] = [];
72
+ const { config, sourceDir, pluginDir, configPath } = loaded;
73
+ const dirName = path.basename(pluginDir);
74
+ const isKnownDataManagerAlias = config.type === 4 && dirName.endsWith("Manager");
75
+ if (config.type !== 0 && dirName !== config.name && !isKnownDataManagerAlias) {
76
+ errors.push(`plugin config name mismatch: ${configPath} name=${config.name} dir=${path.basename(pluginDir)}`);
77
+ }
78
+ if (!fs.existsSync(sourceDir)) {
79
+ errors.push(`plugin source directory not found: ${sourceDir}`);
80
+ return errors;
81
+ }
82
+ for (const source of config.sources ?? []) {
83
+ const p = path.join(sourceDir, source);
84
+ if (!fs.existsSync(p)) errors.push(`missing source resource for ${config.name}: ${source}`);
85
+ }
86
+ for (const fw of config.frameworks ?? []) {
87
+ if (fw.system) continue;
88
+ const p = path.join(sourceDir, fw.name);
89
+ if (!fs.existsSync(p)) errors.push(`missing framework resource for ${config.name}: ${fw.name}`);
90
+ }
91
+ for (const lib of config.libs ?? []) {
92
+ if (lib.system) continue;
93
+ const p = path.join(sourceDir, lib.name);
94
+ if (!fs.existsSync(p)) errors.push(`missing lib resource for ${config.name}: ${lib.name}`);
95
+ }
96
+ return errors;
97
+ }
@@ -0,0 +1,8 @@
1
+ import type { IosToolingReserved } from "../contracts/types.js";
2
+
3
+ export const iosToolingReserved: IosToolingReserved = {
4
+ status: "implemented",
5
+ plan: ["framework-copy", "pbxproj", "plist", "app-delegate"],
6
+ };
7
+
8
+ export { detectIOS as detectIOSPlaceholder } from "./detect.js";
@@ -0,0 +1,36 @@
1
+ import fs from "node:fs";
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
+ }
9
+
10
+ /**
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.
13
+ */
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
+ }
19
+ const children = fs
20
+ .readdirSync(bundled, { withFileTypes: true })
21
+ .filter((d) => d.isDirectory() && !d.name.startsWith("."))
22
+ .map((d) => path.join(bundled, d.name))
23
+ .sort()
24
+ .reverse();
25
+ for (const dir of children) {
26
+ if (fs.existsSync(path.join(dir, "sdk")) && fs.existsSync(path.join(dir, "plugins"))) {
27
+ return dir;
28
+ }
29
+ }
30
+ if (fs.existsSync(path.join(bundled, "sdk")) && fs.existsSync(path.join(bundled, "plugins"))) {
31
+ return bundled;
32
+ }
33
+ throw new Error(
34
+ `iOS SDK layout invalid under ${BUNDLED_IOS_REL}: need sdk/ + plugins/ at top level or under one version directory.`
35
+ );
36
+ }
@@ -0,0 +1,36 @@
1
+ /** Substitute `{path.to.value}` placeholders using channel config (topsdk-tool-ios `checkStr`). */
2
+ export function applyChannelTemplate(value: string, channelConfig: Record<string, unknown>): string {
3
+ let result = value;
4
+ const re = /\{([^}]+)\}/g;
5
+ let m: RegExpExecArray | null;
6
+ while ((m = re.exec(value)) !== null) {
7
+ const token = m[1].startsWith("$C.") ? m[1].slice(3) : m[1];
8
+ const parts = token.split(".");
9
+ let cur: unknown = channelConfig;
10
+ for (const p of parts) {
11
+ if (cur && typeof cur === "object" && p in (cur as Record<string, unknown>)) {
12
+ cur = (cur as Record<string, unknown>)[p];
13
+ } else {
14
+ cur = "";
15
+ break;
16
+ }
17
+ }
18
+ const replacement = cur == null ? "" : String(cur);
19
+ result = result.replace(m[0], replacement);
20
+ }
21
+ return result;
22
+ }
23
+
24
+ export function applyChannelTemplateValue(value: unknown, channelConfig: Record<string, unknown>): unknown {
25
+ if (typeof value === "string") return applyChannelTemplate(value, channelConfig);
26
+ if (Array.isArray(value)) return value.map((item) => applyChannelTemplateValue(item, channelConfig));
27
+ if (value && typeof value === "object") {
28
+ return Object.fromEntries(
29
+ Object.entries(value as Record<string, unknown>).map(([key, item]) => [
30
+ key,
31
+ applyChannelTemplateValue(item, channelConfig),
32
+ ])
33
+ );
34
+ }
35
+ return value;
36
+ }
@@ -0,0 +1,65 @@
1
+ export interface FrameworkConfig {
2
+ name: string;
3
+ system: boolean;
4
+ embed: boolean;
5
+ copyFile?: boolean;
6
+ }
7
+
8
+ export interface LibConfig {
9
+ name: string;
10
+ system: boolean;
11
+ }
12
+
13
+ export interface CodeConfig {
14
+ type: "header" | "method" | "code";
15
+ method?: string;
16
+ content: string;
17
+ addToReturn?: boolean;
18
+ }
19
+
20
+ export interface PluginConfigJson {
21
+ name: string;
22
+ pluginVersion: string;
23
+ key: string;
24
+ type: number;
25
+ frameworks?: FrameworkConfig[];
26
+ libs?: LibConfig[];
27
+ sources?: string[];
28
+ buildSettings?: Record<string, string>;
29
+ OtherLinkerFlags?: string[];
30
+ queriesSchemes?: string[];
31
+ urlScheme?: string;
32
+ infoParams?: Record<string, unknown>;
33
+ requireConfigs?: string[];
34
+ pluginParams?: Record<string, string>;
35
+ appDelegateCodes?: CodeConfig[];
36
+ sceneDelegateCodes?: CodeConfig[];
37
+ }
38
+
39
+ export interface LoadedPluginConfig {
40
+ configPath: string;
41
+ pluginDir: string;
42
+ sourceDir: string;
43
+ config: PluginConfigJson;
44
+ }
45
+
46
+ export type PerformSetting =
47
+ | "sources"
48
+ | "frameworks"
49
+ | "libs"
50
+ | "buildSettings"
51
+ | "OtherLinkerFlags"
52
+ | "queriesSchemes"
53
+ | "infoParams"
54
+ | "urlScheme";
55
+
56
+ export const DEFAULT_PERFORM_SETTINGS: readonly PerformSetting[] = [
57
+ "sources",
58
+ "frameworks",
59
+ "libs",
60
+ "buildSettings",
61
+ "OtherLinkerFlags",
62
+ "queriesSchemes",
63
+ "infoParams",
64
+ "urlScheme",
65
+ ];