@leonxin/meetgames 0.1.8 → 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 (285) 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/cli.d.ts.map +1 -1
  47. package/dist/cli.js +157 -31
  48. package/dist/cli.js.map +1 -1
  49. package/dist/config/meetSdkDefaultConfig.d.ts +19 -2
  50. package/dist/config/meetSdkDefaultConfig.d.ts.map +1 -1
  51. package/dist/config/meetSdkDefaultConfig.js +67 -5
  52. package/dist/config/meetSdkDefaultConfig.js.map +1 -1
  53. package/dist/config/meetSdkIosConfig.d.ts +21 -0
  54. package/dist/config/meetSdkIosConfig.d.ts.map +1 -0
  55. package/dist/config/meetSdkIosConfig.js +66 -0
  56. package/dist/config/meetSdkIosConfig.js.map +1 -0
  57. package/dist/config/meetSdkRemoteConfig.d.ts +18 -1
  58. package/dist/config/meetSdkRemoteConfig.d.ts.map +1 -1
  59. package/dist/config/meetSdkRemoteConfig.js +61 -25
  60. package/dist/config/meetSdkRemoteConfig.js.map +1 -1
  61. package/dist/contracts/types.d.ts +19 -6
  62. package/dist/contracts/types.d.ts.map +1 -1
  63. package/dist/core/doctor.d.ts +17 -0
  64. package/dist/core/doctor.d.ts.map +1 -0
  65. package/dist/core/doctor.js +444 -0
  66. package/dist/core/doctor.js.map +1 -0
  67. package/dist/core/pipeline.d.ts.map +1 -1
  68. package/dist/core/pipeline.js +0 -15
  69. package/dist/core/pipeline.js.map +1 -1
  70. package/dist/core/platform.d.ts +12 -0
  71. package/dist/core/platform.d.ts.map +1 -0
  72. package/dist/core/platform.js +40 -0
  73. package/dist/core/platform.js.map +1 -0
  74. package/dist/core/reporter.js +1 -1
  75. package/dist/core/reporter.js.map +1 -1
  76. package/dist/core/workspace.d.ts +2 -2
  77. package/dist/core/workspace.d.ts.map +1 -1
  78. package/dist/core/workspace.js +4 -5
  79. package/dist/core/workspace.js.map +1 -1
  80. package/dist/index.d.ts +3 -1
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.js +3 -1
  83. package/dist/index.js.map +1 -1
  84. package/dist/ios/channelConfig.d.ts +1 -0
  85. package/dist/ios/channelConfig.d.ts.map +1 -1
  86. package/dist/ios/channelConfig.js +82 -0
  87. package/dist/ios/channelConfig.js.map +1 -1
  88. package/dist/ios/codeUtils.d.ts +1 -0
  89. package/dist/ios/codeUtils.d.ts.map +1 -1
  90. package/dist/ios/codeUtils.js +11 -2
  91. package/dist/ios/codeUtils.js.map +1 -1
  92. package/dist/ios/detect.d.ts +2 -2
  93. package/dist/ios/detect.d.ts.map +1 -1
  94. package/dist/ios/detect.js +49 -10
  95. package/dist/ios/detect.js.map +1 -1
  96. package/dist/ios/entitlements.d.ts +4 -0
  97. package/dist/ios/entitlements.d.ts.map +1 -0
  98. package/dist/ios/entitlements.js +53 -0
  99. package/dist/ios/entitlements.js.map +1 -0
  100. package/dist/ios/fileManager.d.ts.map +1 -1
  101. package/dist/ios/fileManager.js +3 -2
  102. package/dist/ios/fileManager.js.map +1 -1
  103. package/dist/ios/infoPlist.d.ts +1 -1
  104. package/dist/ios/infoPlist.d.ts.map +1 -1
  105. package/dist/ios/infoPlist.js.map +1 -1
  106. package/dist/ios/integrate.d.ts.map +1 -1
  107. package/dist/ios/integrate.js +211 -36
  108. package/dist/ios/integrate.js.map +1 -1
  109. package/dist/ios/pbxprojEditor.d.ts +2 -0
  110. package/dist/ios/pbxprojEditor.d.ts.map +1 -1
  111. package/dist/ios/pbxprojEditor.js +179 -1
  112. package/dist/ios/pbxprojEditor.js.map +1 -1
  113. package/dist/ios/pluginConfig.d.ts +1 -0
  114. package/dist/ios/pluginConfig.d.ts.map +1 -1
  115. package/dist/ios/pluginConfig.js +36 -4
  116. package/dist/ios/pluginConfig.js.map +1 -1
  117. package/dist/ios/sdkBundle.d.ts +1 -1
  118. package/dist/ios/sdkBundle.d.ts.map +1 -1
  119. package/dist/ios/sdkBundle.js +7 -5
  120. package/dist/ios/sdkBundle.js.map +1 -1
  121. package/dist/ios/template.d.ts +1 -0
  122. package/dist/ios/template.d.ts.map +1 -1
  123. package/dist/ios/template.js +14 -1
  124. package/dist/ios/template.js.map +1 -1
  125. package/dist/ios/types.d.ts +2 -2
  126. package/dist/ios/types.d.ts.map +1 -1
  127. package/dist/mcp/server.d.ts.map +1 -1
  128. package/dist/mcp/server.js +14 -13
  129. package/dist/mcp/server.js.map +1 -1
  130. package/dist/mcp/service.d.ts +8 -6
  131. package/dist/mcp/service.d.ts.map +1 -1
  132. package/dist/mcp/service.js +34 -14
  133. package/dist/mcp/service.js.map +1 -1
  134. package/dist/ops/handlers.d.ts.map +1 -1
  135. package/dist/ops/handlers.js +10 -4
  136. package/dist/ops/handlers.js.map +1 -1
  137. package/dist/remote/sdkHomeDownload.d.ts +65 -0
  138. package/dist/remote/sdkHomeDownload.d.ts.map +1 -0
  139. package/dist/remote/sdkHomeDownload.js +208 -0
  140. package/dist/remote/sdkHomeDownload.js.map +1 -0
  141. package/dist/remote/topsdkDownloadSdkConfig.d.ts.map +1 -1
  142. package/dist/remote/topsdkDownloadSdkConfig.js +11 -1
  143. package/dist/remote/topsdkDownloadSdkConfig.js.map +1 -1
  144. package/dist/shared/errors.d.ts +7 -0
  145. package/dist/shared/errors.d.ts.map +1 -0
  146. package/dist/shared/errors.js +16 -0
  147. package/dist/shared/errors.js.map +1 -0
  148. package/dist/shared/fileUtils.d.ts +5 -0
  149. package/dist/shared/fileUtils.d.ts.map +1 -0
  150. package/dist/shared/fileUtils.js +35 -0
  151. package/dist/shared/fileUtils.js.map +1 -0
  152. package/dist/shared/logger.d.ts +10 -0
  153. package/dist/shared/logger.d.ts.map +1 -0
  154. package/dist/shared/logger.js +37 -0
  155. package/dist/shared/logger.js.map +1 -0
  156. package/dist/shared/pathUtils.d.ts +4 -0
  157. package/dist/shared/pathUtils.d.ts.map +1 -0
  158. package/dist/shared/pathUtils.js +22 -0
  159. package/dist/shared/pathUtils.js.map +1 -0
  160. package/dist/shared/processRunner.d.ts +12 -0
  161. package/dist/shared/processRunner.d.ts.map +1 -0
  162. package/dist/shared/processRunner.js +31 -0
  163. package/dist/shared/processRunner.js.map +1 -0
  164. package/docs/AAB_CONVERTER_CLI_PLAN.md +392 -0
  165. package/docs/API.md +246 -32
  166. package/docs/CLI.md +292 -0
  167. package/docs/INTEGRATION.md +116 -0
  168. package/docs/MCP.md +86 -0
  169. package/docs/README.md +19 -10
  170. package/docs/{api → archive/api}/downloadSDKConfig.md +8 -6
  171. package/docs/{api → archive/api}/getChannelConfig-meetgames.md +1 -1
  172. package/docs/archive/ios-migration.md +2139 -0
  173. 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
  174. package/docs/{ → archive/product/}/351/234/200/346/261/202/346/226/207/346/241/243.md +15 -14
  175. package/logs/convert-20260622-155037.log +5 -0
  176. package/logs/convert-20260622-155226.log +6 -0
  177. package/meetsdk-android.json +2 -1
  178. package/meetsdk-ios.json +15 -0
  179. package/package.json +10 -36
  180. package/scripts/package-aab-cli-win.mjs +193 -0
  181. package/src/aab-converter/aab-entry.ts +48 -0
  182. package/src/aab-converter/apksExtractor.ts +119 -0
  183. package/src/aab-converter/bundletoolRunner.ts +63 -0
  184. package/src/aab-converter/cliArgs.ts +194 -0
  185. package/src/aab-converter/convertAabToApk.ts +81 -0
  186. package/src/aab-converter/resourcePaths.ts +43 -0
  187. package/src/aab-converter/signingOptions.ts +29 -0
  188. package/src/aab-converter/types.ts +26 -0
  189. package/src/android/adapter.ts +9 -0
  190. package/src/android/assembleIntegrationJson.ts +33 -0
  191. package/src/android/detect.ts +132 -0
  192. package/src/android/downloadGoogleServicesJson.ts +56 -0
  193. package/src/android/gradle.ts +116 -0
  194. package/src/android/manifest.ts +50 -0
  195. package/src/android/meetSdkRemoteGradle.ts +837 -0
  196. package/src/cli.ts +488 -0
  197. package/src/config/fetchConfigWrite.ts +30 -0
  198. package/src/config/loadAndroidIntegration.ts +41 -0
  199. package/src/config/loadManifest.ts +40 -0
  200. package/src/config/meetSdkDefaultConfig.ts +99 -0
  201. package/src/config/meetSdkIosConfig.ts +87 -0
  202. package/src/config/meetSdkRemoteConfig.ts +1211 -0
  203. package/src/config/topsdkFeatureModules.ts +92 -0
  204. package/src/contracts/types.ts +121 -0
  205. package/src/core/doctor.ts +485 -0
  206. package/src/core/patch.ts +64 -0
  207. package/src/core/pipeline.ts +107 -0
  208. package/src/core/platform.ts +47 -0
  209. package/src/core/previewPatches.ts +23 -0
  210. package/src/core/reporter.ts +24 -0
  211. package/src/core/workspace.ts +29 -0
  212. package/src/entry.ts +7 -0
  213. package/src/index.ts +133 -0
  214. package/src/ios/channelConfig.ts +128 -0
  215. package/src/ios/codeUtils.ts +160 -0
  216. package/src/ios/detect.ts +105 -0
  217. package/src/ios/entitlements.ts +61 -0
  218. package/src/ios/fileManager.ts +48 -0
  219. package/src/ios/infoPlist.ts +55 -0
  220. package/src/ios/integrate.ts +516 -0
  221. package/src/ios/pbxprojEditor.ts +383 -0
  222. package/src/ios/pluginConfig.ts +97 -0
  223. package/src/ios/reserved.ts +8 -0
  224. package/src/ios/sdkBundle.ts +36 -0
  225. package/src/ios/template.ts +36 -0
  226. package/src/ios/types.ts +65 -0
  227. package/src/mcp/server.ts +170 -0
  228. package/src/mcp/service.ts +222 -0
  229. package/src/mcp-entry.ts +7 -0
  230. package/src/ops/fileStore.ts +56 -0
  231. package/src/ops/handlers.ts +304 -0
  232. package/src/remote/fetchJson.ts +22 -0
  233. package/src/remote/sdkHomeDownload.ts +274 -0
  234. package/src/remote/topsdkDownloadSdkConfig.ts +93 -0
  235. package/src/remote/topsdkGetSdkConfig.ts +122 -0
  236. package/src/remote/topsdkSign.ts +10 -0
  237. package/src/shared/errors.ts +16 -0
  238. package/src/shared/fileUtils.ts +41 -0
  239. package/src/shared/logger.ts +49 -0
  240. package/src/shared/pathUtils.ts +24 -0
  241. package/src/shared/processRunner.ts +43 -0
  242. package/test-projects/README.md +51 -0
  243. package/test-projects/_preview/pipeline.patch +281 -0
  244. package/tests/aab-converter.test.ts +213 -0
  245. package/tests/assemble.test.ts +12 -0
  246. package/tests/doctor.test.ts +89 -0
  247. package/tests/downloadGoogleServicesJson.test.ts +47 -0
  248. package/tests/fetch-remote.test.ts +23 -0
  249. package/tests/fetchConfigOverrides.test.ts +28 -0
  250. package/tests/fetchConfigWrite.test.ts +54 -0
  251. package/tests/gradle.test.ts +33 -0
  252. package/tests/integration-json.test.ts +29 -0
  253. package/tests/ios.codeUtils.test.ts +23 -0
  254. package/tests/ios.sdkBundle.test.ts +16 -0
  255. package/tests/loadManifest.test.ts +15 -0
  256. package/tests/manifest-xml.test.ts +30 -0
  257. package/tests/mcp.e2e.ts +217 -0
  258. package/tests/mcp.service.test.ts +53 -0
  259. package/tests/meetSdkRemoteConfig.test.ts +456 -0
  260. package/tests/meetSdkRemoteGradle.test.ts +414 -0
  261. package/tests/pipeline.android.test.ts +96 -0
  262. package/tests/pipeline.integration-json.test.ts +58 -0
  263. package/tests/pipeline.ios.test.ts +385 -0
  264. package/tests/pipeline.preview.patch.test.ts +85 -0
  265. package/tests/platformSelection.test.ts +77 -0
  266. package/tests/sdkHomeDownload.test.ts +124 -0
  267. package/tests/sdkVersionConfig.test.ts +130 -0
  268. package/tests/test-projects-hosts.test.ts +78 -0
  269. package/tests/topsdk.test.ts +53 -0
  270. package/tests/topsdkDownloadSdkConfig.test.ts +81 -0
  271. package/tests/topsdkFeatureModules.test.ts +116 -0
  272. package/tsconfig.json +19 -0
  273. package/vitest.config.ts +9 -0
  274. package/vitest.mcp.config.ts +11 -0
  275. package/bundled/android/sample.txt +0 -1
  276. package/docs/ANDROID.md +0 -133
  277. package/docs/CURSOR-MCP-SETUP.md +0 -72
  278. package/docs/MCP-SKILL.md +0 -63
  279. package/fixtures/api-samples/getChannelConfig-meetgames.sample.json +0 -123
  280. package/fixtures/meetsdk-remote-config.download-shape.json +0 -20
  281. package/fixtures/meetsdk-remote-config.mock.json +0 -69
  282. package/fixtures/recipes/android-default.fixture.yaml +0 -15
  283. package/fixtures/recipes/android-integration.fixture.json +0 -29
  284. package/fixtures/topsdk-config-reference.json +0 -39
  285. /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
+ }