@jetstart/core 1.7.0 → 2.0.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/README.md +189 -74
- package/dist/build/dex-generator.d.ts +27 -0
- package/dist/build/dex-generator.js +202 -0
- package/dist/build/dsl-parser.d.ts +3 -30
- package/dist/build/dsl-parser.js +67 -240
- package/dist/build/dsl-types.d.ts +8 -0
- package/dist/build/gradle.d.ts +51 -0
- package/dist/build/gradle.js +233 -1
- package/dist/build/hot-reload-service.d.ts +36 -0
- package/dist/build/hot-reload-service.js +179 -0
- package/dist/build/js-compiler-service.d.ts +61 -0
- package/dist/build/js-compiler-service.js +421 -0
- package/dist/build/kotlin-compiler.d.ts +54 -0
- package/dist/build/kotlin-compiler.js +450 -0
- package/dist/build/kotlin-parser.d.ts +91 -0
- package/dist/build/kotlin-parser.js +1030 -0
- package/dist/build/override-generator.d.ts +54 -0
- package/dist/build/override-generator.js +430 -0
- package/dist/server/index.d.ts +16 -1
- package/dist/server/index.js +147 -42
- package/dist/websocket/handler.d.ts +20 -4
- package/dist/websocket/handler.js +73 -38
- package/dist/websocket/index.d.ts +8 -0
- package/dist/websocket/index.js +15 -11
- package/dist/websocket/manager.d.ts +2 -2
- package/dist/websocket/manager.js +1 -1
- package/package.json +3 -3
- package/src/build/dex-generator.ts +197 -0
- package/src/build/dsl-parser.ts +73 -272
- package/src/build/dsl-types.ts +9 -0
- package/src/build/gradle.ts +259 -1
- package/src/build/hot-reload-service.ts +178 -0
- package/src/build/js-compiler-service.ts +411 -0
- package/src/build/kotlin-compiler.ts +460 -0
- package/src/build/kotlin-parser.ts +1043 -0
- package/src/build/override-generator.ts +478 -0
- package/src/server/index.ts +162 -54
- package/src/websocket/handler.ts +94 -56
- package/src/websocket/index.ts +27 -14
- package/src/websocket/manager.ts +2 -2
package/dist/build/dsl-parser.js
CHANGED
|
@@ -37,6 +37,7 @@ exports.DSLParser = void 0;
|
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const logger_1 = require("../utils/logger");
|
|
40
|
+
const kotlin_parser_1 = require("./kotlin-parser");
|
|
40
41
|
/**
|
|
41
42
|
* DSL Parser
|
|
42
43
|
* Converts Kotlin Compose code to JSON DSL for runtime interpretation
|
|
@@ -70,17 +71,16 @@ class DSLParser {
|
|
|
70
71
|
static parseContent(content, filePath) {
|
|
71
72
|
try {
|
|
72
73
|
(0, logger_1.log)(`Parsing Kotlin file: ${path.basename(filePath)}`);
|
|
73
|
-
// FIRST: Check if there's a getDefaultDSL() function
|
|
74
|
+
// FIRST: Check if there's a getDefaultDSL() function
|
|
74
75
|
const dslFromFunction = this.extractDSLFromFunction(content);
|
|
75
76
|
if (dslFromFunction) {
|
|
76
|
-
(0, logger_1.log)(`Extracted DSL from getDefaultDSL(): ${dslFromFunction.length} bytes`);
|
|
77
77
|
return {
|
|
78
78
|
success: true,
|
|
79
79
|
dsl: JSON.parse(dslFromFunction)
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
-
// FALLBACK:
|
|
83
|
-
const composableMatch = this.
|
|
82
|
+
// FALLBACK: Find @Composable function
|
|
83
|
+
const { main: composableMatch, library } = this.extractComposables(content);
|
|
84
84
|
if (!composableMatch) {
|
|
85
85
|
(0, logger_1.log)('No main composable found, generating default DSL');
|
|
86
86
|
return {
|
|
@@ -88,11 +88,16 @@ class DSLParser {
|
|
|
88
88
|
dsl: this.generateDefaultDSL()
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
-
// Parse the
|
|
92
|
-
|
|
91
|
+
// Tokenize and Parse the body using Recursive Descent Parser
|
|
92
|
+
(0, logger_1.log)(`Tokenizing composable body (${composableMatch.name})...`);
|
|
93
|
+
const tokenizer = new kotlin_parser_1.Tokenizer(composableMatch.body);
|
|
94
|
+
const tokens = tokenizer.tokenize();
|
|
95
|
+
(0, logger_1.log)(`Generated ${tokens.length} tokens. Parsing...`);
|
|
96
|
+
const parser = new kotlin_parser_1.KotlinParser(tokens, library);
|
|
97
|
+
const rootElement = parser.parse();
|
|
93
98
|
const dsl = {
|
|
94
99
|
version: '1.0',
|
|
95
|
-
screen:
|
|
100
|
+
screen: rootElement
|
|
96
101
|
};
|
|
97
102
|
(0, logger_1.log)(`Successfully parsed DSL: ${JSON.stringify(dsl).length} bytes`);
|
|
98
103
|
return {
|
|
@@ -108,262 +113,84 @@ class DSLParser {
|
|
|
108
113
|
};
|
|
109
114
|
}
|
|
110
115
|
}
|
|
111
|
-
/**
|
|
112
|
-
* Extract DSL JSON from getDefaultDSL() or similar function (legacy support)
|
|
113
|
-
*/
|
|
114
116
|
static extractDSLFromFunction(content) {
|
|
115
|
-
// Look for functions that return JSON strings (legacy approach)
|
|
116
117
|
const functionRegex = /fun\s+getDefaultDSL\s*\(\s*\)\s*:\s*String\s*\{\s*return\s*"""([\s\S]*?)"""/;
|
|
117
118
|
const match = content.match(functionRegex);
|
|
118
119
|
if (match && match[1]) {
|
|
119
|
-
|
|
120
|
-
jsonString = jsonString.replace(/\.trimIndent\(\)/, '');
|
|
121
|
-
return jsonString;
|
|
120
|
+
return match[1].trim().replace(/\.trimIndent\(\)/, '');
|
|
122
121
|
}
|
|
123
122
|
return null;
|
|
124
123
|
}
|
|
125
124
|
/**
|
|
126
125
|
* Find the main @Composable function in the file
|
|
127
126
|
*/
|
|
128
|
-
static findMainComposable(content) {
|
|
129
|
-
// Look for @Composable functions (AppContent, MainScreen, etc.)
|
|
130
|
-
const composableRegex = /@Composable\s+fun\s+(\w+)\s*\([^)]*\)\s*\{/g;
|
|
131
|
-
const matches = [...content.matchAll(composableRegex)];
|
|
132
|
-
(0, logger_1.log)(`Found ${matches.length} @Composable functions`);
|
|
133
|
-
if (matches.length === 0) {
|
|
134
|
-
(0, logger_1.log)('No @Composable functions found in file');
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
// Use the first composable function (should be AppContent, not LoadingScreen)
|
|
138
|
-
const match = matches[0];
|
|
139
|
-
const functionName = match[1];
|
|
140
|
-
(0, logger_1.log)(`Parsing composable function: ${functionName}`);
|
|
141
|
-
const startIndex = match.index + match[0].length;
|
|
142
|
-
// Extract the function body (handle nested braces)
|
|
143
|
-
const body = this.extractFunctionBody(content, startIndex);
|
|
144
|
-
(0, logger_1.log)(`Extracted function body: ${body.substring(0, 100)}...`);
|
|
145
|
-
return {
|
|
146
|
-
name: functionName,
|
|
147
|
-
body
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
127
|
/**
|
|
151
|
-
*
|
|
128
|
+
* Find the main @Composable and a library of all others
|
|
152
129
|
*/
|
|
153
|
-
static
|
|
154
|
-
|
|
155
|
-
let
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (!layoutMatch) {
|
|
172
|
-
// Fallback: Simple text content
|
|
173
|
-
const textMatch = body.match(/Text\s*\(\s*text\s*=\s*"([^"]+)"/);
|
|
174
|
-
if (textMatch) {
|
|
175
|
-
return {
|
|
176
|
-
type: 'Text',
|
|
177
|
-
text: textMatch[1]
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
// Default fallback
|
|
181
|
-
return {
|
|
182
|
-
type: 'Column',
|
|
183
|
-
modifier: { fillMaxSize: true, padding: 16 },
|
|
184
|
-
horizontalAlignment: 'CenterHorizontally',
|
|
185
|
-
verticalArrangement: 'Center',
|
|
186
|
-
children: [
|
|
187
|
-
{
|
|
188
|
-
type: 'Text',
|
|
189
|
-
text: 'Hot Reload Active',
|
|
190
|
-
style: 'headlineMedium'
|
|
191
|
-
}
|
|
192
|
-
]
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
const layoutType = layoutMatch[1];
|
|
196
|
-
const layoutStartIndex = layoutMatch.index + layoutMatch[0].length;
|
|
197
|
-
// Extract FULL layout declaration (parameters + body with children)
|
|
198
|
-
// We need to extract from after "Column(" to the end, including ) { ... }
|
|
199
|
-
const layoutFullContent = body.substring(layoutStartIndex);
|
|
200
|
-
return this.parseLayout(layoutType, layoutFullContent);
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Parse a layout element (Column, Row, Box)
|
|
204
|
-
*/
|
|
205
|
-
static parseLayout(type, content) {
|
|
206
|
-
const element = { type };
|
|
207
|
-
// Parse modifier
|
|
208
|
-
const modifierMatch = content.match(/modifier\s*=\s*Modifier([^,\n}]+)/);
|
|
209
|
-
if (modifierMatch) {
|
|
210
|
-
element.modifier = this.parseModifier(modifierMatch[1]);
|
|
211
|
-
}
|
|
212
|
-
// Parse alignment
|
|
213
|
-
const alignmentMatch = content.match(/horizontalAlignment\s*=\s*Alignment\.(\w+)/);
|
|
214
|
-
if (alignmentMatch) {
|
|
215
|
-
element.horizontalAlignment = alignmentMatch[1];
|
|
216
|
-
}
|
|
217
|
-
const arrangementMatch = content.match(/verticalArrangement\s*=\s*Arrangement\.(\w+)/);
|
|
218
|
-
if (arrangementMatch) {
|
|
219
|
-
element.verticalArrangement = arrangementMatch[1];
|
|
220
|
-
}
|
|
221
|
-
// Parse children (content inside the braces)
|
|
222
|
-
const childrenMatch = content.match(/\)\s*\{([\s\S]+)\}$/);
|
|
223
|
-
if (childrenMatch) {
|
|
224
|
-
element.children = this.parseChildren(childrenMatch[1]);
|
|
225
|
-
}
|
|
226
|
-
return element;
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Parse modifier chain
|
|
230
|
-
*/
|
|
231
|
-
static parseModifier(modifierChain) {
|
|
232
|
-
const modifier = {};
|
|
233
|
-
if (modifierChain.includes('.fillMaxSize()'))
|
|
234
|
-
modifier.fillMaxSize = true;
|
|
235
|
-
if (modifierChain.includes('.fillMaxWidth()'))
|
|
236
|
-
modifier.fillMaxWidth = true;
|
|
237
|
-
if (modifierChain.includes('.fillMaxHeight()'))
|
|
238
|
-
modifier.fillMaxHeight = true;
|
|
239
|
-
const paddingMatch = modifierChain.match(/\.padding\((\d+)\.dp\)/);
|
|
240
|
-
if (paddingMatch) {
|
|
241
|
-
modifier.padding = parseInt(paddingMatch[1]);
|
|
242
|
-
}
|
|
243
|
-
const sizeMatch = modifierChain.match(/\.size\((\d+)\.dp\)/);
|
|
244
|
-
if (sizeMatch) {
|
|
245
|
-
modifier.size = parseInt(sizeMatch[1]);
|
|
246
|
-
}
|
|
247
|
-
const heightMatch = modifierChain.match(/\.height\((\d+)\.dp\)/);
|
|
248
|
-
if (heightMatch) {
|
|
249
|
-
modifier.height = parseInt(heightMatch[1]);
|
|
250
|
-
}
|
|
251
|
-
const widthMatch = modifierChain.match(/\.width\((\d+)\.dp\)/);
|
|
252
|
-
if (widthMatch) {
|
|
253
|
-
modifier.width = parseInt(widthMatch[1]);
|
|
254
|
-
}
|
|
255
|
-
return modifier;
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* Parse children elements (handles multi-line elements)
|
|
259
|
-
* Maintains source code order
|
|
260
|
-
*/
|
|
261
|
-
static parseChildren(content) {
|
|
262
|
-
// Remove all newlines and extra whitespace for easier parsing
|
|
263
|
-
const normalized = content.replace(/\s+/g, ' ');
|
|
264
|
-
// Track elements with their positions for proper ordering
|
|
265
|
-
const elements = [];
|
|
266
|
-
const usedText = new Set();
|
|
267
|
-
// First pass: Parse Button elements and track their text to avoid duplicates
|
|
268
|
-
const buttonRegex = /Button\s*\(\s*onClick\s*=\s*\{[^}]*\}(?:[^)]*modifier\s*=\s*Modifier\.fillMaxWidth\s*\(\s*\))?[^)]*\)\s*\{\s*Text\s*\(\s*"([^"]+)"\s*\)/g;
|
|
269
|
-
let match;
|
|
270
|
-
while ((match = buttonRegex.exec(normalized)) !== null) {
|
|
271
|
-
const buttonText = match[1];
|
|
272
|
-
elements.push({
|
|
273
|
-
position: match.index,
|
|
274
|
-
element: {
|
|
275
|
-
type: 'Button',
|
|
276
|
-
text: buttonText,
|
|
277
|
-
onClick: 'handleButtonClick',
|
|
278
|
-
modifier: normalized.includes('fillMaxWidth') ? { fillMaxWidth: true } : undefined
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
usedText.add(buttonText);
|
|
282
|
-
}
|
|
283
|
-
// Parse Spacer elements
|
|
284
|
-
const spacerRegex = /Spacer\s*\(\s*modifier\s*=\s*Modifier\.height\s*\(\s*(\d+)\.dp\s*\)/g;
|
|
285
|
-
while ((match = spacerRegex.exec(normalized)) !== null) {
|
|
286
|
-
elements.push({
|
|
287
|
-
position: match.index,
|
|
288
|
-
element: {
|
|
289
|
-
type: 'Spacer',
|
|
290
|
-
height: parseInt(match[1])
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
// Parse Text elements (multiple patterns, skip if text is in a button)
|
|
295
|
-
const textPatterns = [
|
|
296
|
-
/Text\s*\(\s*text\s*=\s*"([^"]+)"[^)]*style\s*=\s*MaterialTheme\.typography\.(\w+)/g,
|
|
297
|
-
/Text\s*\(\s*"([^"]+)"[^)]*style\s*=\s*MaterialTheme\.typography\.(\w+)/g,
|
|
298
|
-
/Text\s*\(\s*text\s*=\s*"([^"]+)"/g,
|
|
299
|
-
/Text\s*\(\s*"([^"]+)"\s*\)/g
|
|
300
|
-
];
|
|
301
|
-
for (const regex of textPatterns) {
|
|
302
|
-
while ((match = regex.exec(normalized)) !== null) {
|
|
303
|
-
const text = match[1];
|
|
304
|
-
// Skip if this text is already used in a button
|
|
305
|
-
if (!usedText.has(text)) {
|
|
306
|
-
elements.push({
|
|
307
|
-
position: match.index,
|
|
308
|
-
element: {
|
|
309
|
-
type: 'Text',
|
|
310
|
-
text: text,
|
|
311
|
-
style: match[2] || undefined
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
usedText.add(text);
|
|
130
|
+
static extractComposables(content) {
|
|
131
|
+
const library = new Map();
|
|
132
|
+
let main = null;
|
|
133
|
+
const composableIndices = [...content.matchAll(/@Composable/g)].map(m => m.index);
|
|
134
|
+
for (const startIndex of composableIndices) {
|
|
135
|
+
const funRegex = /fun\s+(\w+)/g;
|
|
136
|
+
funRegex.lastIndex = startIndex;
|
|
137
|
+
const funMatch = funRegex.exec(content);
|
|
138
|
+
if (!funMatch || (funMatch.index - startIndex > 200))
|
|
139
|
+
continue;
|
|
140
|
+
const functionName = funMatch[1];
|
|
141
|
+
const funIndex = funMatch.index;
|
|
142
|
+
const openParenIndex = content.indexOf('(', funIndex);
|
|
143
|
+
let bodyStartIndex = -1;
|
|
144
|
+
if (openParenIndex !== -1) {
|
|
145
|
+
const closeParenIndex = this.findMatchingBracket(content, openParenIndex, '(', ')');
|
|
146
|
+
if (closeParenIndex !== -1) {
|
|
147
|
+
bodyStartIndex = content.indexOf('{', closeParenIndex);
|
|
315
148
|
}
|
|
316
149
|
}
|
|
150
|
+
else {
|
|
151
|
+
bodyStartIndex = content.indexOf('{', funIndex);
|
|
152
|
+
}
|
|
153
|
+
if (bodyStartIndex === -1)
|
|
154
|
+
continue;
|
|
155
|
+
const bodyEndIndex = this.findMatchingBracket(content, bodyStartIndex, '{', '}');
|
|
156
|
+
if (bodyEndIndex !== -1) {
|
|
157
|
+
const body = content.substring(bodyStartIndex + 1, bodyEndIndex).trim();
|
|
158
|
+
library.set(functionName, body);
|
|
159
|
+
// Heuristic for Main: 'Screen' suffix or simply the largest/last one?
|
|
160
|
+
// For now, let's assume the one named 'NotesScreen' or similar is Main.
|
|
161
|
+
// Or just keep the logic: "NotesScreen" (file name matches?)
|
|
162
|
+
// For now, ensure we capture everything.
|
|
163
|
+
if (!main)
|
|
164
|
+
main = { name: functionName, body };
|
|
165
|
+
// If explicitly named 'Screen', prefer it
|
|
166
|
+
if (functionName.endsWith('Screen'))
|
|
167
|
+
main = { name: functionName, body };
|
|
168
|
+
}
|
|
317
169
|
}
|
|
318
|
-
|
|
319
|
-
elements.sort((a, b) => a.position - b.position);
|
|
320
|
-
// Return just the elements, in correct order
|
|
321
|
-
return elements.map(e => e.element);
|
|
170
|
+
return { main, library };
|
|
322
171
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
endIndex++;
|
|
335
|
-
}
|
|
336
|
-
return content.substring(startIndex, endIndex - 1);
|
|
172
|
+
static findMatchingBracket(content, startIndex, openChar, closeChar) {
|
|
173
|
+
let count = 0;
|
|
174
|
+
for (let i = startIndex; i < content.length; i++) {
|
|
175
|
+
if (content[i] === openChar)
|
|
176
|
+
count++;
|
|
177
|
+
else if (content[i] === closeChar)
|
|
178
|
+
count--;
|
|
179
|
+
if (count === 0)
|
|
180
|
+
return i;
|
|
181
|
+
}
|
|
182
|
+
return -1;
|
|
337
183
|
}
|
|
338
|
-
|
|
339
|
-
* Generate default DSL when parsing fails
|
|
340
|
-
*/
|
|
184
|
+
// ... removed obsolete methods ...
|
|
341
185
|
static generateDefaultDSL() {
|
|
342
186
|
return {
|
|
343
187
|
version: '1.0',
|
|
344
188
|
screen: {
|
|
345
189
|
type: 'Column',
|
|
346
|
-
modifier: {
|
|
347
|
-
fillMaxSize: true,
|
|
348
|
-
padding: 16
|
|
349
|
-
},
|
|
350
|
-
horizontalAlignment: 'CenterHorizontally',
|
|
351
|
-
verticalArrangement: 'Center',
|
|
190
|
+
modifier: { fillMaxSize: true, padding: 16 },
|
|
352
191
|
children: [
|
|
353
|
-
{
|
|
354
|
-
|
|
355
|
-
text: 'Welcome to JetStart! 🚀',
|
|
356
|
-
style: 'headlineMedium'
|
|
357
|
-
},
|
|
358
|
-
{
|
|
359
|
-
type: 'Spacer',
|
|
360
|
-
height: 16
|
|
361
|
-
},
|
|
362
|
-
{
|
|
363
|
-
type: 'Text',
|
|
364
|
-
text: 'Edit your code to see hot reload',
|
|
365
|
-
style: 'bodyMedium'
|
|
366
|
-
}
|
|
192
|
+
{ type: 'Text', text: 'Welcome to JetStart! 🚀', style: 'headlineMedium' },
|
|
193
|
+
{ type: 'Text', text: 'Edit your code to see hot reload', style: 'bodyMedium' }
|
|
367
194
|
]
|
|
368
195
|
}
|
|
369
196
|
};
|
|
@@ -23,6 +23,10 @@ export interface DSLElement {
|
|
|
23
23
|
tint?: string;
|
|
24
24
|
contentDescription?: string;
|
|
25
25
|
children?: DSLElement[];
|
|
26
|
+
floatingActionButton?: DSLElement;
|
|
27
|
+
placeholder?: string;
|
|
28
|
+
label?: string;
|
|
29
|
+
value?: string;
|
|
26
30
|
}
|
|
27
31
|
export interface DSLModifier {
|
|
28
32
|
fillMaxSize?: boolean;
|
|
@@ -31,6 +35,10 @@ export interface DSLModifier {
|
|
|
31
35
|
padding?: number;
|
|
32
36
|
paddingHorizontal?: number;
|
|
33
37
|
paddingVertical?: number;
|
|
38
|
+
paddingStart?: number;
|
|
39
|
+
paddingEnd?: number;
|
|
40
|
+
paddingTop?: number;
|
|
41
|
+
paddingBottom?: number;
|
|
34
42
|
size?: number;
|
|
35
43
|
height?: number;
|
|
36
44
|
width?: number;
|
package/dist/build/gradle.d.ts
CHANGED
|
@@ -3,6 +3,57 @@
|
|
|
3
3
|
* Spawns and manages Gradle build processes
|
|
4
4
|
*/
|
|
5
5
|
import { BuildConfig, BuildResult } from '@jetstart/shared';
|
|
6
|
+
/**
|
|
7
|
+
* ADB Helper for auto-installing APKs
|
|
8
|
+
*/
|
|
9
|
+
export declare class AdbHelper {
|
|
10
|
+
private adbPath;
|
|
11
|
+
private connectedDevices;
|
|
12
|
+
constructor();
|
|
13
|
+
/**
|
|
14
|
+
* Find adb executable
|
|
15
|
+
*/
|
|
16
|
+
private findAdb;
|
|
17
|
+
/**
|
|
18
|
+
* Get list of connected devices (FULLY READY devices only)
|
|
19
|
+
* Returns only devices in "device" state (connected and authorized)
|
|
20
|
+
*/
|
|
21
|
+
getDevices(): string[];
|
|
22
|
+
/**
|
|
23
|
+
* Get ALL devices including those in "connecting" or "offline" state
|
|
24
|
+
* Useful for debugging and understanding device availability
|
|
25
|
+
*/
|
|
26
|
+
getAllDeviceStates(): {
|
|
27
|
+
id: string;
|
|
28
|
+
state: string;
|
|
29
|
+
}[];
|
|
30
|
+
/**
|
|
31
|
+
* Install APK on a device
|
|
32
|
+
*/
|
|
33
|
+
installApk(apkPath: string, deviceId?: string): Promise<{
|
|
34
|
+
success: boolean;
|
|
35
|
+
error?: string;
|
|
36
|
+
}>;
|
|
37
|
+
/**
|
|
38
|
+
* Launch app on device
|
|
39
|
+
*/
|
|
40
|
+
launchApp(packageName: string, activityName: string, deviceId?: string): Promise<boolean>;
|
|
41
|
+
/**
|
|
42
|
+
* Connect to a device via wireless ADB with retry logic
|
|
43
|
+
* Called when the JetStart app connects via WebSocket
|
|
44
|
+
*
|
|
45
|
+
* Handles timing issues with wireless ADB:
|
|
46
|
+
* - Devices may need time for user approval
|
|
47
|
+
* - Network handshake can be slow
|
|
48
|
+
* - Retries automatically if device not ready
|
|
49
|
+
*/
|
|
50
|
+
connectWireless(ipAddress: string, retryCount?: number): void;
|
|
51
|
+
/**
|
|
52
|
+
* Wait for a device to reach "device" state after adb connect
|
|
53
|
+
* The device may be "connecting" or "offline" initially
|
|
54
|
+
*/
|
|
55
|
+
private waitForDeviceReady;
|
|
56
|
+
}
|
|
6
57
|
export interface GradleExecutorOptions {
|
|
7
58
|
javaHome?: string;
|
|
8
59
|
androidHome?: string;
|