@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,116 @@
1
+ export const START_REPO_MARKER = "// >>> MEET_INTEGRATE REPOS START";
2
+ export const END_REPO_MARKER = "// >>> MEET_INTEGRATE REPOS END";
3
+ export const START_BLOCK_MARKER = "// >>> MEET_INTEGRATE BLOCK START";
4
+ export const END_BLOCK_MARKER = "// >>> MEET_INTEGRATE BLOCK END";
5
+
6
+ export interface BlockRange {
7
+ start: number;
8
+ openBrace: number;
9
+ end: number;
10
+ }
11
+
12
+ export function findBlockRange(content: string, keyword: string, searchFrom = 0): BlockRange | null {
13
+ // Require `keyword {` so `com.android.application` / `androidx` are not matched.
14
+ const re = new RegExp(`\\b${keyword}\\s*\\{`);
15
+ const slice = content.slice(searchFrom);
16
+ const match = re.exec(slice);
17
+ if (!match || match.index === undefined) return null;
18
+ const keywordIndex = searchFrom + match.index;
19
+ const openBrace = content.indexOf("{", keywordIndex + match[0].length - 1);
20
+ if (openBrace < 0) return null;
21
+ let depth = 0;
22
+ for (let i = openBrace; i < content.length; i += 1) {
23
+ const ch = content[i];
24
+ if (ch === "{") depth += 1;
25
+ if (ch === "}") {
26
+ depth -= 1;
27
+ if (depth === 0) return { start: keywordIndex, openBrace, end: i };
28
+ }
29
+ }
30
+ return null;
31
+ }
32
+
33
+ export function replaceOrInsertManaged(
34
+ content: string,
35
+ blockRange: BlockRange,
36
+ startMarker: string,
37
+ endMarker: string,
38
+ snippet: string
39
+ ): string {
40
+ const blockText = content.slice(blockRange.openBrace + 1, blockRange.end);
41
+ const startInBlock = blockText.indexOf(startMarker);
42
+ const endInBlock = blockText.indexOf(endMarker);
43
+ const insertion = `${snippet}\n`;
44
+
45
+ if (startInBlock >= 0 && endInBlock > startInBlock) {
46
+ const before = blockText.slice(0, startInBlock).replace(/\s*$/, "\n");
47
+ const after = blockText.slice(endInBlock + endMarker.length).replace(/^\s*/, "\n");
48
+ const replaced = `${before}${snippet}${after}`;
49
+ return content.slice(0, blockRange.openBrace + 1) + replaced + content.slice(blockRange.end);
50
+ }
51
+
52
+ const prefix = content.slice(0, blockRange.end);
53
+ return prefix + insertion.trimEnd() + "\n" + content.slice(blockRange.end);
54
+ }
55
+
56
+ function buildRepoSnippet(urls: string[]): string {
57
+ const lines = [START_REPO_MARKER];
58
+ for (const url of urls) {
59
+ if (url === "google") lines.push(" google()");
60
+ else if (url === "mavenCentral") lines.push(" mavenCentral()");
61
+ else lines.push(` maven { url '${url}' }`);
62
+ }
63
+ lines.push(END_REPO_MARKER);
64
+ return lines.join("\n");
65
+ }
66
+
67
+ export function updateRootBuildGradleRepositories(
68
+ content: string,
69
+ urls: string[]
70
+ ): { ok: true; content: string; changed: boolean; warnings: string[] } | { ok: false; error: string } {
71
+ const warnings: string[] = [];
72
+ const allprojects = findBlockRange(content, "allprojects");
73
+ if (!allprojects) {
74
+ return { ok: false, error: "allprojects block not found in root build.gradle" };
75
+ }
76
+ const repositoriesBlock = findBlockRange(content, "repositories", allprojects.start);
77
+ if (!repositoriesBlock || repositoriesBlock.end > allprojects.end) {
78
+ return { ok: false, error: "repositories block under allprojects not found" };
79
+ }
80
+
81
+ const snippet = buildRepoSnippet(urls || []);
82
+ const updated = replaceOrInsertManaged(
83
+ content,
84
+ repositoriesBlock,
85
+ START_REPO_MARKER,
86
+ END_REPO_MARKER,
87
+ snippet
88
+ );
89
+ if (updated === content) warnings.push("root build.gradle repositories unchanged");
90
+ return { ok: true, content: updated, changed: updated !== content, warnings };
91
+ }
92
+
93
+ function buildDependenciesSnippet(lines: string[]): string {
94
+ return [START_BLOCK_MARKER, ...lines, END_BLOCK_MARKER].join("\n");
95
+ }
96
+
97
+ export function updateModuleBuildGradleDependencies(
98
+ content: string,
99
+ dependencyLines: string[]
100
+ ): { ok: true; content: string; changed: boolean; warnings: string[] } | { ok: false; error: string } {
101
+ const warnings: string[] = [];
102
+ const dependenciesBlock = findBlockRange(content, "dependencies");
103
+ if (!dependenciesBlock) {
104
+ return { ok: false, error: "dependencies block not found in module build.gradle" };
105
+ }
106
+ const snippet = buildDependenciesSnippet(dependencyLines);
107
+ const updated = replaceOrInsertManaged(
108
+ content,
109
+ dependenciesBlock,
110
+ START_BLOCK_MARKER,
111
+ END_BLOCK_MARKER,
112
+ snippet
113
+ );
114
+ if (updated === content) warnings.push("module build.gradle dependencies unchanged");
115
+ return { ok: true, content: updated, changed: updated !== content, warnings };
116
+ }
@@ -0,0 +1,50 @@
1
+ export const MANIFEST_PERM_START = "<!-- MEET_INTEGRATE PERMISSIONS START -->";
2
+ export const MANIFEST_PERM_END = "<!-- MEET_INTEGRATE PERMISSIONS END -->";
3
+
4
+ const PERMISSION_NAME_RE = /android:name="([^"]+)"/;
5
+
6
+ export function extractPermissionName(line: string): string | null {
7
+ if (!line.includes("uses-permission")) return null;
8
+ const m = line.match(PERMISSION_NAME_RE);
9
+ return m?.[1] ?? null;
10
+ }
11
+
12
+ /** Remove <uses-permission> lines for the same android:name (inside or outside managed block). */
13
+ export function removePermissionLinesByNames(content: string, names: ReadonlySet<string>): string {
14
+ return content
15
+ .split("\n")
16
+ .filter((line) => {
17
+ const name = extractPermissionName(line);
18
+ return !name || !names.has(name);
19
+ })
20
+ .join("\n");
21
+ }
22
+
23
+ function stripPermissionManagedBlock(content: string): string {
24
+ const start = content.indexOf(MANIFEST_PERM_START);
25
+ const end = content.indexOf(MANIFEST_PERM_END);
26
+ if (start >= 0 && end > start) {
27
+ return content.slice(0, start) + content.slice(end + MANIFEST_PERM_END.length);
28
+ }
29
+ return content;
30
+ }
31
+
32
+ export function insertPermissions(content: string, permissions: string[]): { content: string; changed: boolean } {
33
+ const names = new Set(permissions);
34
+ let cleaned = removePermissionLinesByNames(content, names);
35
+ cleaned = stripPermissionManagedBlock(cleaned);
36
+
37
+ const lines = [
38
+ MANIFEST_PERM_START,
39
+ ...permissions.map((p) => ` <uses-permission android:name="${p}" />`),
40
+ MANIFEST_PERM_END,
41
+ ].join("\n");
42
+
43
+ const closeManifest = cleaned.lastIndexOf("</manifest>");
44
+ if (closeManifest < 0) {
45
+ return { content, changed: false };
46
+ }
47
+ const insert = `\n${lines}\n`;
48
+ const updated = cleaned.slice(0, closeManifest) + insert + cleaned.slice(closeManifest);
49
+ return { content: updated, changed: updated !== content };
50
+ }