@jetstart/core 1.7.0 → 2.0.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 (40) hide show
  1. package/README.md +189 -74
  2. package/dist/build/dex-generator.d.ts +27 -0
  3. package/dist/build/dex-generator.js +202 -0
  4. package/dist/build/dsl-parser.d.ts +3 -30
  5. package/dist/build/dsl-parser.js +67 -240
  6. package/dist/build/dsl-types.d.ts +8 -0
  7. package/dist/build/gradle.d.ts +51 -0
  8. package/dist/build/gradle.js +233 -1
  9. package/dist/build/hot-reload-service.d.ts +36 -0
  10. package/dist/build/hot-reload-service.js +179 -0
  11. package/dist/build/js-compiler-service.d.ts +61 -0
  12. package/dist/build/js-compiler-service.js +421 -0
  13. package/dist/build/kotlin-compiler.d.ts +54 -0
  14. package/dist/build/kotlin-compiler.js +450 -0
  15. package/dist/build/kotlin-parser.d.ts +91 -0
  16. package/dist/build/kotlin-parser.js +1030 -0
  17. package/dist/build/override-generator.d.ts +54 -0
  18. package/dist/build/override-generator.js +430 -0
  19. package/dist/server/index.d.ts +16 -1
  20. package/dist/server/index.js +147 -42
  21. package/dist/websocket/handler.d.ts +20 -4
  22. package/dist/websocket/handler.js +73 -38
  23. package/dist/websocket/index.d.ts +8 -0
  24. package/dist/websocket/index.js +15 -11
  25. package/dist/websocket/manager.d.ts +2 -2
  26. package/dist/websocket/manager.js +1 -1
  27. package/package.json +3 -3
  28. package/src/build/dex-generator.ts +197 -0
  29. package/src/build/dsl-parser.ts +73 -272
  30. package/src/build/dsl-types.ts +9 -0
  31. package/src/build/gradle.ts +259 -1
  32. package/src/build/hot-reload-service.ts +178 -0
  33. package/src/build/js-compiler-service.ts +411 -0
  34. package/src/build/kotlin-compiler.ts +460 -0
  35. package/src/build/kotlin-parser.ts +1043 -0
  36. package/src/build/override-generator.ts +478 -0
  37. package/src/server/index.ts +162 -54
  38. package/src/websocket/handler.ts +94 -56
  39. package/src/websocket/index.ts +27 -14
  40. package/src/websocket/manager.ts +2 -2
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Override Generator
3
+ * Generates $override classes that implement IncrementalChange interface.
4
+ * These classes contain the new method implementations and route calls via access$dispatch.
5
+ */
6
+ export interface OverrideGeneratorResult {
7
+ success: boolean;
8
+ overrideClassFiles: string[];
9
+ errors: string[];
10
+ }
11
+ export declare class OverrideGenerator {
12
+ private static readonly TAG;
13
+ /**
14
+ * Generate $override classes for all compiled class files.
15
+ * The override classes implement IncrementalChange and contain the new method implementations.
16
+ *
17
+ * Strategy: Instead of complex bytecode manipulation, we generate Kotlin source code
18
+ * for the $Override classes and compile them. This is simpler and more maintainable.
19
+ */
20
+ generateOverrides(classFiles: string[], sourceFile: string, outputDir: string): Promise<OverrideGeneratorResult>;
21
+ /**
22
+ * Parse Kotlin source file to extract class and method information
23
+ */
24
+ private parseKotlinSource;
25
+ /**
26
+ * Parse methods from a class body
27
+ */
28
+ private parseMethodsFromClassBody;
29
+ /**
30
+ * Parse top-level functions (not inside a class)
31
+ */
32
+ private parseTopLevelFunctions;
33
+ /**
34
+ * Parse parameter string into typed parameters
35
+ */
36
+ private parseParameters;
37
+ /**
38
+ * Parse a single parameter definition
39
+ */
40
+ private parseParameter;
41
+ /**
42
+ * Generate a method signature string for dispatch routing
43
+ */
44
+ private generateMethodSignature;
45
+ /**
46
+ * Convert Kotlin type to JVM descriptor-style string
47
+ */
48
+ private typeToDescriptor;
49
+ /**
50
+ * Generate Kotlin source code for the $override class
51
+ */
52
+ private generateOverrideSource;
53
+ }
54
+ //# sourceMappingURL=override-generator.d.ts.map
@@ -0,0 +1,430 @@
1
+ "use strict";
2
+ /**
3
+ * Override Generator
4
+ * Generates $override classes that implement IncrementalChange interface.
5
+ * These classes contain the new method implementations and route calls via access$dispatch.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.OverrideGenerator = void 0;
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const logger_1 = require("../utils/logger");
45
+ class OverrideGenerator {
46
+ static TAG = 'OverrideGenerator';
47
+ /**
48
+ * Generate $override classes for all compiled class files.
49
+ * The override classes implement IncrementalChange and contain the new method implementations.
50
+ *
51
+ * Strategy: Instead of complex bytecode manipulation, we generate Kotlin source code
52
+ * for the $Override classes and compile them. This is simpler and more maintainable.
53
+ */
54
+ async generateOverrides(classFiles, sourceFile, outputDir) {
55
+ const overrideClassFiles = [];
56
+ const errors = [];
57
+ try {
58
+ // Read the original Kotlin source to understand method signatures
59
+ const sourceContent = fs.readFileSync(sourceFile, 'utf-8');
60
+ // Parse class and method information from source
61
+ const classInfo = this.parseKotlinSource(sourceContent, sourceFile);
62
+ if (classInfo.length === 0) {
63
+ // No classes found - this is normal for files with only @Composable functions
64
+ // Return success with 0 overrides and let the fallback handle it
65
+ (0, logger_1.log)(`ℹ️ No classes found in ${sourceFile} - using direct class hot reload`);
66
+ return { success: true, overrideClassFiles: [], errors: [] };
67
+ }
68
+ // Generate override class source for each class
69
+ for (const info of classInfo) {
70
+ const overrideSource = this.generateOverrideSource(info);
71
+ const overrideSourcePath = path.join(outputDir, `${info.className}\$override.kt`);
72
+ fs.writeFileSync(overrideSourcePath, overrideSource);
73
+ (0, logger_1.log)(`Generated override source: ${info.className}$override.kt`);
74
+ // Track the generated source file
75
+ overrideClassFiles.push(overrideSourcePath);
76
+ }
77
+ return {
78
+ success: true,
79
+ overrideClassFiles,
80
+ errors: []
81
+ };
82
+ }
83
+ catch (e) {
84
+ const errorMsg = e instanceof Error ? e.message : String(e);
85
+ errors.push(errorMsg);
86
+ return { success: false, overrideClassFiles: [], errors };
87
+ }
88
+ }
89
+ /**
90
+ * Parse Kotlin source file to extract class and method information
91
+ */
92
+ parseKotlinSource(source, filePath) {
93
+ const classes = [];
94
+ // Extract package name
95
+ const packageMatch = source.match(/^package\s+([\w.]+)/m);
96
+ const packageName = packageMatch ? packageMatch[1] : '';
97
+ // Simple regex-based parsing for class definitions
98
+ // This handles basic cases - complex nested classes may need more sophisticated parsing
99
+ const classRegex = /(?:class|object)\s+(\w+)(?:\s*:\s*[\w\s,<>]+)?\s*\{/g;
100
+ let classMatch;
101
+ while ((classMatch = classRegex.exec(source)) !== null) {
102
+ const className = classMatch[1];
103
+ const classStartIndex = classMatch.index;
104
+ // Find matching closing brace
105
+ let braceCount = 0;
106
+ let classEndIndex = classStartIndex;
107
+ let inString = false;
108
+ let stringChar = '';
109
+ for (let i = classStartIndex; i < source.length; i++) {
110
+ const char = source[i];
111
+ const prevChar = i > 0 ? source[i - 1] : '';
112
+ // Handle string literals
113
+ if ((char === '"' || char === '\'') && prevChar !== '\\') {
114
+ if (!inString) {
115
+ inString = true;
116
+ stringChar = char;
117
+ }
118
+ else if (char === stringChar) {
119
+ inString = false;
120
+ }
121
+ }
122
+ if (!inString) {
123
+ if (char === '{')
124
+ braceCount++;
125
+ if (char === '}') {
126
+ braceCount--;
127
+ if (braceCount === 0) {
128
+ classEndIndex = i;
129
+ break;
130
+ }
131
+ }
132
+ }
133
+ }
134
+ const classBody = source.substring(classStartIndex, classEndIndex + 1);
135
+ const methods = this.parseMethodsFromClassBody(classBody, className);
136
+ classes.push({
137
+ packageName,
138
+ className,
139
+ fullClassName: packageName ? `${packageName}.${className}` : className,
140
+ methods
141
+ });
142
+ }
143
+ // Also handle top-level functions (they go in FileNameKt class)
144
+ const topLevelMethods = this.parseTopLevelFunctions(source, filePath);
145
+ if (topLevelMethods.length > 0) {
146
+ const fileName = path.basename(filePath, '.kt');
147
+ const ktClassName = fileName.charAt(0).toUpperCase() + fileName.slice(1) + 'Kt';
148
+ classes.push({
149
+ packageName,
150
+ className: ktClassName,
151
+ fullClassName: packageName ? `${packageName}.${ktClassName}` : ktClassName,
152
+ methods: topLevelMethods
153
+ });
154
+ }
155
+ return classes;
156
+ }
157
+ /**
158
+ * Parse methods from a class body
159
+ */
160
+ parseMethodsFromClassBody(classBody, className) {
161
+ const methods = [];
162
+ // Match function definitions
163
+ // This regex handles: fun name(params): ReturnType
164
+ const funRegex = /fun\s+(\w+)\s*\(([^)]*)\)(?:\s*:\s*([\w<>?,\s]+))?\s*[{=]/g;
165
+ let funMatch;
166
+ while ((funMatch = funRegex.exec(classBody)) !== null) {
167
+ const methodName = funMatch[1];
168
+ const paramsStr = funMatch[2];
169
+ const returnType = funMatch[3]?.trim() || 'Unit';
170
+ // Parse parameters
171
+ const params = this.parseParameters(paramsStr);
172
+ // Generate method signature for dispatch
173
+ const signature = this.generateMethodSignature(methodName, params, returnType);
174
+ methods.push({
175
+ name: methodName,
176
+ parameters: params,
177
+ returnType,
178
+ signature,
179
+ isStatic: false // Assume instance method, TODO: detect companion object
180
+ });
181
+ }
182
+ return methods;
183
+ }
184
+ /**
185
+ * Parse top-level functions (not inside a class)
186
+ */
187
+ parseTopLevelFunctions(source, filePath) {
188
+ const methods = [];
189
+ // Remove class bodies first to get only top-level content
190
+ let topLevelContent = source;
191
+ // Remove all class/object bodies
192
+ const classRegex = /(?:class|object)\s+\w+[^{]*\{/g;
193
+ let match;
194
+ const classPositions = [];
195
+ while ((match = classRegex.exec(source)) !== null) {
196
+ const startIndex = match.index;
197
+ let braceCount = 0;
198
+ let endIndex = startIndex;
199
+ let inString = false;
200
+ let stringChar = '';
201
+ for (let i = startIndex; i < source.length; i++) {
202
+ const char = source[i];
203
+ const prevChar = i > 0 ? source[i - 1] : '';
204
+ if ((char === '"' || char === '\'') && prevChar !== '\\') {
205
+ if (!inString) {
206
+ inString = true;
207
+ stringChar = char;
208
+ }
209
+ else if (char === stringChar) {
210
+ inString = false;
211
+ }
212
+ }
213
+ if (!inString) {
214
+ if (char === '{')
215
+ braceCount++;
216
+ if (char === '}') {
217
+ braceCount--;
218
+ if (braceCount === 0) {
219
+ endIndex = i;
220
+ break;
221
+ }
222
+ }
223
+ }
224
+ }
225
+ classPositions.push({ start: startIndex, end: endIndex + 1 });
226
+ }
227
+ // Sort and remove class bodies from end to start to maintain indices
228
+ classPositions.sort((a, b) => b.start - a.start);
229
+ for (const pos of classPositions) {
230
+ topLevelContent = topLevelContent.substring(0, pos.start) + topLevelContent.substring(pos.end);
231
+ }
232
+ // Now parse functions from top-level content
233
+ const funRegex = /fun\s+(\w+)\s*\(([^)]*)\)(?:\s*:\s*([\w<>?,\s]+))?\s*[{=]/g;
234
+ let funMatch;
235
+ while ((funMatch = funRegex.exec(topLevelContent)) !== null) {
236
+ const methodName = funMatch[1];
237
+ const paramsStr = funMatch[2];
238
+ const returnType = funMatch[3]?.trim() || 'Unit';
239
+ const params = this.parseParameters(paramsStr);
240
+ const signature = this.generateMethodSignature(methodName, params, returnType);
241
+ methods.push({
242
+ name: methodName,
243
+ parameters: params,
244
+ returnType,
245
+ signature,
246
+ isStatic: true // Top-level functions are static
247
+ });
248
+ }
249
+ return methods;
250
+ }
251
+ /**
252
+ * Parse parameter string into typed parameters
253
+ */
254
+ parseParameters(paramsStr) {
255
+ if (!paramsStr.trim())
256
+ return [];
257
+ const params = [];
258
+ // Split by comma, but respect generics
259
+ let depth = 0;
260
+ let current = '';
261
+ for (const char of paramsStr) {
262
+ if (char === '<' || char === '(')
263
+ depth++;
264
+ if (char === '>' || char === ')')
265
+ depth--;
266
+ if (char === ',' && depth === 0) {
267
+ if (current.trim()) {
268
+ const param = this.parseParameter(current.trim());
269
+ if (param)
270
+ params.push(param);
271
+ }
272
+ current = '';
273
+ }
274
+ else {
275
+ current += char;
276
+ }
277
+ }
278
+ if (current.trim()) {
279
+ const param = this.parseParameter(current.trim());
280
+ if (param)
281
+ params.push(param);
282
+ }
283
+ return params;
284
+ }
285
+ /**
286
+ * Parse a single parameter definition
287
+ */
288
+ parseParameter(paramStr) {
289
+ // Handle "name: Type" or "name: Type = default"
290
+ const match = paramStr.match(/(\w+)\s*:\s*([\w<>?,.\s]+?)(?:\s*=.*)?$/);
291
+ if (!match)
292
+ return null;
293
+ return {
294
+ name: match[1],
295
+ type: match[2].trim()
296
+ };
297
+ }
298
+ /**
299
+ * Generate a method signature string for dispatch routing
300
+ */
301
+ generateMethodSignature(methodName, params, returnType) {
302
+ // Format: methodName.(paramTypes)returnType
303
+ // Using JVM-style descriptors
304
+ const paramDescriptors = params.map(p => this.typeToDescriptor(p.type)).join('');
305
+ const returnDescriptor = this.typeToDescriptor(returnType);
306
+ return `${methodName}.(${paramDescriptors})${returnDescriptor}`;
307
+ }
308
+ /**
309
+ * Convert Kotlin type to JVM descriptor-style string
310
+ */
311
+ typeToDescriptor(type) {
312
+ // Simplified mapping - expand as needed
313
+ const typeMap = {
314
+ 'Unit': 'V',
315
+ 'Int': 'I',
316
+ 'Long': 'J',
317
+ 'Float': 'F',
318
+ 'Double': 'D',
319
+ 'Boolean': 'Z',
320
+ 'Byte': 'B',
321
+ 'Char': 'C',
322
+ 'Short': 'S',
323
+ 'String': 'Ljava/lang/String;',
324
+ 'Any': 'Ljava/lang/Object;',
325
+ };
326
+ // Handle nullable types
327
+ const nonNullType = type.replace('?', '');
328
+ if (typeMap[nonNullType]) {
329
+ return typeMap[nonNullType];
330
+ }
331
+ // Handle generic types like List<String>
332
+ const genericMatch = nonNullType.match(/^(\w+)<.*>$/);
333
+ if (genericMatch) {
334
+ return `L${genericMatch[1]};`;
335
+ }
336
+ // Default to object type
337
+ return `L${nonNullType.replace('.', '/')};`;
338
+ }
339
+ /**
340
+ * Generate Kotlin source code for the $override class
341
+ */
342
+ generateOverrideSource(classInfo) {
343
+ const { packageName, className, methods } = classInfo;
344
+ const lines = [];
345
+ // Package declaration
346
+ if (packageName) {
347
+ lines.push(`package ${packageName}`);
348
+ lines.push('');
349
+ }
350
+ // Import IncrementalChange
351
+ lines.push('import com.jetstart.hotreload.IncrementalChange');
352
+ lines.push('');
353
+ // Generate override class
354
+ lines.push(`/**`);
355
+ lines.push(` * Generated override class for ${className}`);
356
+ lines.push(` * Implements IncrementalChange to provide hot-reloaded method implementations`);
357
+ lines.push(` * `);
358
+ lines.push(` * Pattern: args[0] is 'this' for instance methods, method parameters follow`);
359
+ lines.push(` */`);
360
+ lines.push(`class ${className}\$override : IncrementalChange {`);
361
+ lines.push('');
362
+ // Generate access$dispatch method
363
+ lines.push(' override fun access\$dispatch(methodSignature: String, vararg args: Any?): Any? {');
364
+ lines.push(' return when (methodSignature) {');
365
+ for (const method of methods) {
366
+ const { name, parameters, returnType, signature, isStatic } = method;
367
+ // Generate case for this method
368
+ lines.push(` "${signature}" -> {`);
369
+ // For instance methods, cast args[0] to the instance type
370
+ if (!isStatic) {
371
+ lines.push(` val instance = args[0] as ${className}`);
372
+ }
373
+ // Cast arguments and call the method
374
+ if (parameters.length === 0) {
375
+ if (returnType === 'Unit') {
376
+ if (isStatic) {
377
+ lines.push(` ${className}.${name}()`);
378
+ }
379
+ else {
380
+ lines.push(` instance.${name}()`);
381
+ }
382
+ lines.push(' null');
383
+ }
384
+ else {
385
+ if (isStatic) {
386
+ lines.push(` ${className}.${name}()`);
387
+ }
388
+ else {
389
+ lines.push(` instance.${name}()`);
390
+ }
391
+ }
392
+ }
393
+ else {
394
+ // Build argument list with casts
395
+ const argCasts = parameters.map((p, i) => {
396
+ // For instance methods: args[0] is 'this', method params start at args[1]
397
+ // For static methods: method params start at args[0]
398
+ const argIndex = isStatic ? i : i + 1;
399
+ return `args[${argIndex}] as ${p.type}`;
400
+ });
401
+ if (returnType === 'Unit') {
402
+ if (isStatic) {
403
+ lines.push(` ${className}.${name}(${argCasts.join(', ')})`);
404
+ }
405
+ else {
406
+ lines.push(` instance.${name}(${argCasts.join(', ')})`);
407
+ }
408
+ lines.push(' null');
409
+ }
410
+ else {
411
+ if (isStatic) {
412
+ lines.push(` ${className}.${name}(${argCasts.join(', ')})`);
413
+ }
414
+ else {
415
+ lines.push(` instance.${name}(${argCasts.join(', ')})`);
416
+ }
417
+ }
418
+ }
419
+ lines.push(' }');
420
+ }
421
+ // Default case
422
+ lines.push(' else -> throw IllegalArgumentException("Unknown method: $methodSignature")');
423
+ lines.push(' }');
424
+ lines.push(' }');
425
+ lines.push('}');
426
+ return lines.join('\n');
427
+ }
428
+ }
429
+ exports.OverrideGenerator = OverrideGenerator;
430
+ //# sourceMappingURL=override-generator.js.map
@@ -9,6 +9,10 @@ export interface ServerConfig {
9
9
  wsPort?: number;
10
10
  host?: string;
11
11
  displayHost?: string;
12
+ /** Override the URL injected into BuildConfig for emulator builds. Emulators reach the host at 10.0.2.2, not the local network IP. */
13
+ emulatorHost?: string;
14
+ /** Enable web emulator support (initializes kotlinc-js compiler). */
15
+ webEnabled?: boolean;
12
16
  projectPath?: string;
13
17
  projectName?: string;
14
18
  }
@@ -20,9 +24,15 @@ export declare class JetStartServer extends EventEmitter {
20
24
  private config;
21
25
  private sessionManager;
22
26
  private buildService;
27
+ private hotReloadService;
28
+ private jsCompiler;
23
29
  private currentSession;
24
30
  private buildMutex;
25
31
  private latestApkPath;
32
+ private useTrueHotReload;
33
+ private adbHelper;
34
+ private autoInstall;
35
+ private isFileChangeBuild;
26
36
  constructor(config?: ServerConfig);
27
37
  start(): Promise<ServerSession>;
28
38
  stop(): Promise<void>;
@@ -30,8 +40,13 @@ export declare class JetStartServer extends EventEmitter {
30
40
  private setupBuildListeners;
31
41
  private handleRebuild;
32
42
  /**
33
- * Handle UI file updates using DSL hot reload (FAST)
43
+ * Handle UI file updates - uses TRUE hot reload (DEX-based) when available
34
44
  */
45
+ /**
46
+ * Compile a .kt file to a browser ES module and broadcast to web clients.
47
+ * Physical devices use DEX (already sent above). This targets browsers only.
48
+ */
49
+ private sendJsPreview;
35
50
  private handleUIUpdate;
36
51
  }
37
52
  //# sourceMappingURL=index.d.ts.map