@jetstart/cli 1.7.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +133 -41
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +11 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/commands/android-emulator.d.ts.map +1 -0
  6. package/dist/commands/android-emulator.js.map +1 -0
  7. package/dist/commands/build.d.ts +13 -1
  8. package/dist/commands/build.d.ts.map +1 -0
  9. package/dist/commands/build.js +279 -29
  10. package/dist/commands/build.js.map +1 -0
  11. package/dist/commands/clean.d.ts +23 -0
  12. package/dist/commands/clean.d.ts.map +1 -0
  13. package/dist/commands/clean.js +191 -0
  14. package/dist/commands/clean.js.map +1 -0
  15. package/dist/commands/create.d.ts.map +1 -0
  16. package/dist/commands/create.js +41 -0
  17. package/dist/commands/create.js.map +1 -0
  18. package/dist/commands/dev.d.ts.map +1 -0
  19. package/dist/commands/dev.js +51 -9
  20. package/dist/commands/dev.js.map +1 -0
  21. package/dist/commands/index.d.ts.map +1 -0
  22. package/dist/commands/index.js.map +1 -0
  23. package/dist/commands/install-audit.d.ts.map +1 -0
  24. package/dist/commands/install-audit.js.map +1 -0
  25. package/dist/commands/logs.d.ts.map +1 -0
  26. package/dist/commands/logs.js.map +1 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/types/index.d.ts.map +1 -0
  30. package/dist/types/index.js.map +1 -0
  31. package/dist/utils/android-sdk.d.ts.map +1 -0
  32. package/dist/utils/android-sdk.js +2 -2
  33. package/dist/utils/android-sdk.js.map +1 -0
  34. package/dist/utils/downloader.d.ts.map +1 -0
  35. package/dist/utils/downloader.js.map +1 -0
  36. package/dist/utils/emulator-deployer.d.ts.map +1 -0
  37. package/dist/utils/emulator-deployer.js.map +1 -0
  38. package/dist/utils/emulator.d.ts.map +1 -0
  39. package/dist/utils/emulator.js +5 -4
  40. package/dist/utils/emulator.js.map +1 -0
  41. package/dist/utils/index.d.ts.map +1 -0
  42. package/dist/utils/index.js.map +1 -0
  43. package/dist/utils/java.d.ts.map +1 -0
  44. package/dist/utils/java.js +5 -5
  45. package/dist/utils/java.js.map +1 -0
  46. package/dist/utils/logger.d.ts.map +1 -0
  47. package/dist/utils/logger.js.map +1 -0
  48. package/dist/utils/open.d.ts.map +1 -0
  49. package/dist/utils/open.js.map +1 -0
  50. package/dist/utils/prompt.d.ts.map +1 -0
  51. package/dist/utils/prompt.js.map +1 -0
  52. package/dist/utils/spinner.d.ts.map +1 -0
  53. package/dist/utils/spinner.js.map +1 -0
  54. package/dist/utils/system-tools.d.ts.map +1 -0
  55. package/dist/utils/system-tools.js.map +1 -0
  56. package/dist/utils/template.d.ts +13 -1
  57. package/dist/utils/template.d.ts.map +1 -0
  58. package/dist/utils/template.js +134 -1000
  59. package/dist/utils/template.js.map +1 -0
  60. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/1.0.0/com.jetstart.hot-reload.gradle.plugin-1.0.0.pom +15 -0
  61. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/1.0.0/com.jetstart.hot-reload.gradle.plugin-1.0.0.pom.md5 +1 -0
  62. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/1.0.0/com.jetstart.hot-reload.gradle.plugin-1.0.0.pom.sha1 +1 -0
  63. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/1.0.0/com.jetstart.hot-reload.gradle.plugin-1.0.0.pom.sha256 +1 -0
  64. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/1.0.0/com.jetstart.hot-reload.gradle.plugin-1.0.0.pom.sha512 +1 -0
  65. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/maven-metadata.xml +13 -0
  66. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/maven-metadata.xml.md5 +1 -0
  67. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/maven-metadata.xml.sha1 +1 -0
  68. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/maven-metadata.xml.sha256 +1 -0
  69. package/maven-repo/com/jetstart/hot-reload/com.jetstart.hot-reload.gradle.plugin/maven-metadata.xml.sha512 +1 -0
  70. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0-sources.jar +0 -0
  71. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0-sources.jar.md5 +1 -0
  72. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0-sources.jar.sha1 +1 -0
  73. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0-sources.jar.sha256 +1 -0
  74. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0-sources.jar.sha512 +1 -0
  75. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.aar +0 -0
  76. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.aar.md5 +1 -0
  77. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.aar.sha1 +1 -0
  78. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.aar.sha256 +1 -0
  79. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.aar.sha512 +1 -0
  80. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.module +124 -0
  81. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.module.md5 +1 -0
  82. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.module.sha1 +1 -0
  83. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.module.sha256 +1 -0
  84. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.module.sha512 +1 -0
  85. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.pom +46 -0
  86. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.pom.md5 +1 -0
  87. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.pom.sha1 +1 -0
  88. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.pom.sha256 +1 -0
  89. package/maven-repo/com/jetstart/hot-reload-runtime/1.0.0/hot-reload-runtime-1.0.0.pom.sha512 +1 -0
  90. package/maven-repo/com/jetstart/hot-reload-runtime/maven-metadata.xml +13 -0
  91. package/maven-repo/com/jetstart/hot-reload-runtime/maven-metadata.xml.md5 +1 -0
  92. package/maven-repo/com/jetstart/hot-reload-runtime/maven-metadata.xml.sha1 +1 -0
  93. package/maven-repo/com/jetstart/hot-reload-runtime/maven-metadata.xml.sha256 +1 -0
  94. package/maven-repo/com/jetstart/hot-reload-runtime/maven-metadata.xml.sha512 +1 -0
  95. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.jar +0 -0
  96. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.jar.md5 +1 -0
  97. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.jar.sha1 +1 -0
  98. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.jar.sha256 +1 -0
  99. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.jar.sha512 +1 -0
  100. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.module +103 -0
  101. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.module.md5 +1 -0
  102. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.module.sha1 +1 -0
  103. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.module.sha256 +1 -0
  104. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.module.sha512 +1 -0
  105. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.pom +38 -0
  106. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.pom.md5 +1 -0
  107. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.pom.sha1 +1 -0
  108. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.pom.sha256 +1 -0
  109. package/maven-repo/com/jetstart/jetstart-gradle-plugin/1.0.0/jetstart-gradle-plugin-1.0.0.pom.sha512 +1 -0
  110. package/maven-repo/com/jetstart/jetstart-gradle-plugin/maven-metadata.xml +13 -0
  111. package/maven-repo/com/jetstart/jetstart-gradle-plugin/maven-metadata.xml.md5 +1 -0
  112. package/maven-repo/com/jetstart/jetstart-gradle-plugin/maven-metadata.xml.sha1 +1 -0
  113. package/maven-repo/com/jetstart/jetstart-gradle-plugin/maven-metadata.xml.sha256 +1 -0
  114. package/maven-repo/com/jetstart/jetstart-gradle-plugin/maven-metadata.xml.sha512 +1 -0
  115. package/package.json +14 -5
  116. package/scripts/build-java.js +34 -0
  117. package/template/base/README.md +34 -0
  118. package/template/base/app/build.gradle +80 -0
  119. package/template/base/app/proguard-rules.pro +33 -0
  120. package/template/base/app/src/main/AndroidManifest.xml +33 -0
  121. package/template/base/app/src/main/java/__PACKAGE_PATH__/MainActivity.kt +64 -0
  122. package/template/base/app/src/main/java/__PACKAGE_PATH__/data/AppDatabase.kt +46 -0
  123. package/template/base/app/src/main/java/__PACKAGE_PATH__/data/Note.kt +12 -0
  124. package/template/base/app/src/main/java/__PACKAGE_PATH__/data/NoteDao.kt +23 -0
  125. package/template/base/app/src/main/java/__PACKAGE_PATH__/logic/TaggingEngine.kt +26 -0
  126. package/template/base/app/src/main/java/__PACKAGE_PATH__/ui/NotesScreen.kt +185 -0
  127. package/template/base/app/src/main/java/__PACKAGE_PATH__/ui/NotesViewModel.kt +58 -0
  128. package/template/base/app/src/main/java/__PACKAGE_PATH__/ui/TestScreen.kt +12 -0
  129. package/template/base/app/src/main/res/values/colors.xml +10 -0
  130. package/template/base/app/src/main/res/values/strings.xml +4 -0
  131. package/template/base/app/src/main/res/values/themes.xml +9 -0
  132. package/template/base/app/src/main/res/xml/network_security_config.xml +8 -0
  133. package/template/base/app/src/main/res/xml/provider_paths.xml +4 -0
  134. package/template/base/build.gradle +28 -0
  135. package/template/base/gradle/wrapper/gradle-wrapper.jar +0 -0
  136. package/template/base/gradle/wrapper/gradle-wrapper.properties +7 -0
  137. package/template/base/gradle.properties +13 -0
  138. package/template/base/gradlew +248 -0
  139. package/template/base/gradlew.bat +92 -0
  140. package/template/base/jetstart.config.json +11 -0
  141. package/template/base/settings.gradle +20 -0
  142. package/.eslintrc.json +0 -6
  143. package/src/cli.ts +0 -99
  144. package/src/commands/android-emulator.ts +0 -304
  145. package/src/commands/build.ts +0 -60
  146. package/src/commands/create.ts +0 -232
  147. package/src/commands/dev.ts +0 -198
  148. package/src/commands/index.ts +0 -10
  149. package/src/commands/install-audit.ts +0 -227
  150. package/src/commands/logs.ts +0 -117
  151. package/src/index.ts +0 -17
  152. package/src/types/index.ts +0 -53
  153. package/src/utils/android-sdk.ts +0 -512
  154. package/src/utils/downloader.ts +0 -201
  155. package/src/utils/emulator-deployer.ts +0 -210
  156. package/src/utils/emulator.ts +0 -463
  157. package/src/utils/index.ts +0 -8
  158. package/src/utils/java.ts +0 -369
  159. package/src/utils/logger.ts +0 -42
  160. package/src/utils/open.ts +0 -36
  161. package/src/utils/prompt.ts +0 -56
  162. package/src/utils/spinner.ts +0 -25
  163. package/src/utils/system-tools.ts +0 -648
  164. package/src/utils/template.ts +0 -1214
  165. package/tests/create.test.ts +0 -33
  166. package/tests/utils.test.ts +0 -17
  167. package/tsconfig.json +0 -25
