@jetstart/cli 1.7.0 → 2.0.2

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 (167) hide show
  1. package/README.md +133 -41
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +11 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/commands/android-emulator.d.ts.map +1 -0
  6. package/dist/commands/android-emulator.js.map +1 -0
  7. package/dist/commands/build.d.ts +13 -1
  8. package/dist/commands/build.d.ts.map +1 -0
  9. package/dist/commands/build.js +279 -29
  10. package/dist/commands/build.js.map +1 -0
  11. package/dist/commands/clean.d.ts +23 -0
  12. package/dist/commands/clean.d.ts.map +1 -0
  13. package/dist/commands/clean.js +191 -0
  14. package/dist/commands/clean.js.map +1 -0
  15. package/dist/commands/create.d.ts.map +1 -0
  16. package/dist/commands/create.js +41 -0
  17. package/dist/commands/create.js.map +1 -0
  18. package/dist/commands/dev.d.ts.map +1 -0
  19. package/dist/commands/dev.js +51 -9
  20. package/dist/commands/dev.js.map +1 -0
  21. package/dist/commands/index.d.ts.map +1 -0
  22. package/dist/commands/index.js.map +1 -0
  23. package/dist/commands/install-audit.d.ts.map +1 -0
  24. package/dist/commands/install-audit.js.map +1 -0
  25. package/dist/commands/logs.d.ts.map +1 -0
  26. package/dist/commands/logs.js.map +1 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/types/index.d.ts.map +1 -0
  30. package/dist/types/index.js.map +1 -0
  31. package/dist/utils/android-sdk.d.ts.map +1 -0
  32. package/dist/utils/android-sdk.js +2 -2
  33. package/dist/utils/android-sdk.js.map +1 -0
  34. package/dist/utils/downloader.d.ts.map +1 -0
  35. package/dist/utils/downloader.js.map +1 -0
  36. package/dist/utils/emulator-deployer.d.ts.map +1 -0
  37. package/dist/utils/emulator-deployer.js.map +1 -0
  38. package/dist/utils/emulator.d.ts.map +1 -0
  39. package/dist/utils/emulator.js +5 -4
  40. package/dist/utils/emulator.js.map +1 -0
  41. package/dist/utils/index.d.ts.map +1 -0
  42. package/dist/utils/index.js.map +1 -0
  43. package/dist/utils/java.d.ts.map +1 -0
  44. package/dist/utils/java.js +5 -5
  45. package/dist/utils/java.js.map +1 -0
  46. package/dist/utils/logger.d.ts.map +1 -0
  47. package/dist/utils/logger.js.map +1 -0
  48. package/dist/utils/open.d.ts.map +1 -0
  49. package/dist/utils/open.js.map +1 -0
  50. package/dist/utils/prompt.d.ts.map +1 -0
  51. package/dist/utils/prompt.js.map +1 -0
  52. package/dist/utils/spinner.d.ts.map +1 -0
  53. package/dist/utils/spinner.js.map +1 -0
  54. package/dist/utils/system-tools.d.ts.map +1 -0
  55. package/dist/utils/system-tools.js.map +1 -0
  56. package/dist/utils/template.d.ts +13 -1
  57. package/dist/utils/template.d.ts.map +1 -0
  58. package/dist/utils/template.js +134 -1000
  59. package/dist/utils/template.js.map +1 -0
  60. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/1.0.0/com.jetstart.hot-reload.gradle.plugin-1.0.0.pom +15 -0
  61. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/1.0.0/com.jetstart.hot-reload.gradle.plugin-1.0.0.pom.md5 +1 -0
  62. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/1.0.0/com.jetstart.hot-reload.gradle.plugin-1.0.0.pom.sha1 +1 -0
  63. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/1.0.0/com.jetstart.hot-reload.gradle.plugin-1.0.0.pom.sha256 +1 -0
  64. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/1.0.0/com.jetstart.hot-reload.gradle.plugin-1.0.0.pom.sha512 +1 -0
  65. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/maven-metadata.xml +13 -0
  66. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/maven-metadata.xml.md5 +1 -0
  67. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/maven-metadata.xml.sha1 +1 -0
  68. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/maven-metadata.xml.sha256 +1 -0
  69. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/maven-metadata.xml.sha512 +1 -0
  70. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0-sources.jar +0 -0
  71. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0-sources.jar.md5 +1 -0
  72. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0-sources.jar.sha1 +1 -0
  73. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0-sources.jar.sha256 +1 -0
  74. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0-sources.jar.sha512 +1 -0
  75. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.aar +0 -0
  76. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.aar.md5 +1 -0
  77. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.aar.sha1 +1 -0
  78. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.aar.sha256 +1 -0
  79. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.aar.sha512 +1 -0
  80. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.module +124 -0
  81. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.module.md5 +1 -0
  82. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.module.sha1 +1 -0
  83. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.module.sha256 +1 -0
  84. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.module.sha512 +1 -0
  85. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.pom +46 -0
  86. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.pom.md5 +1 -0
  87. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.pom.sha1 +1 -0
  88. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.pom.sha256 +1 -0
  89. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.pom.sha512 +1 -0
  90. package/maven-repo/com/jetstart/hot-reload-runtime/maven-metadata.xml +13 -0
  91. package/maven-repo/com/jetstart/hot-reload-runtime/maven-metadata.xml.md5 +1 -0
  92. package/maven-repo/com/jetstart/hot-reload-runtime/maven-metadata.xml.sha1 +1 -0
  93. package/maven-repo/com/jetstart/hot-reload-runtime/maven-metadata.xml.sha256 +1 -0
  94. package/maven-repo/com/jetstart/hot-reload-runtime/maven-metadata.xml.sha512 +1 -0
  95. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.jar +0 -0
  96. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.jar.md5 +1 -0
  97. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.jar.sha1 +1 -0
  98. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.jar.sha256 +1 -0
  99. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.jar.sha512 +1 -0
  100. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.module +103 -0
  101. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.module.md5 +1 -0
  102. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.module.sha1 +1 -0
  103. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.module.sha256 +1 -0
  104. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.module.sha512 +1 -0
  105. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.pom +38 -0
  106. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.pom.md5 +1 -0
  107. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.pom.sha1 +1 -0
  108. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.pom.sha256 +1 -0
  109. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.pom.sha512 +1 -0
  110. package/maven-repo/com/jetstart/jetstart-gradle-plugin/maven-metadata.xml +13 -0
  111. package/maven-repo/com/jetstart/jetstart-gradle-plugin/maven-metadata.xml.md5 +1 -0
  112. package/maven-repo/com/jetstart/jetstart-gradle-plugin/maven-metadata.xml.sha1 +1 -0
  113. package/maven-repo/com/jetstart/jetstart-gradle-plugin/maven-metadata.xml.sha256 +1 -0
  114. package/maven-repo/com/jetstart/jetstart-gradle-plugin/maven-metadata.xml.sha512 +1 -0
  115. package/package.json +14 -5
  116. package/scripts/build-java.js +34 -0
  117. package/template/base/README.md +34 -0
  118. package/template/base/app/build.gradle +80 -0
  119. package/template/base/app/proguard-rules.pro +33 -0
  120. package/template/base/app/src/main/AndroidManifest.xml +33 -0
  121. package/template/base/app/src/main/java/__PACKAGE_PATH__/MainActivity.kt +64 -0
  122. package/template/base/app/src/main/java/__PACKAGE_PATH__/data/AppDatabase.kt +46 -0
  123. package/template/base/app/src/main/java/__PACKAGE_PATH__/data/Note.kt +12 -0
  124. package/template/base/app/src/main/java/__PACKAGE_PATH__/data/NoteDao.kt +23 -0
  125. package/template/base/app/src/main/java/__PACKAGE_PATH__/logic/TaggingEngine.kt +26 -0
  126. package/template/base/app/src/main/java/__PACKAGE_PATH__/ui/NotesScreen.kt +185 -0
  127. package/template/base/app/src/main/java/__PACKAGE_PATH__/ui/NotesViewModel.kt +58 -0
  128. package/template/base/app/src/main/java/__PACKAGE_PATH__/ui/TestScreen.kt +12 -0
  129. package/template/base/app/src/main/res/values/colors.xml +10 -0
  130. package/template/base/app/src/main/res/values/strings.xml +4 -0
  131. package/template/base/app/src/main/res/values/themes.xml +9 -0
  132. package/template/base/app/src/main/res/xml/network_security_config.xml +8 -0
  133. package/template/base/app/src/main/res/xml/provider_paths.xml +4 -0
  134. package/template/base/build.gradle +28 -0
  135. package/template/base/gradle/wrapper/gradle-wrapper.jar +0 -0
  136. package/template/base/gradle/wrapper/gradle-wrapper.properties +7 -0
  137. package/template/base/gradle.properties +13 -0
  138. package/template/base/gradlew +248 -0
  139. package/template/base/gradlew.bat +92 -0
  140. package/template/base/jetstart.config.json +11 -0
  141. package/template/base/settings.gradle +20 -0
  142. package/.eslintrc.json +0 -6
  143. package/src/cli.ts +0 -99
  144. package/src/commands/android-emulator.ts +0 -304
  145. package/src/commands/build.ts +0 -60
  146. package/src/commands/create.ts +0 -232
  147. package/src/commands/dev.ts +0 -198
  148. package/src/commands/index.ts +0 -10
  149. package/src/commands/install-audit.ts +0 -227
  150. package/src/commands/logs.ts +0 -117
  151. package/src/index.ts +0 -17
  152. package/src/types/index.ts +0 -53
  153. package/src/utils/android-sdk.ts +0 -512
  154. package/src/utils/downloader.ts +0 -201
  155. package/src/utils/emulator-deployer.ts +0 -210
  156. package/src/utils/emulator.ts +0 -463
  157. package/src/utils/index.ts +0 -8
  158. package/src/utils/java.ts +0 -369
  159. package/src/utils/logger.ts +0 -42
  160. package/src/utils/open.ts +0 -36
  161. package/src/utils/prompt.ts +0 -56
  162. package/src/utils/spinner.ts +0 -25
  163. package/src/utils/system-tools.ts +0 -648
  164. package/src/utils/template.ts +0 -1214
  165. package/tests/create.test.ts +0 -33
  166. package/tests/utils.test.ts +0 -17
  167. package/tsconfig.json +0 -25
