@teardown/cli 1.2.38 → 2.0.41

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 (182) hide show
  1. package/bin/teardown.js +11 -1
  2. package/package.json +77 -57
  3. package/src/cli/commands/init.ts +254 -0
  4. package/src/cli/commands/plugins.ts +93 -0
  5. package/src/cli/commands/prebuild.ts +168 -0
  6. package/src/cli/commands/run.ts +727 -0
  7. package/src/cli/commands/start.ts +87 -0
  8. package/src/cli/commands/validate.ts +62 -0
  9. package/src/cli/index.ts +59 -0
  10. package/src/config/index.ts +45 -0
  11. package/src/config/loader.ts +366 -0
  12. package/src/config/schema.ts +235 -0
  13. package/src/config/types.ts +322 -0
  14. package/src/index.ts +177 -0
  15. package/src/pipeline/cache.ts +179 -0
  16. package/src/pipeline/index.ts +10 -0
  17. package/src/pipeline/stages.ts +692 -0
  18. package/src/plugins/base.ts +370 -0
  19. package/src/plugins/capabilities/biometrics.ts +64 -0
  20. package/src/plugins/capabilities/bluetooth.ts +86 -0
  21. package/src/plugins/capabilities/calendar.ts +57 -0
  22. package/src/plugins/capabilities/camera.ts +77 -0
  23. package/src/plugins/capabilities/contacts.ts +57 -0
  24. package/src/plugins/capabilities/deep-linking.ts +124 -0
  25. package/src/plugins/capabilities/firebase.ts +138 -0
  26. package/src/plugins/capabilities/index.ts +96 -0
  27. package/src/plugins/capabilities/location.ts +87 -0
  28. package/src/plugins/capabilities/photo-library.ts +80 -0
  29. package/src/plugins/capabilities/push-notifications.ts +98 -0
  30. package/src/plugins/capabilities/sign-in-with-apple.ts +53 -0
  31. package/src/plugins/context.ts +220 -0
  32. package/src/plugins/index.ts +26 -0
  33. package/src/plugins/resolver.ts +321 -0
  34. package/src/templates/generator.ts +507 -0
  35. package/src/templates/index.ts +9 -0
  36. package/src/templates/paths.ts +25 -0
  37. package/src/transformers/android/gradle.ts +400 -0
  38. package/src/transformers/android/index.ts +19 -0
  39. package/src/transformers/android/manifest.ts +506 -0
  40. package/src/transformers/index.ts +39 -0
  41. package/src/transformers/ios/entitlements.ts +283 -0
  42. package/src/transformers/ios/index.ts +10 -0
  43. package/src/transformers/ios/pbxproj.ts +267 -0
  44. package/src/transformers/ios/plist.ts +198 -0
  45. package/src/utils/fs.ts +429 -0
  46. package/src/utils/index.ts +21 -0
  47. package/src/utils/logger.ts +203 -0
  48. package/templates/.gitignore +63 -0
  49. package/templates/Gemfile +3 -0
  50. package/templates/android/app/build.gradle.kts +97 -0
  51. package/templates/android/app/proguard-rules.pro +10 -0
  52. package/templates/android/app/src/main/AndroidManifest.xml +26 -0
  53. package/templates/android/app/src/main/java/com/appname/MainActivity.kt +22 -0
  54. package/templates/android/app/src/main/java/com/appname/MainApplication.kt +44 -0
  55. package/templates/android/app/src/main/res/values/strings.xml +3 -0
  56. package/templates/android/app/src/main/res/values/styles.xml +7 -0
  57. package/templates/android/build.gradle.kts +44 -0
  58. package/templates/android/gradle.properties +39 -0
  59. package/templates/android/settings.gradle.kts +12 -0
  60. package/templates/babel.config.js +15 -0
  61. package/templates/index.js +7 -0
  62. package/templates/ios/.xcode.env +11 -0
  63. package/templates/ios/AppName/AppDelegate.swift +25 -0
  64. package/templates/ios/AppName/AppName-Bridging-Header.h +4 -0
  65. package/templates/ios/AppName/AppName.entitlements +6 -0
  66. package/templates/ios/AppName/Images.xcassets/AppIcon.appiconset/Contents.json +35 -0
  67. package/templates/ios/AppName/Images.xcassets/Contents.json +6 -0
  68. package/templates/ios/AppName/Info.plist +49 -0
  69. package/templates/ios/AppName/LaunchScreen.storyboard +38 -0
  70. package/templates/ios/AppName.xcodeproj/project.pbxproj +402 -0
  71. package/templates/ios/AppName.xcodeproj/xcshareddata/xcschemes/AppName.xcscheme +78 -0
  72. package/templates/ios/Podfile +35 -0
  73. package/templates/metro.config.js +41 -0
  74. package/templates/package.json +57 -0
  75. package/templates/react-native.config.js +8 -0
  76. package/templates/src/app/index.tsx +34 -0
  77. package/templates/src/assets/fonts/.gitkeep +1 -0
  78. package/templates/src/assets/images/.gitkeep +1 -0
  79. package/templates/src/components/ui/accordion.tsx +114 -0
  80. package/templates/src/components/ui/avatar.tsx +75 -0
  81. package/templates/src/components/ui/button.tsx +93 -0
  82. package/templates/src/components/ui/card.tsx +120 -0
  83. package/templates/src/components/ui/checkbox.tsx +133 -0
  84. package/templates/src/components/ui/chip.tsx +95 -0
  85. package/templates/src/components/ui/dialog.tsx +134 -0
  86. package/templates/src/components/ui/divider.tsx +67 -0
  87. package/templates/src/components/ui/error-view.tsx +82 -0
  88. package/templates/src/components/ui/form-field.tsx +101 -0
  89. package/templates/src/components/ui/index.ts +100 -0
  90. package/templates/src/components/ui/popover.tsx +92 -0
  91. package/templates/src/components/ui/pressable-feedback.tsx +88 -0
  92. package/templates/src/components/ui/radio-group.tsx +153 -0
  93. package/templates/src/components/ui/scroll-shadow.tsx +108 -0
  94. package/templates/src/components/ui/select.tsx +165 -0
  95. package/templates/src/components/ui/skeleton-group.tsx +97 -0
  96. package/templates/src/components/ui/skeleton.tsx +87 -0
  97. package/templates/src/components/ui/spinner.tsx +87 -0
  98. package/templates/src/components/ui/surface.tsx +95 -0
  99. package/templates/src/components/ui/switch.tsx +124 -0
  100. package/templates/src/components/ui/tabs.tsx +154 -0
  101. package/templates/src/components/ui/text-field.tsx +106 -0
  102. package/templates/src/components/ui/toast.tsx +129 -0
  103. package/templates/src/contexts/.gitkeep +2 -0
  104. package/templates/src/core/clients/api/api.client.ts +113 -0
  105. package/templates/src/core/clients/api/index.ts +1 -0
  106. package/templates/src/core/clients/storage/index.ts +1 -0
  107. package/templates/src/core/clients/storage/storage.client.ts +121 -0
  108. package/templates/src/core/constants/index.ts +19 -0
  109. package/templates/src/core/core.ts +40 -0
  110. package/templates/src/core/index.ts +10 -0
  111. package/templates/src/global.css +87 -0
  112. package/templates/src/hooks/index.ts +6 -0
  113. package/templates/src/hooks/use-debounce.ts +23 -0
  114. package/templates/src/hooks/use-mounted.ts +21 -0
  115. package/templates/src/index.ts +28 -0
  116. package/templates/src/lib/index.ts +5 -0
  117. package/templates/src/lib/utils.ts +115 -0
  118. package/templates/src/modules/.gitkeep +6 -0
  119. package/templates/src/navigation/index.ts +8 -0
  120. package/templates/src/navigation/navigation-provider.tsx +36 -0
  121. package/templates/src/navigation/router.tsx +137 -0
  122. package/templates/src/providers/app.provider.tsx +29 -0
  123. package/templates/src/providers/index.ts +5 -0
  124. package/templates/src/routes/(tabs)/_layout.tsx +42 -0
  125. package/templates/src/routes/(tabs)/explore.tsx +161 -0
  126. package/templates/src/routes/(tabs)/home.tsx +138 -0
  127. package/templates/src/routes/(tabs)/profile.tsx +151 -0
  128. package/templates/src/routes/_layout.tsx +18 -0
  129. package/templates/src/routes/settings.tsx +194 -0
  130. package/templates/src/screens/auth/index.ts +6 -0
  131. package/templates/src/screens/auth/login.tsx +165 -0
  132. package/templates/src/screens/auth/register.tsx +203 -0
  133. package/templates/src/screens/home.tsx +204 -0
  134. package/templates/src/screens/index.ts +17 -0
  135. package/templates/src/screens/profile.tsx +210 -0
  136. package/templates/src/screens/settings.tsx +216 -0
  137. package/templates/src/screens/welcome.tsx +101 -0
  138. package/templates/src/styles/index.ts +103 -0
  139. package/templates/src/types/common.ts +71 -0
  140. package/templates/src/types/index.ts +5 -0
  141. package/templates/tsconfig.json +14 -0
  142. package/README.md +0 -15
  143. package/assets/favicon.ico +0 -0
  144. package/dist/commands/dev/dev.js +0 -55
  145. package/dist/commands/init/init-teardown.js +0 -26
  146. package/dist/index.js +0 -20
  147. package/dist/modules/dev/dev-menu/keyboard-handler.js +0 -138
  148. package/dist/modules/dev/dev-menu/open-debugger-keyboard-handler.js +0 -105
  149. package/dist/modules/dev/dev-server/cdp/cdp.adapter.js +0 -12
  150. package/dist/modules/dev/dev-server/cdp/index.js +0 -18
  151. package/dist/modules/dev/dev-server/cdp/types.js +0 -2
  152. package/dist/modules/dev/dev-server/dev-server-checker.js +0 -72
  153. package/dist/modules/dev/dev-server/dev-server.js +0 -269
  154. package/dist/modules/dev/dev-server/inspector/device.event-reporter.js +0 -165
  155. package/dist/modules/dev/dev-server/inspector/device.js +0 -577
  156. package/dist/modules/dev/dev-server/inspector/inspector.js +0 -204
  157. package/dist/modules/dev/dev-server/inspector/types.js +0 -2
  158. package/dist/modules/dev/dev-server/inspector/wss/servers/debugger-connection.server.js +0 -61
  159. package/dist/modules/dev/dev-server/inspector/wss/servers/device-connection.server.js +0 -64
  160. package/dist/modules/dev/dev-server/plugins/devtools.plugin.js +0 -50
  161. package/dist/modules/dev/dev-server/plugins/favicon.plugin.js +0 -19
  162. package/dist/modules/dev/dev-server/plugins/multipart.plugin.js +0 -62
  163. package/dist/modules/dev/dev-server/plugins/systrace.plugin.js +0 -28
  164. package/dist/modules/dev/dev-server/plugins/types.js +0 -2
  165. package/dist/modules/dev/dev-server/plugins/wss/index.js +0 -19
  166. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-api.server.js +0 -66
  167. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-debugger.server.js +0 -128
  168. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-dev-client.server.js +0 -75
  169. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-events.server.js +0 -198
  170. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-hmr.server.js +0 -120
  171. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-message.server.js +0 -357
  172. package/dist/modules/dev/dev-server/plugins/wss/types.js +0 -2
  173. package/dist/modules/dev/dev-server/plugins/wss/web-socket-router.js +0 -57
  174. package/dist/modules/dev/dev-server/plugins/wss/web-socket-server-adapter.js +0 -26
  175. package/dist/modules/dev/dev-server/plugins/wss/web-socket-server.js +0 -46
  176. package/dist/modules/dev/dev-server/plugins/wss/wss.plugin.js +0 -55
  177. package/dist/modules/dev/dev-server/sybmolicate/sybmolicate.plugin.js +0 -36
  178. package/dist/modules/dev/dev-server/sybmolicate/types.js +0 -2
  179. package/dist/modules/dev/terminal/base.terminal.reporter.js +0 -78
  180. package/dist/modules/dev/terminal/terminal.reporter.js +0 -76
  181. package/dist/modules/dev/types.js +0 -2
  182. package/dist/modules/dev/utils/log.js +0 -73
