@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.
- package/README.md +133 -41
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +11 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/android-emulator.d.ts.map +1 -0
- package/dist/commands/android-emulator.js.map +1 -0
- package/dist/commands/build.d.ts +13 -1
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +279 -29
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/clean.d.ts +23 -0
- package/dist/commands/clean.d.ts.map +1 -0
- package/dist/commands/clean.js +191 -0
- package/dist/commands/clean.js.map +1 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +41 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +51 -9
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/install-audit.d.ts.map +1 -0
- package/dist/commands/install-audit.js.map +1 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/android-sdk.d.ts.map +1 -0
- package/dist/utils/android-sdk.js +2 -2
- package/dist/utils/android-sdk.js.map +1 -0
- package/dist/utils/downloader.d.ts.map +1 -0
- package/dist/utils/downloader.js.map +1 -0
- package/dist/utils/emulator-deployer.d.ts.map +1 -0
- package/dist/utils/emulator-deployer.js.map +1 -0
- package/dist/utils/emulator.d.ts.map +1 -0
- package/dist/utils/emulator.js +5 -4
- package/dist/utils/emulator.js.map +1 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/java.d.ts.map +1 -0
- package/dist/utils/java.js +5 -5
- package/dist/utils/java.js.map +1 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/open.d.ts.map +1 -0
- package/dist/utils/open.js.map +1 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/spinner.d.ts.map +1 -0
- package/dist/utils/spinner.js.map +1 -0
- package/dist/utils/system-tools.d.ts.map +1 -0
- package/dist/utils/system-tools.js.map +1 -0
- package/dist/utils/template.d.ts +13 -1
- package/dist/utils/template.d.ts.map +1 -0
- package/dist/utils/template.js +134 -1000
- package/dist/utils/template.js.map +1 -0
- 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
- 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
- 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
- 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
- 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
- package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/maven-metadata.xml +13 -0
- package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/maven-metadata.xml.md5 +1 -0
- package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/maven-metadata.xml.sha1 +1 -0
- package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/maven-metadata.xml.sha256 +1 -0
- package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/maven-metadata.xml.sha512 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0-sources.jar +0 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0-sources.jar.md5 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0-sources.jar.sha1 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0-sources.jar.sha256 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0-sources.jar.sha512 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.aar +0 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.aar.md5 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.aar.sha1 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.aar.sha256 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.aar.sha512 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.module +124 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.module.md5 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.module.sha1 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.module.sha256 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.module.sha512 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.pom +46 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.pom.md5 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.pom.sha1 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.pom.sha256 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.pom.sha512 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/maven-metadata.xml +13 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/maven-metadata.xml.md5 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/maven-metadata.xml.sha1 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/maven-metadata.xml.sha256 +1 -0
- package/maven-repo/com/jetstart/hot-reload-runtime/maven-metadata.xml.sha512 +1 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.jar +0 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.jar.md5 +1 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.jar.sha1 +1 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.jar.sha256 +1 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.jar.sha512 +1 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.module +103 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.module.md5 +1 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.module.sha1 +1 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.module.sha256 +1 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.module.sha512 +1 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.pom +38 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.pom.md5 +1 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.pom.sha1 +1 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.pom.sha256 +1 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.pom.sha512 +1 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/maven-metadata.xml +13 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/maven-metadata.xml.md5 +1 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/maven-metadata.xml.sha1 +1 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/maven-metadata.xml.sha256 +1 -0
- package/maven-repo/com/jetstart/jetstart-gradle-plugin/maven-metadata.xml.sha512 +1 -0
- package/package.json +14 -5
- package/scripts/build-java.js +34 -0
- package/template/base/README.md +34 -0
- package/template/base/app/build.gradle +80 -0
- package/template/base/app/proguard-rules.pro +33 -0
- package/template/base/app/src/main/AndroidManifest.xml +33 -0
- package/template/base/app/src/main/java/__PACKAGE_PATH__/MainActivity.kt +64 -0
- package/template/base/app/src/main/java/__PACKAGE_PATH__/data/AppDatabase.kt +46 -0
- package/template/base/app/src/main/java/__PACKAGE_PATH__/data/Note.kt +12 -0
- package/template/base/app/src/main/java/__PACKAGE_PATH__/data/NoteDao.kt +23 -0
- package/template/base/app/src/main/java/__PACKAGE_PATH__/logic/TaggingEngine.kt +26 -0
- package/template/base/app/src/main/java/__PACKAGE_PATH__/ui/NotesScreen.kt +185 -0
- package/template/base/app/src/main/java/__PACKAGE_PATH__/ui/NotesViewModel.kt +58 -0
- package/template/base/app/src/main/java/__PACKAGE_PATH__/ui/TestScreen.kt +12 -0
- package/template/base/app/src/main/res/values/colors.xml +10 -0
- package/template/base/app/src/main/res/values/strings.xml +4 -0
- package/template/base/app/src/main/res/values/themes.xml +9 -0
- package/template/base/app/src/main/res/xml/network_security_config.xml +8 -0
- package/template/base/app/src/main/res/xml/provider_paths.xml +4 -0
- package/template/base/build.gradle +28 -0
- package/template/base/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/template/base/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/template/base/gradle.properties +13 -0
- package/template/base/gradlew +248 -0
- package/template/base/gradlew.bat +92 -0
- package/template/base/jetstart.config.json +11 -0
- package/template/base/settings.gradle +20 -0
- package/.eslintrc.json +0 -6
- package/src/cli.ts +0 -99
- package/src/commands/android-emulator.ts +0 -304
- package/src/commands/build.ts +0 -60
- package/src/commands/create.ts +0 -232
- package/src/commands/dev.ts +0 -198
- package/src/commands/index.ts +0 -10
- package/src/commands/install-audit.ts +0 -227
- package/src/commands/logs.ts +0 -117
- package/src/index.ts +0 -17
- package/src/types/index.ts +0 -53
- package/src/utils/android-sdk.ts +0 -512
- package/src/utils/downloader.ts +0 -201
- package/src/utils/emulator-deployer.ts +0 -210
- package/src/utils/emulator.ts +0 -463
- package/src/utils/index.ts +0 -8
- package/src/utils/java.ts +0 -369
- package/src/utils/logger.ts +0 -42
- package/src/utils/open.ts +0 -36
- package/src/utils/prompt.ts +0 -56
- package/src/utils/spinner.ts +0 -25
- package/src/utils/system-tools.ts +0 -648
- package/src/utils/template.ts +0 -1214
- package/tests/create.test.ts +0 -33
- package/tests/utils.test.ts +0 -17
- package/tsconfig.json +0 -25
package/dist/utils/template.js
CHANGED
|
@@ -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
|
-
//
|
|
20
|
+
// Placeholder Variables
|
|
70
21
|
// ============================================================================
|
|
71
|
-
|
|
72
22
|
/**
|
|
73
|
-
*
|
|
74
|
-
* Represents UI elements in JSON format that can be interpreted at runtime
|
|
23
|
+
* Build the variable map from TemplateOptions
|
|
75
24
|
*/
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return
|
|
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
|
-
*
|
|
53
|
+
* Known binary/non-text extensions that should be copied without substitution.
|
|
130
54
|
*/
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
*
|
|
181
|
-
* Converts JSON DSL to Compose UI at runtime
|
|
77
|
+
* Replace all {{PLACEHOLDER}} variables in a text string.
|
|
182
78
|
*/
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
//
|
|
106
|
+
// Core Template Functions
|
|
465
107
|
// ============================================================================
|
|
466
|
-
|
|
467
108
|
/**
|
|
468
|
-
*
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
|
|
739
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
899
|
-
|
|
900
|
-
|
|
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', (
|
|
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
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
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
|