@jetstart/cli 1.2.0 → 1.5.0

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 (67) hide show
  1. package/dist/cli.js +18 -1
  2. package/dist/commands/android-emulator.d.ts +8 -0
  3. package/dist/commands/android-emulator.js +280 -0
  4. package/dist/commands/create.d.ts +1 -6
  5. package/dist/commands/create.js +119 -0
  6. package/dist/commands/dev.d.ts +1 -7
  7. package/dist/commands/dev.js +69 -10
  8. package/dist/commands/index.d.ts +2 -0
  9. package/dist/commands/index.js +5 -1
  10. package/dist/commands/install-audit.d.ts +9 -0
  11. package/dist/commands/install-audit.js +185 -0
  12. package/dist/types/index.d.ts +22 -0
  13. package/dist/types/index.js +8 -0
  14. package/dist/utils/android-sdk.d.ts +81 -0
  15. package/dist/utils/android-sdk.js +432 -0
  16. package/dist/utils/downloader.d.ts +35 -0
  17. package/dist/utils/downloader.js +214 -0
  18. package/dist/utils/emulator-deployer.d.ts +29 -0
  19. package/dist/utils/emulator-deployer.js +224 -0
  20. package/dist/utils/emulator.d.ts +101 -0
  21. package/dist/utils/emulator.js +410 -0
  22. package/dist/utils/java.d.ts +25 -0
  23. package/dist/utils/java.js +363 -0
  24. package/dist/utils/system-tools.d.ts +93 -0
  25. package/dist/utils/system-tools.js +599 -0
  26. package/dist/utils/template.js +777 -748
  27. package/package.json +7 -3
  28. package/src/cli.ts +20 -2
  29. package/src/commands/android-emulator.ts +304 -0
  30. package/src/commands/create.ts +128 -5
  31. package/src/commands/dev.ts +71 -18
  32. package/src/commands/index.ts +3 -1
  33. package/src/commands/install-audit.ts +227 -0
  34. package/src/types/index.ts +30 -0
  35. package/src/utils/android-sdk.ts +478 -0
  36. package/src/utils/downloader.ts +201 -0
  37. package/src/utils/emulator-deployer.ts +210 -0
  38. package/src/utils/emulator.ts +463 -0
  39. package/src/utils/java.ts +369 -0
  40. package/src/utils/system-tools.ts +648 -0
  41. package/src/utils/template.ts +875 -867
  42. package/dist/cli.d.ts.map +0 -1
  43. package/dist/cli.js.map +0 -1
  44. package/dist/commands/build.d.ts.map +0 -1
  45. package/dist/commands/build.js.map +0 -1
  46. package/dist/commands/create.d.ts.map +0 -1
  47. package/dist/commands/create.js.map +0 -1
  48. package/dist/commands/dev.d.ts.map +0 -1
  49. package/dist/commands/dev.js.map +0 -1
  50. package/dist/commands/index.d.ts.map +0 -1
  51. package/dist/commands/index.js.map +0 -1
  52. package/dist/commands/logs.d.ts.map +0 -1
  53. package/dist/commands/logs.js.map +0 -1
  54. package/dist/index.d.ts.map +0 -1
  55. package/dist/index.js.map +0 -1
  56. package/dist/types/index.d.ts.map +0 -1
  57. package/dist/types/index.js.map +0 -1
  58. package/dist/utils/index.d.ts.map +0 -1
  59. package/dist/utils/index.js.map +0 -1
  60. package/dist/utils/logger.d.ts.map +0 -1
  61. package/dist/utils/logger.js.map +0 -1
  62. package/dist/utils/prompt.d.ts.map +0 -1
  63. package/dist/utils/prompt.js.map +0 -1
  64. package/dist/utils/spinner.d.ts.map +0 -1
  65. package/dist/utils/spinner.js.map +0 -1
  66. package/dist/utils/template.d.ts.map +0 -1
  67. package/dist/utils/template.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetstart/cli",
3
- "version": "1.2.0",
3
+ "version": "1.5.0",
4
4
  "description": "Command-line interface for JetStart",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -36,24 +36,28 @@
36
36
  },
37
37
  "homepage": "https://github.com/dev-phantom/jetstart#readme",
