@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.
Files changed (72) hide show
  1. package/.eslintrc.json +6 -0
  2. package/README.md +92 -0
  3. package/bin/jetstart.js +3 -0
  4. package/dist/cli.d.ts +7 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +74 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/build.d.ts +12 -0
  9. package/dist/commands/build.d.ts.map +1 -0
  10. package/dist/commands/build.js +53 -0
  11. package/dist/commands/build.js.map +1 -0
  12. package/dist/commands/create.d.ts +12 -0
  13. package/dist/commands/create.d.ts.map +1 -0
  14. package/dist/commands/create.js +95 -0
  15. package/dist/commands/create.js.map +1 -0
  16. package/dist/commands/dev.d.ts +13 -0
  17. package/dist/commands/dev.d.ts.map +1 -0
  18. package/dist/commands/dev.js +152 -0
  19. package/dist/commands/dev.js.map +1 -0
  20. package/dist/commands/index.d.ts +8 -0
  21. package/dist/commands/index.d.ts.map +1 -0
  22. package/dist/commands/index.js +15 -0
  23. package/dist/commands/index.js.map +1 -0
  24. package/dist/commands/logs.d.ts +13 -0
  25. package/dist/commands/logs.d.ts.map +1 -0
  26. package/dist/commands/logs.js +103 -0
  27. package/dist/commands/logs.js.map +1 -0
  28. package/dist/index.d.ts +8 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +23 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/types/index.d.ts +20 -0
  33. package/dist/types/index.d.ts.map +1 -0
  34. package/dist/types/index.js +6 -0
  35. package/dist/types/index.js.map +1 -0
  36. package/dist/utils/index.d.ts +8 -0
  37. package/dist/utils/index.d.ts.map +1 -0
  38. package/dist/utils/index.js +24 -0
  39. package/dist/utils/index.js.map +1 -0
  40. package/dist/utils/logger.d.ts +12 -0
  41. package/dist/utils/logger.d.ts.map +1 -0
  42. package/dist/utils/logger.js +47 -0
  43. package/dist/utils/logger.js.map +1 -0
  44. package/dist/utils/prompt.d.ts +10 -0
  45. package/dist/utils/prompt.d.ts.map +1 -0
  46. package/dist/utils/prompt.js +53 -0
  47. package/dist/utils/prompt.js.map +1 -0
  48. package/dist/utils/spinner.d.ts +9 -0
  49. package/dist/utils/spinner.d.ts.map +1 -0
  50. package/dist/utils/spinner.js +31 -0
  51. package/dist/utils/spinner.js.map +1 -0
  52. package/dist/utils/template.d.ts +7 -0
  53. package/dist/utils/template.d.ts.map +1 -0
  54. package/dist/utils/template.js +516 -0
  55. package/dist/utils/template.js.map +1 -0
  56. package/package.json +80 -0
  57. package/src/cli.ts +80 -0
  58. package/src/commands/build.ts +60 -0
  59. package/src/commands/create.ts +109 -0
  60. package/src/commands/dev.ts +138 -0
  61. package/src/commands/index.ts +8 -0
  62. package/src/commands/logs.ts +117 -0
  63. package/src/index.ts +17 -0
  64. package/src/types/index.ts +22 -0
  65. package/src/utils/index.ts +8 -0
  66. package/src/utils/logger.ts +42 -0
  67. package/src/utils/prompt.ts +56 -0
  68. package/src/utils/spinner.ts +25 -0
  69. package/src/utils/template.ts +635 -0
  70. package/tests/create.test.ts +33 -0
  71. package/tests/utils.test.ts +17 -0
  72. 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
+ }