@teardown/cli 1.2.39 → 2.0.42

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 (260) 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.d.ts +0 -22
  145. package/dist/commands/dev/dev.js +0 -56
  146. package/dist/commands/dev/dev.js.map +0 -1
  147. package/dist/commands/init/init-teardown.d.ts +0 -9
  148. package/dist/commands/init/init-teardown.js +0 -27
  149. package/dist/commands/init/init-teardown.js.map +0 -1
  150. package/dist/index.d.ts +0 -1
  151. package/dist/index.js +0 -21
  152. package/dist/index.js.map +0 -1
  153. package/dist/modules/dev/dev-menu/keyboard-handler.d.ts +0 -21
  154. package/dist/modules/dev/dev-menu/keyboard-handler.js +0 -139
  155. package/dist/modules/dev/dev-menu/keyboard-handler.js.map +0 -1
  156. package/dist/modules/dev/dev-menu/open-debugger-keyboard-handler.d.ts +0 -18
  157. package/dist/modules/dev/dev-menu/open-debugger-keyboard-handler.js +0 -106
  158. package/dist/modules/dev/dev-menu/open-debugger-keyboard-handler.js.map +0 -1
  159. package/dist/modules/dev/dev-server/cdp/cdp.adapter.d.ts +0 -6
  160. package/dist/modules/dev/dev-server/cdp/cdp.adapter.js +0 -13
  161. package/dist/modules/dev/dev-server/cdp/cdp.adapter.js.map +0 -1
  162. package/dist/modules/dev/dev-server/cdp/index.d.ts +0 -2
  163. package/dist/modules/dev/dev-server/cdp/index.js +0 -19
  164. package/dist/modules/dev/dev-server/cdp/index.js.map +0 -1
  165. package/dist/modules/dev/dev-server/cdp/types.d.ts +0 -107
  166. package/dist/modules/dev/dev-server/cdp/types.js +0 -3
  167. package/dist/modules/dev/dev-server/cdp/types.js.map +0 -1
  168. package/dist/modules/dev/dev-server/dev-server-checker.d.ts +0 -22
  169. package/dist/modules/dev/dev-server/dev-server-checker.js +0 -73
  170. package/dist/modules/dev/dev-server/dev-server-checker.js.map +0 -1
  171. package/dist/modules/dev/dev-server/dev-server.d.ts +0 -74
  172. package/dist/modules/dev/dev-server/dev-server.js +0 -272
  173. package/dist/modules/dev/dev-server/dev-server.js.map +0 -1
  174. package/dist/modules/dev/dev-server/inspector/device.d.ts +0 -46
  175. package/dist/modules/dev/dev-server/inspector/device.event-reporter.d.ts +0 -37
  176. package/dist/modules/dev/dev-server/inspector/device.event-reporter.js +0 -166
  177. package/dist/modules/dev/dev-server/inspector/device.event-reporter.js.map +0 -1
  178. package/dist/modules/dev/dev-server/inspector/device.js +0 -578
  179. package/dist/modules/dev/dev-server/inspector/device.js.map +0 -1
  180. package/dist/modules/dev/dev-server/inspector/inspector.d.ts +0 -27
  181. package/dist/modules/dev/dev-server/inspector/inspector.js +0 -225
  182. package/dist/modules/dev/dev-server/inspector/inspector.js.map +0 -1
  183. package/dist/modules/dev/dev-server/inspector/types.d.ts +0 -156
  184. package/dist/modules/dev/dev-server/inspector/types.js +0 -3
  185. package/dist/modules/dev/dev-server/inspector/types.js.map +0 -1
  186. package/dist/modules/dev/dev-server/inspector/wss/servers/debugger-connection.server.d.ts +0 -14
  187. package/dist/modules/dev/dev-server/inspector/wss/servers/debugger-connection.server.js +0 -63
  188. package/dist/modules/dev/dev-server/inspector/wss/servers/debugger-connection.server.js.map +0 -1
  189. package/dist/modules/dev/dev-server/inspector/wss/servers/device-connection.server.d.ts +0 -19
  190. package/dist/modules/dev/dev-server/inspector/wss/servers/device-connection.server.js +0 -66
  191. package/dist/modules/dev/dev-server/inspector/wss/servers/device-connection.server.js.map +0 -1
  192. package/dist/modules/dev/dev-server/plugins/devtools.plugin.d.ts +0 -1
  193. package/dist/modules/dev/dev-server/plugins/devtools.plugin.js +0 -51
  194. package/dist/modules/dev/dev-server/plugins/devtools.plugin.js.map +0 -1
  195. package/dist/modules/dev/dev-server/plugins/favicon.plugin.d.ts +0 -1
  196. package/dist/modules/dev/dev-server/plugins/favicon.plugin.js +0 -19
  197. package/dist/modules/dev/dev-server/plugins/favicon.plugin.js.map +0 -1
  198. package/dist/modules/dev/dev-server/plugins/multipart.plugin.d.ts +0 -1
  199. package/dist/modules/dev/dev-server/plugins/multipart.plugin.js +0 -63
  200. package/dist/modules/dev/dev-server/plugins/multipart.plugin.js.map +0 -1
  201. package/dist/modules/dev/dev-server/plugins/systrace.plugin.d.ts +0 -1
  202. package/dist/modules/dev/dev-server/plugins/systrace.plugin.js +0 -29
  203. package/dist/modules/dev/dev-server/plugins/systrace.plugin.js.map +0 -1
  204. package/dist/modules/dev/dev-server/plugins/types.d.ts +0 -11
  205. package/dist/modules/dev/dev-server/plugins/types.js +0 -3
  206. package/dist/modules/dev/dev-server/plugins/types.js.map +0 -1
  207. package/dist/modules/dev/dev-server/plugins/wss/index.d.ts +0 -3
  208. package/dist/modules/dev/dev-server/plugins/wss/index.js +0 -20
  209. package/dist/modules/dev/dev-server/plugins/wss/index.js.map +0 -1
  210. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-api.server.d.ts +0 -37
  211. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-api.server.js +0 -67
  212. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-api.server.js.map +0 -1
  213. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-debugger.server.d.ts +0 -63
  214. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-debugger.server.js +0 -129
  215. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-debugger.server.js.map +0 -1
  216. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-dev-client.server.d.ts +0 -32
  217. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-dev-client.server.js +0 -76
  218. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-dev-client.server.js.map +0 -1
  219. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-events.server.d.ts +0 -75
  220. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-events.server.js +0 -199
  221. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-events.server.js.map +0 -1
  222. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-hmr.server.d.ts +0 -44
  223. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-hmr.server.js +0 -121
  224. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-hmr.server.js.map +0 -1
  225. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-message.server.d.ts +0 -135
  226. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-message.server.js +0 -364
  227. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-message.server.js.map +0 -1
  228. package/dist/modules/dev/dev-server/plugins/wss/types.d.ts +0 -6
  229. package/dist/modules/dev/dev-server/plugins/wss/types.js +0 -3
  230. package/dist/modules/dev/dev-server/plugins/wss/types.js.map +0 -1
  231. package/dist/modules/dev/dev-server/plugins/wss/web-socket-router.d.ts +0 -32
  232. package/dist/modules/dev/dev-server/plugins/wss/web-socket-router.js +0 -58
  233. package/dist/modules/dev/dev-server/plugins/wss/web-socket-router.js.map +0 -1
  234. package/dist/modules/dev/dev-server/plugins/wss/web-socket-server-adapter.d.ts +0 -13
  235. package/dist/modules/dev/dev-server/plugins/wss/web-socket-server-adapter.js +0 -27
  236. package/dist/modules/dev/dev-server/plugins/wss/web-socket-server-adapter.js.map +0 -1
  237. package/dist/modules/dev/dev-server/plugins/wss/web-socket-server.d.ts +0 -39
  238. package/dist/modules/dev/dev-server/plugins/wss/web-socket-server.js +0 -47
  239. package/dist/modules/dev/dev-server/plugins/wss/web-socket-server.js.map +0 -1
  240. package/dist/modules/dev/dev-server/plugins/wss/wss.plugin.d.ts +0 -24
  241. package/dist/modules/dev/dev-server/plugins/wss/wss.plugin.js +0 -56
  242. package/dist/modules/dev/dev-server/plugins/wss/wss.plugin.js.map +0 -1
  243. package/dist/modules/dev/dev-server/sybmolicate/sybmolicate.plugin.d.ts +0 -7
  244. package/dist/modules/dev/dev-server/sybmolicate/sybmolicate.plugin.js +0 -41
  245. package/dist/modules/dev/dev-server/sybmolicate/sybmolicate.plugin.js.map +0 -1
  246. package/dist/modules/dev/dev-server/sybmolicate/types.d.ts +0 -64
  247. package/dist/modules/dev/dev-server/sybmolicate/types.js +0 -3
  248. package/dist/modules/dev/dev-server/sybmolicate/types.js.map +0 -1
  249. package/dist/modules/dev/terminal/base.terminal.reporter.d.ts +0 -25
  250. package/dist/modules/dev/terminal/base.terminal.reporter.js +0 -79
  251. package/dist/modules/dev/terminal/base.terminal.reporter.js.map +0 -1
  252. package/dist/modules/dev/terminal/terminal.reporter.d.ts +0 -13
  253. package/dist/modules/dev/terminal/terminal.reporter.js +0 -83
  254. package/dist/modules/dev/terminal/terminal.reporter.js.map +0 -1
  255. package/dist/modules/dev/types.d.ts +0 -20
  256. package/dist/modules/dev/types.js +0 -3
  257. package/dist/modules/dev/types.js.map +0 -1
  258. package/dist/modules/dev/utils/log.d.ts +0 -23
  259. package/dist/modules/dev/utils/log.js +0 -74
  260. package/dist/modules/dev/utils/log.js.map +0 -1