38
38
  "dependencies": {
39
- "@jetstart/core": "^1.2.0",
40
- "@jetstart/shared": "^1.2.0",
39
+ "@jetstart/core": "^1.5.0",
40
+ "@jetstart/shared": "^1.5.0",
41
41
  "axios": "^1.6.2",
42
42
  "chalk": "^4.1.2",
43
43
  "commander": "^11.1.0",
44
44
  "dotenv": "^16.3.1",
45
+ "extract-zip": "^2.0.1",
45
46
  "fs-extra": "^11.2.0",
46
47
  "inquirer": "^8.2.6",
47
48
  "ora": "^5.4.1",
48
49
  "qrcode-terminal": "^0.12.0",
50
+ "which": "^4.0.0",
49
51
  "ws": "^8.14.2"
50
52
  },
51
53
  "devDependencies": {
54
+ "@types/extract-zip": "^2.0.1",
52
55
  "@types/fs-extra": "^11.0.4",
53
56
  "@types/inquirer": "^8.2.10",
54
57
  "@types/jest": "^29.5.0",
55
58
  "@types/node": "^20.10.0",
56
59
  "@types/qrcode-terminal": "0.12.2",
60
+ "@types/which": "^3.0.3",
57
61
  "@types/ws": "^8.5.10",
58
62
  "jest": "^29.5.0",
59
63
  "ts-jest": "^29.1.0",
package/src/cli.ts CHANGED
@@ -11,14 +11,16 @@ import { createCommand } from './commands/create';
11
11
  import { devCommand } from './commands/dev';
12
12
  import { buildCommand } from './commands/build';
13
13
  import { logsCommand } from './commands/logs';
14
+ import { installAuditCommand } from './commands/install-audit';
15
+ import { androidEmulatorCommand } from './commands/android-emulator';
14
16
  import { JETSTART_VERSION } from '@jetstart/shared';
15
-
17
+ import {version} from '../package.json';
16
18
  const program = new Command();
17
19
 
18
20
  program
19
21
  .name('jetstart')
20
22
  .description('Fast, wireless Android development with Kotlin and Jetpack Compose')
21
- .version(JETSTART_VERSION);
23
+ .version(version || JETSTART_VERSION);
22
24
 
23
25
  // Create command
24
26
  program
@@ -27,6 +29,7 @@ program
27
29
  .option('-p, --package <name>', 'Package name (e.g., com.example.app)')
28
30
  .option('-t, --template <name>', 'Template to use', 'default')
29
31
  .option('--skip-install', 'Skip npm install')
32
+ .option('--full-install', 'Automatically install all required Android dependencies')
30
33
  .action(createCommand);
31
34
 
32
35
  // Dev command
@@ -37,6 +40,8 @@ program
37
40
  .option('-H, --host <host>', 'Host address (defaults to auto-detected network IP)')
38
41
  .option('--no-qr', 'Do not show QR code')
39
42
  .option('--no-open', 'Do not open browser')
43
+ .option('--emulator', 'Deploy to running Android emulator')
44
+ .option('--avd <name>', 'Target specific emulator by name')
40
45
  .action(devCommand);
41
46
 
42
47
  // Build command
@@ -58,6 +63,19 @@ program
58
63
  .option('-n, --lines <number>', 'Number of lines to show', '100')
59
64
  .action(logsCommand);
60
65
 
66
+ // Install audit command
67
+ program
68
+ .command('install-audit')
69
+ .description('Audit installation of required tools and dependencies')
70
+ .option('--json', 'Output results in JSON format')
71
+ .action(installAuditCommand);
72
+
73
+ // Android emulator command
74
+ program
75
+ .command('android-emulator')
76
+ .description('Manage Android emulators (AVDs)')
77
+ .action(androidEmulatorCommand);
78
+
61
79
  // Error handling
62
80
  program.on('command:*', (operands) => {
63
81
  console.error(chalk.red(`Error: Unknown command '${operands[0]}'`));
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Android emulator management command
3
+ */
4
+
5
+ import inquirer from 'inquirer';
6
+ import chalk from 'chalk';
7
+ import { createAVDManager } from '../utils/emulator';
8
+ import { error as logError, success, info, warning } from '../utils/logger';
9
+
10
+ /**
11
+ * Display list of AVDs
12
+ */
13
+ async function handleListAVDs(): Promise<void> {
14
+ const avdManager = createAVDManager();
15
+
16
+ const avds = await avdManager.listAVDs();
17
+
18
+ if (avds.length === 0) {
19
+ console.log();
20
+ warning('No Android emulators found.');
21
+ console.log();
22
+
23
+ const { shouldCreate } = await inquirer.prompt([
24
+ {
25
+ type: 'confirm',
26
+ name: 'shouldCreate',
27
+ message: 'Would you like to create a JetStart-optimized emulator?',
28
+ default: true,
29
+ },
30
+ ]);
31
+
32
+ if (shouldCreate) {
33
+ await handleCreateJetStartAVD();
34
+ }
35
+ return;
36
+ }
37
+
38
+ console.log();
39
+ console.log(chalk.bold('Available Android Virtual Devices:'));
40
+ console.log('━'.repeat(70));
41
+ console.log();
42
+
43
+ for (const avd of avds) {
44
+ const statusIcon = avd.running ? chalk.green('✓') : chalk.gray('○');
45
+ const statusText = avd.running ? chalk.green('(Running)') : '';
46
+
47
+ console.log(` ${statusIcon} ${chalk.bold(avd.name)} ${statusText}`);
48
+ console.log(` - Device: ${avd.device || 'Unknown'}`);
49
+ console.log(` - Target: ${avd.target || 'Unknown'}`);
50
+ if (avd.basedOn) {
51
+ console.log(` - Based on: ${avd.basedOn}`);
52
+ }
53
+ console.log();
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Start an emulator
59
+ */
60
+ async function handleStartEmulator(): Promise<void> {
61
+ const avdManager = createAVDManager();
62
+ const avds = await avdManager.listAVDs();
63
+
64
+ if (avds.length === 0) {
65
+ warning('No emulators available. Create one first.');
66
+ return;
67
+ }
68
+
69
+ // Filter out running emulators
70
+ const availableAVDs = avds.filter((avd) => !avd.running);
71
+
72
+ if (availableAVDs.length === 0) {
73
+ info('All emulators are already running.');
74
+ return;
75
+ }
76
+
77
+ const { avdName } = await inquirer.prompt([
78
+ {
79
+ type: 'list',
80
+ name: 'avdName',
81
+ message: 'Select emulator to start:',
82
+ choices: availableAVDs.map((avd) => ({ name: `${avd.name} (${avd.target})`, value: avd.name })),
83
+ },
84
+ ]);
85
+
86
+ await avdManager.startEmulator({ avdName });
87
+ }
88
+
89
+ /**
90
+ * Stop an emulator
91
+ */
92
+ async function handleStopEmulator(): Promise<void> {
93
+ const avdManager = createAVDManager();
94
+ const avds = await avdManager.listAVDs();
95
+
96
+ // Filter running emulators
97
+ const runningAVDs = avds.filter((avd) => avd.running);
98
+
99
+ if (runningAVDs.length === 0) {
100
+ warning('No emulators are currently running.');
101
+ return;
102
+ }
103
+
104
+ const { avdName } = await inquirer.prompt([
105
+ {
106
+ type: 'list',
107
+ name: 'avdName',
108
+ message: 'Select emulator to stop:',
109
+ choices: runningAVDs.map((avd) => ({ name: `${avd.name} (${avd.target})`, value: avd.name })),
110
+ },
111
+ ]);
112
+
113
+ await avdManager.stopEmulator(avdName);
114
+ }
115
+
116
+ /**
117
+ * Create JetStart-optimized AVD
118
+ */
119
+ async function handleCreateJetStartAVD(): Promise<void> {
120
+ const avdManager = createAVDManager();
121
+
122
+ try {
123
+ await avdManager.createJetStartAVD();
124
+ } catch (err: any) {
125
+ logError(`Failed to create AVD: ${err.message}`);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Create custom AVD
131
+ */
132
+ async function handleCreateCustomAVD(): Promise<void> {
133
+ const avdManager = createAVDManager();
134
+
135
+ const answers = await inquirer.prompt([
136
+ {
137
+ type: 'input',
138
+ name: 'name',
139
+ message: 'Enter AVD name:',
140
+ default: 'my-android-emulator',
141
+ validate: (input: string) => {
142
+ if (!input || input.trim().length === 0) {
143
+ return 'Name cannot be empty';
144
+ }
145
+ if (!/^[a-zA-Z0-9_-]+$/.test(input)) {
146
+ return 'Name can only contain letters, numbers, hyphens, and underscores';
147
+ }
148
+ return true;
149
+ },
150
+ },
151
+ {
152
+ type: 'list',
153
+ name: 'device',
154
+ message: 'Select device profile:',
155
+ choices: [
156
+ { name: 'Pixel 5', value: 'pixel_5' },
157
+ { name: 'Pixel 6', value: 'pixel_6' },
158
+ { name: 'Pixel 8', value: 'pixel_8' },
159
+ { name: 'Nexus 5', value: 'Nexus 5' },
160
+ ],
161
+ default: 'pixel_5',
162
+ },
163
+ {
164
+ type: 'list',
165
+ name: 'apiLevel',
166
+ message: 'Select API level:',
167
+ choices: [
168
+ { name: 'API 34 (Android 14) - Recommended', value: 34 },
169
+ { name: 'API 33 (Android 13)', value: 33 },
170
+ { name: 'API 31 (Android 12)', value: 31 },
171
+ { name: 'API 29 (Android 10)', value: 29 },
172
+ { name: 'API 24 (Android 7.0) - Minimum', value: 24 },
173
+ ],
174
+ default: 34,
175
+ },
176
+ {
177
+ type: 'list',
178
+ name: 'abi',
179
+ message: 'Select ABI (architecture):',
180
+ choices: [
181
+ { name: 'x86_64 (Intel/AMD 64-bit)', value: 'x86_64' },
182
+ { name: 'arm64-v8a (ARM 64-bit)', value: 'arm64-v8a' },
183
+ ],
184
+ default: process.arch === 'arm64' ? 'arm64-v8a' : 'x86_64',
185
+ },
186
+ ]);
187
+
188
+ try {
189
+ await avdManager.createAVD({
190
+ name: answers.name,
191
+ device: answers.device,
192
+ apiLevel: answers.apiLevel,
193
+ abi: answers.abi,
194
+ });
195
+ } catch (err: any) {
196
+ logError(`Failed to create AVD: ${err.message}`);
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Delete an AVD
202
+ */
203
+ async function handleDeleteAVD(): Promise<void> {
204
+ const avdManager = createAVDManager();
205
+ const avds = await avdManager.listAVDs();
206
+
207
+ if (avds.length === 0) {
208
+ warning('No emulators available to delete.');
209
+ return;
210
+ }
211
+
212
+ const { avdName } = await inquirer.prompt([
213
+ {
214
+ type: 'list',
215
+ name: 'avdName',
216
+ message: 'Select emulator to delete:',
217
+ choices: avds.map((avd) => ({ name: `${avd.name} (${avd.target})`, value: avd.name })),
218
+ },
219
+ ]);
220
+
221
+ const { confirm } = await inquirer.prompt([
222
+ {
223
+ type: 'confirm',
224
+ name: 'confirm',
225
+ message: `Are you sure you want to delete "${avdName}"?`,
226
+ default: false,
227
+ },
228
+ ]);
229
+
230
+ if (confirm) {
231
+ await avdManager.deleteAVD(avdName);
232
+ } else {
233
+ info('Deletion cancelled');
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Android emulator command handler
239
+ */
240
+ export async function androidEmulatorCommand(): Promise<void> {
241
+ try {
242
+ console.log();
243
+ console.log(chalk.cyan.bold(' JetStart Android Emulator Manager'));
244
+ console.log();
245
+
246
+ let running = true;
247
+
248
+ while (running) {
249
+ const { action } = await inquirer.prompt([
250
+ {
251
+ type: 'list',
252
+ name: 'action',
253
+ message: 'What would you like to do?',
254
+ choices: [
255
+ { name: 'List existing emulators', value: 'list' },
256
+ { name: 'Start emulator', value: 'start' },
257
+ { name: 'Stop emulator', value: 'stop' },
258
+ new inquirer.Separator(),
259
+ { name: 'Create JetStart-optimized emulator', value: 'create-jetstart' },
260
+ { name: 'Create custom emulator', value: 'create-custom' },
261
+ { name: 'Delete emulator', value: 'delete' },
262
+ new inquirer.Separator(),
263
+ { name: 'Exit', value: 'exit' },
264
+ ],
265
+ },
266
+ ]);
267
+
268
+ switch (action) {
269
+ case 'list':
270
+ await handleListAVDs();
271
+ break;
272
+ case 'start':
273
+ await handleStartEmulator();
274
+ break;
275
+ case 'stop':
276
+ await handleStopEmulator();
277
+ break;
278
+ case 'create-jetstart':
279
+ await handleCreateJetStartAVD();
280
+ break;
281
+ case 'create-custom':
282
+ await handleCreateCustomAVD();
283
+ break;
284
+ case 'delete':
285
+ await handleDeleteAVD();
286
+ break;
287
+ case 'exit':
288
+ running = false;
289
+ console.log();
290
+ success('Goodbye!');
291
+ console.log();
292
+ break;
293
+ }
294
+
295
+ if (running && action !== 'list') {
296
+ console.log();
297
+ }
298
+ }
299
+ } catch (err: any) {
300
+ console.log();
301
+ logError(`Emulator management failed: ${err.message}`);
302
+ process.exit(1);
303
+ }
304
+ }
@@ -6,16 +6,120 @@
6
6
  import path from 'path';
7
7
  import fs from 'fs-extra';
8
8
  import chalk from 'chalk';
9
- import { log, success, error } from '../utils/logger';
9
+ import inquirer from 'inquirer';
10
+ import { log, success, error, info, warning } from '../utils/logger';
10
11
  import { startSpinner, stopSpinner } from '../utils/spinner';
11
12
  import { prompt } from '../utils/prompt';
12
13
  import { generateProjectTemplate } from '../utils/template';
13
14
  import { isValidProjectName, isValidPackageName } from '@jetstart/shared';
15
+ import { CreateOptions } from '../types';
16
+ import { detectJava, installJava, isJavaCompatible } from '../utils/java';
17
+ import { createSDKManager, REQUIRED_SDK_COMPONENTS } from '../utils/android-sdk';
18
+ import { findAndroidSDK } from '../utils/system-tools';
14
19
 
15
- interface CreateOptions {
16
- package?: string;
17
- template?: string;
18
- skipInstall?: boolean;
20
+ /**
21
+ * Run full installation (automated mode)
22
+ */
23
+ async function runFullInstallation(): Promise<void> {
24
+ info('Starting full automated installation...');
25
+ console.log();
26
+
27
+ // 1. Check and install Java
28
+ const java = await detectJava();
29
+ if (!java || !(await isJavaCompatible(java.version))) {
30
+ await installJava();
31
+ } else {
32
+ success(`Java ${java.version} already installed`);
33
+ }
34
+
35
+ // 2. Check and install Android SDK
36
+ const sdkManager = createSDKManager();
37
+ const sdkRoot = await findAndroidSDK();
38
+
39
+ if (!sdkRoot) {
40
+ info('Installing Android SDK components...');
41
+ await sdkManager.installCmdlineTools();
42
+ } else {
43
+ success(`Android SDK found at ${sdkRoot}`);
44
+ }
45
+
46
+ // 3. Install required SDK components
47
+ for (const component of REQUIRED_SDK_COMPONENTS) {
48
+ await sdkManager.installComponent(component);
49
+ }
50
+
51
+ console.log();
52
+ success('All dependencies installed successfully!');
53
+ console.log();
54
+ }
55
+
56
+ /**
57
+ * Run interactive installation
58
+ */
59
+ async function runInteractiveInstallation(): Promise<void> {
60
+ info('Checking dependencies...');
61
+ console.log();
62
+
63
+ // Check Java
64
+ const java = await detectJava();
65
+ if (!java || !(await isJavaCompatible(java.version))) {
66
+ const { shouldInstall } = await inquirer.prompt([
67
+ {
68
+ type: 'confirm',
69
+ name: 'shouldInstall',
70
+ message: 'Java 17+ not found. Would you like to install it?',
71
+ default: true,
72
+ },
73
+ ]);
74
+
75
+ if (shouldInstall) {
76
+ await installJava();
77
+ } else {
78
+ warning('Java installation skipped. You may need to install it manually.');
79
+ }
80
+ } else {
81
+ success(`Java ${java.version} detected`);
82
+ }
83
+
84
+ // Check Android SDK
85
+ const sdkRoot = await findAndroidSDK();
86
+ if (!sdkRoot) {
87
+ const { shouldInstall } = await inquirer.prompt([
88
+ {
89
+ type: 'confirm',
90
+ name: 'shouldInstall',
91
+ message: 'Android SDK not found. Would you like to install it?',
92
+ default: true,
93
+ },
94
+ ]);
95
+
96
+ if (shouldInstall) {
97
+ const sdkManager = createSDKManager();
98
+ await sdkManager.installCmdlineTools();
99
+
100
+ // Ask about components
101
+ const { installComponents } = await inquirer.prompt([
102
+ {
103
+ type: 'confirm',
104
+ name: 'installComponents',
105
+ message: 'Install required SDK components?',
106
+ default: true,
107
+ },
108
+ ]);
109
+
110
+ if (installComponents) {
111
+ for (const component of REQUIRED_SDK_COMPONENTS) {
112
+ await sdkManager.installComponent(component);
113
+ }
114
+ }
115
+ } else {
116
+ warning('Android SDK installation skipped. Project creation may fail without SDK.');
117
+ }
118
+ } else {
119
+ success(`Android SDK found at ${sdkRoot}`);
120
+ }
121
+
122
+ console.log();
19
123
  }
20
124
 
21
125
  export async function createCommand(name: string, options: CreateOptions) {
@@ -38,6 +142,25 @@ export async function createCommand(name: string, options: CreateOptions) {
38
142
  log(`Creating JetStart project: ${chalk.cyan(name)}`);
39
143
  console.log();
40
144
 
145
+ // Run installation if requested
146
+ if (options.fullInstall) {
147
+ await runFullInstallation();
148
+ } else {
149
+ // Interactive mode: ask user
150
+ const { shouldCheckDeps } = await inquirer.prompt([
151
+ {
152
+ type: 'confirm',
153
+ name: 'shouldCheckDeps',
154
+ message: 'Check and install dependencies?',
155
+ default: true,
156
+ },
157
+ ]);
158
+
159
+ if (shouldCheckDeps) {
160
+ await runInteractiveInstallation();
161
+ }
162
+ }
163
+
41
164
  // Get package name
42
165
  let packageName = options.package;
43
166
  if (!packageName) {
@@ -5,18 +5,14 @@
5
5
 
6
6
  import * as path from 'path';
7
7
  import * as os from 'os';
8
+ import * as fs from 'fs-extra';
8
9
  import chalk from 'chalk';
9
10
  import qrcode from 'qrcode-terminal';
10
- import { log, success, error, info } from '../utils/logger';
11
+ import { log, success, error, info, warning } from '../utils/logger';
11
12
  import { JetStartServer } from '@jetstart/core';
12
13
  import { DEFAULT_CORE_PORT, DEFAULT_WS_PORT } from '@jetstart/shared';
13
-
14
- interface DevOptions {
15
- port?: string;
16
- host?: string;
17
- qr?: boolean;
18
- open?: boolean;
19
- }
14
+ import { DevOptions } from '../types';
15
+ import { EmulatorDeployer } from '../utils/emulator-deployer';
20
16
 
21
17
  export async function devCommand(options: DevOptions) {
22
18
  try {
@@ -32,6 +28,40 @@ export async function devCommand(options: DevOptions) {
32
28
  const projectPath = process.cwd();
33
29
  const projectName = path.basename(projectPath);
34
30
 
31
+ // Setup emulator deployment if requested
32
+ let deployer: EmulatorDeployer | null = null;
33
+ let packageName: string | null = null;
34
+
35
+ if (options.emulator) {
36
+ try {
37
+ deployer = await EmulatorDeployer.findOrSelectEmulator(options.avd);
38
+
39
+ // Try to read package name from build.gradle (modern Android)
40
+ const buildGradlePath = path.join(projectPath, 'app/build.gradle');
41
+ info(`Looking for build.gradle at: ${buildGradlePath}`);
42
+ if (await fs.pathExists(buildGradlePath)) {
43
+ const buildGradleContent = await fs.readFile(buildGradlePath, 'utf8');
44
+ // Match both applicationId "..." and applicationId '...'
45
+ const packageMatch = buildGradleContent.match(/applicationId\s+["']([^"']+)["']/);
46
+ packageName = packageMatch ? packageMatch[1] : null;
47
+ info(`Package name: ${packageName || 'NOT FOUND'}`);
48
+ } else {
49
+ warning('build.gradle not found - emulator deployment requires package name');
50
+ }
51
+
52
+ if (packageName) {
53
+ success('Emulator deployment enabled');
54
+ } else {
55
+ warning('Emulator deployment disabled: package name not found');
56
+ }
57
+ console.log();
58
+ } catch (err: any) {
59
+ error(`Emulator setup failed: ${err.message}`);
60
+ info('Continuing without emulator deployment...');
61
+ console.log();
62
+ }
63
+ }
64
+
35
65
  // Create and start Core server
36
66
  // Bind to 0.0.0.0 to accept connections on all interfaces,
37
67
  // but pass the detected IP for display and client connections
@@ -46,9 +76,37 @@ export async function devCommand(options: DevOptions) {
46
76
 
47
77
  const session = await server.start();
48
78
 
79
+ // Listen for build completion and deploy to emulator
80
+ if (deployer && packageName) {
81
+ info(`Setting up build:complete listener for package: ${packageName}`);
82
+ let hasDeployed = false; // Track if we've already deployed to prevent reinstall loops
83
+
84
+ server.on('build:complete', async (result: any) => {
85
+ info(`Build complete event received! APK: ${result.apkPath || 'NO APK PATH'}`);
86
+
87
+ // Only deploy if this is the first time OR if it's a file-change build (not initial client connection)
88
+ if (!hasDeployed && result.apkPath && deployer) {
89
+ try {
90
+ info('Deploying to emulator (initial deployment)...');
91
+ await deployer.installAPK(result.apkPath, packageName!);
92
+ await deployer.launchApp(packageName!);
93
+ hasDeployed = true; // Mark as deployed to prevent reinstall loop
94
+ success('Initial deployment complete. Future builds will be sent via hot reload.');
95
+ } catch (err: any) {
96
+ warning(`Emulator deployment failed: ${err.message}`);
97
+ }
98
+ } else if (hasDeployed) {
99
+ info('Skipping deployment (app already installed, using hot reload)');
100
+ } else {
101
+ warning(`Skipping deployment: apkPath=${result.apkPath}, deployer=${!!deployer}`);
102
+ }
103
+ });
104
+ } else {
105
+ info(`Emulator deployment not configured: deployer=${!!deployer}, packageName=${packageName}`);
106
+ }
107
+
49
108
  // Get URLs
50
109
  const serverUrl = `http://${host}:${port}`;
51
- const wsUrl = `ws://${host}:${wsPort}`;
52
110
  const localUrl = `http://localhost:${port}`;
53
111
 
54
112
  console.log();
@@ -61,17 +119,12 @@ export async function devCommand(options: DevOptions) {
61
119
 
62
120
  // Generate QR code for mobile connection
63
121
  if (showQR) {
64
- const qrData = {
65
- serverUrl,
66
- wsUrl,
67
- sessionId: session.id,
68
- token: session.token,
69
- projectName,
70
- version: '0.1.0',
71
- };
122
+ // Ultra-compact format: host|port|wsPort|sessionId|token|projectName
123
+ // Using short alphanumeric IDs for minimal QR code size
124
+ const qrData = `${host}|${port}|${wsPort}|${session.id}|${session.token}|${projectName}`;
72
125
 
73
126
  console.log(chalk.bold('Scan QR or connect manually:'));
74
- qrcode.generate(JSON.stringify(qrData), { small: true });
127
+ qrcode.generate(qrData, { small: true });
75
128
  console.log();
76
129
  info(`IP: ${chalk.cyan(host)}`);
77
130
  info(`Session: ${chalk.dim(session.id)}`);
@@ -5,4 +5,6 @@
5
5
  export { createCommand } from './create';
6
6
  export { devCommand } from './dev';
7
7
  export { buildCommand } from './build';
8
- export { logsCommand } from './logs';
8
+ export { logsCommand } from './logs';
9
+ export { installAuditCommand } from './install-audit';
10
+ export { androidEmulatorCommand } from './android-emulator';