@jetstart/cli 1.1.1
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/.eslintrc.json +6 -0
- package/README.md +92 -0
- package/bin/jetstart.js +3 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +74 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/build.d.ts +12 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +53 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/create.d.ts +12 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +95 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/dev.d.ts +13 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +152 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/index.d.ts +8 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +15 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/logs.d.ts +13 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +103 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +20 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +24 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +47 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/prompt.d.ts +10 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +53 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/spinner.d.ts +9 -0
- package/dist/utils/spinner.d.ts.map +1 -0
- package/dist/utils/spinner.js +31 -0
- package/dist/utils/spinner.js.map +1 -0
- package/dist/utils/template.d.ts +7 -0
- package/dist/utils/template.d.ts.map +1 -0
- package/dist/utils/template.js +516 -0
- package/dist/utils/template.js.map +1 -0
- package/package.json +80 -0
- package/src/cli.ts +80 -0
- package/src/commands/build.ts +60 -0
- package/src/commands/create.ts +109 -0
- package/src/commands/dev.ts +138 -0
- package/src/commands/index.ts +8 -0
- package/src/commands/logs.ts +117 -0
- package/src/index.ts +17 -0
- package/src/types/index.ts +22 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logger.ts +42 -0
- package/src/utils/prompt.ts +56 -0
- package/src/utils/spinner.ts +25 -0
- package/src/utils/template.ts +635 -0
- package/tests/create.test.ts +33 -0
- package/tests/utils.test.ts +17 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Generator
|
|
3
|
+
* Creates project structure from templates
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import fs from 'fs-extra';
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
import { TemplateOptions } from '../types';
|
|
10
|
+
import { MIN_ANDROID_API_LEVEL, TARGET_ANDROID_API_LEVEL } from '@jetstart/shared';
|
|
11
|
+
|
|
12
|
+
export async function generateProjectTemplate(
|
|
13
|
+
projectPath: string,
|
|
14
|
+
options: TemplateOptions
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
const { projectName, packageName } = options;
|
|
17
|
+
|
|
18
|
+
// Create directory structure
|
|
19
|
+
await createDirectoryStructure(projectPath);
|
|
20
|
+
|
|
21
|
+
// Generate files
|
|
22
|
+
await generateRootBuildGradle(projectPath);
|
|
23
|
+
await generateBuildGradle(projectPath, options);
|
|
24
|
+
await generateSettingsGradle(projectPath, projectName);
|
|
25
|
+
await generateGradleProperties(projectPath);
|
|
26
|
+
await generateGradleWrapper(projectPath);
|
|
27
|
+
await generateMainActivity(projectPath, packageName);
|
|
28
|
+
await generateHotReload(projectPath, packageName);
|
|
29
|
+
await generateDSLInterpreter(projectPath, packageName);
|
|
30
|
+
await generateDSLTypes(projectPath, packageName);
|
|
31
|
+
await generateAndroidManifest(projectPath, options);
|
|
32
|
+
await generateResourceFiles(projectPath, projectName);
|
|
33
|
+
await generateLocalProperties(projectPath);
|
|
34
|
+
await generateJetStartConfig(projectPath, options);
|
|
35
|
+
await generateGitignore(projectPath);
|
|
36
|
+
await generateReadme(projectPath, projectName);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function createDirectoryStructure(projectPath: string): Promise<void> {
|
|
40
|
+
const dirs = [
|
|
41
|
+
'app/src/main/java',
|
|
42
|
+
'app/src/main/res/layout',
|
|
43
|
+
'app/src/main/res/values',
|
|
44
|
+
'app/src/main/res/drawable',
|
|
45
|
+
'gradle/wrapper',
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
for (const dir of dirs) {
|
|
49
|
+
await fs.ensureDir(path.join(projectPath, dir));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function generateBuildGradle(
|
|
54
|
+
projectPath: string,
|
|
55
|
+
options: TemplateOptions
|
|
56
|
+
): Promise<void> {
|
|
57
|
+
const content = `plugins {
|
|
58
|
+
id 'com.android.application'
|
|
59
|
+
id 'org.jetbrains.kotlin.android'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
android {
|
|
63
|
+
namespace '${options.packageName}'
|
|
64
|
+
compileSdk ${TARGET_ANDROID_API_LEVEL}
|
|
65
|
+
|
|
66
|
+
defaultConfig {
|
|
67
|
+
applicationId "${options.packageName}"
|
|
68
|
+
minSdk ${MIN_ANDROID_API_LEVEL}
|
|
69
|
+
targetSdk ${TARGET_ANDROID_API_LEVEL}
|
|
70
|
+
versionCode 1
|
|
71
|
+
versionName "1.0.0"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
buildTypes {
|
|
75
|
+
release {
|
|
76
|
+
minifyEnabled false
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
compileOptions {
|
|
81
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
82
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
kotlinOptions {
|
|
86
|
+
jvmTarget = '17'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
buildFeatures {
|
|
90
|
+
compose true
|
|
91
|
+
buildConfig true // Required for JetStart hot reload
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
composeOptions {
|
|
95
|
+
kotlinCompilerExtensionVersion = '1.5.6'
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
dependencies {
|
|
100
|
+
implementation 'androidx.core:core-ktx:1.12.0'
|
|
101
|
+
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
|
|
102
|
+
implementation 'androidx.activity:activity-compose:1.8.1'
|
|
103
|
+
implementation platform('androidx.compose:compose-bom:2023.10.01')
|
|
104
|
+
implementation 'androidx.compose.ui:ui'
|
|
105
|
+
implementation 'androidx.compose.ui:ui-graphics'
|
|
106
|
+
implementation 'androidx.compose.ui:ui-tooling-preview'
|
|
107
|
+
implementation 'androidx.compose.material3:material3'
|
|
108
|
+
|
|
109
|
+
// JetStart Hot Reload dependencies
|
|
110
|
+
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
|
111
|
+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
|
|
112
|
+
}`;
|
|
113
|
+
|
|
114
|
+
await fs.writeFile(path.join(projectPath, 'app/build.gradle'), content);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function generateSettingsGradle(
|
|
118
|
+
projectPath: string,
|
|
119
|
+
projectName: string
|
|
120
|
+
): Promise<void> {
|
|
121
|
+
const content = `rootProject.name = "${projectName}"
|
|
122
|
+
include ':app'`;
|
|
123
|
+
|
|
124
|
+
await fs.writeFile(path.join(projectPath, 'settings.gradle'), content);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function generateGradleProperties(projectPath: string): Promise<void> {
|
|
128
|
+
const content = `org.gradle.jvmargs=-Xmx2048m
|
|
129
|
+
android.useAndroidX=true
|
|
130
|
+
kotlin.code.style=official`;
|
|
131
|
+
|
|
132
|
+
await fs.writeFile(path.join(projectPath, 'gradle.properties'), content);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function generateMainActivity(
|
|
136
|
+
projectPath: string,
|
|
137
|
+
packageName: string
|
|
138
|
+
): Promise<void> {
|
|
139
|
+
const packagePath = packageName.replace(/\./g, '/');
|
|
140
|
+
const activityPath = path.join(
|
|
141
|
+
projectPath,
|
|
142
|
+
'app/src/main/java',
|
|
143
|
+
packagePath,
|
|
144
|
+
'MainActivity.kt'
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const content = `package ${packageName}
|
|
148
|
+
|
|
149
|
+
import android.os.Bundle
|
|
150
|
+
import androidx.activity.ComponentActivity
|
|
151
|
+
import androidx.activity.compose.setContent
|
|
152
|
+
import androidx.compose.foundation.layout.*
|
|
153
|
+
import androidx.compose.material3.*
|
|
154
|
+
import androidx.compose.runtime.*
|
|
155
|
+
import androidx.compose.ui.Alignment
|
|
156
|
+
import androidx.compose.ui.Modifier
|
|
157
|
+
import androidx.compose.ui.unit.dp
|
|
158
|
+
|
|
159
|
+
class MainActivity : ComponentActivity() {
|
|
160
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
161
|
+
super.onCreate(savedInstanceState)
|
|
162
|
+
|
|
163
|
+
// Initialize hot reload - reads from BuildConfig injected by jetstart dev
|
|
164
|
+
try {
|
|
165
|
+
val serverUrl = BuildConfig.JETSTART_SERVER_URL
|
|
166
|
+
val sessionId = BuildConfig.JETSTART_SESSION_ID
|
|
167
|
+
HotReload.connect(this, serverUrl, sessionId)
|
|
168
|
+
} catch (e: Exception) {
|
|
169
|
+
// BuildConfig not available yet, hot reload will be disabled
|
|
170
|
+
android.util.Log.w("MainActivity", "Hot reload not configured: \${e.message}")
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
setContent {
|
|
174
|
+
MaterialTheme {
|
|
175
|
+
Surface(
|
|
176
|
+
modifier = Modifier.fillMaxSize(),
|
|
177
|
+
color = MaterialTheme.colorScheme.background
|
|
178
|
+
) {
|
|
179
|
+
// Check if we should render from DSL (hot reload mode)
|
|
180
|
+
val dsl by DSLInterpreter.currentDSL.collectAsState()
|
|
181
|
+
|
|
182
|
+
if (dsl != null) {
|
|
183
|
+
// Hot reload mode: render from DSL sent by server
|
|
184
|
+
DSLInterpreter.RenderDSL(dsl!!)
|
|
185
|
+
} else {
|
|
186
|
+
// Normal mode: render actual Compose code
|
|
187
|
+
AppContent()
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
override fun onDestroy() {
|
|
195
|
+
super.onDestroy()
|
|
196
|
+
HotReload.disconnect()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Main App Content - REAL Kotlin Compose Code!
|
|
202
|
+
* This gets parsed to DSL and sent via hot reload
|
|
203
|
+
*/
|
|
204
|
+
@Composable
|
|
205
|
+
fun AppContent() {
|
|
206
|
+
Column(
|
|
207
|
+
modifier = Modifier
|
|
208
|
+
.fillMaxSize()
|
|
209
|
+
.padding(16.dp),
|
|
210
|
+
horizontalAlignment = Alignment.CenterHorizontally,
|
|
211
|
+
verticalArrangement = Arrangement.Center
|
|
212
|
+
) {
|
|
213
|
+
Text(
|
|
214
|
+
text = "Welcome to JetStart! 🚀",
|
|
215
|
+
style = MaterialTheme.typography.headlineMedium
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
Spacer(modifier = Modifier.height(16.dp))
|
|
219
|
+
|
|
220
|
+
Text(
|
|
221
|
+
text = "Edit this code and save to see hot reload!",
|
|
222
|
+
style = MaterialTheme.typography.bodyMedium
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
Spacer(modifier = Modifier.height(24.dp))
|
|
226
|
+
|
|
227
|
+
Button(
|
|
228
|
+
onClick = { /* Handle click */ },
|
|
229
|
+
modifier = Modifier.fillMaxWidth()
|
|
230
|
+
) {
|
|
231
|
+
Text("Click Me!")
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}`;
|
|
235
|
+
|
|
236
|
+
await fs.ensureDir(path.dirname(activityPath));
|
|
237
|
+
await fs.writeFile(activityPath, content);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function generateAndroidManifest(
|
|
241
|
+
projectPath: string,
|
|
242
|
+
options: TemplateOptions
|
|
243
|
+
): Promise<void> {
|
|
244
|
+
const themeName = options.projectName.replace(/[^a-zA-Z0-9]/g, '');
|
|
245
|
+
const content = `<?xml version="1.0" encoding="utf-8"?>
|
|
246
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
247
|
+
|
|
248
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
249
|
+
|
|
250
|
+
<application
|
|
251
|
+
android:allowBackup="true"
|
|
252
|
+
android:label="@string/app_name"
|
|
253
|
+
android:theme="@style/Theme.${themeName}"
|
|
254
|
+
android:networkSecurityConfig="@xml/network_security_config">
|
|
255
|
+
<activity
|
|
256
|
+
android:name=".MainActivity"
|
|
257
|
+
android:exported="true">
|
|
258
|
+
<intent-filter>
|
|
259
|
+
<action android:name="android.intent.action.MAIN" />
|
|
260
|
+
<category android:name="android.intent.category.LAUNCHER" />
|
|
261
|
+
</intent-filter>
|
|
262
|
+
</activity>
|
|
263
|
+
</application>
|
|
264
|
+
|
|
265
|
+
</manifest>`;
|
|
266
|
+
|
|
267
|
+
await fs.writeFile(
|
|
268
|
+
path.join(projectPath, 'app/src/main/AndroidManifest.xml'),
|
|
269
|
+
content
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function generateJetStartConfig(
|
|
274
|
+
projectPath: string,
|
|
275
|
+
options: TemplateOptions
|
|
276
|
+
): Promise<void> {
|
|
277
|
+
const config = {
|
|
278
|
+
projectName: options.projectName,
|
|
279
|
+
packageName: options.packageName,
|
|
280
|
+
version: '1.0.0',
|
|
281
|
+
jetstart: {
|
|
282
|
+
version: '0.1.0',
|
|
283
|
+
enableHotReload: true,
|
|
284
|
+
enableLogs: true,
|
|
285
|
+
port: 8765,
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
await fs.writeJSON(
|
|
290
|
+
path.join(projectPath, 'jetstart.config.json'),
|
|
291
|
+
config,
|
|
292
|
+
{ spaces: 2 }
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function generateGitignore(projectPath: string): Promise<void> {
|
|
297
|
+
const content = `# Build
|
|
298
|
+
/build
|
|
299
|
+
/app/build
|
|
300
|
+
.gradle
|
|
301
|
+
*.hprof
|
|
302
|
+
|
|
303
|
+
# IDE
|
|
304
|
+
.idea
|
|
305
|
+
*.iml
|
|
306
|
+
.vscode
|
|
307
|
+
.DS_Store
|
|
308
|
+
|
|
309
|
+
# Claude Code
|
|
310
|
+
.claude
|
|
311
|
+
.claude-worktrees
|
|
312
|
+
|
|
313
|
+
# JetStart
|
|
314
|
+
.jetstart
|
|
315
|
+
|
|
316
|
+
# Android
|
|
317
|
+
local.properties
|
|
318
|
+
*.apk
|
|
319
|
+
*.aab
|
|
320
|
+
*.ap_
|
|
321
|
+
*.dex
|
|
322
|
+
*.class
|
|
323
|
+
bin/
|
|
324
|
+
gen/
|
|
325
|
+
out/
|
|
326
|
+
captures/
|
|
327
|
+
.externalNativeBuild
|
|
328
|
+
.cxx
|
|
329
|
+
|
|
330
|
+
# Log files
|
|
331
|
+
*.log
|
|
332
|
+
|
|
333
|
+
# Keystore files
|
|
334
|
+
*.jks
|
|
335
|
+
*.keystore`;
|
|
336
|
+
|
|
337
|
+
await fs.writeFile(path.join(projectPath, '.gitignore'), content);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function generateReadme(projectPath: string, projectName: string): Promise<void> {
|
|
341
|
+
const content = `# ${projectName}
|
|
342
|
+
|
|
343
|
+
A JetStart project with Kotlin and Jetpack Compose.
|
|
344
|
+
|
|
345
|
+
## Getting Started
|
|
346
|
+
|
|
347
|
+
\`\`\`bash
|
|
348
|
+
# Start development server
|
|
349
|
+
jetstart dev
|
|
350
|
+
|
|
351
|
+
# Build production APK
|
|
352
|
+
jetstart build
|
|
353
|
+
|
|
354
|
+
# View logs
|
|
355
|
+
jetstart logs
|
|
356
|
+
\`\`\`
|
|
357
|
+
|
|
358
|
+
## Project Structure
|
|
359
|
+
|
|
360
|
+
\`\`\`
|
|
361
|
+
${projectName}/
|
|
362
|
+
├── app/
|
|
363
|
+
│ └── src/
|
|
364
|
+
│ └── main/
|
|
365
|
+
│ ├── java/ # Kotlin source files
|
|
366
|
+
│ └── res/ # Resources
|
|
367
|
+
├── jetstart.config.json # JetStart configuration
|
|
368
|
+
└── build.gradle # Gradle build file
|
|
369
|
+
\`\`\`
|
|
370
|
+
|
|
371
|
+
## Learn More
|
|
372
|
+
|
|
373
|
+
- [JetStart Documentation](https://github.com/phantom/jetstart)
|
|
374
|
+
- [Jetpack Compose](https://developer.android.com/jetpack/compose)
|
|
375
|
+
`;
|
|
376
|
+
|
|
377
|
+
await fs.writeFile(path.join(projectPath, 'README.md'), content);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function generateRootBuildGradle(projectPath: string): Promise<void> {
|
|
381
|
+
const content = `// Top-level build file
|
|
382
|
+
buildscript {
|
|
383
|
+
ext {
|
|
384
|
+
kotlin_version = '1.9.21'
|
|
385
|
+
compose_version = '1.5.4'
|
|
386
|
+
}
|
|
387
|
+
repositories {
|
|
388
|
+
google()
|
|
389
|
+
mavenCentral()
|
|
390
|
+
}
|
|
391
|
+
dependencies {
|
|
392
|
+
classpath 'com.android.tools.build:gradle:8.2.0'
|
|
393
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
allprojects {
|
|
398
|
+
repositories {
|
|
399
|
+
google()
|
|
400
|
+
mavenCentral()
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
task clean(type: Delete) {
|
|
405
|
+
delete rootProject.buildDir
|
|
406
|
+
}`;
|
|
407
|
+
|
|
408
|
+
await fs.writeFile(path.join(projectPath, 'build.gradle'), content);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async function generateGradleWrapper(projectPath: string): Promise<void> {
|
|
412
|
+
// Use system Gradle to initialize proper wrapper
|
|
413
|
+
// This generates:
|
|
414
|
+
// - gradle/wrapper/gradle-wrapper.jar
|
|
415
|
+
// - gradle/wrapper/gradle-wrapper.properties
|
|
416
|
+
// - gradlew (Unix shell script)
|
|
417
|
+
// - gradlew.bat (Windows batch script)
|
|
418
|
+
|
|
419
|
+
return new Promise<void>((resolve) => {
|
|
420
|
+
// Try to use system gradle to generate wrapper
|
|
421
|
+
const gradleCmd = process.platform === 'win32' ? 'gradle.bat' : 'gradle';
|
|
422
|
+
|
|
423
|
+
const gradleProcess = spawn(gradleCmd, ['wrapper', '--gradle-version', '8.2'], {
|
|
424
|
+
cwd: projectPath,
|
|
425
|
+
shell: true,
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
gradleProcess.on('close', (code) => {
|
|
429
|
+
// Continue regardless of success/failure
|
|
430
|
+
// If gradle wrapper command fails, the build will fall back to system gradle
|
|
431
|
+
resolve();
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
gradleProcess.on('error', () => {
|
|
435
|
+
// Continue even if gradle command not found
|
|
436
|
+
resolve();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// Timeout after 30 seconds
|
|
440
|
+
setTimeout(() => {
|
|
441
|
+
gradleProcess.kill();
|
|
442
|
+
resolve();
|
|
443
|
+
}, 30000);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
async function generateResourceFiles(
|
|
448
|
+
projectPath: string,
|
|
449
|
+
projectName: string
|
|
450
|
+
): Promise<void> {
|
|
451
|
+
// Generate strings.xml
|
|
452
|
+
const stringsXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
453
|
+
<resources>
|
|
454
|
+
<string name="app_name">${projectName}</string>
|
|
455
|
+
</resources>`;
|
|
456
|
+
|
|
457
|
+
await fs.writeFile(
|
|
458
|
+
path.join(projectPath, 'app/src/main/res/values/strings.xml'),
|
|
459
|
+
stringsXml
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
// Generate colors.xml
|
|
463
|
+
const colorsXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
464
|
+
<resources>
|
|
465
|
+
<color name="purple_200">#FFBB86FC</color>
|
|
466
|
+
<color name="purple_500">#FF6200EE</color>
|
|
467
|
+
<color name="purple_700">#FF3700B3</color>
|
|
468
|
+
<color name="teal_200">#FF03DAC5</color>
|
|
469
|
+
<color name="teal_700">#FF018786</color>
|
|
470
|
+
<color name="black">#FF000000</color>
|
|
471
|
+
<color name="white">#FFFFFFFF</color>
|
|
472
|
+
</resources>`;
|
|
473
|
+
|
|
474
|
+
await fs.writeFile(
|
|
475
|
+
path.join(projectPath, 'app/src/main/res/values/colors.xml'),
|
|
476
|
+
colorsXml
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
// Generate themes.xml
|
|
480
|
+
const themesXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
481
|
+
<resources>
|
|
482
|
+
<style name="Theme.${projectName.replace(/[^a-zA-Z0-9]/g, '')}" parent="android:Theme.Material.Light.NoActionBar" />
|
|
483
|
+
</resources>`;
|
|
484
|
+
|
|
485
|
+
await fs.writeFile(
|
|
486
|
+
path.join(projectPath, 'app/src/main/res/values/themes.xml'),
|
|
487
|
+
themesXml
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
// Generate network_security_config.xml for development (allows cleartext traffic)
|
|
491
|
+
const networkSecurityConfig = `<?xml version="1.0" encoding="utf-8"?>
|
|
492
|
+
<network-security-config>
|
|
493
|
+
<base-config cleartextTrafficPermitted="true">
|
|
494
|
+
<trust-anchors>
|
|
495
|
+
<certificates src="system" />
|
|
496
|
+
</trust-anchors>
|
|
497
|
+
</base-config>
|
|
498
|
+
</network-security-config>`;
|
|
499
|
+
|
|
500
|
+
await fs.ensureDir(path.join(projectPath, 'app/src/main/res/xml'));
|
|
501
|
+
await fs.writeFile(
|
|
502
|
+
path.join(projectPath, 'app/src/main/res/xml/network_security_config.xml'),
|
|
503
|
+
networkSecurityConfig
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async function generateLocalProperties(projectPath: string): Promise<void> {
|
|
508
|
+
// Auto-detect Android SDK location
|
|
509
|
+
let androidSdkPath: string | undefined;
|
|
510
|
+
|
|
511
|
+
// Check environment variables first
|
|
512
|
+
androidSdkPath = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
|
|
513
|
+
|
|
514
|
+
// If not found, check common Windows locations
|
|
515
|
+
if (!androidSdkPath && process.platform === 'win32') {
|
|
516
|
+
const commonPaths = [
|
|
517
|
+
'C:\\Android',
|
|
518
|
+
path.join(require('os').homedir(), 'AppData', 'Local', 'Android', 'Sdk'),
|
|
519
|
+
'C:\\Android\\Sdk',
|
|
520
|
+
'C:\\Program Files (x86)\\Android\\android-sdk',
|
|
521
|
+
];
|
|
522
|
+
|
|
523
|
+
for (const p of commonPaths) {
|
|
524
|
+
if (fs.existsSync(p)) {
|
|
525
|
+
androidSdkPath = p;
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// If not found on macOS/Linux, check common paths
|
|
532
|
+
if (!androidSdkPath && process.platform !== 'win32') {
|
|
533
|
+
const commonPaths = [
|
|
534
|
+
path.join(require('os').homedir(), 'Android', 'Sdk'),
|
|
535
|
+
path.join(require('os').homedir(), 'Library', 'Android', 'sdk'),
|
|
536
|
+
'/opt/android-sdk',
|
|
537
|
+
];
|
|
538
|
+
|
|
539
|
+
for (const p of commonPaths) {
|
|
540
|
+
if (fs.existsSync(p)) {
|
|
541
|
+
androidSdkPath = p;
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (!androidSdkPath) {
|
|
548
|
+
console.warn('[Warning] Android SDK not found. You may need to set ANDROID_HOME or create local.properties manually.');
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Create local.properties with SDK path
|
|
553
|
+
const content = `# Auto-generated by JetStart
|
|
554
|
+
sdk.dir=${androidSdkPath.replace(/\\/g, '\\\\')}
|
|
555
|
+
`;
|
|
556
|
+
|
|
557
|
+
await fs.writeFile(path.join(projectPath, 'local.properties'), content);
|
|
558
|
+
console.log(`[JetStart] Created local.properties with SDK: ${androidSdkPath}`);
|
|
559
|
+
}
|
|
560
|
+
async function generateHotReload(
|
|
561
|
+
projectPath: string,
|
|
562
|
+
packageName: string
|
|
563
|
+
): Promise<void> {
|
|
564
|
+
const packagePath = packageName.replace(/\./g, '/');
|
|
565
|
+
const hotReloadPath = path.join(
|
|
566
|
+
projectPath,
|
|
567
|
+
'app/src/main/java',
|
|
568
|
+
packagePath,
|
|
569
|
+
'HotReload.kt'
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
// Copy from my-app template
|
|
573
|
+
const sourceFile = path.join(__dirname, '../../../../my-app/app/src/main/java/com/jetstart/myapp/HotReload.kt');
|
|
574
|
+
|
|
575
|
+
try {
|
|
576
|
+
let content = await fs.readFile(sourceFile, 'utf-8');
|
|
577
|
+
// Replace package name
|
|
578
|
+
content = content.replace(/package com\.jetstart\.myapp/g, `package ${packageName}`);
|
|
579
|
+
|
|
580
|
+
await fs.ensureDir(path.dirname(hotReloadPath));
|
|
581
|
+
await fs.writeFile(hotReloadPath, content);
|
|
582
|
+
} catch (error) {
|
|
583
|
+
console.warn('[Warning] Could not copy HotReload.kt from my-app. You may need to add it manually.');
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
async function generateDSLInterpreter(
|
|
588
|
+
projectPath: string,
|
|
589
|
+
packageName: string
|
|
590
|
+
): Promise<void> {
|
|
591
|
+
const packagePath = packageName.replace(/\./g, '/');
|
|
592
|
+
const interpreterPath = path.join(
|
|
593
|
+
projectPath,
|
|
594
|
+
'app/src/main/java',
|
|
595
|
+
packagePath,
|
|
596
|
+
'DSLInterpreter.kt'
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
const sourceFile = path.join(__dirname, '../../../../my-app/app/src/main/java/com/jetstart/myapp/DSLInterpreter.kt');
|
|
600
|
+
|
|
601
|
+
try {
|
|
602
|
+
let content = await fs.readFile(sourceFile, 'utf-8');
|
|
603
|
+
content = content.replace(/package com\.jetstart\.myapp/g, `package ${packageName}`);
|
|
604
|
+
|
|
605
|
+
await fs.ensureDir(path.dirname(interpreterPath));
|
|
606
|
+
await fs.writeFile(interpreterPath, content);
|
|
607
|
+
} catch (error) {
|
|
608
|
+
console.warn('[Warning] Could not copy DSLInterpreter.kt from my-app. You may need to add it manually.');
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
async function generateDSLTypes(
|
|
613
|
+
projectPath: string,
|
|
614
|
+
packageName: string
|
|
615
|
+
): Promise<void> {
|
|
616
|
+
const packagePath = packageName.replace(/\./g, '/');
|
|
617
|
+
const typesPath = path.join(
|
|
618
|
+
projectPath,
|
|
619
|
+
'app/src/main/java',
|
|
620
|
+
packagePath,
|
|
621
|
+
'DSLTypes.kt'
|
|
622
|
+
);
|
|
623
|
+
|
|
624
|
+
const sourceFile = path.join(__dirname, '../../../../my-app/app/src/main/java/com/jetstart/myapp/DSLTypes.kt');
|
|
625
|
+
|
|
626
|
+
try {
|
|
627
|
+
let content = await fs.readFile(sourceFile, 'utf-8');
|
|
628
|
+
content = content.replace(/package com\.jetstart\.myapp/g, `package ${packageName}`);
|
|
629
|
+
|
|
630
|
+
await fs.ensureDir(path.dirname(typesPath));
|
|
631
|
+
await fs.writeFile(typesPath, content);
|
|
632
|
+
} catch (error) {
|
|
633
|
+
console.warn('[Warning] Could not copy DSLTypes.kt from my-app. You may need to add it manually.');
|
|
634
|
+
}
|
|
635
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for create command
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { isValidProjectName, isValidPackageName } from '@jetstart/shared';
|
|
6
|
+
|
|
7
|
+
describe('Create Command', () => {
|
|
8
|
+
describe('Project name validation', () => {
|
|
9
|
+
it('should accept valid project names', () => {
|
|
10
|
+
expect(isValidProjectName('myApp')).toBe(true);
|
|
11
|
+
expect(isValidProjectName('MyProject123')).toBe(true);
|
|
12
|
+
expect(isValidProjectName('test-app')).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should reject invalid project names', () => {
|
|
16
|
+
expect(isValidProjectName('123app')).toBe(false);
|
|
17
|
+
expect(isValidProjectName('my app')).toBe(false);
|
|
18
|
+
expect(isValidProjectName('')).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('Package name validation', () => {
|
|
23
|
+
it('should accept valid package names', () => {
|
|
24
|
+
expect(isValidPackageName('com.example.app')).toBe(true);
|
|
25
|
+
expect(isValidPackageName('com.company.myapp')).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should reject invalid package names', () => {
|
|
29
|
+
expect(isValidPackageName('InvalidName')).toBe(false);
|
|
30
|
+
expect(isValidPackageName('com')).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for utility functions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
describe('Logger Utilities', () => {
|
|
6
|
+
it('should format log messages correctly', () => {
|
|
7
|
+
// Logger tests would go here
|
|
8
|
+
expect(true).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('Spinner Utilities', () => {
|
|
13
|
+
it('should create and control spinners', () => {
|
|
14
|
+
// Spinner tests would go here
|
|
15
|
+
expect(true).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2022"],
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"allowSyntheticDefaultImports": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true,
|
|
16
|
+
"composite": true,
|
|
17
|
+
"outDir": "./dist",
|
|
18
|
+
"rootDir": "./src"
|
|
19
|
+
},
|
|
20
|
+
"references": [
|
|
21
|
+
{ "path": "../shared" }
|
|
22
|
+
],
|
|
23
|
+
"include": ["src/**/*"],
|
|
24
|
+
"exclude": ["node_modules", "dist", "tests"]
|
|
25
|
+
}
|