@jetstart/core 1.1.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.
Files changed (116) hide show
  1. package/.eslintrc.json +6 -0
  2. package/README.md +124 -0
  3. package/dist/build/builder.d.ts +57 -0
  4. package/dist/build/builder.d.ts.map +1 -0
  5. package/dist/build/builder.js +151 -0
  6. package/dist/build/builder.js.map +1 -0
  7. package/dist/build/cache.d.ts +51 -0
  8. package/dist/build/cache.d.ts.map +1 -0
  9. package/dist/build/cache.js +152 -0
  10. package/dist/build/cache.js.map +1 -0
  11. package/dist/build/dsl-parser.d.ts +54 -0
  12. package/dist/build/dsl-parser.d.ts.map +1 -0
  13. package/dist/build/dsl-parser.js +373 -0
  14. package/dist/build/dsl-parser.js.map +1 -0
  15. package/dist/build/dsl-types.d.ts +47 -0
  16. package/dist/build/dsl-types.d.ts.map +1 -0
  17. package/dist/build/dsl-types.js +7 -0
  18. package/dist/build/dsl-types.js.map +1 -0
  19. package/dist/build/gradle-injector.d.ts +14 -0
  20. package/dist/build/gradle-injector.d.ts.map +1 -0
  21. package/dist/build/gradle-injector.js +77 -0
  22. package/dist/build/gradle-injector.js.map +1 -0
  23. package/dist/build/gradle.d.ts +43 -0
  24. package/dist/build/gradle.d.ts.map +1 -0
  25. package/dist/build/gradle.js +281 -0
  26. package/dist/build/gradle.js.map +1 -0
  27. package/dist/build/index.d.ts +10 -0
  28. package/dist/build/index.d.ts.map +1 -0
  29. package/dist/build/index.js +26 -0
  30. package/dist/build/index.js.map +1 -0
  31. package/dist/build/parser.d.ts +12 -0
  32. package/dist/build/parser.d.ts.map +1 -0
  33. package/dist/build/parser.js +71 -0
  34. package/dist/build/parser.js.map +1 -0
  35. package/dist/build/watcher.d.ts +30 -0
  36. package/dist/build/watcher.d.ts.map +1 -0
  37. package/dist/build/watcher.js +120 -0
  38. package/dist/build/watcher.js.map +1 -0
  39. package/dist/index.d.ts +11 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +26 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/server/http.d.ts +12 -0
  44. package/dist/server/http.d.ts.map +1 -0
  45. package/dist/server/http.js +32 -0
  46. package/dist/server/http.js.map +1 -0
  47. package/dist/server/index.d.ts +35 -0
  48. package/dist/server/index.d.ts.map +1 -0
  49. package/dist/server/index.js +262 -0
  50. package/dist/server/index.js.map +1 -0
  51. package/dist/server/middleware.d.ts +7 -0
  52. package/dist/server/middleware.d.ts.map +1 -0
  53. package/dist/server/middleware.js +42 -0
  54. package/dist/server/middleware.js.map +1 -0
  55. package/dist/server/routes.d.ts +7 -0
  56. package/dist/server/routes.d.ts.map +1 -0
  57. package/dist/server/routes.js +104 -0
  58. package/dist/server/routes.js.map +1 -0
  59. package/dist/types/index.d.ts +20 -0
  60. package/dist/types/index.d.ts.map +1 -0
  61. package/dist/types/index.js +6 -0
  62. package/dist/types/index.js.map +1 -0
  63. package/dist/utils/index.d.ts +7 -0
  64. package/dist/utils/index.d.ts.map +1 -0
  65. package/dist/utils/index.js +23 -0
  66. package/dist/utils/index.js.map +1 -0
  67. package/dist/utils/logger.d.ts +10 -0
  68. package/dist/utils/logger.d.ts.map +1 -0
  69. package/dist/utils/logger.js +33 -0
  70. package/dist/utils/logger.js.map +1 -0
  71. package/dist/utils/qr.d.ts +8 -0
  72. package/dist/utils/qr.d.ts.map +1 -0
  73. package/dist/utils/qr.js +48 -0
  74. package/dist/utils/qr.js.map +1 -0
  75. package/dist/utils/session.d.ts +18 -0
  76. package/dist/utils/session.d.ts.map +1 -0
  77. package/dist/utils/session.js +49 -0
  78. package/dist/utils/session.js.map +1 -0
  79. package/dist/websocket/handler.d.ts +25 -0
  80. package/dist/websocket/handler.d.ts.map +1 -0
  81. package/dist/websocket/handler.js +126 -0
  82. package/dist/websocket/handler.js.map +1 -0
  83. package/dist/websocket/index.d.ts +18 -0
  84. package/dist/websocket/index.d.ts.map +1 -0
  85. package/dist/websocket/index.js +40 -0
  86. package/dist/websocket/index.js.map +1 -0
  87. package/dist/websocket/manager.d.ts +16 -0
  88. package/dist/websocket/manager.d.ts.map +1 -0
  89. package/dist/websocket/manager.js +58 -0
  90. package/dist/websocket/manager.js.map +1 -0
  91. package/package.json +78 -0
  92. package/src/build/builder.ts +192 -0
  93. package/src/build/cache.ts +144 -0
  94. package/src/build/dsl-parser.ts +382 -0
  95. package/src/build/dsl-types.ts +50 -0
  96. package/src/build/gradle-injector.ts +64 -0
  97. package/src/build/gradle.ts +305 -0
  98. package/src/build/index.ts +10 -0
  99. package/src/build/parser.ts +75 -0
  100. package/src/build/watcher.ts +103 -0
  101. package/src/index.ts +20 -0
  102. package/src/server/http.ts +38 -0
  103. package/src/server/index.ts +272 -0
  104. package/src/server/middleware.ts +43 -0
  105. package/src/server/routes.ts +116 -0
  106. package/src/types/index.ts +21 -0
  107. package/src/utils/index.ts +7 -0
  108. package/src/utils/logger.ts +28 -0
  109. package/src/utils/qr.ts +46 -0
  110. package/src/utils/session.ts +58 -0
  111. package/src/websocket/handler.ts +150 -0
  112. package/src/websocket/index.ts +56 -0
  113. package/src/websocket/manager.ts +63 -0
  114. package/tests/build.test.ts +13 -0
  115. package/tests/server.test.ts +13 -0
  116. package/tsconfig.json +25 -0
