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