@jetstart/core 1.6.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.
- 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/README.md
CHANGED
|
@@ -1,124 +1,239 @@
|
|
|
1
|
-
# @jetstart/core
|
|
1
|
+
# @jetstart/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Central build server and real-time orchestration layer for JetStart.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`@jetstart/core` is the engine that powers `jetstart`. It runs three networked services and orchestrates the complete hot reload pipeline — from detecting a file change to having new code running on a physical Android device in under 100ms.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
9
|
+
```
|
|
10
|
+
src/
|
|
11
|
+
├── build/
|
|
12
|
+
│ ├── kotlin-compiler.ts # Invokes kotlinc, builds classpath from SDK + Gradle caches
|
|
13
|
+
│ ├── dex-generator.ts # Runs d8 to convert .class files to classes.dex
|
|
14
|
+
│ ├── override-generator.ts # Generates $Override companion classes (InstantRun-style)
|
|
15
|
+
│ ├── hot-reload-service.ts # Orchestrates compile → override → dex pipeline
|
|
16
|
+
│ ├── gradle.ts # GradleExecutor + AdbHelper for full builds
|
|
17
|
+
│ ├── gradle-injector.ts # Injects JetStart plugin config into build.gradle
|
|
18
|
+
│ ├── watcher.ts # chokidar-based file watcher with debounce
|
|
19
|
+
│ ├── builder.ts # High-level build manager
|
|
20
|
+
│ ├── cache.ts # Incremental build cache
|
|
21
|
+
│ ├── parser.ts # Gradle output parser
|
|
22
|
+
│ ├── dsl-parser.ts # Compose DSL parser
|
|
23
|
+
│ └── js-compiler-service.ts # kotlinc-js → ES module for web emulator
|
|
24
|
+
├── server/
|
|
25
|
+
│ ├── http.ts # Express HTTP server setup
|
|
26
|
+
│ ├── routes.ts # REST API routes
|
|
27
|
+
│ └── middleware.ts # Request middleware
|
|
28
|
+
├── websocket/
|
|
29
|
+
│ ├── manager.ts # Connection registry, session routing
|
|
30
|
+
│ ├── handler.ts # Message dispatch + session/token validation
|
|
31
|
+
│ └── index.ts
|
|
32
|
+
├── utils/
|
|
33
|
+
│ ├── logger.ts # Colored terminal logger
|
|
34
|
+
│ ├── qr.ts # QR code generation
|
|
35
|
+
│ └── session.ts # Session creation and lifecycle
|
|
36
|
+
└── types/
|
|
37
|
+
└── index.ts # ServerSession, QRCodeOptions
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Architecture
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
File change detected (chokidar — watches *.kt, *.xml, *.gradle, 300ms debounce)
|
|
46
|
+
│
|
|
47
|
+
├─► KotlinCompiler
|
|
48
|
+
│ kotlinc + Compose compiler plugin (bundled in Kotlin 2.0+, or Gradle cache fallback)
|
|
49
|
+
│ Classpath: android.jar + Gradle module cache + transforms-3 cache + project build outputs
|
|
50
|
+
│ Uses @argfile to avoid Windows command-line length limits
|
|
51
|
+
│ → .class files in a temp directory
|
|
52
|
+
│
|
|
53
|
+
├─► OverrideGenerator
|
|
54
|
+
│ Generates $Override companion classes for each modified class (InstantRun pattern)
|
|
55
|
+
│ Compiles override source files back through KotlinCompiler
|
|
56
|
+
│ Falls back to direct class reload if override generation fails
|
|
57
|
+
│
|
|
58
|
+
├─► DexGenerator
|
|
59
|
+
│ Runs d8 from $ANDROID_HOME/build-tools/<latest>
|
|
60
|
+
│ --min-api 24 (Android 7.0+)
|
|
61
|
+
│ → classes.dex
|
|
62
|
+
│
|
|
63
|
+
└─► WebSocketHandler.sendDexReload()
|
|
64
|
+
Broadcasts base64-encoded DEX + class name list to all authenticated Android clients
|
|
65
|
+
Android runtime loads classes via custom ClassLoader — no reinstall needed
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
For the web emulator, a parallel path compiles via `kotlinc-js` to an ES module and broadcasts it as `core:js-update`.
|
|
69
|
+
|
|
70
|
+
---
|
|
15
71
|
|
|
16
72
|
## Usage
|
|
17
73
|
|
|
18
|
-
### As a Library
|
|
19
74
|
```typescript
|
|
20
75
|
import { JetStartServer } from '@jetstart/core';
|
|
21
76
|
|
|
22
77
|
const server = new JetStartServer({
|
|
23
78
|
httpPort: 8765,
|
|
24
79
|
wsPort: 8766,
|
|
25
|
-
host: '0.0.0.0',
|
|
80
|
+
host: '0.0.0.0', // Bind to all interfaces
|
|
81
|
+
displayHost: '192.168.1.5', // LAN IP used in QR code and terminal output
|
|
82
|
+
projectPath: '/path/to/my-app',
|
|
83
|
+
projectName: 'my-app',
|
|
84
|
+
// For Android emulators — host is reachable at 10.0.2.2 inside the AVD
|
|
85
|
+
emulatorHost: '10.0.2.2',
|
|
26
86
|
});
|
|
27
87
|
|
|
28
|
-
await server.start();
|
|
29
|
-
|
|
88
|
+
const session = await server.start();
|
|
89
|
+
// session.id and session.token are embedded in the QR code
|
|
90
|
+
|
|
91
|
+
server.on('build:complete', (result) => {
|
|
92
|
+
console.log('APK ready:', result.apkPath);
|
|
93
|
+
});
|
|
30
94
|
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
npm run start
|
|
95
|
+
await server.stop();
|
|
34
96
|
```
|
|
35
97
|
|
|
36
|
-
|
|
98
|
+
---
|
|
37
99
|
|
|
38
|
-
|
|
100
|
+
## HTTP API
|
|
39
101
|
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
GET /health
|
|
43
|
-
Response: { status: 'ok', version: '0.1.0', uptime: 123 }
|
|
44
|
-
```
|
|
102
|
+
All endpoints are served on the HTTP port (default `8765`).
|
|
45
103
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
POST /session/create
|
|
49
|
-
Body: { projectName: 'MyApp', projectPath: '/path/to/project' }
|
|
50
|
-
Response: { session: {...}, qrCode: 'data:image/png;base64,...' }
|
|
51
|
-
```
|
|
104
|
+
### `GET /`
|
|
105
|
+
Redirects to `https://web.jetstart.site` with the active session's connection parameters (`host`, `port`, `wsPort`, `sessionId`, `token`, `version`, `projectName`) as query parameters, so the web emulator connects automatically. Returns a plain status page if no session is active.
|
|
52
106
|
|
|
53
|
-
|
|
107
|
+
### `GET /health`
|
|
108
|
+
```json
|
|
109
|
+
{ "status": "ok", "version": "0.1.0", "uptime": 42.3 }
|
|
54
110
|
```
|
|
55
|
-
|
|
56
|
-
|
|
111
|
+
|
|
112
|
+
### `GET /version`
|
|
113
|
+
```json
|
|
114
|
+
{ "version": "0.1.0" }
|
|
57
115
|
```
|
|
58
116
|
|
|
59
|
-
|
|
117
|
+
### `POST /session/create`
|
|
118
|
+
Creates a new dev session and returns a base64 QR code PNG.
|
|
119
|
+
|
|
120
|
+
Request body:
|
|
121
|
+
```json
|
|
122
|
+
{ "projectName": "my-app", "projectPath": "/abs/path/to/my-app" }
|
|
60
123
|
```
|
|
61
|
-
|
|
62
|
-
Response:
|
|
124
|
+
|
|
125
|
+
Response:
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"session": { "id": "abc123", "token": "xyz789", "projectName": "my-app", "createdAt": 1711900000000 },
|
|
129
|
+
"qrCode": "data:image/png;base64,..."
|
|
130
|
+
}
|
|
63
131
|
```
|
|
64
132
|
|
|
65
|
-
###
|
|
133
|
+
### `GET /session/:sessionId`
|
|
134
|
+
Returns the session object (404 if not found).
|
|
66
135
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
- `client:status` - Status update
|
|
70
|
-
- `client:log` - Log message
|
|
71
|
-
- `client:heartbeat` - Keep-alive ping
|
|
136
|
+
### `GET /download/:filename`
|
|
137
|
+
Streams the most recently built APK as a file download.
|
|
72
138
|
|
|
73
|
-
|
|
74
|
-
- `core:connected` - Connection confirmed
|
|
75
|
-
- `core:build-start` - Build started
|
|
76
|
-
- `core:build-status` - Build progress update
|
|
77
|
-
- `core:build-complete` - Build finished
|
|
78
|
-
- `core:build-error` - Build failed
|
|
79
|
-
- `core:reload` - Trigger app reload
|
|
139
|
+
---
|
|
80
140
|
|
|
81
|
-
##
|
|
82
|
-
|
|
83
|
-
|
|
141
|
+
## WebSocket Protocol
|
|
142
|
+
|
|
143
|
+
The WebSocket server runs on port `8766`. Every client must send `client:connect` with the matching `sessionId` and `token` (both embedded in the QR code). Mismatched connections are closed immediately:
|
|
144
|
+
|
|
145
|
+
- Close code `4001` — session mismatch (device built against a different session, rescan QR)
|
|
146
|
+
- Close code `4002` — token mismatch
|
|
84
147
|
|
|
85
|
-
|
|
148
|
+
### Messages: device/browser → core
|
|
86
149
|
|
|
87
|
-
|
|
150
|
+
| Type | Key fields | Description |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `client:connect` | `sessionId`, `token`, `deviceInfo` | Authenticate with session credentials |
|
|
153
|
+
| `client:status` | `status` | Send a `SessionStatus` update |
|
|
154
|
+
| `client:log` | `log: LogEntry` | Forward a device log entry to the server |
|
|
155
|
+
| `client:heartbeat` | — | Keep-alive ping |
|
|
156
|
+
| `client:disconnect` | `reason?` | Graceful disconnect |
|
|
157
|
+
| `client:click` | `action`, `elementType`, `elementText?` | UI interaction from web emulator |
|
|
158
|
+
|
|
159
|
+
### Messages: core → device/browser
|
|
160
|
+
|
|
161
|
+
| Type | Key fields | Description |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| `core:connected` | `projectName` | Authentication accepted |
|
|
164
|
+
| `core:build-start` | — | Gradle build has begun |
|
|
165
|
+
| `core:build-status` | `status: BuildStatus` | Mid-build progress update |
|
|
166
|
+
| `core:build-complete` | `apkInfo`, `downloadUrl` | APK ready for download/install |
|
|
167
|
+
| `core:build-error` | `error`, `details?` | Build failed |
|
|
168
|
+
| `core:dex-reload` | `dexBase64`, `classNames[]` | Hot reload DEX patch for Android devices |
|
|
169
|
+
| `core:js-update` | `jsBase64`, `sourceFile`, `byteSize` | ES module update for the web emulator |
|
|
170
|
+
| `core:log` | `log: LogEntry` | Device log broadcast to dashboard clients |
|
|
171
|
+
| `core:disconnect` | `reason` | Server shutting down |
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## File Watcher
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { FileWatcher } from '@jetstart/core';
|
|
179
|
+
|
|
180
|
+
const watcher = new FileWatcher({
|
|
88
181
|
projectPath: '/path/to/project',
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
debuggable: true,
|
|
92
|
-
minifyEnabled: false,
|
|
93
|
-
versionCode: 1,
|
|
94
|
-
versionName: '1.0.0',
|
|
95
|
-
applicationId: 'com.example.app',
|
|
182
|
+
callback: (changedFiles) => { /* handle changes */ },
|
|
183
|
+
debounceMs: 300,
|
|
96
184
|
});
|
|
97
185
|
|
|
98
|
-
|
|
186
|
+
watcher.watch('/path/to/project');
|
|
187
|
+
// Watches **/*.kt, **/*.xml, **/*.gradle, **/*.gradle.kts
|
|
188
|
+
// Ignores: node_modules, build, .gradle, .git, dist
|
|
189
|
+
|
|
190
|
+
watcher.stop();
|
|
99
191
|
```
|
|
100
192
|
|
|
101
|
-
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Hot Reload Service
|
|
196
|
+
|
|
102
197
|
```typescript
|
|
103
|
-
import {
|
|
198
|
+
import { HotReloadService } from '@jetstart/core';
|
|
104
199
|
|
|
105
|
-
const
|
|
200
|
+
const service = new HotReloadService('/path/to/project');
|
|
106
201
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
202
|
+
// Check that kotlinc and d8 are available
|
|
203
|
+
const env = await service.checkEnvironment();
|
|
204
|
+
if (!env.ready) console.log(env.issues);
|
|
110
205
|
|
|
111
|
-
//
|
|
112
|
-
|
|
206
|
+
// Compile a changed file and get the DEX payload
|
|
207
|
+
const result = await service.hotReload('/path/to/project/app/src/main/java/com/example/MainActivity.kt');
|
|
208
|
+
if (result.success) {
|
|
209
|
+
console.log(`Done in ${result.compileTime + result.dexTime}ms`);
|
|
210
|
+
// result.dexBase64 — send this to the device
|
|
211
|
+
// result.classNames — fully-qualified class names patched
|
|
212
|
+
}
|
|
113
213
|
```
|
|
114
214
|
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Gradle & ADB
|
|
218
|
+
|
|
219
|
+
`GradleExecutor` runs full Gradle builds (debug or release). It prefers system Gradle over the project `gradlew` wrapper for speed, auto-creates `local.properties` if the Android SDK is found but not configured, and supports Gradle build flags `--parallel --build-cache --configure-on-demand --daemon`.
|
|
220
|
+
|
|
221
|
+
`AdbHelper` handles wireless ADB connections with retry logic (up to 5 attempts, escalating delays) to account for user-approval timing on the device, and polls for device readiness after `adb connect` since the device may transition through `connecting` → `offline` → `device` states.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
115
225
|
## Environment Variables
|
|
116
226
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
227
|
+
| Variable | Description |
|
|
228
|
+
|---|---|
|
|
229
|
+
| `ANDROID_HOME` / `ANDROID_SDK_ROOT` | Android SDK path — required for hot reload and Gradle builds |
|
|
230
|
+
| `KOTLIN_HOME` | Kotlin installation path — used to find `kotlinc` |
|
|
231
|
+
| `JAVA_HOME` | JDK path |
|
|
232
|
+
| `DEBUG` | Enable verbose logging |
|
|
233
|
+
|
|
234
|
+
---
|
|
121
235
|
|
|
122
236
|
## License
|
|
123
237
|
|
|
124
|
-
|
|
238
|
+
MIT
|
|
239
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DEX Generator Service
|
|
3
|
+
* Converts .class files to .dex using d8 (Android DEX compiler)
|
|
4
|
+
*/
|
|
5
|
+
export interface DexResult {
|
|
6
|
+
success: boolean;
|
|
7
|
+
dexPath: string;
|
|
8
|
+
dexBytes: Buffer | null;
|
|
9
|
+
errors: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare class DexGenerator {
|
|
12
|
+
private static readonly TAG;
|
|
13
|
+
private d8Path;
|
|
14
|
+
/**
|
|
15
|
+
* Find d8 executable in Android SDK
|
|
16
|
+
*/
|
|
17
|
+
findD8(): Promise<string | null>;
|
|
18
|
+
/**
|
|
19
|
+
* Convert .class files to a single .dex file
|
|
20
|
+
*/
|
|
21
|
+
generateDex(classFiles: string[], outputDir?: string): Promise<DexResult>;
|
|
22
|
+
/**
|
|
23
|
+
* Run a command and return result
|
|
24
|
+
*/
|
|
25
|
+
private runCommand;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=dex-generator.d.ts.map
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DEX Generator Service
|
|
4
|
+
* Converts .class files to .dex using d8 (Android DEX compiler)
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.DexGenerator = void 0;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const os = __importStar(require("os"));
|
|
44
|
+
const child_process_1 = require("child_process");
|
|
45
|
+
const logger_1 = require("../utils/logger");
|
|
46
|
+
class DexGenerator {
|
|
47
|
+
static TAG = 'DexGenerator';
|
|
48
|
+
d8Path = null;
|
|
49
|
+
/**
|
|
50
|
+
* Find d8 executable in Android SDK
|
|
51
|
+
*/
|
|
52
|
+
async findD8() {
|
|
53
|
+
if (this.d8Path)
|
|
54
|
+
return this.d8Path;
|
|
55
|
+
// Check multiple locations for Android SDK
|
|
56
|
+
let androidHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
|
|
57
|
+
// Fallback to common Windows locations
|
|
58
|
+
if (!androidHome) {
|
|
59
|
+
const commonLocations = [
|
|
60
|
+
'C:\\Android',
|
|
61
|
+
path.join(os.homedir(), 'AppData', 'Local', 'Android', 'Sdk'),
|
|
62
|
+
'C:\\Users\\Public\\Android\\Sdk',
|
|
63
|
+
];
|
|
64
|
+
for (const loc of commonLocations) {
|
|
65
|
+
if (fs.existsSync(path.join(loc, 'build-tools'))) {
|
|
66
|
+
androidHome = loc;
|
|
67
|
+
(0, logger_1.log)(`Found Android SDK at: ${loc}`);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!androidHome) {
|
|
73
|
+
(0, logger_1.error)('ANDROID_HOME or ANDROID_SDK_ROOT not set');
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
// d8 is in build-tools
|
|
77
|
+
const buildToolsDir = path.join(androidHome, 'build-tools');
|
|
78
|
+
if (!fs.existsSync(buildToolsDir)) {
|
|
79
|
+
(0, logger_1.error)('Android build-tools not found');
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
// Find latest build-tools version
|
|
83
|
+
const versions = fs.readdirSync(buildToolsDir)
|
|
84
|
+
.filter(v => /^\d+\.\d+\.\d+$/.test(v))
|
|
85
|
+
.sort((a, b) => {
|
|
86
|
+
const aParts = a.split('.').map(Number);
|
|
87
|
+
const bParts = b.split('.').map(Number);
|
|
88
|
+
for (let i = 0; i < 3; i++) {
|
|
89
|
+
if (aParts[i] !== bParts[i])
|
|
90
|
+
return bParts[i] - aParts[i];
|
|
91
|
+
}
|
|
92
|
+
return 0;
|
|
93
|
+
});
|
|
94
|
+
if (versions.length === 0) {
|
|
95
|
+
(0, logger_1.error)('No Android build-tools version found');
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const d8Name = os.platform() === 'win32' ? 'd8.bat' : 'd8';
|
|
99
|
+
const d8Path = path.join(buildToolsDir, versions[0], d8Name);
|
|
100
|
+
if (!fs.existsSync(d8Path)) {
|
|
101
|
+
(0, logger_1.error)(`d8 not found at: ${d8Path}`);
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
this.d8Path = d8Path;
|
|
105
|
+
(0, logger_1.log)(`Found d8 at: ${d8Path} (build-tools ${versions[0]})`);
|
|
106
|
+
return d8Path;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Convert .class files to a single .dex file
|
|
110
|
+
*/
|
|
111
|
+
async generateDex(classFiles, outputDir) {
|
|
112
|
+
const d8 = await this.findD8();
|
|
113
|
+
if (!d8) {
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
dexPath: '',
|
|
117
|
+
dexBytes: null,
|
|
118
|
+
errors: ['d8 not found - Android SDK build-tools not installed']
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (classFiles.length === 0) {
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
dexPath: '',
|
|
125
|
+
dexBytes: null,
|
|
126
|
+
errors: ['No class files provided']
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// Create output directory
|
|
130
|
+
const dexOutputDir = outputDir || path.join(os.tmpdir(), 'jetstart-dex', Date.now().toString());
|
|
131
|
+
fs.mkdirSync(dexOutputDir, { recursive: true });
|
|
132
|
+
(0, logger_1.log)(`Generating DEX from ${classFiles.length} class files...`);
|
|
133
|
+
// Build d8 arguments
|
|
134
|
+
const args = [
|
|
135
|
+
'--output', dexOutputDir,
|
|
136
|
+
'--min-api', '24',
|
|
137
|
+
...classFiles
|
|
138
|
+
];
|
|
139
|
+
const result = await this.runCommand(d8, args);
|
|
140
|
+
if (!result.success) {
|
|
141
|
+
return {
|
|
142
|
+
success: false,
|
|
143
|
+
dexPath: '',
|
|
144
|
+
dexBytes: null,
|
|
145
|
+
errors: [result.stderr || 'DEX generation failed']
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// Find generated dex file
|
|
149
|
+
const dexPath = path.join(dexOutputDir, 'classes.dex');
|
|
150
|
+
if (!fs.existsSync(dexPath)) {
|
|
151
|
+
return {
|
|
152
|
+
success: false,
|
|
153
|
+
dexPath: '',
|
|
154
|
+
dexBytes: null,
|
|
155
|
+
errors: ['DEX file not generated']
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const dexBytes = fs.readFileSync(dexPath);
|
|
159
|
+
(0, logger_1.log)(`Generated DEX: ${dexBytes.length} bytes`);
|
|
160
|
+
return {
|
|
161
|
+
success: true,
|
|
162
|
+
dexPath,
|
|
163
|
+
dexBytes,
|
|
164
|
+
errors: []
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Run a command and return result
|
|
169
|
+
*/
|
|
170
|
+
runCommand(cmd, args) {
|
|
171
|
+
return new Promise((resolve) => {
|
|
172
|
+
const proc = (0, child_process_1.spawn)(cmd, args, {
|
|
173
|
+
shell: os.platform() === 'win32',
|
|
174
|
+
env: process.env
|
|
175
|
+
});
|
|
176
|
+
let stdout = '';
|
|
177
|
+
let stderr = '';
|
|
178
|
+
proc.stdout?.on('data', (data) => {
|
|
179
|
+
stdout += data.toString();
|
|
180
|
+
});
|
|
181
|
+
proc.stderr?.on('data', (data) => {
|
|
182
|
+
stderr += data.toString();
|
|
183
|
+
});
|
|
184
|
+
proc.on('close', (code) => {
|
|
185
|
+
resolve({
|
|
186
|
+
success: code === 0,
|
|
187
|
+
stdout,
|
|
188
|
+
stderr
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
proc.on('error', (err) => {
|
|
192
|
+
resolve({
|
|
193
|
+
success: false,
|
|
194
|
+
stdout: '',
|
|
195
|
+
stderr: err.message
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
exports.DexGenerator = DexGenerator;
|
|
202
|
+
//# sourceMappingURL=dex-generator.js.map
|
|
@@ -13,42 +13,15 @@ export declare class DSLParser {
|
|
|
13
13
|
* Parse Kotlin content and extract UI definition
|
|
14
14
|
*/
|
|
15
15
|
static parseContent(content: string, filePath: string): ParseResult;
|
|
16
|
-
/**
|
|
17
|
-
* Extract DSL JSON from getDefaultDSL() or similar function (legacy support)
|
|
18
|
-
*/
|
|
19
16
|
private static extractDSLFromFunction;
|
|
20
17
|
/**
|
|
21
18
|
* Find the main @Composable function in the file
|
|
22
19
|
*/
|
|
23
|
-
private static findMainComposable;
|
|
24
|
-
/**
|
|
25
|
-
* Extract function body handling nested braces
|
|
26
|
-
*/
|
|
27
|
-
private static extractFunctionBody;
|
|
28
|
-
/**
|
|
29
|
-
* Parse the composable body and extract UI structure
|
|
30
|
-
*/
|
|
31
|
-
private static parseComposableBody;
|
|
32
|
-
/**
|
|
33
|
-
* Parse a layout element (Column, Row, Box)
|
|
34
|
-
*/
|
|
35
|
-
private static parseLayout;
|
|
36
|
-
/**
|
|
37
|
-
* Parse modifier chain
|
|
38
|
-
*/
|
|
39
|
-
private static parseModifier;
|
|
40
|
-
/**
|
|
41
|
-
* Parse children elements (handles multi-line elements)
|
|
42
|
-
* Maintains source code order
|
|
43
|
-
*/
|
|
44
|
-
private static parseChildren;
|
|
45
|
-
/**
|
|
46
|
-
* Extract content within parentheses (handles nesting)
|
|
47
|
-
*/
|
|
48
|
-
private static extractParenthesesContent;
|
|
49
20
|
/**
|
|
50
|
-
*
|
|
21
|
+
* Find the main @Composable and a library of all others
|
|
51
22
|
*/
|
|
23
|
+
private static extractComposables;
|
|
24
|
+
private static findMatchingBracket;
|
|
52
25
|
private static generateDefaultDSL;
|
|
53
26
|
}
|
|
54
27
|
//# sourceMappingURL=dsl-parser.d.ts.map
|