@@ -0,0 +1,50 @@
1
+ /**
2
+ * DSL Type Definitions for Server-Side
3
+ * Represents UI elements that can be sent to Android app as JSON
4
+ */
5
+
6
+ export interface UIDefinition {
7
+ version: string;
8
+ screen: DSLElement;
9
+ }
10
+
11
+ export interface DSLElement {
12
+ type: string;
13
+ text?: string;
14
+ style?: string;
15
+ color?: string;
16
+ modifier?: DSLModifier;
17
+ horizontalAlignment?: string;
18
+ verticalArrangement?: string;
19
+ contentAlignment?: string;
20
+ height?: number;
21
+ width?: number;
22
+ onClick?: string;
23
+ enabled?: boolean;
24
+ imageVector?: string;
25
+ tint?: string;
26
+ contentDescription?: string;
27
+ children?: DSLElement[];
28
+ }
29
+
30
+ export interface DSLModifier {
31
+ fillMaxSize?: boolean;
32
+ fillMaxWidth?: boolean;
33
+ fillMaxHeight?: boolean;
34
+ padding?: number;
35
+ paddingHorizontal?: number;
36
+ paddingVertical?: number;
37
+ size?: number;
38
+ height?: number;
39
+ width?: number;
40
+ weight?: number;
41
+ }
42
+
43
+ /**
44
+ * Parse result from Kotlin file
45
+ */
46
+ export interface ParseResult {
47
+ success: boolean;
48
+ dsl?: UIDefinition;
49
+ errors?: string[];
50
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Gradle BuildConfig Injector
3
+ * Injects buildConfigField values into build.gradle
4
+ */
5
+
6
+ import * as fs from 'fs-extra';
7
+ import * as path from 'path';
8
+ import { log } from '../utils/logger';
9
+
10
+ export interface BuildConfigField {
11
+ type: string;
12
+ name: string;
13
+ value: string;
14
+ }
15
+
16
+ /**
17
+ * Inject buildConfigFields into app/build.gradle
18
+ */
19
+ export async function injectBuildConfigFields(
20
+ projectPath: string,
21
+ fields: BuildConfigField[]
22
+ ): Promise<void> {
23
+ const buildGradlePath = path.join(projectPath, 'app', 'build.gradle');
24
+
25
+ if (!fs.existsSync(buildGradlePath)) {
26
+ log('Warning: build.gradle not found, skipping injection');
27
+ return;
28
+ }
29
+
30
+ let content = await fs.readFile(buildGradlePath, 'utf-8');
31
+
32
+ // Find the defaultConfig block
33
+ const defaultConfigRegex = /defaultConfig\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/;
34
+ const match = content.match(defaultConfigRegex);
35
+
36
+ if (!match) {
37
+ log('Warning: defaultConfig not found in build.gradle');
38
+ return;
39
+ }
40
+
41
+ const defaultConfigBlock = match[1];
42
+
43
+ // Remove existing JetStart buildConfigFields
44
+ let updatedBlock = defaultConfigBlock.replace(
45
+ /\/\/ JetStart injected fields[\s\S]*?\/\/ End JetStart fields\n/g,
46
+ ''
47
+ );
48
+
49
+ // Add new buildConfigFields
50
+ const fieldLines = fields.map(f => {
51
+ // For String type, escape the value with quotes
52
+ const escapedValue = f.type === 'String' ? `\\"${f.value}\\"` : f.value;
53
+ return ` buildConfigField "${f.type}", "${f.name}", "${escapedValue}"`;
54
+ }).join('\n');
55
+
56
+ updatedBlock = updatedBlock.trimEnd() + '\n\n // JetStart injected fields\n' +
57
+ fieldLines + '\n // End JetStart fields\n ';
58
+
59
+ // Replace the defaultConfig block
60
+ content = content.replace(defaultConfigRegex, `defaultConfig {${updatedBlock}}`);
61
+
62
+ await fs.writeFile(buildGradlePath, content, 'utf-8');
63
+ log('Injected buildConfigFields into build.gradle');
64
+ }
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Gradle Executor
3
+ * Spawns and manages Gradle build processes
4
+ */
5
+
6
+ import { spawn, ChildProcess } from 'child_process';
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import * as os from 'os';
10
+ import { BuildConfig, BuildResult } from '@jetstart/shared';
11
+ import { BuildOutputParser } from './parser';
12
+
13
+ export interface GradleExecutorOptions {
14
+ javaHome?: string;
15
+ androidHome?: string;
16
+ }
17
+
18
+ export class GradleExecutor {
19
+ private javaHome: string | undefined;
20
+ private androidHome: string | undefined;
21
+
22
+ constructor(options: GradleExecutorOptions = {}) {
23
+ this.javaHome = options.javaHome || process.env.JAVA_HOME;
24
+ this.androidHome = options.androidHome || process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
25
+ }
26
+
27
+ /**
28
+ * Execute Gradle build
29
+ */
30
+ async execute(config: BuildConfig): Promise<BuildResult> {
31
+ const startTime = Date.now();
32
+ const gradlePath = this.findGradle(config.projectPath);
33
+
34
+ // If Gradle not found, return mock build for testing
35
+ if (!gradlePath) {
36
+ console.log('[Gradle] No Gradle found, returning mock successful build for testing');
37
+
38
+ // Simulate build delay
39
+ await new Promise(resolve => setTimeout(resolve, 2000));
40
+
41
+ return {
42
+ success: true,
43
+ buildTime: Date.now() - startTime,
44
+ apkPath: path.join(config.projectPath, 'build/outputs/apk/debug/app-debug.apk'),
45
+ apkSize: 5242880, // Mock size: 5MB
46
+ };
47
+ }
48
+
49
+ // Ensure Android SDK is configured
50
+ await this.ensureAndroidSdk(config.projectPath);
51
+
52
+ const args = this.buildGradleArgs(config);
53
+ const env = this.buildEnv();
54
+
55
+ return this.runGradle(gradlePath, args, config.projectPath, env, startTime);
56
+ }
57
+
58
+ /**
59
+ * Ensure Android SDK is configured (auto-detect and create local.properties)
60
+ */
61
+ private async ensureAndroidSdk(projectPath: string): Promise<void> {
62
+ const localPropsPath = path.join(projectPath, 'local.properties');
63
+
64
+ // Check if local.properties already exists
65
+ if (fs.existsSync(localPropsPath)) {
66
+ return; // Already configured
67
+ }
68
+
69
+ // Try to find Android SDK
70
+ let androidSdkPath = this.androidHome;
71
+
72
+ // If not in environment, check common Windows locations
73
+ if (!androidSdkPath && os.platform() === 'win32') {
74
+ const commonPaths = [
75
+ 'C:\\Android', // Check C:\Android first (command-line tools location)
76
+ path.join(os.homedir(), 'AppData', 'Local', 'Android', 'Sdk'),
77
+ 'C:\\Android\\Sdk',
78
+ 'C:\\Program Files (x86)\\Android\\android-sdk',
79
+ ];
80
+
81
+ for (const p of commonPaths) {
82
+ if (fs.existsSync(p)) {
83
+ androidSdkPath = p;
84
+ console.log(`[Gradle] Auto-detected Android SDK at: ${androidSdkPath}`);
85
+ break;
86
+ }
87
+ }
88
+ }
89
+
90
+ // If not found on macOS/Linux, check common paths
91
+ if (!androidSdkPath && os.platform() !== 'win32') {
92
+ const commonPaths = [
93
+ path.join(os.homedir(), 'Android', 'Sdk'),
94
+ path.join(os.homedir(), 'Library', 'Android', 'sdk'),
95
+ '/opt/android-sdk',
96
+ ];
97
+
98
+ for (const p of commonPaths) {
99
+ if (fs.existsSync(p)) {
100
+ androidSdkPath = p;
101
+ console.log(`[Gradle] Auto-detected Android SDK at: ${androidSdkPath}`);
102
+ break;
103
+ }
104
+ }
105
+ }
106
+
107
+ if (!androidSdkPath) {
108
+ console.warn('[Gradle] Android SDK not found! Set ANDROID_HOME environment variable.');
109
+ console.warn('[Gradle] Download Android SDK from: https://developer.android.com/studio');
110
+ return;
111
+ }
112
+
113
+ // Create local.properties with SDK path
114
+ const localProps = `# Auto-generated by JetStart
115
+ sdk.dir=${androidSdkPath.replace(/\\/g, '\\\\')}
116
+ `;
117
+
118
+ fs.writeFileSync(localPropsPath, localProps);
119
+ console.log(`[Gradle] Created local.properties with SDK path: ${androidSdkPath}`);
120
+ }
121
+
122
+ /**
123
+ * Find Gradle executable (prioritize system gradle for speed)
124
+ */
125
+ private findGradle(projectPath: string): string | null {
126
+ const isWindows = os.platform() === 'win32';
127
+
128
+ // CHECK SYSTEM GRADLE FIRST (instant, no download needed)
129
+ const gradleName = isWindows ? 'gradle.bat' : 'gradle';
130
+ const systemGradle = this.findInPath(gradleName);
131
+
132
+ if (systemGradle) {
133
+ console.log('[Gradle] Using system Gradle (faster than wrapper)');
134
+ return systemGradle;
135
+ }
136
+
137
+ // Fallback to gradlew wrapper in project (slower, downloads Gradle)
138
+ const gradlewName = isWindows ? 'gradlew.bat' : 'gradlew';
139
+ const gradlewPath = path.join(projectPath, gradlewName);
140
+
141
+ if (fs.existsSync(gradlewPath)) {
142
+ console.log('[Gradle] Using project wrapper (may download Gradle on first run)');
143
+ return gradlewPath;
144
+ }
145
+
146
+ return null;
147
+ }
148
+
149
+ /**
150
+ * Find executable in PATH
151
+ */
152
+ private findInPath(executableName: string): string | null {
153
+ const pathEnv = process.env.PATH || '';
154
+ const pathSeparator = os.platform() === 'win32' ? ';' : ':';
155
+ const paths = pathEnv.split(pathSeparator);
156
+
157
+ for (const dir of paths) {
158
+ const fullPath = path.join(dir, executableName);
159
+ if (fs.existsSync(fullPath)) {
160
+ return fullPath;
161
+ }
162
+ }
163
+
164
+ return null;
165
+ }
166
+
167
+ /**
168
+ * Build Gradle arguments
169
+ */
170
+ private buildGradleArgs(config: BuildConfig): string[] {
171
+ const args: string[] = [];
172
+
173
+ // Task based on build type
174
+ if (config.buildType === 'debug') {
175
+ args.push('assembleDebug');
176
+ } else {
177
+ args.push('assembleRelease');
178
+ }
179
+
180
+ // Performance optimizations
181
+ args.push('--parallel');
182
+ args.push('--build-cache');
183
+ args.push('--configure-on-demand');
184
+
185
+ // Build universal APK for all architectures (removed single-ABI restriction)
186
+ // This ensures the APK works on all device architectures
187
+ // Note: This makes the APK larger but ensures compatibility
188
+
189
+ // Daemon for faster subsequent builds
190
+ args.push('--daemon');
191
+
192
+ // Console output
193
+ args.push('--console=plain');
194
+
195
+ return args;
196
+ }
197
+
198
+ /**
199
+ * Build environment variables
200
+ */
201
+ private buildEnv(): NodeJS.ProcessEnv {
202
+ const env = { ...process.env };
203
+
204
+ if (this.javaHome) {
205
+ env.JAVA_HOME = this.javaHome;
206
+ }
207
+
208
+ if (this.androidHome) {
209
+ env.ANDROID_HOME = this.androidHome;
210
+ env.ANDROID_SDK_ROOT = this.androidHome;
211
+ }
212
+
213
+ return env;
214
+ }
215
+
216
+ /**
217
+ * Run Gradle process and collect output
218
+ */
219
+ private runGradle(
220
+ gradlePath: string,
221
+ args: string[],
222
+ cwd: string,
223
+ env: NodeJS.ProcessEnv,
224
+ startTime: number
225
+ ): Promise<BuildResult> {
226
+ return new Promise((resolve) => {
227
+ let output = '';
228
+
229
+ console.log(`[Gradle] Running: ${gradlePath} ${args.join(' ')}`);
230
+ console.log(`[Gradle] Working directory: ${cwd}`);
231
+
232
+ const process = spawn(gradlePath, args, {
233
+ cwd,
234
+ env,
235
+ shell: true,
236
+ });
237
+
238
+ process.stdout.on('data', (data: Buffer) => {
239
+ const text = data.toString();
240
+ output += text;
241
+ // Stream output to console in real-time
242
+ console.log(text.trim());
243
+ });
244
+
245
+ process.stderr.on('data', (data: Buffer) => {
246
+ const text = data.toString();
247
+ output += text;
248
+ // Stream errors to console in real-time
249
+ console.error(text.trim());
250
+ });
251
+
252
+ process.on('close', (code: number) => {
253
+ console.log(`[Gradle] Process exited with code: ${code}`);
254
+ const result = BuildOutputParser.parse(output, startTime);
255
+
256
+ // If process exited with error but no errors were parsed, add generic error
257
+ if (code !== 0 && (!result.errors || result.errors.length === 0)) {
258
+ result.errors = [{
259
+ file: '',
260
+ line: 0,
261
+ column: 0,
262
+ message: `Gradle process exited with code ${code}`,
263
+ severity: 'error' as any,
264
+ }];
265
+ result.success = false;
266
+ }
267
+
268
+ // If build succeeded but APK path not found, manually search for it
269
+ if (result.success && !result.apkPath) {
270
+ const possiblePaths = [
271
+ path.join(cwd, 'app/build/outputs/apk/debug/app-debug.apk'),
272
+ path.join(cwd, 'app/build/intermediates/apk/debug/app-debug.apk'),
273
+ path.join(cwd, 'build/outputs/apk/debug/app-debug.apk'),
274
+ ];
275
+
276
+ for (const apkPath of possiblePaths) {
277
+ if (fs.existsSync(apkPath)) {
278
+ result.apkPath = apkPath;
279
+ result.apkSize = fs.statSync(apkPath).size;
280
+ console.log(`[Gradle] Found APK at: ${apkPath} (${(result.apkSize / 1024 / 1024).toFixed(2)} MB)`);
281
+ break;
282
+ }
283
+ }
284
+ }
285
+
286
+ resolve(result);
287
+ });
288
+
289
+ process.on('error', (err: Error) => {
290
+ console.error(`[Gradle] Failed to spawn process: ${err.message}`);
291
+ resolve({
292
+ success: false,
293
+ buildTime: Date.now() - startTime,
294
+ errors: [{
295
+ file: '',
296
+ line: 0,
297
+ column: 0,
298
+ message: `Failed to spawn Gradle process: ${err.message}`,
299
+ severity: 'error' as any,
300
+ }],
301
+ });
302
+ });
303
+ });
304
+ }
305
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Build Module
3
+ * Handles Kotlin/Gradle compilation, file watching, and build caching
4
+ */
5
+
6
+ export * from './builder';
7
+ export * from './gradle';
8
+ export * from './parser';
9
+ export * from './cache';
10
+ export * from './watcher';
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Build Output Parser
3
+ * Parses Gradle build output to extract errors, warnings, and build info
4
+ */
5
+
6
+ import { BuildResult, BuildError, BuildWarning, ErrorSeverity } from '@jetstart/shared';
7
+
8
+ export class BuildOutputParser {
9
+ /**
10
+ * Parse Gradle build output
11
+ */
12
+ static parse(output: string, startTime: number): BuildResult {
13
+ const lines = output.split('\n');
14
+ const errors: BuildError[] = [];
15
+ const warnings: BuildWarning[] = [];
16
+ let apkPath: string | undefined;
17
+ let apkSize: number | undefined;
18
+
19
+ for (const line of lines) {
20
+ // Parse Kotlin compiler errors: e: /path/file.kt:10:5: Error message
21
+ const errorMatch = line.match(/^e: (.+):(\d+):(\d+): (.+)$/);
22
+ if (errorMatch) {
23
+ errors.push({
24
+ file: errorMatch[1],
25
+ line: parseInt(errorMatch[2], 10),
26
+ column: parseInt(errorMatch[3], 10),
27
+ message: errorMatch[4],
28
+ severity: ErrorSeverity.ERROR,
29
+ });
30
+ continue;
31
+ }
32
+
33
+ // Parse Kotlin compiler warnings: w: /path/file.kt:10:5: Warning message
34
+ const warningMatch = line.match(/^w: (.+):(\d+):(\d+): (.+)$/);
35
+ if (warningMatch) {
36
+ warnings.push({
37
+ file: warningMatch[1],
38
+ line: parseInt(warningMatch[2], 10),
39
+ message: warningMatch[4],
40
+ });
41
+ continue;
42
+ }
43
+
44
+ // Parse APK location (check both outputs and intermediates directories)
45
+ if (line.includes('.apk') && (line.includes('app/build/outputs') || line.includes('app\\build\\outputs') || line.includes('app/build/intermediates') || line.includes('app\\build\\intermediates'))) {
46
+ const apkMatch = line.match(/([^\s]+\.apk)/);
47
+ if (apkMatch) {
48
+ apkPath = apkMatch[1];
49
+ }
50
+ }
51
+
52
+ // Parse build success
53
+ if (line.includes('BUILD SUCCESSFUL')) {
54
+ // Success is determined later
55
+ }
56
+
57
+ // Parse build failure
58
+ if (line.includes('BUILD FAILED')) {
59
+ // Failure is determined by errors array
60
+ }
61
+ }
62
+
63
+ const buildTime = Date.now() - startTime;
64
+ const success = errors.length === 0 && output.includes('BUILD SUCCESSFUL');
65
+
66
+ return {
67
+ success,
68
+ apkPath,
69
+ apkSize,
70
+ buildTime,
71
+ errors: errors.length > 0 ? errors : undefined,
72
+ warnings: warnings.length > 0 ? warnings : undefined,
73
+ };
74
+ }
75
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * File Watcher
3
+ * Watches for file changes using chokidar
4
+ */
5
+
6
+ import * as chokidar from 'chokidar';
7
+ import * as path from 'path';
8
+
9
+ export interface FileWatcherOptions {
10
+ projectPath: string;
11
+ callback: (files: string[]) => void;
12
+ debounceMs?: number;
13
+ }
14
+
15
+ export class FileWatcher {
16
+ private watcher: chokidar.FSWatcher | null = null;
17
+ private callback: (files: string[]) => void;
18
+ private debounceTimer: NodeJS.Timeout | null = null;
19
+ private debounceMs: number;
20
+ private changedFiles: Set<string> = new Set();
21
+
22
+ constructor(options: FileWatcherOptions) {
23
+ this.callback = options.callback;
24
+ this.debounceMs = options.debounceMs || 300; // 300ms default
25
+ }
26
+
27
+ /**
28
+ * Start watching for file changes
29
+ */
30
+ watch(projectPath: string): void {
31
+ if (this.watcher) {
32
+ this.stop();
33
+ }
34
+
35
+ // Watch .kt, .xml, .gradle files
36
+ const patterns = [
37
+ path.join(projectPath, '**/*.kt'),
38
+ path.join(projectPath, '**/*.xml'),
39
+ path.join(projectPath, '**/*.gradle'),
40
+ path.join(projectPath, '**/*.gradle.kts'),
41
+ ];
42
+
43
+ this.watcher = chokidar.watch(patterns, {
44
+ ignored: [
45
+ '**/node_modules/**',
46
+ '**/build/**',
47
+ '**/.gradle/**',
48
+ '**/.git/**',
49
+ '**/dist/**',
50
+ ],
51
+ persistent: true,
52
+ ignoreInitial: true,
53
+ });
54
+
55
+ this.watcher.on('change', (filePath: string) => {
56
+ this.onFileChange(filePath);
57
+ });
58
+
59
+ this.watcher.on('add', (filePath: string) => {
60
+ this.onFileChange(filePath);
61
+ });
62
+
63
+ this.watcher.on('unlink', (filePath: string) => {
64
+ this.onFileChange(filePath);
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Stop watching
70
+ */
71
+ stop(): void {
72
+ if (this.watcher) {
73
+ this.watcher.close();
74
+ this.watcher = null;
75
+ }
76
+
77
+ if (this.debounceTimer) {
78
+ clearTimeout(this.debounceTimer);
79
+ this.debounceTimer = null;
80
+ }
81
+
82
+ this.changedFiles.clear();
83
+ }
84
+
85
+ /**
86
+ * Handle file change with debouncing
87
+ */
88
+ private onFileChange(filePath: string): void {
89
+ this.changedFiles.add(filePath);
90
+
91
+ // Clear existing timer
92
+ if (this.debounceTimer) {
93
+ clearTimeout(this.debounceTimer);
94
+ }
95
+
96
+ // Set new timer
97
+ this.debounceTimer = setTimeout(() => {
98
+ const files = Array.from(this.changedFiles);
99
+ this.changedFiles.clear();
100
+ this.callback(files);
101
+ }, this.debounceMs);
102
+ }
103
+ }
package/src/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Core Package Entry Point
3
+ * Central build server and WebSocket orchestration
4
+ */
5
+
6
+ export * from './server';
7
+ export * from './websocket';
8
+ export * from './build';
9
+ export * from './types';
10
+ export * from './utils';
11
+
12
+ // Re-export shared types
13
+ export type {
14
+ Session,
15
+ SessionStatus,
16
+ BuildConfig,
17
+ BuildResult,
18
+ DeviceInfo,
19
+ WSMessage,
20
+ } from '@jetstart/shared';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * HTTP Server
3
+ * Serves APKs, handles REST endpoints
4
+ */
5
+
6
+ import express, { Express } from 'express';
7
+ import { Server } from 'http';
8
+ import { setupMiddleware } from './middleware';
9
+ import { setupRoutes } from './routes';
10
+ import { log } from '../utils/logger';
11
+
12
+ export interface HttpServerConfig {
13
+ port: number;
14
+ host: string;
15
+ getLatestApk?: () => string | null;
16
+ }
17
+
18
+ export async function createHttpServer(config: HttpServerConfig): Promise<Server> {
19
+ const app: Express = express();
20
+
21
+ // Setup middleware
22
+ setupMiddleware(app);
23
+
24
+ // Setup routes
25
+ setupRoutes(app, config.getLatestApk);
26
+
27
+ // Start server
28
+ return new Promise((resolve, reject) => {
29
+ const server = app.listen(config.port, config.host, () => {
30
+ log(`HTTP server listening on ${config.host}:${config.port}`);
31
+ resolve(server);
32
+ });
33
+
34
+ server.on('error', (err: Error) => {
35
+ reject(err);
36
+ });
37
+ });
38
+ }