@@ -0,0 +1,692 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { loadConfig, validateConfig } from "../config";
5
+ import type { FileChange, PrebuildError, PrebuildOptions, PrebuildWarning, ProcessedAppConfig } from "../config/types";
6
+ import type { BasePlugin, PluginConstructor } from "../plugins/base";
7
+ import { createPluginContext, type PluginContext } from "../plugins/context";
8
+ import { DependencyResolver } from "../plugins/resolver";
9
+ import { TemplateGenerator } from "../templates";
10
+ import { GradleTransformer } from "../transformers/android/gradle";
11
+ import { AndroidManifestTransformer } from "../transformers/android/manifest";
12
+ import { EntitlementsTransformer } from "../transformers/ios/entitlements";
13
+ import { PbxprojTransformer } from "../transformers/ios/pbxproj";
14
+ import { PlistTransformer } from "../transformers/ios/plist";
15
+ import { createVirtualFileSystem } from "../utils/fs";
16
+ import { createLogger, type Logger } from "../utils/logger";
17
+ import { IncrementalBuildCache } from "./cache";
18
+
19
+ /**
20
+ * Pipeline stage interface
21
+ */
22
+ export interface PipelineStage {
23
+ name: string;
24
+ execute(context: PipelineContext): Promise<void>;
25
+ }
26
+
27
+ /**
28
+ * Pipeline execution context
29
+ */
30
+ export interface PipelineContext {
31
+ config: ProcessedAppConfig;
32
+ pluginContext: PluginContext;
33
+ options: PrebuildOptions;
34
+ logger: Logger;
35
+ resolver: DependencyResolver;
36
+ }
37
+
38
+ /**
39
+ * Pipeline result
40
+ */
41
+ export interface PipelineResult {
42
+ success: boolean;
43
+ config?: ProcessedAppConfig;
44
+ changes?: FileChange[];
45
+ warnings: PrebuildWarning[];
46
+ errors: PrebuildError[];
47
+ duration: number;
48
+ }
49
+
50
+ /**
51
+ * Main prebuild pipeline
52
+ */
53
+ export class Pipeline {
54
+ private stages: PipelineStage[] = [];
55
+ private resolver: DependencyResolver;
56
+ private logger: Logger;
57
+ private cache: IncrementalBuildCache;
58
+
59
+ constructor(options: { logger?: Logger } = {}) {
60
+ this.logger = options.logger ?? createLogger({ prefix: "pipeline" });
61
+ this.resolver = new DependencyResolver(this.logger);
62
+ this.cache = new IncrementalBuildCache(this.logger);
63
+
64
+ // Initialize default stages
65
+ this.stages = [
66
+ new ConfigValidationStage(),
67
+ new PluginResolutionStage(),
68
+ new TemplateGenerationStage(),
69
+ new PluginPreProcessStage(),
70
+ new PluginTransformStage(),
71
+ new PluginPostProcessStage(),
72
+ new iOSGenerationStage(),
73
+ new IOSPostInstallStage(),
74
+ new AndroidGenerationStage(),
75
+ ];
76
+ }
77
+
78
+ /**
79
+ * Register a plugin with configuration
80
+ */
81
+ registerPlugin<TConfig>(plugin: BasePlugin<TConfig>, config?: TConfig): this {
82
+ this.resolver.registerPlugin(plugin, config);
83
+ return this;
84
+ }
85
+
86
+ /**
87
+ * Register a plugin from constructor
88
+ */
89
+ registerPluginFromConstructor<TConfig>(Constructor: PluginConstructor<TConfig>, config?: TConfig): this {
90
+ this.resolver.registerPluginFromConstructor(Constructor, config);
91
+ return this;
92
+ }
93
+
94
+ /**
95
+ * Add a custom stage to the pipeline
96
+ */
97
+ addStage(stage: PipelineStage): this {
98
+ this.stages.push(stage);
99
+ return this;
100
+ }
101
+
102
+ /**
103
+ * Execute the pipeline
104
+ */
105
+ async execute(options: PrebuildOptions): Promise<PipelineResult> {
106
+ const startTime = Date.now();
107
+ const warnings: PrebuildWarning[] = [];
108
+ const errors: PrebuildError[] = [];
109
+
110
+ const projectRoot = options.projectRoot ?? process.cwd();
111
+
112
+ this.logger.info("Starting prebuild pipeline...");
113
+
114
+ try {
115
+ // Load configuration
116
+ const configResult = await loadConfig(projectRoot);
117
+
118
+ if (!configResult.success || !configResult.config) {
119
+ return {
120
+ success: false,
121
+ warnings,
122
+ errors: configResult.errors?.map((e) => ({
123
+ code: "CONFIG_ERROR",
124
+ message: e.message,
125
+ })) ?? [{ code: "CONFIG_ERROR", message: "Failed to load configuration" }],
126
+ duration: Date.now() - startTime,
127
+ };
128
+ }
129
+
130
+ // Check cache for incremental build
131
+ if (!options.clean && (await this.cache.shouldSkip(configResult.config, projectRoot))) {
132
+ this.logger.info("No changes detected, skipping prebuild");
133
+ return {
134
+ success: true,
135
+ warnings,
136
+ errors,
137
+ duration: Date.now() - startTime,
138
+ };
139
+ }
140
+
141
+ // Create virtual file system
142
+ const fs = createVirtualFileSystem({
143
+ dryRun: options.dryRun ?? false,
144
+ projectRoot,
145
+ });
146
+
147
+ // Create plugin context
148
+ const pluginContext = createPluginContext({
149
+ projectRoot,
150
+ platform: options.platform === "all" ? "both" : options.platform,
151
+ config: configResult.config,
152
+ fs,
153
+ log: this.logger,
154
+ isDryRun: options.dryRun ?? false,
155
+ isVerbose: options.verbose ?? false,
156
+ });
157
+
158
+ // Create pipeline context
159
+ const pipelineContext: PipelineContext = {
160
+ config: pluginContext.config,
161
+ pluginContext,
162
+ options,
163
+ logger: this.logger,
164
+ resolver: this.resolver,
165
+ };
166
+
167
+ // Execute stages
168
+ for (const stage of this.stages) {
169
+ // Skip platform-specific stages if not targeting that platform
170
+ if (stage.name === "ios-generation" && options.platform === "android") {
171
+ continue;
172
+ }
173
+ if (stage.name === "ios-postinstall" && options.platform === "android") {
174
+ continue;
175
+ }
176
+ if (stage.name === "android-generation" && options.platform === "ios") {
177
+ continue;
178
+ }
179
+
180
+ this.logger.debug(`Executing stage: ${stage.name}`);
181
+
182
+ try {
183
+ await stage.execute(pipelineContext);
184
+ } catch (error) {
185
+ errors.push({
186
+ code: "STAGE_ERROR",
187
+ message: `Stage "${stage.name}" failed: ${error instanceof Error ? error.message : String(error)}`,
188
+ stack: error instanceof Error ? error.stack : undefined,
189
+ });
190
+
191
+ return {
192
+ success: false,
193
+ config: pipelineContext.config,
194
+ changes: fs.getChanges(),
195
+ warnings: [...warnings, ...pluginContext.getWarnings()],
196
+ errors,
197
+ duration: Date.now() - startTime,
198
+ };
199
+ }
200
+ }
201
+
202
+ // Get final changes
203
+ const changes = fs.getChanges();
204
+
205
+ // Update cache
206
+ if (!options.dryRun) {
207
+ await this.cache.update(
208
+ configResult.config,
209
+ projectRoot,
210
+ changes.map((c) => c.path)
211
+ );
212
+ }
213
+
214
+ this.logger.success(`Prebuild completed in ${Date.now() - startTime}ms`);
215
+
216
+ return {
217
+ success: true,
218
+ config: pipelineContext.config,
219
+ changes,
220
+ warnings: [...warnings, ...pluginContext.getWarnings()],
221
+ errors,
222
+ duration: Date.now() - startTime,
223
+ };
224
+ } catch (error) {
225
+ errors.push({
226
+ code: "PIPELINE_ERROR",
227
+ message: error instanceof Error ? error.message : String(error),
228
+ stack: error instanceof Error ? error.stack : undefined,
229
+ });
230
+
231
+ return {
232
+ success: false,
233
+ warnings,
234
+ errors,
235
+ duration: Date.now() - startTime,
236
+ };
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Get the dependency resolver
242
+ */
243
+ getResolver(): DependencyResolver {
244
+ return this.resolver;
245
+ }
246
+
247
+ /**
248
+ * Clear the build cache
249
+ */
250
+ async clearCache(projectRoot: string): Promise<void> {
251
+ await this.cache.clear(projectRoot);
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Stage 1: Config Validation
257
+ */
258
+ class ConfigValidationStage implements PipelineStage {
259
+ name = "config-validation";
260
+
261
+ async execute(context: PipelineContext): Promise<void> {
262
+ const { logger, options } = context;
263
+
264
+ if (options.skipValidation) {
265
+ logger.debug("Skipping config validation");
266
+ return;
267
+ }
268
+
269
+ logger.debug("Validating configuration...");
270
+
271
+ const validation = validateConfig(context.config);
272
+
273
+ if (!validation.success) {
274
+ const errorMessages = validation.errors?.map((e) => e.message).join(", ");
275
+ throw new Error(`Configuration validation failed: ${errorMessages}`);
276
+ }
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Stage 2: Plugin Resolution
282
+ */
283
+ class PluginResolutionStage implements PipelineStage {
284
+ name = "plugin-resolution";
285
+
286
+ async execute(context: PipelineContext): Promise<void> {
287
+ const { logger, resolver } = context;
288
+
289
+ logger.debug("Resolving plugin dependencies...");
290
+
291
+ // Validate all plugin configurations
292
+ const validationErrors = resolver.validateAllConfigs();
293
+ if (validationErrors.length > 0) {
294
+ const messages = validationErrors.map((e) => `${e.plugin}: ${e.error}`);
295
+ throw new Error(`Plugin configuration errors: ${messages.join("; ")}`);
296
+ }
297
+
298
+ // Resolve execution order (this will throw if there are cycles or missing deps)
299
+ const orderedPlugins = resolver.resolveOrder();
300
+ logger.debug(`Plugin execution order: ${orderedPlugins.map((p) => p.name).join(" -> ")}`);
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Stage 3: Template Generation
306
+ *
307
+ * Generates native iOS and Android project files from templates
308
+ * if they don't already exist.
309
+ */
310
+ class TemplateGenerationStage implements PipelineStage {
311
+ name = "template-generation";
312
+
313
+ async execute(context: PipelineContext): Promise<void> {
314
+ const { logger, config, options } = context;
315
+
316
+ logger.debug("Checking if native projects need to be generated...");
317
+
318
+ const projectRoot = options.projectRoot ?? process.cwd();
319
+
320
+ // Create template variables from config
321
+ const variables = {
322
+ // Common
323
+ slug: config.slug,
324
+
325
+ // iOS
326
+ appName: config.name,
327
+ bundleIdentifier:
328
+ config.ios?.bundleIdentifier ?? `com.example.${config.name.toLowerCase().replace(/[^a-z0-9]/g, "")}`,
329
+ version: config.version ?? "1.0.0",
330
+ buildNumber: config.ios?.buildNumber ?? 1,
331
+
332
+ // Android
333
+ packageName: config.android?.packageName ?? `com.example.${config.name.toLowerCase().replace(/[^a-z0-9]/g, "")}`,
334
+ versionCode: config.android?.versionCode ?? 1,
335
+ versionName: config.version ?? "1.0.0",
336
+ };
337
+
338
+ const generator = new TemplateGenerator({
339
+ projectRoot,
340
+ variables,
341
+ logger,
342
+ dryRun: options.dryRun ?? false,
343
+ force: false, // Don't overwrite existing files
344
+ });
345
+
346
+ // Check if we should generate iOS
347
+ const shouldGenerateiOS = options.platform !== "android";
348
+ const shouldGenerateAndroid = options.platform !== "ios";
349
+
350
+ if (shouldGenerateiOS) {
351
+ const iosExists = await generator.iosProjectExists();
352
+ if (!iosExists) {
353
+ logger.info("iOS project not found, generating from template...");
354
+ const result = await generator.generateiOS();
355
+ if (result.success) {
356
+ logger.success(`Generated iOS project (${result.filesCreated.length} files)`);
357
+ } else {
358
+ logger.warn(`iOS generation had errors: ${result.errors.join(", ")}`);
359
+ }
360
+ } else {
361
+ logger.debug("iOS project already exists, skipping template generation");
362
+ }
363
+ }
364
+
365
+ if (shouldGenerateAndroid) {
366
+ const androidExists = await generator.androidProjectExists();
367
+ if (!androidExists) {
368
+ logger.info("Android project not found, generating from template...");
369
+ const result = await generator.generateAndroid();
370
+ if (result.success) {
371
+ logger.success(`Generated Android project (${result.filesCreated.length} files)`);
372
+ } else {
373
+ logger.warn(`Android generation had errors: ${result.errors.join(", ")}`);
374
+ }
375
+ } else {
376
+ logger.debug("Android project already exists, skipping template generation");
377
+ }
378
+ }
379
+
380
+ // Generate package.json if it doesn't exist
381
+ const packageExists = await generator.packageJsonExists();
382
+ if (!packageExists) {
383
+ logger.info("package.json not found, generating from template...");
384
+ const result = await generator.generatePackageJson();
385
+ if (result.success && result.filesCreated.length > 0) {
386
+ logger.success("Generated package.json");
387
+ } else if (result.errors.length > 0) {
388
+ logger.warn(`package.json generation had errors: ${result.errors.join(", ")}`);
389
+ }
390
+ }
391
+
392
+ // Generate src folder if it doesn't exist
393
+ const srcExists = await generator.srcFolderExists();
394
+ if (!srcExists) {
395
+ logger.info("src/ folder not found, generating from template...");
396
+ const result = await generator.generateSrcFolder();
397
+ if (result.success) {
398
+ logger.success(`Generated src/ folder (${result.filesCreated.length} files)`);
399
+ } else {
400
+ logger.warn(`src/ generation had errors: ${result.errors.join(", ")}`);
401
+ }
402
+ }
403
+
404
+ // Generate root config files if they don't exist
405
+ const configResult = await generator.generateRootConfigFiles();
406
+ if (configResult.filesCreated.length > 0) {
407
+ logger.success(`Generated config files: ${configResult.filesCreated.map((f) => f.split("/").pop()).join(", ")}`);
408
+ }
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Stage 4: Plugin Pre-Process
414
+ */
415
+ class PluginPreProcessStage implements PipelineStage {
416
+ name = "plugin-preprocess";
417
+
418
+ async execute(context: PipelineContext): Promise<void> {
419
+ const { logger, resolver, pluginContext, options } = context;
420
+
421
+ logger.debug("Running plugin pre-process hooks...");
422
+
423
+ const plugins = resolver.resolveOrder();
424
+
425
+ for (const plugin of plugins) {
426
+ // Skip plugins that don't support the target platform
427
+ if (options.platform !== "all") {
428
+ if (!plugin.supportsPlatform(options.platform)) {
429
+ logger.debug(`Skipping ${plugin.name} (doesn't support ${options.platform})`);
430
+ continue;
431
+ }
432
+ }
433
+
434
+ logger.debug(`Pre-processing: ${plugin.name}`);
435
+ await plugin.preProcess(pluginContext);
436
+
437
+ // Register capabilities
438
+ for (const capability of plugin.provides) {
439
+ pluginContext.registerCapability(capability);
440
+ }
441
+ }
442
+ }
443
+ }
444
+
445
+ /**
446
+ * Stage 4: Plugin Transform
447
+ */
448
+ class PluginTransformStage implements PipelineStage {
449
+ name = "plugin-transform";
450
+
451
+ async execute(context: PipelineContext): Promise<void> {
452
+ const { logger, resolver, pluginContext, options } = context;
453
+
454
+ logger.debug("Running plugin transformations...");
455
+
456
+ const plugins = resolver.resolveOrder();
457
+
458
+ for (const plugin of plugins) {
459
+ // Skip plugins that don't support the target platform
460
+ if (options.platform !== "all") {
461
+ if (!plugin.supportsPlatform(options.platform)) {
462
+ continue;
463
+ }
464
+ }
465
+
466
+ const pluginConfig = resolver.getPluginConfig(plugin.name);
467
+ logger.debug(`Transforming: ${plugin.name}`);
468
+
469
+ context.config = plugin.transform(context.config, pluginConfig, pluginContext);
470
+ pluginContext.config = context.config;
471
+ }
472
+ }
473
+ }
474
+
475
+ /**
476
+ * Stage 5: Plugin Post-Process
477
+ */
478
+ class PluginPostProcessStage implements PipelineStage {
479
+ name = "plugin-postprocess";
480
+
481
+ async execute(context: PipelineContext): Promise<void> {
482
+ const { logger, resolver, pluginContext, options } = context;
483
+
484
+ logger.debug("Running plugin post-process hooks...");
485
+
486
+ const plugins = resolver.resolveOrder();
487
+
488
+ for (const plugin of plugins) {
489
+ // Skip plugins that don't support the target platform
490
+ if (options.platform !== "all") {
491
+ if (!plugin.supportsPlatform(options.platform)) {
492
+ continue;
493
+ }
494
+ }
495
+
496
+ logger.debug(`Post-processing: ${plugin.name}`);
497
+ await plugin.postProcess(pluginContext);
498
+ }
499
+ }
500
+ }
501
+
502
+ /**
503
+ * Stage 6: iOS Generation
504
+ */
505
+ class iOSGenerationStage implements PipelineStage {
506
+ name = "ios-generation";
507
+
508
+ async execute(context: PipelineContext): Promise<void> {
509
+ const { logger, pluginContext, config } = context;
510
+
511
+ logger.debug("Generating iOS native files...");
512
+
513
+ // Transform Info.plist
514
+ const plistTransformer = new PlistTransformer();
515
+ await plistTransformer.transform(pluginContext, config);
516
+
517
+ // Transform entitlements
518
+ const entitlementsTransformer = new EntitlementsTransformer();
519
+ await entitlementsTransformer.transform(pluginContext, config);
520
+
521
+ // Transform pbxproj (Xcode project)
522
+ const pbxprojTransformer = new PbxprojTransformer();
523
+ await pbxprojTransformer.transform(pluginContext, config);
524
+
525
+ logger.debug("iOS generation complete");
526
+ }
527
+ }
528
+
529
+ /**
530
+ * Stage 7: iOS Post-Install
531
+ *
532
+ * Runs `pod install` after iOS project generation to install CocoaPods dependencies
533
+ * and create the Xcode workspace with proper schemes.
534
+ */
535
+ class IOSPostInstallStage implements PipelineStage {
536
+ name = "ios-postinstall";
537
+
538
+ async execute(context: PipelineContext): Promise<void> {
539
+ const { logger, options } = context;
540
+
541
+ // Skip in dry-run mode
542
+ if (options.dryRun) {
543
+ logger.debug("Skipping pod install (dry-run mode)");
544
+ return;
545
+ }
546
+
547
+ const projectRoot = options.projectRoot ?? process.cwd();
548
+ const iosDir = join(projectRoot, "ios");
549
+ const podfilePath = join(iosDir, "Podfile");
550
+ const podfileLockPath = join(iosDir, "Podfile.lock");
551
+
552
+ // Check if Podfile exists
553
+ if (!existsSync(podfilePath)) {
554
+ logger.debug("No Podfile found, skipping pod install");
555
+ return;
556
+ }
557
+
558
+ // Check if pod command is available
559
+ const podAvailable = await this.isPodAvailable();
560
+ if (!podAvailable) {
561
+ logger.warn("CocoaPods not installed. Run 'pod install' manually in the ios/ directory.");
562
+ return;
563
+ }
564
+
565
+ // Use --repo-update for fresh installs (no Podfile.lock)
566
+ const isFreshInstall = !existsSync(podfileLockPath);
567
+ if (isFreshInstall) {
568
+ logger.info("Installing CocoaPods dependencies (first time, updating repos)...");
569
+ } else {
570
+ logger.info("Installing CocoaPods dependencies...");
571
+ }
572
+
573
+ try {
574
+ await this.runPodInstall(iosDir, logger, isFreshInstall);
575
+ logger.success("CocoaPods dependencies installed");
576
+ } catch (error) {
577
+ // Surface the error clearly - this is important for debugging
578
+ const errorMessage = error instanceof Error ? error.message : String(error);
579
+ logger.error(`pod install failed: ${errorMessage}`);
580
+ logger.error("To fix this, try running manually:");
581
+ logger.error(" cd ios && pod install --repo-update");
582
+ // Don't fail the pipeline, but make it very visible
583
+ }
584
+ }
585
+
586
+ /**
587
+ * Check if CocoaPods is installed
588
+ */
589
+ private async isPodAvailable(): Promise<boolean> {
590
+ return new Promise((resolve) => {
591
+ const child = spawn("which", ["pod"], {
592
+ stdio: ["ignore", "pipe", "pipe"],
593
+ });
594
+
595
+ child.on("close", (code) => {
596
+ resolve(code === 0);
597
+ });
598
+
599
+ child.on("error", () => {
600
+ resolve(false);
601
+ });
602
+ });
603
+ }
604
+
605
+ /**
606
+ * Run pod install in the iOS directory
607
+ */
608
+ private async runPodInstall(iosDir: string, logger: Logger, repoUpdate = false): Promise<void> {
609
+ return new Promise((resolve, reject) => {
610
+ // Use --repo-update for fresh installs to ensure specs are up to date
611
+ const args = repoUpdate ? ["install", "--repo-update"] : ["install"];
612
+
613
+ const child = spawn("pod", args, {
614
+ cwd: iosDir,
615
+ stdio: ["ignore", "pipe", "pipe"],
616
+ env: {
617
+ ...process.env,
618
+ // Required for CocoaPods UTF-8 support
619
+ LANG: "en_US.UTF-8",
620
+ LC_ALL: "en_US.UTF-8",
621
+ },
622
+ });
623
+
624
+ let stdout = "";
625
+ let stderr = "";
626
+
627
+ child.stdout?.on("data", (data) => {
628
+ stdout += data.toString();
629
+ // Log progress for verbose output
630
+ const lines = data.toString().split("\n");
631
+ for (const line of lines) {
632
+ if (line.trim()) {
633
+ logger.debug(`[pod] ${line.trim()}`);
634
+ }
635
+ }
636
+ });
637
+
638
+ child.stderr?.on("data", (data) => {
639
+ stderr += data.toString();
640
+ // Also log stderr for visibility
641
+ const lines = data.toString().split("\n");
642
+ for (const line of lines) {
643
+ if (line.trim()) {
644
+ logger.debug(`[pod:err] ${line.trim()}`);
645
+ }
646
+ }
647
+ });
648
+
649
+ child.on("close", (code) => {
650
+ if (code === 0) {
651
+ resolve();
652
+ } else {
653
+ reject(new Error(`pod install exited with code ${code}: ${stderr || stdout}`));
654
+ }
655
+ });
656
+
657
+ child.on("error", (err) => {
658
+ reject(err);
659
+ });
660
+ });
661
+ }
662
+ }
663
+
664
+ /**
665
+ * Stage 8: Android Generation
666
+ */
667
+ class AndroidGenerationStage implements PipelineStage {
668
+ name = "android-generation";
669
+
670
+ async execute(context: PipelineContext): Promise<void> {
671
+ const { logger, pluginContext, config } = context;
672
+
673
+ logger.debug("Generating Android native files...");
674
+
675
+ // Transform AndroidManifest.xml
676
+ const manifestTransformer = new AndroidManifestTransformer();
677
+ await manifestTransformer.transform(pluginContext, config);
678
+
679
+ // Transform build.gradle
680
+ const gradleTransformer = new GradleTransformer();
681
+ await gradleTransformer.transform(pluginContext, config);
682
+
683
+ logger.debug("Android generation complete");
684
+ }
685
+ }
686
+
687
+ /**
688
+ * Create a new pipeline instance
689
+ */
690
+ export function createPipeline(options?: { logger?: Logger }): Pipeline {
691
+ return new Pipeline(options);
692
+ }