@@ -1,512 +0,0 @@
1
- /**
2
- * Android SDK Manager wrapper
3
- */
4
-
5
- import { spawn, ChildProcess } from 'child_process';
6
- import * as fs from 'fs-extra';
7
- import * as path from 'path';
8
- import * as os from 'os';
9
- import { downloadAndExtract } from './downloader';
10
- import { findAndroidSDK, getDefaultSDKPath } from './system-tools';
11
- import { startSpinner, stopSpinner, updateSpinner } from './spinner';
12
- import { success, error as logError, warning, info } from './logger';
13
-
14
- export interface SDKComponent {
15
- name: string;
16
- path: string;
17
- version?: string;
18
- installed: boolean;
19
- }
20
-
21
- /**
22
- * Required SDK components for JetStart
23
- */
24
- export const REQUIRED_SDK_COMPONENTS = [
25
- 'platform-tools', // adb, fastboot
26
- 'platforms;android-34', // Target API
27
- 'platforms;android-24', // Minimum API
28
- 'build-tools;34.0.0', // Latest build tools
29
- 'emulator', // Android Emulator
30
- 'system-images;android-34;google_apis;x86_64', // For AVD
31
- ];
32
-
33
- /**
34
- * Android cmdline-tools download URLs
35
- */
36
- const CMDLINE_TOOLS_URLS = {
37
- win32: 'https://dl.google.com/android/repository/commandlinetools-win-11076708_latest.zip',
38
- darwin: 'https://dl.google.com/android/repository/commandlinetools-mac-11076708_latest.zip',
39
- linux: 'https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip',
40
- };
41
-
42
- /**
43
- * Android SDK Manager class
44
- */
45
- export class AndroidSDKManager {
46
- private sdkRoot: string;
47
-
48
- constructor(sdkRoot?: string) {
49
- this.sdkRoot = sdkRoot || '';
50
- }
51
-
52
- /**
53
- * Initialize SDK root, detecting or creating as needed
54
- */
55
- async ensureSDKRoot(): Promise<string> {
56
- if (this.sdkRoot && await fs.pathExists(this.sdkRoot)) {
57
- return this.sdkRoot;
58
- }
59
-
60
- // Try to find existing SDK
61
- const existingSDK = await findAndroidSDK();
62
- if (existingSDK) {
63
- this.sdkRoot = existingSDK;
64
- return this.sdkRoot;
65
- }
66
-
67
- // Create new SDK at default location
68
- this.sdkRoot = getDefaultSDKPath();
69
- await fs.ensureDir(this.sdkRoot);
70
-
71
- info(`Creating Android SDK at: ${this.sdkRoot}`);
72
-
73
- return this.sdkRoot;
74
- }
75
-
76
- /**
77
- * Install Android cmdline-tools
78
- */
79
- async installCmdlineTools(): Promise<void> {
80
- const sdkRoot = await this.ensureSDKRoot();
81
- const platform = os.platform() as 'win32' | 'darwin' | 'linux';
82
-
83
- const url = CMDLINE_TOOLS_URLS[platform];
84
- if (!url) {
85
- throw new Error(`Unsupported platform: ${platform}`);
86
- }
87
-
88
- const extractPath = path.join(sdkRoot, 'cmdline-tools');
89
- const latestPath = path.join(extractPath, 'latest');
90
-
91
- // Check if already installed
92
- if (await fs.pathExists(latestPath)) {
93
- success('Android cmdline-tools already installed');
94
- return;
95
- }
96
-
97
- // Download and extract
98
- await downloadAndExtract(url, extractPath, 'Downloading Android cmdline-tools');
99
-
100
- // The extracted folder is named 'cmdline-tools', need to move it to 'latest'
101
- const extractedPath = path.join(extractPath, 'cmdline-tools');
102
- if (await fs.pathExists(extractedPath)) {
103
- await fs.move(extractedPath, latestPath);
104
- }
105
-
106
- // Set environment variables for this process
107
- process.env.ANDROID_HOME = sdkRoot;
108
- process.env.ANDROID_SDK_ROOT = sdkRoot;
109
-
110
- success('Android cmdline-tools installed');
111
- }
112
-
113
- /**
114
- * Resolve sdkmanager path and tools root
115
- * Checks multiple standard paths:
116
- * 1. cmdline-tools/latest/bin (Standard)
117
- * 2. cmdline-tools/bin (Alternative)
118
- * 3. tools/bin (Legacy)
119
- */
120
- private async resolveSDKToolsPath(): Promise<{ binPath: string | null; toolsRoot: string }> {
121
- const sdkmanagerName = os.platform() === 'win32' ? 'sdkmanager.bat' : 'sdkmanager';
122
-
123
- // Potential paths to check (in order of preference)
124
- const candidates = [
125
- {
126
- bin: path.join(this.sdkRoot, 'cmdline-tools', 'latest', 'bin', sdkmanagerName),
127
- root: path.join(this.sdkRoot, 'cmdline-tools', 'latest')
128
- },
129
- {
130
- bin: path.join(this.sdkRoot, 'cmdline-tools', 'bin', sdkmanagerName),
131
- root: path.join(this.sdkRoot, 'cmdline-tools')
132
- },
133
- {
134
- bin: path.join(this.sdkRoot, 'tools', 'bin', sdkmanagerName),
135
- root: path.join(this.sdkRoot, 'tools')
136
- }
137
- ];
138
-
139
- for (const candidate of candidates) {
140
- if (await fs.pathExists(candidate.bin)) {
141
- return { binPath: candidate.bin, toolsRoot: candidate.root };
142
- }
143
- }
144
-
145
- return { binPath: null, toolsRoot: '' };
146
- }
147
-
148
- /**
149
- * Get path to sdkmanager executable (Legacy method, kept for reference but unused internally now)
150
- */
151
- private getSDKManagerPath(): string {
152
- const sdkmanagerName = os.platform() === 'win32' ? 'sdkmanager.bat' : 'sdkmanager';
153
- return path.join(this.sdkRoot, 'cmdline-tools', 'latest', 'bin', sdkmanagerName);
154
- }
155
-
156
- /**
157
- * Get path to avdmanager executable
158
- */
159
- private getAVDManagerPath(): string {
160
- const avdmanagerName = os.platform() === 'win32' ? 'avdmanager.bat' : 'avdmanager';
161
- return path.join(this.sdkRoot, 'cmdline-tools', 'latest', 'bin', avdmanagerName);
162
- }
163
-
164
- /**
165
- * Run sdkmanager command
166
- */
167
- private async runSDKManager(
168
- args: string[],
169
- onProgress?: (progress: number, message: string) => void
170
- ): Promise<string> {
171
- // Resolve proper sdkmanager path and tools root
172
- const { binPath, toolsRoot } = await this.resolveSDKToolsPath();
173
-
174
- if (!binPath) {
175
- throw new Error('sdkmanager not found. Install cmdline-tools first.');
176
- }
177
-
178
- return new Promise((resolve, reject) => {
179
- const proc = spawn(binPath, args, {
180
- env: {
181
- ...process.env,
182
- ANDROID_HOME: this.sdkRoot,
183
- ANDROID_SDK_ROOT: this.sdkRoot,
184
- // Accept licenses automatically
185
- JAVA_OPTS:
186
- '-Dcom.android.sdkmanager.toolsdir=' + toolsRoot,
187
- },
188
- shell: true,
189
- });
190
-
191
- let output = '';
192
- let errorOutput = '';
193
-
194
- proc.stdout?.on('data', (data) => {
195
- output += data.toString();
196
- });
197
-
198
- // Parse stderr for progress updates
199
- proc.stderr?.on('data', (data) => {
200
- const text = data.toString();
201
- errorOutput += text;
202
-
203
- if (onProgress) {
204
- // Parse progress percentage: "10% ▋...................."
205
- const progressMatch = text.match(/(\d+)%/);
206
- if (progressMatch) {
207
- const percent = parseInt(progressMatch[1], 10);
208
-
209
- // Extract current operation
210
- const lines = text.split('\n');
211
- const currentOp =
212
- lines.find(
213
- (l: string) =>
214
- l.includes('Downloading') ||
215
- l.includes('Installing') ||
216
- l.includes('Extracting') ||
217
- l.includes('Unzipping')
218
- ) || 'Processing...';
219
-
220
- onProgress(percent, currentOp.trim());
221
- }
222
- }
223
- });
224
-
225
- proc.on('close', (code) => {
226
- if (code === 0) {
227
- resolve(output);
228
- } else {
229
- reject(new Error(`sdkmanager exited with code ${code}: ${errorOutput}`));
230
- }
231
- });
232
-
233
- proc.on('error', (err) => {
234
- reject(err);
235
- });
236
- });
237
- }
238
-
239
- /**
240
- * Accept all SDK licenses
241
- */
242
- async acceptLicenses(): Promise<void> {
243
- const spinner = startSpinner('Accepting SDK licenses...');
244
-
245
- try {
246
- // Resolve tools path
247
- const { binPath, toolsRoot } = await this.resolveSDKToolsPath();
248
-
249
- if (!binPath) {
250
- throw new Error('sdkmanager not found. Install cmdline-tools first.');
251
- }
252
-
253
- await new Promise<void>((resolve, reject) => {
254
- const proc = spawn(binPath, ['--licenses'], {
255
- env: {
256
- ...process.env,
257
- ANDROID_HOME: this.sdkRoot,
258
- ANDROID_SDK_ROOT: this.sdkRoot,
259
- // ADD MISSING JAVA_OPTS (critical fix)
260
- JAVA_OPTS:
261
- '-Dcom.android.sdkmanager.toolsdir=' + toolsRoot,
262
- },
263
- // ADD explicit stdio configuration
264
- stdio: ['pipe', 'pipe', 'pipe'],
265
- shell: true,
266
- });
267
-
268
- // Check if stdin is writable before writing
269
- if (proc.stdin) {
270
- const success = proc.stdin.write('y\n'.repeat(100));
271
- if (!success) {
272
- // Handle backpressure - wait for drain
273
- proc.stdin.once('drain', () => {
274
- proc.stdin?.end();
275
- });
276
- } else {
277
- proc.stdin.end();
278
- }
279
- } else {
280
- reject(new Error('stdin not available'));
281
- return;
282
- }
283
-
284
- // Collect stderr for debugging
285
- let errorOutput = '';
286
- proc.stderr?.on('data', (data) => {
287
- errorOutput += data.toString();
288
- });
289
-
290
- proc.on('close', (code) => {
291
- if (code === 0 || code === null) {
292
- resolve();
293
- } else {
294
- reject(
295
- new Error(`Failed to accept licenses: exit code ${code}\n${errorOutput}`)
296
- );
297
- }
298
- });
299
-
300
- proc.on('error', (err) => {
301
- reject(err);
302
- });
303
- });
304
-
305
- stopSpinner(spinner, true, 'SDK licenses accepted');
306
- } catch (error) {
307
- stopSpinner(spinner, false, 'Failed to accept licenses');
308
- throw error;
309
- }
310
- }
311
-
312
- /**
313
- * Install an SDK component
314
- */
315
- async installComponent(component: string, progressLabel?: string): Promise<void> {
316
- const label = progressLabel || `Installing ${component}`;
317
- const spinner = startSpinner(label);
318
-
319
- try {
320
- // Accept licenses first
321
- await this.acceptLicenses();
322
-
323
- // Install component with progress updates
324
- await this.runSDKManager(['--install', component], (percent, message) => {
325
- // Update spinner text with progress
326
- spinner.text = `${label} (${percent}%) - ${message}`;
327
- });
328
-
329
- stopSpinner(spinner, true, `${component} installed`);
330
- } catch (error) {
331
- stopSpinner(spinner, false, `Failed to install ${component}`);
332
- throw error;
333
- }
334
- }
335
-
336
- /**
337
- * List installed SDK components
338
- */
339
- async listInstalled(): Promise<SDKComponent[]> {
340
- try {
341
- const output = await this.runSDKManager(['--list_installed']);
342
- return this.parseSDKList(output);
343
- } catch (error) {
344
- return [];
345
- }
346
- }
347
-
348
- /**
349
- * Parse sdkmanager list output
350
- */
351
- private parseSDKList(output: string): SDKComponent[] {
352
- const components: SDKComponent[] = [];
353
- const lines = output.split('\n');
354
-
355
- for (const line of lines) {
356
- // Example: "build-tools;34.0.0 | 34.0.0 | Android SDK Build-Tools 34"
357
- const match = line.match(/^([^|]+)\|([^|]+)\|(.+)$/);
358
- if (match) {
359
- const name = match[1].trim();
360
- const version = match[2].trim();
361
-
362
- components.push({
363
- name,
364
- version,
365
- path: path.join(this.sdkRoot, name.replace(/;/g, path.sep)),
366
- installed: true,
367
- });
368
- }
369
- }
370
-
371
- return components;
372
- }
373
-
374
- /**
375
- * Update all installed components
376
- */
377
- async updateAll(): Promise<void> {
378
- const spinner = startSpinner('Updating SDK components...');
379
-
380
- try {
381
- await this.runSDKManager(['--update']);
382
- stopSpinner(spinner, true, 'SDK components updated');
383
- } catch (error) {
384
- stopSpinner(spinner, false, 'Failed to update components');
385
- throw error;
386
- }
387
- }
388
-
389
- /**
390
- * Check if a component is installed
391
- */
392
- async isComponentInstalled(component: string): Promise<boolean> {
393
- const installed = await this.listInstalled();
394
- return installed.some((c) => c.name === component);
395
- }
396
-
397
- /**
398
- * Install all required components for JetStart
399
- */
400
- async installRequiredComponents(): Promise<void> {
401
- info('Installing required Android SDK components...');
402
- console.log();
403
-
404
- for (let i = 0; i < REQUIRED_SDK_COMPONENTS.length; i++) {
405
- const component = REQUIRED_SDK_COMPONENTS[i];
406
- const overallProgress = `[${i + 1}/${REQUIRED_SDK_COMPONENTS.length}]`;
407
-
408
- try {
409
- await this.installComponent(component, `${overallProgress} Installing ${component}`);
410
- } catch (error) {
411
- warning(`Failed to install ${component}: ${(error as Error).message}`);
412
- }
413
- }
414
-
415
- console.log();
416
- success('All required SDK components installed');
417
- }
418
-
419
- /**
420
- * Detect missing required components
421
- */
422
- async detectMissingComponents(): Promise<string[]> {
423
- const installed = await this.listInstalled();
424
- const installedNames = installed.map((c) => c.name);
425
-
426
- return REQUIRED_SDK_COMPONENTS.filter((c) => !installedNames.includes(c));
427
- }
428
-
429
- /**
430
- * Create local.properties file with SDK path
431
- */
432
- async createLocalProperties(projectPath: string): Promise<void> {
433
- const localPropertiesPath = path.join(projectPath, 'local.properties');
434
-
435
- // Check if already exists
436
- if (await fs.pathExists(localPropertiesPath)) {
437
- return;
438
- }
439
-
440
- const sdkRoot = await this.ensureSDKRoot();
441
-
442
- // Escape backslashes for Windows paths
443
- const sdkPathEscaped = sdkRoot.replace(/\\/g, '\\\\');
444
-
445
- const content = `# Automatically generated by JetStart
446
- sdk.dir=${sdkPathEscaped}
447
- `;
448
-
449
- await fs.writeFile(localPropertiesPath, content, 'utf8');
450
- info(`Created local.properties with SDK path`);
451
- }
452
- }
453
-
454
- /**
455
- * Create an SDK manager instance
456
- */
457
- export function createSDKManager(sdkRoot?: string): AndroidSDKManager {
458
- // Check for mock mode (for testing)
459
- if (process.env.JETSTART_MOCK_SDK === 'true') {
460
- return new MockAndroidSDKManager() as any;
461
- }
462
-
463
- return new AndroidSDKManager(sdkRoot);
464
- }
465
-
466
- /**
467
- * Mock SDK manager for testing
468
- */
469
- class MockAndroidSDKManager {
470
- async ensureSDKRoot(): Promise<string> {
471
- return '/mock/android/sdk';
472
- }
473
-
474
- async installCmdlineTools(): Promise<void> {
475
- info('[MOCK] Installing cmdline-tools');
476
- }
477
-
478
- async acceptLicenses(): Promise<void> {
479
- info('[MOCK] Accepting licenses');
480
- }
481
-
482
- async installComponent(component: string): Promise<void> {
483
- info(`[MOCK] Installing ${component}`);
484
- }
485
-
486
- async listInstalled(): Promise<SDKComponent[]> {
487
- return [
488
- { name: 'platforms;android-34', version: '1', path: '/mock/platforms/android-34', installed: true },
489
- { name: 'build-tools;34.0.0', version: '34.0.0', path: '/mock/build-tools/34.0.0', installed: true },
490
- ];
491
- }
492
-
493
- async updateAll(): Promise<void> {
494
- info('[MOCK] Updating all components');
495
- }
496
-
497
- async isComponentInstalled(component: string): Promise<boolean> {
498
- return true;
499
- }
500
-
501
- async installRequiredComponents(): Promise<void> {
502
- info('[MOCK] Installing required components');
503
- }
504
-
505
- async detectMissingComponents(): Promise<string[]> {
506
- return [];
507
- }
508
-
509
- async createLocalProperties(projectPath: string): Promise<void> {
510
- info(`[MOCK] Creating local.properties at ${projectPath}`);
511
- }
512
- }
@@ -1,201 +0,0 @@
1
- /**
2
- * Download utilities with progress tracking
3
- */
4
-
5
- import axios from 'axios';
6
- import * as fs from 'fs-extra';
7
- import * as path from 'path';
8
- import { startSpinner, stopSpinner } from './spinner';
9
- import { error as logError } from './logger';
10
-
11
- export interface DownloadOptions {
12
- url: string;
13
- destination: string;
14
- progressLabel?: string;
15
- expectedSize?: number;
16
- timeout?: number;
17
- }
18
-
19
- /**
20
- * Format bytes into human-readable format
21
- */
22
- export function formatBytes(bytes: number): string {
23
- if (bytes === 0) return '0 B';
24
- if (bytes < 1024) return `${bytes} B`;
25
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
26
- if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
27
- return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
28
- }
29
-
30
- /**
31
- * Download a file with progress tracking
32
- */
33
- export async function downloadWithProgress(options: DownloadOptions): Promise<void> {
34
- const {
35
- url,
36
- destination,
37
- progressLabel = 'Downloading',
38
- timeout = 300000, // 5 minutes default
39
- } = options;
40
-
41
- // Ensure destination directory exists
42
- await fs.ensureDir(path.dirname(destination));
43
-
44
- const response = await axios({
45
- url,
46
- method: 'GET',
47
- responseType: 'stream',
48
- timeout,
49
- headers: {
50
- 'User-Agent': 'JetStart-CLI',
51
- },
52
- });
53
-
54
- const totalSize = parseInt(response.headers['content-length'] || '0', 10);
55
- let downloadedSize = 0;
56
-
57
- const spinner = startSpinner(`${progressLabel} (0%)`);
58
-
59
- return new Promise((resolve, reject) => {
60
- const writer = fs.createWriteStream(destination);
61
-
62
- response.data.on('data', (chunk: Buffer) => {
63
- downloadedSize += chunk.length;
64
-
65
- if (totalSize > 0) {
66
- const percent = Math.round((downloadedSize / totalSize) * 100);
67
- const downloaded = formatBytes(downloadedSize);
68
- const total = formatBytes(totalSize);
69
- spinner.text = `${progressLabel} (${percent}%) - ${downloaded} / ${total}`;
70
- } else {
71
- const downloaded = formatBytes(downloadedSize);
72
- spinner.text = `${progressLabel} - ${downloaded}`;
73
- }
74
- });
75
-
76
- response.data.pipe(writer);
77
-
78
- writer.on('finish', () => {
79
- stopSpinner(spinner, true, `${progressLabel} completed`);
80
- resolve();
81
- });
82
-
83
- writer.on('error', (err) => {
84
- stopSpinner(spinner, false, `${progressLabel} failed`);
85
- reject(err);
86
- });
87
-
88
- response.data.on('error', (err: Error) => {
89
- stopSpinner(spinner, false, `${progressLabel} failed`);
90
- reject(err);
91
- });
92
- });
93
- }
94
-
95
- /**
96
- * Sleep for a specified number of milliseconds
97
- */
98
- function sleep(ms: number): Promise<void> {
99
- return new Promise((resolve) => setTimeout(resolve, ms));
100
- }
101
-
102
- /**
103
- * Download a file with retry logic and exponential backoff
104
- */
105
- export async function downloadWithRetry(
106
- options: DownloadOptions,
107
- maxRetries: number = 3
108
- ): Promise<void> {
109
- let lastError: Error | null = null;
110
-
111
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
112
- try {
113
- await downloadWithProgress(options);
114
- return; // Success!
115
- } catch (err) {
116
- lastError = err as Error;
117
-
118
- if (attempt < maxRetries) {
119
- const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s
120
- console.log(`\nDownload failed (attempt ${attempt}/${maxRetries}). Retrying in ${delay / 1000}s...`);
121
- await sleep(delay);
122
- }
123
- }
124
- }
125
-
126
- logError(`Download failed after ${maxRetries} attempts: ${lastError?.message}`);
127
- throw lastError;
128
- }
129
-
130
- /**
131
- * Download and extract a ZIP file
132
- */
133
- export async function downloadAndExtract(
134
- url: string,
135
- extractPath: string,
136
- progressLabel?: string
137
- ): Promise<void> {
138
- const extract = require('extract-zip');
139
- const tempZip = path.join(require('os').tmpdir(), `jetstart-download-${Date.now()}.zip`);
140
-
141
- try {
142
- // Download
143
- await downloadWithRetry({
144
- url,
145
- destination: tempZip,
146
- progressLabel: progressLabel || 'Downloading archive',
147
- });
148
-
149
- // Extract
150
- const spinner = startSpinner('Extracting archive...');
151
- try {
152
- await extract(tempZip, { dir: path.resolve(extractPath) });
153
- stopSpinner(spinner, true, 'Archive extracted successfully');
154
- } catch (err) {
155
- stopSpinner(spinner, false, 'Failed to extract archive');
156
- throw err;
157
- }
158
- } finally {
159
- // Clean up temp file
160
- try {
161
- await fs.remove(tempZip);
162
- } catch {
163
- // Ignore cleanup errors
164
- }
165
- }
166
- }
167
-
168
- /**
169
- * Check if a URL is accessible
170
- */
171
- export async function checkUrlAccessible(url: string): Promise<boolean> {
172
- try {
173
- const response = await axios.head(url, {
174
- timeout: 10000,
175
- headers: {
176
- 'User-Agent': 'JetStart-CLI',
177
- },
178
- });
179
- return response.status >= 200 && response.status < 400;
180
- } catch {
181
- return false;
182
- }
183
- }
184
-
185
- /**
186
- * Get file size from URL without downloading
187
- */
188
- export async function getRemoteFileSize(url: string): Promise<number | null> {
189
- try {
190
- const response = await axios.head(url, {
191
- timeout: 10000,
192
- headers: {
193
- 'User-Agent': 'JetStart-CLI',
194
- },
195
- });
196
- const size = response.headers['content-length'];
197
- return size ? parseInt(size, 10) : null;
198
- } catch {
199
- return null;
200
- }
201
- }