@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
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kotlin Compiler Service
|
|
3
|
+
* Compiles Kotlin files to .class files for hot reload
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import * as os from 'os';
|
|
9
|
+
import { spawn } from 'child_process';
|
|
10
|
+
import { log, error as logError } from '../utils/logger';
|
|
11
|
+
|
|
12
|
+
export interface CompileResult {
|
|
13
|
+
success: boolean;
|
|
14
|
+
classFiles: string[];
|
|
15
|
+
errors: string[];
|
|
16
|
+
outputDir: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class KotlinCompiler {
|
|
20
|
+
private static readonly TAG = 'KotlinCompiler';
|
|
21
|
+
private kotlincPath: string | null = null;
|
|
22
|
+
private composeCompilerPath: string | null = null;
|
|
23
|
+
private staticClasspath: string[] = []; // SDK + deps cached; project classes always fresh
|
|
24
|
+
|
|
25
|
+
constructor(private projectPath: string) {}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Find Compose compiler plugin JAR
|
|
29
|
+
* For Kotlin 2.0+, the Compose compiler is bundled with kotlinc
|
|
30
|
+
*/
|
|
31
|
+
async findComposeCompiler(): Promise<string | null> {
|
|
32
|
+
if (this.composeCompilerPath) return this.composeCompilerPath;
|
|
33
|
+
|
|
34
|
+
// First check if kotlinc has a bundled Compose compiler (Kotlin 2.0+)
|
|
35
|
+
const kotlincPath = await this.findKotlinc();
|
|
36
|
+
if (kotlincPath) {
|
|
37
|
+
const kotlincDir = path.dirname(path.dirname(kotlincPath)); // Go up from bin/kotlinc
|
|
38
|
+
const bundledComposePlugin = path.join(kotlincDir, 'lib', 'compose-compiler-plugin.jar');
|
|
39
|
+
|
|
40
|
+
if (fs.existsSync(bundledComposePlugin)) {
|
|
41
|
+
this.composeCompilerPath = bundledComposePlugin;
|
|
42
|
+
log(`Found bundled Compose compiler (Kotlin 2.0+)`);
|
|
43
|
+
return this.composeCompilerPath;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Fallback to Gradle cache for older Kotlin versions
|
|
48
|
+
const gradleCache = path.join(os.homedir(), '.gradle', 'caches', 'modules-2', 'files-2.1');
|
|
49
|
+
const composeCompilerDir = path.join(gradleCache, 'androidx.compose.compiler', 'compiler');
|
|
50
|
+
|
|
51
|
+
if (!fs.existsSync(composeCompilerDir)) {
|
|
52
|
+
log('Compose compiler not found');
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Find latest version
|
|
57
|
+
const versions = fs.readdirSync(composeCompilerDir)
|
|
58
|
+
.filter(v => fs.statSync(path.join(composeCompilerDir, v)).isDirectory())
|
|
59
|
+
.sort().reverse();
|
|
60
|
+
|
|
61
|
+
for (const version of versions) {
|
|
62
|
+
const versionDir = path.join(composeCompilerDir, version);
|
|
63
|
+
const hashes = fs.readdirSync(versionDir);
|
|
64
|
+
|
|
65
|
+
for (const hash of hashes) {
|
|
66
|
+
const hashDir = path.join(versionDir, hash);
|
|
67
|
+
if (!fs.statSync(hashDir).isDirectory()) continue;
|
|
68
|
+
|
|
69
|
+
const files = fs.readdirSync(hashDir);
|
|
70
|
+
for (const file of files) {
|
|
71
|
+
if (file.endsWith('.jar') && !file.endsWith('-sources.jar')) {
|
|
72
|
+
this.composeCompilerPath = path.join(hashDir, file);
|
|
73
|
+
log(`Found Compose compiler: ${version}`);
|
|
74
|
+
return this.composeCompilerPath;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Find kotlinc executable
|
|
85
|
+
*/
|
|
86
|
+
async findKotlinc(): Promise<string | null> {
|
|
87
|
+
if (this.kotlincPath) return this.kotlincPath;
|
|
88
|
+
|
|
89
|
+
// Check common locations
|
|
90
|
+
const locations = [
|
|
91
|
+
// From environment variable
|
|
92
|
+
process.env.KOTLIN_HOME ? path.join(process.env.KOTLIN_HOME, 'bin', 'kotlinc') : null,
|
|
93
|
+
// From Android Studio
|
|
94
|
+
process.env.ANDROID_STUDIO_HOME ? path.join(process.env.ANDROID_STUDIO_HOME, 'plugins', 'Kotlin', 'kotlinc', 'bin', 'kotlinc') : null,
|
|
95
|
+
// System-wide installation (Windows)
|
|
96
|
+
'C:\\Program Files\\kotlinc\\bin\\kotlinc.bat',
|
|
97
|
+
'C:\\kotlinc\\bin\\kotlinc.bat',
|
|
98
|
+
// System-wide installation (Unix)
|
|
99
|
+
'/usr/local/bin/kotlinc',
|
|
100
|
+
'/usr/bin/kotlinc',
|
|
101
|
+
// Homebrew (macOS)
|
|
102
|
+
'/opt/homebrew/bin/kotlinc',
|
|
103
|
+
].filter(Boolean) as string[];
|
|
104
|
+
|
|
105
|
+
for (const loc of locations) {
|
|
106
|
+
const execPath = os.platform() === 'win32' && !loc.endsWith('.bat') ? `${loc}.bat` : loc;
|
|
107
|
+
if (fs.existsSync(execPath)) {
|
|
108
|
+
this.kotlincPath = execPath;
|
|
109
|
+
log(`Found kotlinc at: ${execPath}`);
|
|
110
|
+
return execPath;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Try to find via 'where' (Windows) or 'which' (Unix)
|
|
115
|
+
try {
|
|
116
|
+
const cmd = os.platform() === 'win32' ? 'where' : 'which';
|
|
117
|
+
const result = await this.runCommand(cmd, ['kotlinc']);
|
|
118
|
+
if (result.success && result.stdout.trim()) {
|
|
119
|
+
this.kotlincPath = result.stdout.trim().split('\n')[0];
|
|
120
|
+
log(`Found kotlinc via ${cmd}: ${this.kotlincPath}`);
|
|
121
|
+
return this.kotlincPath;
|
|
122
|
+
}
|
|
123
|
+
} catch (e) {
|
|
124
|
+
// Ignore
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
logError('kotlinc not found. Please install Kotlin or set KOTLIN_HOME');
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Build the classpath for compilation
|
|
133
|
+
* This needs to include Android SDK, Compose, and project dependencies
|
|
134
|
+
*/
|
|
135
|
+
async buildClasspath(): Promise<string[]> {
|
|
136
|
+
if (this.staticClasspath.length > 0) {
|
|
137
|
+
return [...this.staticClasspath, ...this.getProjectClasspathEntries()];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const classpath: string[] = [];
|
|
141
|
+
// Check multiple locations for Android SDK
|
|
142
|
+
let androidHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
|
|
143
|
+
|
|
144
|
+
// Fallback to common Windows locations
|
|
145
|
+
if (!androidHome) {
|
|
146
|
+
const commonLocations = [
|
|
147
|
+
'C:\\Android',
|
|
148
|
+
path.join(os.homedir(), 'AppData', 'Local', 'Android', 'Sdk'),
|
|
149
|
+
'C:\\Users\\Public\\Android\\Sdk',
|
|
150
|
+
];
|
|
151
|
+
for (const loc of commonLocations) {
|
|
152
|
+
if (fs.existsSync(path.join(loc, 'platforms'))) {
|
|
153
|
+
androidHome = loc;
|
|
154
|
+
log(`Found Android SDK at: ${loc}`);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!androidHome) {
|
|
161
|
+
logError('ANDROID_HOME or ANDROID_SDK_ROOT not set');
|
|
162
|
+
return classpath;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Find android.jar
|
|
166
|
+
const platformsDir = path.join(androidHome, 'platforms');
|
|
167
|
+
if (fs.existsSync(platformsDir)) {
|
|
168
|
+
const platforms = fs.readdirSync(platformsDir)
|
|
169
|
+
.filter(d => d.startsWith('android-'))
|
|
170
|
+
.sort((a, b) => {
|
|
171
|
+
const aNum = parseInt(a.replace('android-', ''));
|
|
172
|
+
const bNum = parseInt(b.replace('android-', ''));
|
|
173
|
+
return bNum - aNum;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
if (platforms.length > 0) {
|
|
177
|
+
const androidJar = path.join(platformsDir, platforms[0], 'android.jar');
|
|
178
|
+
if (fs.existsSync(androidJar)) {
|
|
179
|
+
classpath.push(androidJar);
|
|
180
|
+
log(`Using Android SDK: ${platforms[0]}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Add ALL Gradle cached dependencies (Compose, AndroidX, Kotlin, etc.)
|
|
186
|
+
const gradleCache = path.join(os.homedir(), '.gradle', 'caches', 'modules-2', 'files-2.1');
|
|
187
|
+
if (fs.existsSync(gradleCache)) {
|
|
188
|
+
// Scan for all required dependency groups
|
|
189
|
+
const requiredGroups = [
|
|
190
|
+
'androidx.compose.runtime',
|
|
191
|
+
'androidx.compose.ui',
|
|
192
|
+
'androidx.compose.foundation',
|
|
193
|
+
'androidx.compose.material3',
|
|
194
|
+
'androidx.compose.material',
|
|
195
|
+
'androidx.compose.animation',
|
|
196
|
+
'androidx.annotation',
|
|
197
|
+
'androidx.core',
|
|
198
|
+
'androidx.activity',
|
|
199
|
+
'androidx.lifecycle',
|
|
200
|
+
'androidx.savedstate',
|
|
201
|
+
'androidx.collection',
|
|
202
|
+
'org.jetbrains.kotlin',
|
|
203
|
+
'org.jetbrains.kotlinx',
|
|
204
|
+
'org.jetbrains.annotations',
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
for (const group of requiredGroups) {
|
|
208
|
+
const groupDir = path.join(gradleCache, group);
|
|
209
|
+
if (fs.existsSync(groupDir)) {
|
|
210
|
+
// Get all artifacts in this group
|
|
211
|
+
const artifacts = fs.readdirSync(groupDir);
|
|
212
|
+
for (const artifact of artifacts) {
|
|
213
|
+
const artifactDir = path.join(groupDir, artifact);
|
|
214
|
+
if (!fs.statSync(artifactDir).isDirectory()) continue;
|
|
215
|
+
|
|
216
|
+
// Find latest version
|
|
217
|
+
const versions = fs.readdirSync(artifactDir)
|
|
218
|
+
.filter(v => fs.statSync(path.join(artifactDir, v)).isDirectory())
|
|
219
|
+
.sort().reverse();
|
|
220
|
+
|
|
221
|
+
if (versions.length > 0) {
|
|
222
|
+
const versionDir = path.join(artifactDir, versions[0]);
|
|
223
|
+
const hashes = fs.readdirSync(versionDir);
|
|
224
|
+
for (const hash of hashes) {
|
|
225
|
+
const hashDir = path.join(versionDir, hash);
|
|
226
|
+
if (!fs.statSync(hashDir).isDirectory()) continue;
|
|
227
|
+
|
|
228
|
+
const files = fs.readdirSync(hashDir);
|
|
229
|
+
// Add all JARs (not sources or javadoc)
|
|
230
|
+
for (const file of files) {
|
|
231
|
+
if (file.endsWith('.jar') &&
|
|
232
|
+
!file.endsWith('-sources.jar') &&
|
|
233
|
+
!file.endsWith('-javadoc.jar')) {
|
|
234
|
+
classpath.push(path.join(hashDir, file));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Scan transforms-3 cache - grab ALL classes.jar (Compose, Material3, Room, DivKit, etc.)
|
|
245
|
+
const transformsCache = path.join(os.homedir(), '.gradle', 'caches', 'transforms-3');
|
|
246
|
+
if (fs.existsSync(transformsCache)) {
|
|
247
|
+
try {
|
|
248
|
+
for (const hash of fs.readdirSync(transformsCache)) {
|
|
249
|
+
const transformedDir = path.join(transformsCache, hash, 'transformed');
|
|
250
|
+
if (!fs.existsSync(transformedDir)) continue;
|
|
251
|
+
try {
|
|
252
|
+
for (const pkg of fs.readdirSync(transformedDir)) {
|
|
253
|
+
const classesJar = path.join(transformedDir, pkg, 'jars', 'classes.jar');
|
|
254
|
+
if (fs.existsSync(classesJar) && !classpath.includes(classesJar)) {
|
|
255
|
+
classpath.push(classesJar);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
} catch (e) { /* ignore */ }
|
|
259
|
+
}
|
|
260
|
+
log(`Added ${classpath.length} transforms-3 JARs to classpath`);
|
|
261
|
+
} catch (e) { /* ignore */ }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Cache static entries; project build outputs always fetched fresh
|
|
265
|
+
this.staticClasspath = [...classpath];
|
|
266
|
+
const projectEntries = this.getProjectClasspathEntries();
|
|
267
|
+
log(`Built static classpath with ${classpath.length} entries + ${projectEntries.length} project entries`);
|
|
268
|
+
return [...classpath, ...projectEntries];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Get project build output classpath entries.
|
|
273
|
+
* Always called fresh ΓÇö never cached ΓÇö so new class files from Gradle builds are always visible.
|
|
274
|
+
*/
|
|
275
|
+
private getProjectClasspathEntries(): string[] {
|
|
276
|
+
const entries: string[] = [];
|
|
277
|
+
const candidates = [
|
|
278
|
+
path.join(this.projectPath, 'app', 'build', 'tmp', 'kotlin-classes', 'debug'),
|
|
279
|
+
path.join(this.projectPath, 'app', 'build', 'intermediates', 'javac', 'debug', 'classes'),
|
|
280
|
+
path.join(this.projectPath, 'app', 'build', 'intermediates', 'compile_and_runtime_not_namespaced_r_class_jar', 'debug', 'R.jar'),
|
|
281
|
+
path.join(this.projectPath, 'app', 'build', 'intermediates', 'classes', 'debug', 'transformDebugClassesWithAsm', 'jars', '0.jar'),
|
|
282
|
+
];
|
|
283
|
+
for (const c of candidates) {
|
|
284
|
+
if (fs.existsSync(c)) entries.push(c);
|
|
285
|
+
}
|
|
286
|
+
return entries;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Recursively find JAR files in a directory up to maxDepth
|
|
291
|
+
*/
|
|
292
|
+
private findJarsRecursive(dir: string, classpath: string[], maxDepth: number, currentDepth = 0): void {
|
|
293
|
+
if (currentDepth > maxDepth || !fs.existsSync(dir)) return;
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
297
|
+
for (const entry of entries) {
|
|
298
|
+
const fullPath = path.join(dir, entry.name);
|
|
299
|
+
if (entry.isDirectory()) {
|
|
300
|
+
this.findJarsRecursive(fullPath, classpath, maxDepth, currentDepth + 1);
|
|
301
|
+
} else if (entry.name.endsWith('.jar') && !classpath.includes(fullPath)) {
|
|
302
|
+
classpath.push(fullPath);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
} catch (e) {
|
|
306
|
+
// Ignore permission errors
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Compile a single Kotlin file
|
|
312
|
+
*/
|
|
313
|
+
async compileFile(filePath: string): Promise<CompileResult> {
|
|
314
|
+
const kotlinc = await this.findKotlinc();
|
|
315
|
+
if (!kotlinc) {
|
|
316
|
+
return {
|
|
317
|
+
success: false,
|
|
318
|
+
classFiles: [],
|
|
319
|
+
errors: ['kotlinc not found'],
|
|
320
|
+
outputDir: ''
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const classpath = await this.buildClasspath();
|
|
325
|
+
if (classpath.length === 0) {
|
|
326
|
+
return {
|
|
327
|
+
success: false,
|
|
328
|
+
classFiles: [],
|
|
329
|
+
errors: ['Failed to build classpath - Android SDK not found'],
|
|
330
|
+
outputDir: ''
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Create temp output directory
|
|
335
|
+
const outputDir = path.join(os.tmpdir(), 'jetstart-compile', Date.now().toString());
|
|
336
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
337
|
+
|
|
338
|
+
log(`Compiling ${path.basename(filePath)}...`);
|
|
339
|
+
|
|
340
|
+
// Find Compose compiler plugin for @Composable support
|
|
341
|
+
const composeCompiler = await this.findComposeCompiler();
|
|
342
|
+
|
|
343
|
+
// On Windows, command line can be too long with many classpath entries
|
|
344
|
+
// Use an argument file (@argfile) to avoid this limitation
|
|
345
|
+
const classpathStr = classpath.join(os.platform() === 'win32' ? ';' : ':');
|
|
346
|
+
const argLines = [
|
|
347
|
+
`-d`,
|
|
348
|
+
outputDir,
|
|
349
|
+
`-classpath`,
|
|
350
|
+
classpathStr,
|
|
351
|
+
`-jvm-target`,
|
|
352
|
+
`17`,
|
|
353
|
+
`-Xskip-prerelease-check`,
|
|
354
|
+
`-Xno-call-assertions`,
|
|
355
|
+
`-Xno-param-assertions`,
|
|
356
|
+
];
|
|
357
|
+
|
|
358
|
+
// Add Compose compiler plugin if found
|
|
359
|
+
if (composeCompiler) {
|
|
360
|
+
argLines.push(`-Xplugin=${composeCompiler}`);
|
|
361
|
+
log(`Using Compose compiler plugin`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
argLines.push(filePath);
|
|
365
|
+
const argFileContent = argLines.join('\n');
|
|
366
|
+
|
|
367
|
+
const argFilePath = path.join(outputDir, 'kotlinc-args.txt');
|
|
368
|
+
fs.writeFileSync(argFilePath, argFileContent);
|
|
369
|
+
|
|
370
|
+
log(`Using argument file: ${argFilePath}`);
|
|
371
|
+
|
|
372
|
+
// Build kotlinc arguments using @argfile
|
|
373
|
+
const args = [`@${argFilePath}`];
|
|
374
|
+
|
|
375
|
+
const result = await this.runCommand(kotlinc, args);
|
|
376
|
+
|
|
377
|
+
if (!result.success) {
|
|
378
|
+
return {
|
|
379
|
+
success: false,
|
|
380
|
+
classFiles: [],
|
|
381
|
+
errors: [result.stderr || 'Compilation failed'],
|
|
382
|
+
outputDir
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Find generated class files
|
|
387
|
+
const classFiles = this.findClassFiles(outputDir);
|
|
388
|
+
|
|
389
|
+
log(`Compiled ${classFiles.length} class files`);
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
success: true,
|
|
393
|
+
classFiles,
|
|
394
|
+
errors: [],
|
|
395
|
+
outputDir
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Find all .class files in a directory
|
|
401
|
+
*/
|
|
402
|
+
private findClassFiles(dir: string): string[] {
|
|
403
|
+
const files: string[] = [];
|
|
404
|
+
|
|
405
|
+
const walk = (d: string) => {
|
|
406
|
+
if (!fs.existsSync(d)) return;
|
|
407
|
+
const entries = fs.readdirSync(d, { withFileTypes: true });
|
|
408
|
+
for (const entry of entries) {
|
|
409
|
+
const fullPath = path.join(d, entry.name);
|
|
410
|
+
if (entry.isDirectory()) {
|
|
411
|
+
walk(fullPath);
|
|
412
|
+
} else if (entry.name.endsWith('.class')) {
|
|
413
|
+
files.push(fullPath);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
walk(dir);
|
|
419
|
+
return files;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Run a command and return result
|
|
424
|
+
*/
|
|
425
|
+
private runCommand(cmd: string, args: string[]): Promise<{ success: boolean; stdout: string; stderr: string }> {
|
|
426
|
+
return new Promise((resolve) => {
|
|
427
|
+
const proc = spawn(cmd, args, {
|
|
428
|
+
shell: os.platform() === 'win32',
|
|
429
|
+
env: process.env
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
let stdout = '';
|
|
433
|
+
let stderr = '';
|
|
434
|
+
|
|
435
|
+
proc.stdout?.on('data', (data) => {
|
|
436
|
+
stdout += data.toString();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
proc.stderr?.on('data', (data) => {
|
|
440
|
+
stderr += data.toString();
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
proc.on('close', (code) => {
|
|
444
|
+
resolve({
|
|
445
|
+
success: code === 0,
|
|
446
|
+
stdout,
|
|
447
|
+
stderr
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
proc.on('error', (err) => {
|
|
452
|
+
resolve({
|
|
453
|
+
success: false,
|
|
454
|
+
stdout: '',
|
|
455
|
+
stderr: err.message
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|