@@ -1,1214 +0,0 @@
1
- /**
2
- * Template Generator
3
- * Creates project structure from templates
4
- */
5
-
6
- import path from 'path';
7
- import fs from 'fs-extra';
8
- import { spawn } from 'child_process';
9
- import { TemplateOptions } from '../types';
10
- import { MIN_ANDROID_API_LEVEL, TARGET_ANDROID_API_LEVEL } from '@jetstart/shared';
11
-
12
- export async function generateProjectTemplate(
13
- projectPath: string,
14
- options: TemplateOptions
15
- ): Promise<void> {
16
- const { projectName, packageName } = options;
17
-
18
- // Create directory structure
19
- await createDirectoryStructure(projectPath);
20
-
21
- // Generate files
22
- await generateRootBuildGradle(projectPath);
23
- await generateBuildGradle(projectPath, options);
24
- await generateSettingsGradle(projectPath, projectName);
25
- await generateGradleProperties(projectPath);
26
- await generateGradleWrapper(projectPath);
27
- await generateMainActivity(projectPath, packageName);
28
- await generateJetStart(projectPath, packageName);
29
- await generateAndroidManifest(projectPath, options);
30
- await generateResourceFiles(projectPath, projectName);
31
- await generateLocalProperties(projectPath);
32
- await generateJetStartConfig(projectPath, options);
33
- await generateGitignore(projectPath);
34
- await generateReadme(projectPath, projectName);
35
- }
36
-
37
- async function generateJetStart(
38
- projectPath: string,
39
- packageName: string
40
- ): Promise<void> {
41
- const packagePath = packageName.replace(/\./g, '/');
42
- const jetStartPath = path.join(
43
- projectPath,
44
- 'app/src/main/java',
45
- packagePath,
46
- 'JetStart.kt'
47
- );
48
-
49
- const content = `package ${packageName}
50
-
51
- // Android
52
- import android.app.Activity
53
- import android.content.Intent
54
- import android.net.Uri
55
- import android.os.Build
56
- import android.util.Log
57
- import androidx.core.content.FileProvider
58
-
59
- // Compose
60
- import androidx.compose.foundation.layout.*
61
- import androidx.compose.material3.*
62
- import androidx.compose.runtime.*
63
- import androidx.compose.ui.Alignment
64
- import androidx.compose.ui.Modifier
65
- import androidx.compose.ui.graphics.Color
66
- import androidx.compose.ui.text.font.FontWeight
67
- import androidx.compose.ui.unit.dp
68
-
69
- // Third-party
70
- import kotlinx.coroutines.flow.MutableStateFlow
71
- import kotlinx.coroutines.flow.StateFlow
72
- import okhttp3.*
73
-
74
- // Standard library & JSON
75
- import org.json.JSONArray
76
- import org.json.JSONObject
77
- import java.io.File
78
- import java.io.IOException
79
-
80
- // ============================================================================
81
- // DSL Type Definitions
82
- // ============================================================================
83
-
84
- /**
85
- * DSL Type Definitions
86
- * Represents UI elements in JSON format that can be interpreted at runtime
87
- */
88
-
89
- data class UIDefinition(
90
- val version: String = "1.0",
91
- val screen: DSLElement
92
- )
93
-
94
- data class DSLElement(
95
- val type: String,
96
- val text: String? = null,
97
- val style: String? = null,
98
- val color: String? = null,
99
- val modifier: DSLModifier? = null,
100
- val horizontalAlignment: String? = null,
101
- val verticalArrangement: String? = null,
102
- val contentAlignment: String? = null,
103
- val height: Int? = null,
104
- val width: Int? = null,
105
- val onClick: String? = null,
106
- val enabled: Boolean? = true,
107
- val imageVector: String? = null,
108
- val tint: String? = null,
109
- val contentDescription: String? = null,
110
- val children: List<DSLElement>? = null
111
- )
112
-
113
- data class DSLModifier(
114
- val fillMaxSize: Boolean? = null,
115
- val fillMaxWidth: Boolean? = null,
116
- val fillMaxHeight: Boolean? = null,
117
- val padding: Int? = null,
118
- val paddingHorizontal: Int? = null,
119
- val paddingVertical: Int? = null,
120
- val size: Int? = null,
121
- val height: Int? = null,
122
- val width: Int? = null,
123
- val weight: Float? = null
124
- )
125
-
126
- /**
127
- * Parse JSON string to UIDefinition
128
- */
129
- fun parseUIDefinition(json: String): UIDefinition {
130
- val obj = JSONObject(json)
131
- val version = obj.optString("version", "1.0")
132
- val screenObj = obj.getJSONObject("screen")
133
-
134
- return UIDefinition(
135
- version = version,
136
- screen = parseDSLElement(screenObj)
137
- )
138
- }
139
-
140
- /**
141
- * Parse JSONObject to DSLElement
142
- */
143
- fun parseDSLElement(obj: JSONObject): DSLElement {
144
- val children = if (obj.has("children")) {
145
- val childrenArray = obj.getJSONArray("children")
146
- List(childrenArray.length()) { i ->
147
- parseDSLElement(childrenArray.getJSONObject(i))
148
- }
149
- } else null
150
-
151
- val modifier = if (obj.has("modifier")) {
152
- val modObj = obj.getJSONObject("modifier")
153
- DSLModifier(
154
- fillMaxSize = modObj.optBoolean("fillMaxSize"),
155
- fillMaxWidth = modObj.optBoolean("fillMaxWidth"),
156
- fillMaxHeight = modObj.optBoolean("fillMaxHeight"),
157
- padding = if (modObj.has("padding")) modObj.getInt("padding") else null,
158
- paddingHorizontal = if (modObj.has("paddingHorizontal")) modObj.getInt("paddingHorizontal") else null,
159
- paddingVertical = if (modObj.has("paddingVertical")) modObj.getInt("paddingVertical") else null,
160
- size = if (modObj.has("size")) modObj.getInt("size") else null,
161
- height = if (modObj.has("height")) modObj.getInt("height") else null,
162
- width = if (modObj.has("width")) modObj.getInt("width") else null,
163
- weight = if (modObj.has("weight")) modObj.getDouble("weight").toFloat() else null
164
- )
165
- } else null
166
-
167
- return DSLElement(
168
- type = obj.getString("type"),
169
- text = if (obj.has("text")) obj.getString("text") else null,
170
- style = if (obj.has("style")) obj.getString("style") else null,
171
- color = if (obj.has("color")) obj.getString("color") else null,
172
- modifier = modifier,
173
- horizontalAlignment = if (obj.has("horizontalAlignment")) obj.getString("horizontalAlignment") else null,
174
- verticalArrangement = if (obj.has("verticalArrangement")) obj.getString("verticalArrangement") else null,
175
- contentAlignment = if (obj.has("contentAlignment")) obj.getString("contentAlignment") else null,
176
- height = if (obj.has("height")) obj.getInt("height") else null,
177
- width = if (obj.has("width")) obj.getInt("width") else null,
178
- onClick = if (obj.has("onClick")) obj.getString("onClick") else null,
179
- enabled = obj.optBoolean("enabled", true),
180
- imageVector = if (obj.has("imageVector")) obj.getString("imageVector") else null,
181
- tint = if (obj.has("tint")) obj.getString("tint") else null,
182
- contentDescription = if (obj.has("contentDescription")) obj.getString("contentDescription") else null,
183
- children = children
184
- )
185
- }
186
-
187
- // ============================================================================
188
- // DSL Interpreter
189
- // ============================================================================
190
-
191
- /**
192
- * DSL Interpreter
193
- * Converts JSON DSL to Compose UI at runtime
194
- */
195
- object DSLInterpreter {
196
- private const val TAG = "DSLInterpreter"
197
-
198
- private val _currentDSL = MutableStateFlow<UIDefinition?>(null)
199
- val currentDSL: StateFlow<UIDefinition?> = _currentDSL
200
-
201
- /**
202
- * Update the current DSL definition
203
- */
204
- fun updateDSL(jsonString: String) {
205
- try {
206
- val definition = parseUIDefinition(jsonString)
207
- _currentDSL.value = definition
208
- Log.d(TAG, "DSL updated successfully")
209
- } catch (e: Exception) {
210
- Log.e(TAG, "Failed to parse DSL: \${e.message}", e)
211
- }
212
- }
213
-
214
- /**
215
- * Render DSL as Compose UI
216
- */
217
- @Composable
218
- fun RenderDSL(definition: UIDefinition) {
219
- RenderElement(definition.screen)
220
- }
221
-
222
- /**
223
- * Render individual DSL element
224
- */
225
- @Composable
226
- fun RenderElement(element: DSLElement) {
227
- when (element.type) {
228
- "Column" -> RenderColumn(element)
229
- "Row" -> RenderRow(element)
230
- "Box" -> RenderBox(element)
231
- "Text" -> RenderText(element)
232
- "Button" -> RenderButton(element)
233
- "Spacer" -> RenderSpacer(element)
234
- else -> {
235
- Log.w(TAG, "Unknown element type: \${element.type}")
236
- Text("Unsupported: \${element.type}", color = Color.Red)
237
- }
238
- }
239
- }
240
-
241
- @Composable
242
- private fun RenderColumn(element: DSLElement) {
243
- Column(
244
- modifier = parseModifier(element.modifier),
245
- horizontalAlignment = parseHorizontalAlignment(element.horizontalAlignment),
246
- verticalArrangement = parseVerticalArrangement(element.verticalArrangement)
247
- ) {
248
- element.children?.forEach { child ->
249
- RenderElement(child)
250
- }
251
- }
252
- }
253
-
254
- @Composable
255
- private fun RenderRow(element: DSLElement) {
256
- Row(
257
- modifier = parseModifier(element.modifier),
258
- verticalAlignment = parseVerticalAlignment(element.horizontalAlignment),
259
- horizontalArrangement = parseHorizontalArrangement(element.verticalArrangement)
260
- ) {
261
- element.children?.forEach { child ->
262
- RenderElement(child)
263
- }
264
- }
265
- }
266
-
267
- @Composable
268
- private fun RenderBox(element: DSLElement) {
269
- Box(
270
- modifier = parseModifier(element.modifier),
271
- contentAlignment = parseContentAlignment(element.contentAlignment)
272
- ) {
273
- element.children?.forEach { child ->
274
- RenderElement(child)
275
- }
276
- }
277
- }
278
-
279
- @Composable
280
- private fun RenderText(element: DSLElement) {
281
- Text(
282
- text = element.text ?: "",
283
- style = parseTextStyle(element.style),
284
- color = parseColor(element.color) ?: Color.Unspecified,
285
- modifier = parseModifier(element.modifier)
286
- )
287
- }
288
-
289
- @Composable
290
- private fun RenderButton(element: DSLElement) {
291
- Button(
292
- onClick = { handleClick(element.onClick, element.text) },
293
- modifier = parseModifier(element.modifier),
294
- enabled = element.enabled ?: true
295
- ) {
296
- Text(element.text ?: "Button")
297
- }
298
- }
299
-
300
- @Composable
301
- private fun RenderSpacer(element: DSLElement) {
302
- Spacer(
303
- modifier = Modifier
304
- .height(element.height?.dp ?: 0.dp)
305
- .width(element.width?.dp ?: 0.dp)
306
- )
307
- }
308
-
309
- /**
310
- * Parse DSL modifier to Compose Modifier
311
- */
312
- private fun parseModifier(dslModifier: DSLModifier?): Modifier {
313
- var modifier: Modifier = Modifier
314
-
315
- dslModifier?.let { m ->
316
- if (m.fillMaxSize == true) modifier = modifier.fillMaxSize()
317
- if (m.fillMaxWidth == true) modifier = modifier.fillMaxWidth()
318
- if (m.fillMaxHeight == true) modifier = modifier.fillMaxHeight()
319
-
320
- m.padding?.let { modifier = modifier.padding(it.dp) }
321
- m.paddingHorizontal?.let { modifier = modifier.padding(horizontal = it.dp) }
322
- m.paddingVertical?.let { modifier = modifier.padding(vertical = it.dp) }
323
-
324
- m.size?.let { modifier = modifier.size(it.dp) }
325
- m.height?.let { modifier = modifier.height(it.dp) }
326
- m.width?.let { modifier = modifier.width(it.dp) }
327
-
328
- // Note: weight() is only available in RowScope/ColumnScope
329
- // We'll handle it separately when needed
330
- }
331
-
332
- return modifier
333
- }
334
-
335
- /**
336
- * Parse alignment strings
337
- */
338
- private fun parseHorizontalAlignment(alignment: String?): Alignment.Horizontal {
339
- return when (alignment?.lowercase()) {
340
- "start" -> Alignment.Start
341
- "centerhorizontally", "center" -> Alignment.CenterHorizontally
342
- "end" -> Alignment.End
343
- else -> Alignment.Start
344
- }
345
- }
346
-
347
- private fun parseVerticalAlignment(alignment: String?): Alignment.Vertical {
348
- return when (alignment?.lowercase()) {
349
- "top" -> Alignment.Top
350
- "centervertically", "center" -> Alignment.CenterVertically
351
- "bottom" -> Alignment.Bottom
352
- else -> Alignment.Top
353
- }
354
- }
355
-
356
- private fun parseContentAlignment(alignment: String?): Alignment {
357
- return when (alignment?.lowercase()) {
358
- "center" -> Alignment.Center
359
- "topcenter" -> Alignment.TopCenter
360
- "topstart" -> Alignment.TopStart
361
- "topend" -> Alignment.TopEnd
362
- "bottomcenter" -> Alignment.BottomCenter
363
- "bottomstart" -> Alignment.BottomStart
364
- "bottomend" -> Alignment.BottomEnd
365
- "centerstart" -> Alignment.CenterStart
366
- "centerend" -> Alignment.CenterEnd
367
- else -> Alignment.TopStart
368
- }
369
- }
370
-
371
- private fun parseVerticalArrangement(arrangement: String?): Arrangement.Vertical {
372
- return when (arrangement?.lowercase()) {
373
- "top" -> Arrangement.Top
374
- "center" -> Arrangement.Center
375
- "bottom" -> Arrangement.Bottom
376
- "spacebetween" -> Arrangement.SpaceBetween
377
- "spacearound" -> Arrangement.SpaceAround
378
- "spaceevenly" -> Arrangement.SpaceEvenly
379
- else -> Arrangement.Top
380
- }
381
- }
382
-
383
- private fun parseHorizontalArrangement(arrangement: String?): Arrangement.Horizontal {
384
- return when (arrangement?.lowercase()) {
385
- "start" -> Arrangement.Start
386
- "center" -> Arrangement.Center
387
- "end" -> Arrangement.End
388
- "spacebetween" -> Arrangement.SpaceBetween
389
- "spacearound" -> Arrangement.SpaceAround
390
- "spaceevenly" -> Arrangement.SpaceEvenly
391
- else -> Arrangement.Start
392
- }
393
- }
394
-
395
- /**
396
- * Parse text style
397
- */
398
- @Composable
399
- private fun parseTextStyle(style: String?): androidx.compose.ui.text.TextStyle {
400
- return when (style?.lowercase()) {
401
- "headlinelarge" -> MaterialTheme.typography.headlineLarge
402
- "headlinemedium" -> MaterialTheme.typography.headlineMedium
403
- "headlinesmall" -> MaterialTheme.typography.headlineSmall
404
- "titlelarge" -> MaterialTheme.typography.titleLarge
405
- "titlemedium" -> MaterialTheme.typography.titleMedium
406
- "titlesmall" -> MaterialTheme.typography.titleSmall
407
- "bodylarge" -> MaterialTheme.typography.bodyLarge
408
- "bodymedium" -> MaterialTheme.typography.bodyMedium
409
- "bodysmall" -> MaterialTheme.typography.bodySmall
410
- "labellarge" -> MaterialTheme.typography.labelLarge
411
- "labelmedium" -> MaterialTheme.typography.labelMedium
412
- "labelsmall" -> MaterialTheme.typography.labelSmall
413
- else -> MaterialTheme.typography.bodyMedium
414
- }
415
- }
416
-
417
- /**
418
- * Parse color from string
419
- */
420
- private fun parseColor(colorString: String?): Color? {
421
- if (colorString == null) return null
422
-
423
- return try {
424
- when {
425
- colorString.startsWith("#") -> {
426
- // Hex color
427
- Color(android.graphics.Color.parseColor(colorString))
428
- }
429
- else -> null
430
- }
431
- } catch (e: Exception) {
432
- Log.w(TAG, "Failed to parse color: \$colorString")
433
- null
434
- }
435
- }
436
-
437
- /**
438
- * Handle click events
439
- */
440
- private fun handleClick(action: String?, text: String?) {
441
- if (action != null) {
442
- Log.d(TAG, "Button clicked: \$action")
443
-
444
- // Send click event to dev server
445
- sendClickEvent(action, "Button", text)
446
- }
447
- }
448
-
449
- /**
450
- * Send click event to dev server via WebSocket
451
- */
452
- private fun sendClickEvent(action: String, elementType: String, elementText: String?) {
453
- try {
454
- val ws = HotReload.getWebSocket()
455
- if (ws != null) {
456
- val message = JSONObject().apply {
457
- put("type", "client:click")
458
- put("timestamp", System.currentTimeMillis())
459
- put("action", action)
460
- put("elementType", elementType)
461
- elementText?.let { put("elementText", it) }
462
- }
463
-
464
- ws.send(message.toString())
465
- Log.d(TAG, "Sent click event to server: \$action")
466
- } else {
467
- Log.w(TAG, "WebSocket not available, cannot send click event")
468
- }
469
- } catch (e: Exception) {
470
- Log.e(TAG, "Failed to send click event: \${e.message}")
471
- }
472
- }
473
- }
474
-
475
- // ============================================================================
476
- // Hot Reload Manager
477
- // ============================================================================
478
-
479
- /**
480
- * Hot Reload Manager
481
- * Connects to JetStart dev server and automatically reloads the app when code changes
482
- */
483
- object HotReload {
484
- private const val TAG = "HotReload"
485
- private var webSocket: WebSocket? = null
486
- private var activity: Activity? = null
487
- private var connectionTime: Long = 0
488
- private var ignoreFirstBuild = true
489
- private val httpClient = OkHttpClient()
490
-
491
- /**
492
- * Get the current WebSocket connection (for sending messages)
493
- */
494
- fun getWebSocket(): WebSocket? = webSocket
495
-
496
- fun connect(activity: Activity, serverUrl: String, sessionId: String) {
497
- this.activity = activity
498
-
499
- val wsUrl = serverUrl.replace("http://", "ws://").replace("https://", "wss://")
500
- Log.d(TAG, "Connecting to dev server: \$wsUrl")
501
-
502
- val client = OkHttpClient()
503
- val request = Request.Builder()
504
- .url(wsUrl)
505
- .build()
506
-
507
- webSocket = client.newWebSocket(request, object : WebSocketListener() {
508
- override fun onOpen(webSocket: WebSocket, response: Response) {
509
- Log.d(TAG, "WebSocket connected")
510
- connectionTime = System.currentTimeMillis()
511
- ignoreFirstBuild = true // Ignore the first build-complete after connecting
512
-
513
- // Send connect message
514
- val connectMsg = JSONObject().apply {
515
- put("type", "client:connect")
516
- put("sessionId", sessionId)
517
- put("clientType", "test-app")
518
- }
519
- webSocket.send(connectMsg.toString())
520
- }
521
-
522
- override fun onMessage(webSocket: WebSocket, text: String) {
523
- Log.d(TAG, "Received: \$text")
524
-
525
- try {
526
- val json = JSONObject(text)
527
- val type = json.getString("type")
528
-
529
- when (type) {
530
- "core:ui-update" -> {
531
- // DSL-based hot reload (FAST)
532
- if (ignoreFirstBuild) {
533
- Log.d(TAG, "Ignoring first UI update (old build)")
534
- ignoreFirstBuild = false
535
- return@onMessage
536
- }
537
-
538
- val timestamp = json.optLong("timestamp", 0)
539
- val dslContent = json.optString("dslContent", "")
540
-
541
- Log.d(TAG, "UI update received at \$timestamp, connection at \$connectionTime")
542
-
543
- // Only update if changes happened AFTER we connected
544
- if (timestamp > connectionTime && dslContent.isNotEmpty()) {
545
- Log.d(TAG, "New UI update detected, recomposing (\${dslContent.length} bytes)")
546
-
547
- // Update DSL on main thread
548
- activity?.runOnUiThread {
549
- DSLInterpreter.updateDSL(dslContent)
550
- }
551
- } else {
552
- Log.d(TAG, "Ignoring old UI update")
553
- }
554
- }
555
-
556
- "core:reload" -> {
557
- Log.d(TAG, "Reload triggered!")
558
- // Restart activity on main thread
559
- activity?.runOnUiThread {
560
- activity?.recreate()
561
- }
562
- }
563
-
564
- "core:build-complete" -> {
565
- // Full APK rebuild (SLOW - fallback for non-UI changes)
566
- if (ignoreFirstBuild) {
567
- Log.d(TAG, "Ignoring first build-complete (old build)")
568
- ignoreFirstBuild = false
569
- return@onMessage
570
- }
571
-
572
- val timestamp = json.optLong("timestamp", 0)
573
- val downloadUrl = json.optString("downloadUrl", "")
574
-
575
- Log.d(TAG, "Build complete at \$timestamp, connection at \$connectionTime")
576
- Log.d(TAG, "Download URL: \$downloadUrl")
577
-
578
- // Only reload if build happened AFTER we connected
579
- if (timestamp > connectionTime && downloadUrl.isNotEmpty()) {
580
- Log.d(TAG, "New build detected, downloading and installing APK")
581
- downloadAndInstallApk(downloadUrl)
582
- } else {
583
- Log.d(TAG, "Ignoring old build (timestamp before connection)")
584
- }
585
- }
586
- }
587
- } catch (e: Exception) {
588
- Log.e(TAG, "Failed to parse message: \${e.message}")
589
- }
590
- }
591
-
592
- override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
593
- Log.e(TAG, "WebSocket error: \${t.message}")
594
- // Auto-reconnect after 5 seconds
595
- android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
596
- connect(activity!!, serverUrl, sessionId)
597
- }, 5000)
598
- }
599
-
600
- override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
601
- Log.d(TAG, "WebSocket closed: \$reason")
602
- }
603
- })
604
- }
605
-
606
- fun disconnect() {
607
- webSocket?.close(1000, "App closing")
608
- webSocket = null
609
- activity = null
610
- }
611
-
612
- private fun downloadAndInstallApk(downloadUrl: String) {
613
- Log.d(TAG, "Starting APK download from: \$downloadUrl")
614
-
615
- val request = Request.Builder()
616
- .url(downloadUrl)
617
- .build()
618
-
619
- httpClient.newCall(request).enqueue(object : Callback {
620
- override fun onFailure(call: Call, e: IOException) {
621
- Log.e(TAG, "APK download failed: \${e.message}")
622
- }
623
-
624
- override fun onResponse(call: Call, response: Response) {
625
- if (!response.isSuccessful) {
626
- Log.e(TAG, "APK download failed with code: \${response.code}")
627
- return
628
- }
629
-
630
- try {
631
- val apkData = response.body?.bytes()
632
- if (apkData == null) {
633
- Log.e(TAG, "APK data is null")
634
- return
635
- }
636
-
637
- Log.d(TAG, "APK downloaded successfully (\${apkData.size} bytes)")
638
-
639
- // Save APK to cache directory
640
- val cacheDir = activity?.cacheDir
641
- if (cacheDir == null) {
642
- Log.e(TAG, "Cache directory is null")
643
- return
644
- }
645
-
646
- val apkFile = File(cacheDir, "update.apk")
647
- apkFile.outputStream().use { output ->
648
- output.write(apkData)
649
- }
650
-
651
- Log.d(TAG, "APK saved to: \${apkFile.absolutePath}")
652
-
653
- // Install APK on main thread
654
- activity?.runOnUiThread {
655
- installApk(apkFile)
656
- }
657
- } catch (e: Exception) {
658
- Log.e(TAG, "Failed to save/install APK: \${e.message}")
659
- }
660
- }
661
- })
662
- }
663
-
664
- private fun installApk(apkFile: File) {
665
- try {
666
- val context = activity ?: return
667
-
668
- val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
669
- FileProvider.getUriForFile(
670
- context,
671
- "\${context.packageName}.fileprovider",
672
- apkFile
673
- )
674
- } else {
675
- Uri.fromFile(apkFile)
676
- }
677
-
678
- val intent = Intent(Intent.ACTION_VIEW).apply {
679
- setDataAndType(uri, "application/vnd.android.package-archive")
680
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
681
- addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
682
- }
683
-
684
- context.startActivity(intent)
685
- Log.d(TAG, "Installation intent started")
686
- } catch (e: Exception) {
687
- Log.e(TAG, "Failed to install APK: \${e.message}")
688
- }
689
- }
690
- }
691
- `;
692
-
693
- await fs.ensureDir(path.dirname(jetStartPath));
694
- await fs.writeFile(jetStartPath, content);
695
- }
696
-
697
- async function createDirectoryStructure(projectPath: string): Promise<void> {
698
- const dirs = [
699
- 'app/src/main/java',
700
- 'app/src/main/res/layout',
701
- 'app/src/main/res/values',
702
- 'app/src/main/res/drawable',
703
- 'gradle/wrapper',
704
- ];
705
-
706
- for (const dir of dirs) {
707
- await fs.ensureDir(path.join(projectPath, dir));
708
- }
709
- }
710
-
711
- async function generateBuildGradle(
712
- projectPath: string,
713
- options: TemplateOptions
714
- ): Promise<void> {
715
- const content = `plugins {
716
- id 'com.android.application'
717
- id 'org.jetbrains.kotlin.android'
718
- }
719
-
720
- android {
721
- namespace '${options.packageName}'
722
- compileSdk ${TARGET_ANDROID_API_LEVEL}
723
-
724
- defaultConfig {
725
- applicationId "${options.packageName}"
726
- minSdk ${MIN_ANDROID_API_LEVEL}
727
- targetSdk ${TARGET_ANDROID_API_LEVEL}
728
- versionCode 1
729
- versionName "1.0.0"
730
- }
731
-
732
- buildTypes {
733
- release {
734
- minifyEnabled false
735
- }
736
- }
737
-
738
- compileOptions {
739
- sourceCompatibility JavaVersion.VERSION_17
740
- targetCompatibility JavaVersion.VERSION_17
741
- }
742
-
743
- kotlinOptions {
744
- jvmTarget = '17'
745
- }
746
-
747
- buildFeatures {
748
- compose true
749
- buildConfig true // Required for JetStart hot reload
750
- }
751
-
752
- composeOptions {
753
- kotlinCompilerExtensionVersion = '1.5.6'
754
- }
755
- }
756
-
757
- dependencies {
758
- implementation 'androidx.core:core-ktx:1.12.0'
759
- implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
760
- implementation 'androidx.activity:activity-compose:1.8.1'
761
- implementation platform('androidx.compose:compose-bom:2023.10.01')
762
- implementation 'androidx.compose.ui:ui'
763
- implementation 'androidx.compose.ui:ui-graphics'
764
- implementation 'androidx.compose.ui:ui-tooling-preview'
765
- implementation 'androidx.compose.material3:material3'
766
-
767
- // JetStart Hot Reload dependencies
768
- implementation 'com.squareup.okhttp3:okhttp:4.12.0'
769
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
770
- }`;
771
-
772
- await fs.writeFile(path.join(projectPath, 'app/build.gradle'), content);
773
- }
774
-
775
- async function generateSettingsGradle(
776
- projectPath: string,
777
- projectName: string
778
- ): Promise<void> {
779
- const content = `rootProject.name = "${projectName}"
780
- include ':app'`;
781
-
782
- await fs.writeFile(path.join(projectPath, 'settings.gradle'), content);
783
- }
784
-
785
- async function generateGradleProperties(projectPath: string): Promise<void> {
786
- const content = `org.gradle.jvmargs=-Xmx2048m
787
- android.useAndroidX=true
788
- kotlin.code.style=official`;
789
-
790
- await fs.writeFile(path.join(projectPath, 'gradle.properties'), content);
791
- }
792
-
793
- async function generateMainActivity(
794
- projectPath: string,
795
- packageName: string
796
- ): Promise<void> {
797
- const packagePath = packageName.replace(/\./g, '/');
798
- const activityPath = path.join(
799
- projectPath,
800
- 'app/src/main/java',
801
- packagePath,
802
- 'MainActivity.kt'
803
- );
804
-
805
- const content = `package ${packageName}
806
-
807
- import android.os.Bundle
808
- import androidx.activity.ComponentActivity
809
- import androidx.activity.compose.setContent
810
- import androidx.compose.foundation.layout.*
811
- import androidx.compose.material3.*
812
- import androidx.compose.runtime.*
813
- import androidx.compose.ui.Alignment
814
- import androidx.compose.ui.Modifier
815
- import androidx.compose.ui.unit.dp
816
-
817
- class MainActivity : ComponentActivity() {
818
- override fun onCreate(savedInstanceState: Bundle?) {
819
- super.onCreate(savedInstanceState)
820
-
821
- // Initialize hot reload - reads from BuildConfig injected by jetstart dev
822
- try {
823
- val serverUrl = BuildConfig.JETSTART_SERVER_URL
824
- val sessionId = BuildConfig.JETSTART_SESSION_ID
825
- HotReload.connect(this, serverUrl, sessionId)
826
- } catch (e: Exception) {
827
- // BuildConfig not available yet, hot reload will be disabled
828
- android.util.Log.w("MainActivity", "Hot reload not configured: \${e.message}")
829
- }
830
-
831
- setContent {
832
- MaterialTheme {
833
- Surface(
834
- modifier = Modifier.fillMaxSize(),
835
- color = MaterialTheme.colorScheme.background
836
- ) {
837
- // Check if we should render from DSL (hot reload mode)
838
- val dsl by DSLInterpreter.currentDSL.collectAsState()
839
-
840
- if (dsl != null) {
841
- // Hot reload mode: render from DSL sent by server
842
- DSLInterpreter.RenderDSL(dsl!!)
843
- } else {
844
- // Normal mode: render actual Compose code
845
- AppContent()
846
- }
847
- }
848
- }
849
- }
850
- }
851
-
852
- override fun onDestroy() {
853
- super.onDestroy()
854
- HotReload.disconnect()
855
- }
856
- }
857
-
858
- /**
859
- * Main App Content - REAL Kotlin Compose Code!
860
- * This gets parsed to DSL and sent via hot reload
861
- */
862
- @Composable
863
- fun AppContent() {
864
- Column(
865
- modifier = Modifier
866
- .fillMaxSize()
867
- .padding(16.dp),
868
- horizontalAlignment = Alignment.CenterHorizontally,
869
- verticalArrangement = Arrangement.Center
870
- ) {
871
- Text(
872
- text = "Welcome to JetStart! 🚀",
873
- style = MaterialTheme.typography.headlineMedium
874
- )
875
-
876
- Spacer(modifier = Modifier.height(16.dp))
877
-
878
- Text(
879
- text = "Edit this code and save to see hot reload!",
880
- style = MaterialTheme.typography.bodyMedium
881
- )
882
-
883
- Spacer(modifier = Modifier.height(24.dp))
884
-
885
- Button(
886
- onClick = { /* Handle click */ },
887
- modifier = Modifier.fillMaxWidth()
888
- ) {
889
- Text("Click Me!")
890
- }
891
- }
892
- }`;
893
-
894
- await fs.ensureDir(path.dirname(activityPath));
895
- await fs.writeFile(activityPath, content);
896
- }
897
-
898
- async function generateAndroidManifest(
899
- projectPath: string,
900
- options: TemplateOptions
901
- ): Promise<void> {
902
- const themeName = options.projectName.replace(/[^a-zA-Z0-9]/g, '');
903
- const content = `<?xml version="1.0" encoding="utf-8"?>
904
- <manifest xmlns:android="http://schemas.android.com/apk/res/android">
905
-
906
- <uses-permission android:name="android.permission.INTERNET" />
907
-
908
- <application
909
- android:allowBackup="true"
910
- android:label="@string/app_name"
911
- android:theme="@style/Theme.${themeName}"
912
- android:networkSecurityConfig="@xml/network_security_config">
913
- <activity
914
- android:name=".MainActivity"
915
- android:exported="true">
916
- <intent-filter>
917
- <action android:name="android.intent.action.MAIN" />
918
- <category android:name="android.intent.category.LAUNCHER" />
919
- </intent-filter>
920
- </activity>
921
- </application>
922
-
923
- </manifest>`;
924
-
925
- await fs.writeFile(
926
- path.join(projectPath, 'app/src/main/AndroidManifest.xml'),
927
- content
928
- );
929
- }
930
-
931
- async function generateJetStartConfig(
932
- projectPath: string,
933
- options: TemplateOptions
934
- ): Promise<void> {
935
- const config = {
936
- projectName: options.projectName,
937
- packageName: options.packageName,
938
- version: '1.0.0',
939
- jetstart: {
940
- version: '0.1.0',
941
- enableHotReload: true,
942
- enableLogs: true,
943
- port: 8765,
944
- },
945
- };
946
-
947
- await fs.writeJSON(
948
- path.join(projectPath, 'jetstart.config.json'),
949
- config,
950
- { spaces: 2 }
951
- );
952
- }
953
-
954
- async function generateGitignore(projectPath: string): Promise<void> {
955
- const content = `# Build
956
- /build
957
- /app/build
958
- .gradle
959
- *.hprof
960
-
961
- # IDE
962
- .idea
963
- *.iml
964
- .vscode
965
- .DS_Store
966
-
967
-
968
- # JetStart
969
- .jetstart
970
-
971
- # Android
972
- local.properties
973
- *.apk
974
- *.aab
975
- *.ap_
976
- *.dex
977
- *.class
978
- bin/
979
- gen/
980
- out/
981
- captures/
982
- .externalNativeBuild
983
- .cxx
984
-
985
- # Log files
986
- *.log
987
-
988
- # Keystore files
989
- *.jks
990
- *.keystore`;
991
-
992
- await fs.writeFile(path.join(projectPath, '.gitignore'), content);
993
- }
994
-
995
- async function generateReadme(projectPath: string, projectName: string): Promise<void> {
996
- const content = `# ${projectName}
997
-
998
- A JetStart project with Kotlin and Jetpack Compose.
999
-
1000
- ## Getting Started
1001
-
1002
- \`\`\`bash
1003
- # Start development server
1004
- jetstart dev
1005
-
1006
- # Build production APK
1007
- jetstart build
1008
-
1009
- # View logs
1010
- jetstart logs
1011
- \`\`\`
1012
-
1013
- ## Project Structure
1014
-
1015
- \`\`\`
1016
- ${projectName}/
1017
- ├── app/
1018
- │ └── src/
1019
- │ └── main/
1020
- │ ├── java/ # Kotlin source files
1021
- │ └── res/ # Resources
1022
- ├── jetstart.config.json # JetStart configuration
1023
- └── build.gradle # Gradle build file
1024
- \`\`\`
1025
-
1026
- ## Learn More
1027
-
1028
- - [JetStart Documentation](https://github.com/phantom/jetstart)
1029
- - [Jetpack Compose](https://developer.android.com/jetpack/compose)
1030
- `;
1031
-
1032
- await fs.writeFile(path.join(projectPath, 'README.md'), content);
1033
- }
1034
-
1035
- async function generateRootBuildGradle(projectPath: string): Promise<void> {
1036
- const content = `// Top-level build file
1037
- buildscript {
1038
- ext {
1039
- kotlin_version = '1.9.21'
1040
- compose_version = '1.5.4'
1041
- }
1042
- repositories {
1043
- google()
1044
- mavenCentral()
1045
- }
1046
- dependencies {
1047
- classpath 'com.android.tools.build:gradle:8.2.0'
1048
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
1049
- }
1050
- }
1051
-
1052
- allprojects {
1053
- repositories {
1054
- google()
1055
- mavenCentral()
1056
- }
1057
- }
1058
-
1059
- task clean(type: Delete) {
1060
- delete rootProject.buildDir
1061
- }`;
1062
-
1063
- await fs.writeFile(path.join(projectPath, 'build.gradle'), content);
1064
- }
1065
-
1066
- async function generateGradleWrapper(projectPath: string): Promise<void> {
1067
- // Use system Gradle to initialize proper wrapper
1068
- // This generates:
1069
- // - gradle/wrapper/gradle-wrapper.jar
1070
- // - gradle/wrapper/gradle-wrapper.properties
1071
- // - gradlew (Unix shell script)
1072
- // - gradlew.bat (Windows batch script)
1073
-
1074
- return new Promise<void>((resolve) => {
1075
- // Try to use system gradle to generate wrapper
1076
- const gradleCmd = process.platform === 'win32' ? 'gradle.bat' : 'gradle';
1077
-
1078
- const gradleProcess = spawn(gradleCmd, ['wrapper', '--gradle-version', '8.2'], {
1079
- cwd: projectPath,
1080
- shell: true,
1081
- });
1082
-
1083
- gradleProcess.on('close', (code) => {
1084
- // Continue regardless of success/failure
1085
- // If gradle wrapper command fails, the build will fall back to system gradle
1086
- resolve();
1087
- });
1088
-
1089
- gradleProcess.on('error', () => {
1090
- // Continue even if gradle command not found
1091
- resolve();
1092
- });
1093
-
1094
- // Timeout after 30 seconds
1095
- setTimeout(() => {
1096
- gradleProcess.kill();
1097
- resolve();
1098
- }, 30000);
1099
- });
1100
- }
1101
-
1102
- async function generateResourceFiles(
1103
- projectPath: string,
1104
- projectName: string
1105
- ): Promise<void> {
1106
- // Generate strings.xml
1107
- const stringsXml = `<?xml version="1.0" encoding="utf-8"?>
1108
- <resources>
1109
- <string name="app_name">${projectName}</string>
1110
- </resources>`;
1111
-
1112
- await fs.writeFile(
1113
- path.join(projectPath, 'app/src/main/res/values/strings.xml'),
1114
- stringsXml
1115
- );
1116
-
1117
- // Generate colors.xml
1118
- const colorsXml = `<?xml version="1.0" encoding="utf-8"?>
1119
- <resources>
1120
- <color name="purple_200">#FFBB86FC</color>
1121
- <color name="purple_500">#FF6200EE</color>
1122
- <color name="purple_700">#FF3700B3</color>
1123
- <color name="teal_200">#FF03DAC5</color>
1124
- <color name="teal_700">#FF018786</color>
1125
- <color name="black">#FF000000</color>
1126
- <color name="white">#FFFFFFFF</color>
1127
- </resources>`;
1128
-
1129
- await fs.writeFile(
1130
- path.join(projectPath, 'app/src/main/res/values/colors.xml'),
1131
- colorsXml
1132
- );
1133
-
1134
- // Generate themes.xml
1135
- const themesXml = `<?xml version="1.0" encoding="utf-8"?>
1136
- <resources>
1137
- <style name="Theme.${projectName.replace(/[^a-zA-Z0-9]/g, '')}" parent="android:Theme.Material.Light.NoActionBar" />
1138
- </resources>`;
1139
-
1140
- await fs.writeFile(
1141
- path.join(projectPath, 'app/src/main/res/values/themes.xml'),
1142
- themesXml
1143
- );
1144
-
1145
- // Generate network_security_config.xml for development (allows cleartext traffic)
1146
- const networkSecurityConfig = `<?xml version="1.0" encoding="utf-8"?>
1147
- <network-security-config>
1148
- <base-config cleartextTrafficPermitted="true">
1149
- <trust-anchors>
1150
- <certificates src="system" />
1151
- </trust-anchors>
1152
- </base-config>
1153
- </network-security-config>`;
1154
-
1155
- await fs.ensureDir(path.join(projectPath, 'app/src/main/res/xml'));
1156
- await fs.writeFile(
1157
- path.join(projectPath, 'app/src/main/res/xml/network_security_config.xml'),
1158
- networkSecurityConfig
1159
- );
1160
- }
1161
-
1162
- async function generateLocalProperties(projectPath: string): Promise<void> {
1163
- // Auto-detect Android SDK location
1164
- let androidSdkPath: string | undefined;
1165
-
1166
- // Check environment variables first
1167
- androidSdkPath = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
1168
-
1169
- // If not found, check common Windows locations
1170
- if (!androidSdkPath && process.platform === 'win32') {
1171
- const commonPaths = [
1172
- 'C:\\Android',
1173
- path.join(require('os').homedir(), 'AppData', 'Local', 'Android', 'Sdk'),
1174
- 'C:\\Android\\Sdk',
1175
- 'C:\\Program Files (x86)\\Android\\android-sdk',
1176
- ];
1177
-
1178
- for (const p of commonPaths) {
1179
- if (fs.existsSync(p)) {
1180
- androidSdkPath = p;
1181
- break;
1182
- }
1183
- }
1184
- }
1185
-
1186
- // If not found on macOS/Linux, check common paths
1187
- if (!androidSdkPath && process.platform !== 'win32') {
1188
- const commonPaths = [
1189
- path.join(require('os').homedir(), 'Android', 'Sdk'),
1190
- path.join(require('os').homedir(), 'Library', 'Android', 'sdk'),
1191
- '/opt/android-sdk',
1192
- ];
1193
-
1194
- for (const p of commonPaths) {
1195
- if (fs.existsSync(p)) {
1196
- androidSdkPath = p;
1197
- break;
1198
- }
1199
- }
1200
- }
1201
-
1202
- if (!androidSdkPath) {
1203
- console.warn('[Warning] Android SDK not found. You may need to set ANDROID_HOME or create local.properties manually.');
1204
- return;
1205
- }
1206
-
1207
- // Create local.properties with SDK path
1208
- const content = `# Auto-generated by JetStart
1209
- sdk.dir=${androidSdkPath.replace(/\\/g, '\\\\')}
1210
- `;
1211
-
1212
- await fs.writeFile(path.join(projectPath, 'local.properties'), content);
1213
- console.log(`[JetStart] Created local.properties with SDK: ${androidSdkPath}`);
1214
- }