@@ -0,0 +1,506 @@
1
+ import { XMLBuilder, XMLParser } from "fast-xml-parser";
2
+ import type { AndroidIntentFilter, ProcessedAppConfig } from "../../config/types";
3
+ import type { PluginContext } from "../../plugins/context";
4
+
5
+ /**
6
+ * Android Manifest structure (parsed from XML)
7
+ */
8
+ export interface AndroidManifestXml {
9
+ "?xml"?: {
10
+ "@_version": string;
11
+ "@_encoding": string;
12
+ };
13
+ manifest: {
14
+ "@_xmlns:android": string;
15
+ "@_package": string;
16
+ "uses-permission"?: AndroidPermission[];
17
+ "uses-feature"?: AndroidFeature[];
18
+ queries?: AndroidQueries;
19
+ application: AndroidApplication;
20
+ };
21
+ }
22
+
23
+ export interface AndroidPermission {
24
+ "@_android:name": string;
25
+ "@_android:maxSdkVersion"?: string;
26
+ }
27
+
28
+ export interface AndroidFeature {
29
+ "@_android:name": string;
30
+ "@_android:required"?: string;
31
+ }
32
+
33
+ export interface AndroidQueries {
34
+ package?: Array<{ "@_android:name": string }>;
35
+ intent?: AndroidIntentXml[];
36
+ }
37
+
38
+ export interface AndroidApplication {
39
+ "@_android:name"?: string;
40
+ "@_android:label"?: string;
41
+ "@_android:icon"?: string;
42
+ "@_android:roundIcon"?: string;
43
+ "@_android:allowBackup"?: string;
44
+ "@_android:theme"?: string;
45
+ "@_android:networkSecurityConfig"?: string;
46
+ "@_android:supportsRtl"?: string;
47
+ activity?: AndroidActivityXml[];
48
+ service?: AndroidServiceXml[];
49
+ receiver?: AndroidReceiverXml[];
50
+ provider?: AndroidProviderXml[];
51
+ "meta-data"?: AndroidMetaData[];
52
+ }
53
+
54
+ export interface AndroidActivityXml {
55
+ "@_android:name": string;
56
+ "@_android:label"?: string;
57
+ "@_android:exported"?: string;
58
+ "@_android:launchMode"?: string;
59
+ "@_android:screenOrientation"?: string;
60
+ "@_android:configChanges"?: string;
61
+ "@_android:windowSoftInputMode"?: string;
62
+ "@_android:theme"?: string;
63
+ "intent-filter"?: AndroidIntentFilterXml[];
64
+ }
65
+
66
+ export interface AndroidIntentFilterXml {
67
+ "@_android:autoVerify"?: string;
68
+ action?: Array<{ "@_android:name": string }>;
69
+ category?: Array<{ "@_android:name": string }>;
70
+ data?: Array<{
71
+ "@_android:scheme"?: string;
72
+ "@_android:host"?: string;
73
+ "@_android:pathPrefix"?: string;
74
+ "@_android:path"?: string;
75
+ }>;
76
+ }
77
+
78
+ export interface AndroidIntentXml {
79
+ action?: Array<{ "@_android:name": string }>;
80
+ data?: Array<{
81
+ "@_android:scheme"?: string;
82
+ "@_android:host"?: string;
83
+ }>;
84
+ }
85
+
86
+ export interface AndroidServiceXml {
87
+ "@_android:name": string;
88
+ "@_android:exported"?: string;
89
+ "@_android:permission"?: string;
90
+ "intent-filter"?: AndroidIntentFilterXml[];
91
+ "meta-data"?: AndroidMetaData[];
92
+ }
93
+
94
+ export interface AndroidReceiverXml {
95
+ "@_android:name": string;
96
+ "@_android:exported"?: string;
97
+ "intent-filter"?: AndroidIntentFilterXml[];
98
+ }
99
+
100
+ export interface AndroidProviderXml {
101
+ "@_android:name": string;
102
+ "@_android:authorities": string;
103
+ "@_android:exported"?: string;
104
+ "@_android:grantUriPermissions"?: string;
105
+ }
106
+
107
+ export interface AndroidMetaData {
108
+ "@_android:name": string;
109
+ "@_android:value"?: string;
110
+ "@_android:resource"?: string;
111
+ }
112
+
113
+ /**
114
+ * XML Parser configuration
115
+ */
116
+ const parserOptions = {
117
+ ignoreAttributes: false,
118
+ attributeNamePrefix: "@_",
119
+ allowBooleanAttributes: true,
120
+ parseAttributeValue: false,
121
+ trimValues: true,
122
+ isArray: (name: string) => {
123
+ // These elements should always be treated as arrays even if there's only one
124
+ const alwaysArray = [
125
+ "uses-permission",
126
+ "uses-feature",
127
+ "activity",
128
+ "service",
129
+ "receiver",
130
+ "provider",
131
+ "meta-data",
132
+ "intent-filter",
133
+ "action",
134
+ "category",
135
+ "data",
136
+ "package",
137
+ "intent",
138
+ ];
139
+ return alwaysArray.includes(name);
140
+ },
141
+ };
142
+
143
+ const builderOptions = {
144
+ ignoreAttributes: false,
145
+ attributeNamePrefix: "@_",
146
+ format: true,
147
+ indentBy: " ",
148
+ suppressEmptyNode: false,
149
+ };
150
+
151
+ /**
152
+ * Android Manifest transformer
153
+ */
154
+ export class AndroidManifestTransformer {
155
+ private parser: XMLParser;
156
+ private builder: XMLBuilder;
157
+
158
+ constructor() {
159
+ this.parser = new XMLParser(parserOptions);
160
+ this.builder = new XMLBuilder(builderOptions);
161
+ }
162
+
163
+ /**
164
+ * Transform the AndroidManifest.xml with configuration
165
+ */
166
+ async transform(context: PluginContext, config: ProcessedAppConfig): Promise<void> {
167
+ const { fs, log, androidPaths } = context;
168
+
169
+ log.debug("Transforming AndroidManifest.xml...");
170
+
171
+ let content: string;
172
+
173
+ try {
174
+ content = await fs.readFile(androidPaths.manifest);
175
+ } catch {
176
+ log.warn("AndroidManifest.xml not found, creating default manifest");
177
+ content = this.createDefaultManifest(config);
178
+ }
179
+
180
+ // Parse XML
181
+ const manifest = this.parser.parse(content) as AndroidManifestXml;
182
+
183
+ // Apply transformations
184
+ this.applyPackageName(manifest, config);
185
+ this.applyPermissions(manifest, config);
186
+ this.applyFeatures(manifest, config);
187
+ this.applyMetadata(manifest, config);
188
+ this.applyIntentFilters(manifest, config);
189
+
190
+ // Build XML
191
+ const xmlHeader = '<?xml version="1.0" encoding="utf-8"?>\n';
192
+ const updatedContent = xmlHeader + this.builder.build(manifest);
193
+
194
+ // Write back
195
+ await fs.writeFile(androidPaths.manifest, updatedContent);
196
+
197
+ log.debug("AndroidManifest.xml transformation complete");
198
+ }
199
+
200
+ /**
201
+ * Create default AndroidManifest.xml
202
+ */
203
+ private createDefaultManifest(config: ProcessedAppConfig): string {
204
+ const packageName = config.android?.packageName ?? "com.example.app";
205
+
206
+ return `<?xml version="1.0" encoding="utf-8"?>
207
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
208
+ package="${packageName}">
209
+
210
+ <uses-permission android:name="android.permission.INTERNET" />
211
+
212
+ <application
213
+ android:name=".MainApplication"
214
+ android:label="@string/app_name"
215
+ android:icon="@mipmap/ic_launcher"
216
+ android:roundIcon="@mipmap/ic_launcher_round"
217
+ android:allowBackup="false"
218
+ android:theme="@style/AppTheme">
219
+ <activity
220
+ android:name=".MainActivity"
221
+ android:label="@string/app_name"
222
+ android:exported="true"
223
+ android:launchMode="singleTask"
224
+ android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode"
225
+ android:windowSoftInputMode="adjustResize">
226
+ <intent-filter>
227
+ <action android:name="android.intent.action.MAIN" />
228
+ <category android:name="android.intent.category.LAUNCHER" />
229
+ </intent-filter>
230
+ </activity>
231
+ </application>
232
+ </manifest>`;
233
+ }
234
+
235
+ /**
236
+ * Apply package name to manifest
237
+ */
238
+ private applyPackageName(manifest: AndroidManifestXml, config: ProcessedAppConfig): void {
239
+ if (config.android?.packageName) {
240
+ manifest.manifest["@_package"] = config.android.packageName;
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Apply permissions to manifest
246
+ */
247
+ private applyPermissions(manifest: AndroidManifestXml, config: ProcessedAppConfig): void {
248
+ if (!manifest.manifest["uses-permission"]) {
249
+ manifest.manifest["uses-permission"] = [];
250
+ }
251
+
252
+ // Normalize to array if XML parser returned a single object
253
+ if (!Array.isArray(manifest.manifest["uses-permission"])) {
254
+ manifest.manifest["uses-permission"] = [manifest.manifest["uses-permission"]];
255
+ }
256
+
257
+ const existingPermissions = new Set(manifest.manifest["uses-permission"].map((p) => p["@_android:name"]));
258
+
259
+ for (const permission of config.resolvedAndroidPermissions) {
260
+ if (!existingPermissions.has(permission)) {
261
+ manifest.manifest["uses-permission"].push({
262
+ "@_android:name": permission,
263
+ });
264
+ }
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Apply features to manifest
270
+ */
271
+ private applyFeatures(manifest: AndroidManifestXml, config: ProcessedAppConfig): void {
272
+ if (!manifest.manifest["uses-feature"]) {
273
+ manifest.manifest["uses-feature"] = [];
274
+ }
275
+
276
+ // Normalize to array if XML parser returned a single object
277
+ if (!Array.isArray(manifest.manifest["uses-feature"])) {
278
+ manifest.manifest["uses-feature"] = [manifest.manifest["uses-feature"]];
279
+ }
280
+
281
+ const existingFeatures = new Set(manifest.manifest["uses-feature"].map((f) => f["@_android:name"]));
282
+
283
+ for (const feature of config.resolvedAndroidFeatures) {
284
+ if (!existingFeatures.has(feature)) {
285
+ manifest.manifest["uses-feature"].push({
286
+ "@_android:name": feature,
287
+ "@_android:required": "true",
288
+ });
289
+ }
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Apply metadata to manifest
295
+ */
296
+ private applyMetadata(manifest: AndroidManifestXml, config: ProcessedAppConfig): void {
297
+ if (!manifest.manifest.application["meta-data"]) {
298
+ manifest.manifest.application["meta-data"] = [];
299
+ }
300
+
301
+ // Normalize to array if XML parser returned a single object
302
+ if (!Array.isArray(manifest.manifest.application["meta-data"])) {
303
+ manifest.manifest.application["meta-data"] = [manifest.manifest.application["meta-data"]];
304
+ }
305
+
306
+ const existingMetadata = new Map(manifest.manifest.application["meta-data"].map((m) => [m["@_android:name"], m]));
307
+
308
+ for (const [key, value] of Object.entries(config.resolvedAndroidMetadata)) {
309
+ if (existingMetadata.has(key)) {
310
+ // Update existing
311
+ const existing = existingMetadata.get(key)!;
312
+ existing["@_android:value"] = value;
313
+ } else {
314
+ // Add new
315
+ manifest.manifest.application["meta-data"].push({
316
+ "@_android:name": key,
317
+ "@_android:value": value,
318
+ });
319
+ }
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Apply intent filters to MainActivity
325
+ */
326
+ private applyIntentFilters(manifest: AndroidManifestXml, config: ProcessedAppConfig): void {
327
+ if (config.resolvedIntentFilters.length === 0 && !config.scheme) {
328
+ return;
329
+ }
330
+
331
+ // Find MainActivity
332
+ let activities = manifest.manifest.application.activity;
333
+ if (!activities) {
334
+ return;
335
+ }
336
+
337
+ // Normalize to array if XML parser returned a single object
338
+ if (!Array.isArray(activities)) {
339
+ activities = [activities];
340
+ manifest.manifest.application.activity = activities;
341
+ }
342
+
343
+ if (activities.length === 0) {
344
+ return;
345
+ }
346
+
347
+ const mainActivity = activities.find((a) => a["@_android:name"] === ".MainActivity") || activities[0];
348
+
349
+ if (!mainActivity["intent-filter"]) {
350
+ mainActivity["intent-filter"] = [];
351
+ }
352
+
353
+ // Normalize to array if XML parser returned a single object
354
+ if (!Array.isArray(mainActivity["intent-filter"])) {
355
+ mainActivity["intent-filter"] = [mainActivity["intent-filter"]];
356
+ }
357
+
358
+ // Add custom scheme if configured
359
+ if (config.scheme) {
360
+ const schemeFilter: AndroidIntentFilterXml = {
361
+ action: [{ "@_android:name": "android.intent.action.VIEW" }],
362
+ category: [
363
+ { "@_android:name": "android.intent.category.DEFAULT" },
364
+ { "@_android:name": "android.intent.category.BROWSABLE" },
365
+ ],
366
+ data: [{ "@_android:scheme": config.scheme }],
367
+ };
368
+ mainActivity["intent-filter"].push(schemeFilter);
369
+ }
370
+
371
+ // Add resolved intent filters
372
+ for (const filter of config.resolvedIntentFilters) {
373
+ const xmlFilter = this.convertIntentFilter(filter);
374
+ mainActivity["intent-filter"].push(xmlFilter);
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Convert internal intent filter to XML format
380
+ */
381
+ private convertIntentFilter(filter: AndroidIntentFilter): AndroidIntentFilterXml {
382
+ const xmlFilter: AndroidIntentFilterXml = {};
383
+
384
+ if (filter.autoVerify) {
385
+ xmlFilter["@_android:autoVerify"] = "true";
386
+ }
387
+
388
+ if (filter.actions.length > 0) {
389
+ xmlFilter.action = filter.actions.map((a) => ({ "@_android:name": a }));
390
+ }
391
+
392
+ if (filter.categories.length > 0) {
393
+ xmlFilter.category = filter.categories.map((c) => ({ "@_android:name": c }));
394
+ }
395
+
396
+ if (filter.data && filter.data.length > 0) {
397
+ xmlFilter.data = filter.data.map((d) => ({
398
+ ...(d.scheme && { "@_android:scheme": d.scheme }),
399
+ ...(d.host && { "@_android:host": d.host }),
400
+ ...(d.pathPrefix && { "@_android:pathPrefix": d.pathPrefix }),
401
+ }));
402
+ }
403
+
404
+ return xmlFilter;
405
+ }
406
+
407
+ /**
408
+ * Add a permission to an existing manifest
409
+ */
410
+ static addPermission(manifest: AndroidManifestXml, permission: string): void {
411
+ if (!manifest.manifest["uses-permission"]) {
412
+ manifest.manifest["uses-permission"] = [];
413
+ }
414
+
415
+ // Normalize to array if XML parser returned a single object
416
+ if (!Array.isArray(manifest.manifest["uses-permission"])) {
417
+ manifest.manifest["uses-permission"] = [manifest.manifest["uses-permission"]];
418
+ }
419
+
420
+ const exists = manifest.manifest["uses-permission"].some((p) => p["@_android:name"] === permission);
421
+
422
+ if (!exists) {
423
+ manifest.manifest["uses-permission"].push({
424
+ "@_android:name": permission,
425
+ });
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Add a feature to an existing manifest
431
+ */
432
+ static addFeature(manifest: AndroidManifestXml, feature: string, required = true): void {
433
+ if (!manifest.manifest["uses-feature"]) {
434
+ manifest.manifest["uses-feature"] = [];
435
+ }
436
+
437
+ // Normalize to array if XML parser returned a single object
438
+ if (!Array.isArray(manifest.manifest["uses-feature"])) {
439
+ manifest.manifest["uses-feature"] = [manifest.manifest["uses-feature"]];
440
+ }
441
+
442
+ const exists = manifest.manifest["uses-feature"].some((f) => f["@_android:name"] === feature);
443
+
444
+ if (!exists) {
445
+ manifest.manifest["uses-feature"].push({
446
+ "@_android:name": feature,
447
+ "@_android:required": String(required),
448
+ });
449
+ }
450
+ }
451
+
452
+ /**
453
+ * Add metadata to the application
454
+ */
455
+ static addMetadata(manifest: AndroidManifestXml, name: string, value: string, isResource = false): void {
456
+ if (!manifest.manifest.application["meta-data"]) {
457
+ manifest.manifest.application["meta-data"] = [];
458
+ }
459
+
460
+ // Normalize to array if XML parser returned a single object
461
+ if (!Array.isArray(manifest.manifest.application["meta-data"])) {
462
+ manifest.manifest.application["meta-data"] = [manifest.manifest.application["meta-data"]];
463
+ }
464
+
465
+ const existing = manifest.manifest.application["meta-data"].find((m) => m["@_android:name"] === name);
466
+
467
+ if (existing) {
468
+ if (isResource) {
469
+ existing["@_android:resource"] = value;
470
+ delete existing["@_android:value"];
471
+ } else {
472
+ existing["@_android:value"] = value;
473
+ delete existing["@_android:resource"];
474
+ }
475
+ } else {
476
+ const metadata: AndroidMetaData = {
477
+ "@_android:name": name,
478
+ };
479
+
480
+ if (isResource) {
481
+ metadata["@_android:resource"] = value;
482
+ } else {
483
+ metadata["@_android:value"] = value;
484
+ }
485
+
486
+ manifest.manifest.application["meta-data"].push(metadata);
487
+ }
488
+ }
489
+
490
+ /**
491
+ * Parse an AndroidManifest.xml file
492
+ */
493
+ static parse(content: string): AndroidManifestXml {
494
+ const parser = new XMLParser(parserOptions);
495
+ return parser.parse(content) as AndroidManifestXml;
496
+ }
497
+
498
+ /**
499
+ * Build XML from AndroidManifest object
500
+ */
501
+ static build(manifest: AndroidManifestXml): string {
502
+ const builder = new XMLBuilder(builderOptions);
503
+ const xmlHeader = '<?xml version="1.0" encoding="utf-8"?>\n';
504
+ return xmlHeader + builder.build(manifest);
505
+ }
506
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * File transformers for Teardown Launchpad
3
+ *
4
+ * Provides transformers for native iOS and Android files
5
+ * including plist, entitlements, Xcode project, manifest, and Gradle.
6
+ */
7
+
8
+ export type {
9
+ AndroidActivityXml,
10
+ AndroidApplication,
11
+ AndroidFeature,
12
+ AndroidIntentFilterXml,
13
+ AndroidManifestXml,
14
+ AndroidMetaData,
15
+ AndroidPermission,
16
+ AndroidProviderXml,
17
+ AndroidQueries,
18
+ AndroidReceiverXml,
19
+ AndroidServiceXml,
20
+ } from "./android";
21
+ // Android transformers
22
+ export {
23
+ AndroidManifestTransformer,
24
+ GradleTransformer,
25
+ StringsTransformer,
26
+ } from "./android";
27
+ export type {
28
+ BuildSettings,
29
+ CFBundleURLType,
30
+ EntitlementsContent,
31
+ InfoPlistContent,
32
+ } from "./ios";
33
+ // iOS transformers
34
+ export {
35
+ EntitlementsTransformer,
36
+ PbxprojTransformer,
37
+ PlistTransformer,
38
+ XcodeProjectManipulator,
39
+ } from "./ios";