@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
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* JsCompilerService
|
|
4
|
+
* Compiles Kotlin Compose source to browser-executable ES modules via kotlinc-js.
|
|
5
|
+
*
|
|
6
|
+
* Pipeline (two steps required by K2 compiler):
|
|
7
|
+
* 1. user.kt → screen.klib (compile)
|
|
8
|
+
* 2. screen.klib → screen.mjs (link, includes stdlib + stubs)
|
|
9
|
+
*
|
|
10
|
+
* The resulting .mjs exports renderScreen() which builds a plain JS object tree.
|
|
11
|
+
* The browser imports it, calls renderScreen(() => ScreenName()), renders tree as HTML.
|
|
12
|
+
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.JsCompilerService = void 0;
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const os = __importStar(require("os"));
|
|
51
|
+
const child_process_1 = require("child_process");
|
|
52
|
+
const logger_1 = require("../utils/logger");
|
|
53
|
+
class JsCompilerService {
|
|
54
|
+
kotlincJsPath = null;
|
|
55
|
+
stdlibKlib = null;
|
|
56
|
+
stubsKlib = null;
|
|
57
|
+
workDir;
|
|
58
|
+
constructor() {
|
|
59
|
+
this.workDir = path.join(os.tmpdir(), 'jetstart-js-compiler');
|
|
60
|
+
fs.mkdirSync(this.workDir, { recursive: true });
|
|
61
|
+
this.init();
|
|
62
|
+
}
|
|
63
|
+
init() {
|
|
64
|
+
const isWin = process.platform === 'win32';
|
|
65
|
+
const ext = isWin ? '.bat' : '';
|
|
66
|
+
const candidates = [
|
|
67
|
+
'C:\\kotlinc\\bin\\kotlinc-js.bat',
|
|
68
|
+
path.join(process.env.KOTLIN_HOME || '', 'bin', `kotlinc-js${ext}`),
|
|
69
|
+
path.join(process.env.PATH?.split(isWin ? ';' : ':')[0] || '', `kotlinc-js${ext}`),
|
|
70
|
+
];
|
|
71
|
+
for (const c of candidates) {
|
|
72
|
+
if (c && fs.existsSync(c)) {
|
|
73
|
+
this.kotlincJsPath = c;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!this.kotlincJsPath) {
|
|
78
|
+
(0, logger_1.warn)('[JsCompiler] kotlinc-js not found — web live preview disabled');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const kotlincDir = path.dirname(path.dirname(this.kotlincJsPath));
|
|
82
|
+
const stdlib = path.join(kotlincDir, 'lib', 'kotlin-stdlib-js.klib');
|
|
83
|
+
if (fs.existsSync(stdlib)) {
|
|
84
|
+
this.stdlibKlib = stdlib;
|
|
85
|
+
(0, logger_1.log)(`[JsCompiler] kotlinc-js ready: ${path.basename(this.kotlincJsPath)}`);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
(0, logger_1.warn)('[JsCompiler] kotlin-stdlib-js.klib not found — web live preview disabled');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
isAvailable() {
|
|
92
|
+
return !!(this.kotlincJsPath && this.stdlibKlib);
|
|
93
|
+
}
|
|
94
|
+
writeStubsSource() {
|
|
95
|
+
const p = path.join(this.workDir, 'compose-stubs.kt');
|
|
96
|
+
if (!fs.existsSync(p)) {
|
|
97
|
+
fs.writeFileSync(p, COMPOSE_STUBS, 'utf8');
|
|
98
|
+
}
|
|
99
|
+
return p;
|
|
100
|
+
}
|
|
101
|
+
/** Build the Compose shims klib once. Returns klib path or null. */
|
|
102
|
+
ensureStubsKlib() {
|
|
103
|
+
if (this.stubsKlib && fs.existsSync(this.stubsKlib))
|
|
104
|
+
return this.stubsKlib;
|
|
105
|
+
const src = this.writeStubsSource();
|
|
106
|
+
const outKlib = path.join(this.workDir, 'compose_stubs.klib');
|
|
107
|
+
const argFile = path.join(this.workDir, 'stubs-args.txt');
|
|
108
|
+
fs.writeFileSync(argFile, [
|
|
109
|
+
'-ir-output-name', 'compose_stubs',
|
|
110
|
+
'-ir-output-dir', this.workDir,
|
|
111
|
+
'-libraries', this.stdlibKlib,
|
|
112
|
+
'-module-kind', 'es',
|
|
113
|
+
'-target', 'es2015',
|
|
114
|
+
'-Xir-produce-klib-file',
|
|
115
|
+
src,
|
|
116
|
+
].join('\n'), 'utf8');
|
|
117
|
+
(0, logger_1.log)('[JsCompiler] Building Compose shims klib (one-time, ~30s)...');
|
|
118
|
+
const r = (0, child_process_1.spawnSync)(this.kotlincJsPath, [`@${argFile}`], {
|
|
119
|
+
shell: true, encoding: 'utf8', timeout: 120000,
|
|
120
|
+
});
|
|
121
|
+
if ((r.status === 0 || !r.stderr?.includes('error:')) && fs.existsSync(outKlib)) {
|
|
122
|
+
this.stubsKlib = outKlib;
|
|
123
|
+
(0, logger_1.log)('[JsCompiler] Compose shims klib ready');
|
|
124
|
+
return outKlib;
|
|
125
|
+
}
|
|
126
|
+
(0, logger_1.error)('[JsCompiler] Stubs klib failed: ' + (r.stderr || r.stdout || '').slice(0, 300));
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Compile a .kt Compose file to a browser-runnable ES module.
|
|
131
|
+
* Returns base64-encoded .mjs content.
|
|
132
|
+
*/
|
|
133
|
+
/**
|
|
134
|
+
* Remove horizontalArrangement = ... lines that appear inside non-Row containers.
|
|
135
|
+
* LazyVerticalStaggeredGrid etc. don't accept this param in our stubs.
|
|
136
|
+
* Row/LazyRow DO accept it — those lines are kept.
|
|
137
|
+
*/
|
|
138
|
+
removeHorizontalArrangementOutsideRow(text) {
|
|
139
|
+
const lines = text.split('\n');
|
|
140
|
+
const out = [];
|
|
141
|
+
const stack = [];
|
|
142
|
+
for (const line of lines) {
|
|
143
|
+
const t = line.trim();
|
|
144
|
+
// Track container openings (rough, single-line detection)
|
|
145
|
+
if (/\bLazyVerticalStaggeredGrid\s*\(/.test(t))
|
|
146
|
+
stack.push('LazyVerticalStaggeredGrid');
|
|
147
|
+
else if (/\bLazyVerticalGrid\s*\(/.test(t))
|
|
148
|
+
stack.push('LazyVerticalGrid');
|
|
149
|
+
else if (/\bLazyColumn\s*\(/.test(t))
|
|
150
|
+
stack.push('LazyColumn');
|
|
151
|
+
else if (/\bLazyRow\s*\(/.test(t) || /\bRow\s*\(/.test(t))
|
|
152
|
+
stack.push('Row');
|
|
153
|
+
else if (/\bColumn\s*\(/.test(t))
|
|
154
|
+
stack.push('Column');
|
|
155
|
+
else if (/\bScaffold\s*\(/.test(t))
|
|
156
|
+
stack.push('Scaffold');
|
|
157
|
+
else if (/\bCard[^(]*\(/.test(t))
|
|
158
|
+
stack.push('Card');
|
|
159
|
+
// Remove horizontalArrangement if not in a Row
|
|
160
|
+
if (/^\s*horizontalArrangement\s*=/.test(line)) {
|
|
161
|
+
const current = stack[stack.length - 1];
|
|
162
|
+
if (current !== 'Row')
|
|
163
|
+
continue; // skip
|
|
164
|
+
}
|
|
165
|
+
out.push(line);
|
|
166
|
+
}
|
|
167
|
+
return out.join('\n');
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Remove a brace-balanced block (including optional trailing else block).
|
|
171
|
+
* Used to surgically strip LaunchedEffect, forEach, broken if-else blocks etc.
|
|
172
|
+
*/
|
|
173
|
+
removeBalancedBlock(text, re) {
|
|
174
|
+
let match;
|
|
175
|
+
re.lastIndex = 0;
|
|
176
|
+
while ((match = re.exec(text)) !== null) {
|
|
177
|
+
let i = match.index + match[0].length - 1; // last char should be '{'
|
|
178
|
+
let depth = 1;
|
|
179
|
+
const blockStart = match.index;
|
|
180
|
+
i++;
|
|
181
|
+
while (i < text.length && depth > 0) {
|
|
182
|
+
if (text[i] === '{')
|
|
183
|
+
depth++;
|
|
184
|
+
else if (text[i] === '}')
|
|
185
|
+
depth--;
|
|
186
|
+
i++;
|
|
187
|
+
}
|
|
188
|
+
// Check for else
|
|
189
|
+
const rest = text.slice(i);
|
|
190
|
+
const elseM = rest.match(/^\s*else\s*\{/);
|
|
191
|
+
if (elseM) {
|
|
192
|
+
i += elseM[0].length;
|
|
193
|
+
depth = 1;
|
|
194
|
+
while (i < text.length && depth > 0) {
|
|
195
|
+
if (text[i] === '{')
|
|
196
|
+
depth++;
|
|
197
|
+
else if (text[i] === '}')
|
|
198
|
+
depth--;
|
|
199
|
+
i++;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
text = text.slice(0, blockStart) + text.slice(i);
|
|
203
|
+
re.lastIndex = blockStart;
|
|
204
|
+
}
|
|
205
|
+
return text;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Remove "} else { BLOCK }" where the else body matches a predicate.
|
|
209
|
+
*/
|
|
210
|
+
removeElseBlockIf(text, predicate) {
|
|
211
|
+
const re = /\}\s*else\s*\{([^{}]|\{[^{}]*\})*\}/gs;
|
|
212
|
+
return text.replace(re, (match) => predicate(match) ? '}' : match);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Preprocess a Kotlin Compose source file for kotlinc-js compilation.
|
|
216
|
+
*
|
|
217
|
+
* The user's file uses Android/Compose/Java imports that don't exist in
|
|
218
|
+
* a browser context. This method transforms the file so that only the
|
|
219
|
+
* Compose UI structure remains — which maps directly to our jetstart.compose
|
|
220
|
+
* shims. The result compiles cleanly with kotlinc-js.
|
|
221
|
+
*/
|
|
222
|
+
preprocessFile(ktFilePath, runDir) {
|
|
223
|
+
let text = fs.readFileSync(ktFilePath, 'utf8');
|
|
224
|
+
// ── Pass 1: Whole-file transformations ────────────────────────────────────
|
|
225
|
+
// Imports / package
|
|
226
|
+
text = text.replace(/^import\s+.*/gm, '');
|
|
227
|
+
text = text.replace(/^package\s+.*/gm, '');
|
|
228
|
+
// Annotations
|
|
229
|
+
text = text.replace(/^@OptIn[^\n]*/gm, '');
|
|
230
|
+
text = text.replace(/^@HiltViewModel[^\n]*/gm, '');
|
|
231
|
+
text = text.replace(/^@Composable\s*/gm, '');
|
|
232
|
+
// dp/sp
|
|
233
|
+
text = text.replace(/(\d+(?:\.\d+)?)\.dp\b/g, '$1');
|
|
234
|
+
text = text.replace(/(\d+(?:\.\d+)?)\.sp\b/g, '$1');
|
|
235
|
+
// Icons
|
|
236
|
+
text = text.replace(/Icons\.(Filled|Outlined|Rounded|Sharp|TwoTone)\./g, 'Icons.Default.');
|
|
237
|
+
// ViewModel params
|
|
238
|
+
text = text.replace(/,?\s*\w+\s*:\s*\w*ViewModel\s*=\s*viewModel\([^)]*\)/g, '');
|
|
239
|
+
text = text.replace(/,?\s*\w+\s*:\s*\w*ViewModel\b/g, '');
|
|
240
|
+
text = text.replace(/\(\s*,/g, '(');
|
|
241
|
+
text = text.replace(/,\s*\)/g, ')');
|
|
242
|
+
// ViewModel usage
|
|
243
|
+
text = text.replace(/\bval\s+(\w+)\s+by\s+viewModel\.\w+\.collectAsState\(\)/g, 'val $1 = ""');
|
|
244
|
+
text = text.replace(/\bvar\s+(\w+)\s+by\s+viewModel\.\w+\.collectAsState\(\)/g, 'var $1 = ""');
|
|
245
|
+
text = text.replace(/\bviewModel\.\w+\.collectAsState\(\)/g, '"preview"');
|
|
246
|
+
text = text.replace(/\bviewModel::\w+/g, '{}');
|
|
247
|
+
text = text.replace(/,?\s*viewModel\s*=\s*\w+[^,)\n]*/g, '');
|
|
248
|
+
text = text.replace(/\bviewModel\.\w+\b/g, 'Unit');
|
|
249
|
+
// Delegate by remember
|
|
250
|
+
text = text.replace(/\bval\s+(\w+)\s+by\s+remember\s*\{[^}]+\}/g, 'val $1 = ""');
|
|
251
|
+
text = text.replace(/\bvar\s+(\w+)\s+by\s+remember\s*\{\s*mutableStateOf\((false|true)\)\s*\}/g, 'var $1: Boolean = $2');
|
|
252
|
+
text = text.replace(/\bvar\s+(\w+)\s+by\s+remember\s*\{[^}]+\}/g, 'var $1 = ""');
|
|
253
|
+
// Data class fields
|
|
254
|
+
text = text.replace(/\bnote\.content\b/g, '"Note content"');
|
|
255
|
+
text = text.replace(/\bnote\.\w+\b/g, '"preview"');
|
|
256
|
+
text = text.replace(/\b(\w+)\s*:\s*Note\b/g, '$1: String = "Note"');
|
|
257
|
+
// Preview lists
|
|
258
|
+
text = text.replace(/\bval\s+suggestedTags\b[^\n]*/g, 'val suggestedTags: List<String> = listOf()');
|
|
259
|
+
text = text.replace(/suggestedTags\.isEmpty\(\)/g, 'true');
|
|
260
|
+
text = text.replace(/suggestedTags\.isNotEmpty\(\)/g, 'false');
|
|
261
|
+
text = text.replace(/\bval\s+searchResults\b[^\n]*/g, 'val searchResults: List<String> = listOf("Note 1", "Note 2", "Note 3")');
|
|
262
|
+
// Named M3 colors
|
|
263
|
+
const colorMap = {
|
|
264
|
+
onSurfaceVariant: '"#49454F"', onBackground: '"#1C1B1F"',
|
|
265
|
+
outline: '"#79747E"', surfaceVariant: '"#E7E0EC"',
|
|
266
|
+
onPrimaryContainer: '"#21005D"', inverseOnSurface: '"#F4EFF4"',
|
|
267
|
+
inverseSurface: '"#313033"', tertiaryContainer: '"#FFD8E4"',
|
|
268
|
+
};
|
|
269
|
+
for (const [k, v] of Object.entries(colorMap)) {
|
|
270
|
+
text = text.replace(new RegExp(`MaterialTheme\\.colorScheme\\.${k}\\b`, 'g'), v);
|
|
271
|
+
}
|
|
272
|
+
// Remove arg-style attributes that aren't in stubs
|
|
273
|
+
text = text.replace(/,?\s*colors\s*=\s*\w+Defaults\.\w+\s*\([\s\S]*?\)/g, '');
|
|
274
|
+
text = text.replace(/,?\s*(?:content)?[Ww]indow[Ii]nsets[^\n,)]*/g, '');
|
|
275
|
+
text = text.replace(/,?\s*verticalItemSpacing\s*=\s*[^\n,)]+/g, '');
|
|
276
|
+
text = text.replace(/,?\s*contentPadding\s*=\s*[^\n,)]+/g, '');
|
|
277
|
+
text = text.replace(/,?\s*minLines\s*=\s*\d+/g, '');
|
|
278
|
+
text = text.replace(/,?\s*elevation\s*=\s*CardDefaults\.[^)]+\)/g, '');
|
|
279
|
+
text = text.replace(/PaddingValues\((\d+)\)/g, '$1');
|
|
280
|
+
text = text.replace(/PaddingValues\([^)]*\)/g, '0');
|
|
281
|
+
// Named padding → single value
|
|
282
|
+
text = text.replace(/\.padding\(\s*(?:start|left|horizontal)\s*=\s*(\d+)[^)]*\)/g, '.padding($1)');
|
|
283
|
+
text = text.replace(/\.padding\(\s*(?:top|vertical)\s*=\s*(\d+)[^)]*\)/g, '.padding($1)');
|
|
284
|
+
text = text.replace(/\.padding\(padding\)/g, '.padding(0)');
|
|
285
|
+
// Shape removal
|
|
286
|
+
text = text.replace(/,?\s*shape\s*=\s*(?:MaterialTheme\.shapes\.\w+|RoundedCornerShape\([^)]*\)|CircleShape)/g, '');
|
|
287
|
+
// Modifier
|
|
288
|
+
text = text.replace(/\bModifier\b(?!\(\)|[\w])/g, 'Modifier()');
|
|
289
|
+
text = text.replace(/\bModifier\(\)\s*\n\s*\./g, 'Modifier().');
|
|
290
|
+
text = text.replace(/\)\s*\n\s*\.(fill|padding|height|width|size|weight|background|clip|alpha|wrap)/g, ').$1');
|
|
291
|
+
// horizontalArrangement not in staggered grid stubs
|
|
292
|
+
text = this.removeHorizontalArrangementOutsideRow(text);
|
|
293
|
+
// Scroll
|
|
294
|
+
text = text.replace(/\.horizontalScroll\([^)]*\)|\.verticalScroll\([^)]*\)/g, '');
|
|
295
|
+
text = text.replace(/,?\s*state\s*=\s*remember(?:ScrollState|LazyListState)\(\)/g, '');
|
|
296
|
+
// Dialog → Box
|
|
297
|
+
text = text.replace(/\bDialog\b\s*\(/g, 'Box(');
|
|
298
|
+
// Scaffold padding → _
|
|
299
|
+
text = text.replace(/\)\s*\{\s*padding\s*->/g, ') { _ ->');
|
|
300
|
+
// Misc
|
|
301
|
+
text = text.replace(/,?\s*singleLine\s*=\s*\w+/g, '');
|
|
302
|
+
text = text.replace(/,?\s*isError\s*=\s*\w+/g, '');
|
|
303
|
+
text = text.replace(/,?\s*keyboardOptions\s*=\s*[^,)\n]+/g, '');
|
|
304
|
+
text = text.replace(/,?\s*keyboardActions\s*=\s*[^,)\n]+/g, '');
|
|
305
|
+
text = text.replace(/,?\s*interactionSource\s*=\s*[^,)\n]+/g, '');
|
|
306
|
+
text = text.replace(/SimpleDateFormat\([^)]*\)\.format\([^)]*\)/g, '"Today"');
|
|
307
|
+
// Stray Unit(...) calls
|
|
308
|
+
text = text.replace(/\bUnit\([^)]*\)/g, '{}');
|
|
309
|
+
text = text.replace(/\{\s*Unit\s*\}/g, '{}');
|
|
310
|
+
// Fix extra ) from removed args in grid calls
|
|
311
|
+
text = text.replace(/StaggeredGridCells\.Fixed\((\d+)\)\),/g, 'StaggeredGridCells.Fixed($1),');
|
|
312
|
+
text = text.replace(/modifier\s*=\s*Modifier\(\)\),/g, 'modifier = Modifier(),');
|
|
313
|
+
// ── Pass 2: Brace-balanced block removal ──────────────────────────────────
|
|
314
|
+
// LaunchedEffect / SideEffect / DisposableEffect
|
|
315
|
+
for (const kw of ['LaunchedEffect', 'DisposableEffect', 'SideEffect']) {
|
|
316
|
+
text = this.removeBalancedBlock(text, new RegExp(`\\b${kw}\\b[^{]*\\{`, 'g'));
|
|
317
|
+
}
|
|
318
|
+
// .forEach { ... }
|
|
319
|
+
text = this.removeBalancedBlock(text, /\.forEach\s*\{/g);
|
|
320
|
+
// if (false) { ... } [else { ... }]
|
|
321
|
+
text = this.removeBalancedBlock(text, /if\s*\(\s*false\s*\)\s*\{/g);
|
|
322
|
+
// if (listOf<...>().isNotEmpty()) { ... }
|
|
323
|
+
text = this.removeBalancedBlock(text, /if\s*\(\s*listOf<[^>]*>\(\)\.isNotEmpty\(\)\s*\)\s*\{/g);
|
|
324
|
+
// if (X.isNotEmpty()) { ... } — generic tag guards
|
|
325
|
+
text = this.removeBalancedBlock(text, /if\s*\(\s*\w+\.isNotEmpty\(\)\s*\)\s*\{/g);
|
|
326
|
+
// Remove else blocks containing broken/removed suggestedTags code
|
|
327
|
+
text = this.removeElseBlockIf(text, (body) => body.includes('suggestedTags') || body.includes('Modifier()),'));
|
|
328
|
+
// ── Pass 3: Final cleanup ─────────────────────────────────────────────────
|
|
329
|
+
text = text.replace(/\n{3,}/g, '\n\n');
|
|
330
|
+
text = text.replace(/,\s*\)/g, ')');
|
|
331
|
+
text = text.replace(/\(\s*,/g, '(');
|
|
332
|
+
// Detect the screen function name to wrap the entry point
|
|
333
|
+
const screenMatch = text.match(/^fun\s+(\w+Screen)/m) ||
|
|
334
|
+
text.match(/^fun\s+(\w+)/m);
|
|
335
|
+
const screenName = screenMatch ? screenMatch[1] : null;
|
|
336
|
+
const renderWrapper = screenName
|
|
337
|
+
? `\n\n@OptIn(kotlin.js.ExperimentalJsExport::class)\n@JsExport\nfun __jetstart_render__(): dynamic = renderScreen { ${screenName}() }\n`
|
|
338
|
+
: '';
|
|
339
|
+
const result = 'import jetstart.compose.*\n\n' + text.trim() + renderWrapper + '\n';
|
|
340
|
+
const preprocessedPath = path.join(runDir, 'preprocessed.kt');
|
|
341
|
+
fs.writeFileSync(preprocessedPath, result, 'utf8');
|
|
342
|
+
return preprocessedPath;
|
|
343
|
+
}
|
|
344
|
+
async compile(ktFilePath) {
|
|
345
|
+
if (!this.isAvailable())
|
|
346
|
+
return { success: false, error: 'kotlinc-js unavailable' };
|
|
347
|
+
const stubsKlib = this.ensureStubsKlib();
|
|
348
|
+
if (!stubsKlib)
|
|
349
|
+
return { success: false, error: 'Compose shims compilation failed' };
|
|
350
|
+
const t0 = Date.now();
|
|
351
|
+
const runId = `run_${Date.now()}`;
|
|
352
|
+
const runDir = path.join(this.workDir, runId);
|
|
353
|
+
fs.mkdirSync(runDir, { recursive: true });
|
|
354
|
+
try {
|
|
355
|
+
// ── Step 1: .kt → screen.klib ─────────────────────────────────────────
|
|
356
|
+
const screenKlib = path.join(runDir, 'screen.klib');
|
|
357
|
+
const args1 = path.join(runDir, 'step1.txt');
|
|
358
|
+
const libs1 = [this.stdlibKlib, stubsKlib].join(process.platform === 'win32' ? ';' : ':');
|
|
359
|
+
// Preprocess: strip Android imports, annotations, and types not available in JS
|
|
360
|
+
const preprocessedKtPath = this.preprocessFile(ktFilePath, runDir);
|
|
361
|
+
fs.writeFileSync(args1, [
|
|
362
|
+
'-ir-output-name', 'screen',
|
|
363
|
+
'-ir-output-dir', runDir,
|
|
364
|
+
'-libraries', libs1,
|
|
365
|
+
'-module-kind', 'es',
|
|
366
|
+
'-target', 'es2015',
|
|
367
|
+
'-Xir-produce-klib-file',
|
|
368
|
+
preprocessedKtPath,
|
|
369
|
+
].join('\n'), 'utf8');
|
|
370
|
+
const r1 = (0, child_process_1.spawnSync)(this.kotlincJsPath, [`@${args1}`], {
|
|
371
|
+
shell: true, encoding: 'utf8', timeout: 120000,
|
|
372
|
+
});
|
|
373
|
+
if (!fs.existsSync(screenKlib)) {
|
|
374
|
+
const errLines = (r1.stderr || r1.stdout || '').split('\n')
|
|
375
|
+
.filter((l) => l.includes('error:')).slice(0, 8).join('\n');
|
|
376
|
+
return { success: false, error: `Step 1 failed:\n${errLines}` };
|
|
377
|
+
}
|
|
378
|
+
// ── Step 2: screen.klib → screen.mjs ──────────────────────────────────
|
|
379
|
+
const outDir = path.join(runDir, 'out');
|
|
380
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
381
|
+
const args2 = path.join(runDir, 'step2.txt');
|
|
382
|
+
fs.writeFileSync(args2, [
|
|
383
|
+
'-ir-output-name', 'screen',
|
|
384
|
+
'-ir-output-dir', outDir,
|
|
385
|
+
'-libraries', libs1,
|
|
386
|
+
'-module-kind', 'es',
|
|
387
|
+
'-target', 'es2015',
|
|
388
|
+
'-Xir-produce-js',
|
|
389
|
+
`-Xinclude=${screenKlib}`,
|
|
390
|
+
].join('\n'), 'utf8');
|
|
391
|
+
const r2 = (0, child_process_1.spawnSync)(this.kotlincJsPath, [`@${args2}`], {
|
|
392
|
+
shell: true, encoding: 'utf8', timeout: 120000,
|
|
393
|
+
});
|
|
394
|
+
const mjsPath = path.join(outDir, 'screen.mjs');
|
|
395
|
+
if (!fs.existsSync(mjsPath)) {
|
|
396
|
+
const errLines = (r2.stderr || r2.stdout || '').split('\n')
|
|
397
|
+
.filter((l) => !l.startsWith('warning:') && l.trim()).slice(0, 8).join('\n');
|
|
398
|
+
return { success: false, error: `Step 2 failed:\n${errLines}` };
|
|
399
|
+
}
|
|
400
|
+
// Detect the screen function name from source
|
|
401
|
+
const ktSrc = fs.readFileSync(ktFilePath, 'utf8');
|
|
402
|
+
const screenMatch = ktSrc.match(/fun\s+(\w+Screen)\s*[({]/);
|
|
403
|
+
const screenFunctionName = screenMatch?.[1] ?? 'Screen';
|
|
404
|
+
const mjsBytes = fs.readFileSync(mjsPath);
|
|
405
|
+
const jsBase64 = mjsBytes.toString('base64');
|
|
406
|
+
const elapsed = Date.now() - t0;
|
|
407
|
+
(0, logger_1.log)(`[JsCompiler] ✅ ${path.basename(ktFilePath)} → JS in ${elapsed}ms (${mjsBytes.length} bytes)`);
|
|
408
|
+
return { success: true, jsBase64, byteSize: mjsBytes.length, compileTimeMs: elapsed, screenFunctionName };
|
|
409
|
+
}
|
|
410
|
+
finally {
|
|
411
|
+
try {
|
|
412
|
+
fs.rmSync(runDir, { recursive: true, force: true });
|
|
413
|
+
}
|
|
414
|
+
catch { }
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
exports.JsCompilerService = JsCompilerService;
|
|
419
|
+
// ── Compose shim stubs (validated against kotlinc-js 2.3.0) ──────────────────
|
|
420
|
+
const COMPOSE_STUBS = "package jetstart.compose\n\nexternal fun println(s: String): Unit\n\nobject ComposeTree {\n val root: dynamic = js(\"({type:'root',children:[]})\")\n val stack: dynamic = js(\"([])\")\n fun push(node: dynamic) {\n val cur: dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n cur.children.push(node)\n stack.push(node)\n }\n fun pop() { stack.pop() }\n fun current(): dynamic = if (stack.length > 0) stack[stack.length - 1] else root\n fun reset() {\n root.children.splice(0) // clear children array in-place\n stack.splice(0) // clear stack\n }\n // Run lambda in isolated sub-tree — prevents slot content from leaking\n fun captureSlot(lambda: ()->Unit): dynamic {\n val slotRoot: dynamic = js(\"({type:'slot',children:[]})\")\n stack.push(slotRoot)\n lambda()\n stack.pop()\n return slotRoot.children\n }\n}\n\nclass Modifier {\n val s: dynamic = js(\"({})\")\n fun fillMaxSize(): Modifier { s.fillMaxSize = true; return this }\n fun fillMaxWidth(): Modifier { s.fillMaxWidth = true; return this }\n fun fillMaxHeight(): Modifier { s.fillMaxHeight = true; return this }\n fun padding(all: Int): Modifier { s.padding = all; return this }\n fun padding(all: Float): Modifier { s.padding = all; return this }\n fun padding(horizontal: Int = 0, vertical: Int = 0): Modifier { s.paddingH=horizontal; s.paddingV=vertical; return this }\n fun height(dp: Int): Modifier { s.height=dp; return this }\n fun height(dp: Float): Modifier { s.height=dp; return this }\n fun width(dp: Int): Modifier { s.width=dp; return this }\n fun width(dp: Float): Modifier { s.width=dp; return this }\n fun size(dp: Int): Modifier { s.size=dp; return this }\n fun weight(f: Float): Modifier { s.weight=f; return this }\n fun weight(f: Int): Modifier { s.weight=f.toFloat(); return this }\n fun background(color: String): Modifier { s.background=color; return this }\n fun clip(shape: String): Modifier { s.clip=shape; return this }\n fun clickable(onClick: ()->Unit): Modifier { s.clickable=true; return this }\n fun alpha(a: Float): Modifier { s.alpha=a; return this }\n fun border(width: Int, color: String): Modifier { s.borderWidth=width; s.borderColor=color; return this }\n fun wrapContentWidth(): Modifier { s.wrapWidth=true; return this }\n fun wrapContentHeight(): Modifier { s.wrapHeight=true; return this }\n fun offset(x: Int=0, y: Int=0): Modifier { s.offsetX=x; s.offsetY=y; return this }\n companion object { operator fun invoke(): Modifier = Modifier() }\n}\n\nobject Arrangement {\n val Top = \"top\"; val Bottom = \"bottom\"; val Center = \"center\"\n val Start = \"start\"; val End = \"end\"\n val SpaceBetween = \"space-between\"; val SpaceEvenly = \"space-evenly\"; val SpaceAround = \"space-around\"\n fun spacedBy(dp: Int) = \"spacedBy(${\"$\"}dp)\"\n fun spacedBy(dp: Float) = \"spacedBy(${\"$\"}{dp.toInt()})\"\n}\nobject Alignment {\n val Top = \"top\"; val Bottom = \"bottom\"; val CenterVertically = \"centerVertically\"\n val CenterHorizontally = \"centerHorizontally\"; val Center = \"center\"\n val Start = \"start\"; val End = \"end\"\n val TopStart = \"topStart\"; val TopEnd = \"topEnd\"\n val BottomStart = \"bottomStart\"; val BottomEnd = \"bottomEnd\"\n val TopCenter = \"topCenter\"; val BottomCenter = \"bottomCenter\"\n}\nobject MaterialTheme {\n val colorScheme = ColorScheme(); val typography = Typography(); val shapes = Shapes()\n}\nclass ColorScheme {\n val primary = \"#6750A4\"; val onPrimary = \"#FFFFFF\"\n val secondary = \"#625B71\"; val surface = \"#FFFBFE\"\n val background = \"#FFFBFE\"; val error = \"#B3261E\"\n val primaryContainer = \"#EADDFF\"; val secondaryContainer = \"#E8DEF8\"\n val surfaceVariant = \"#E7E0EC\"; val outline = \"#79747E\"\n val onBackground = \"#1C1B1F\"; val onSurface = \"#1C1B1F\"\n val tertiaryContainer = \"#FFD8E4\"; val inverseSurface = \"#313033\"\n val inverseOnSurface = \"#F4EFF4\"; val onPrimaryContainer = \"#21005D\"\n}\nclass Typography {\n val displayLarge = \"displayLarge\"; val displayMedium = \"displayMedium\"; val displaySmall = \"displaySmall\"\n val headlineLarge = \"headlineLarge\"; val headlineMedium = \"headlineMedium\"; val headlineSmall = \"headlineSmall\"\n val titleLarge = \"titleLarge\"; val titleMedium = \"titleMedium\"; val titleSmall = \"titleSmall\"\n val bodyLarge = \"bodyLarge\"; val bodyMedium = \"bodyMedium\"; val bodySmall = \"bodySmall\"\n val labelLarge = \"labelLarge\"; val labelMedium = \"labelMedium\"; val labelSmall = \"labelSmall\"\n}\nclass Shapes { val small=\"small\"; val medium=\"medium\"; val large=\"large\"; val extraLarge=\"extraLarge\" }\nobject FontWeight { val Bold=\"bold\"; val Normal=\"normal\"; val Light=\"light\"; val Medium=\"medium\"; val SemiBold=\"semibold\" }\nobject TextAlign { val Start=\"start\"; val Center=\"center\"; val End=\"end\"; val Justify=\"justify\" }\nobject ContentScale { val Crop=\"crop\"; val Fit=\"fit\"; val FillBounds=\"fillBounds\"; val FillWidth=\"fillWidth\" }\nobject Color {\n val White = \"#FFFFFF\"; val Black = \"#000000\"; val Transparent = \"transparent\"\n val Red = \"#F44336\"; val Blue = \"#2196F3\"; val Green = \"#4CAF50\"\n val Gray = \"#9E9E9E\"; val LightGray = \"#E0E0E0\"; val DarkGray = \"#424242\"\n val Unspecified = \"unspecified\"\n}\n\nfun rememberSaveable(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun remember(vararg inputs: Any?, calculation: ()->Any?): Any? = calculation()\nfun <T> mutableStateOf(value: T): MutableState<T> = MutableStateImpl(value)\ninterface MutableState<T> { var value: T }\nclass MutableStateImpl<T>(override var value: T): MutableState<T>\noperator fun <T> MutableState<T>.getValue(thisRef: Any?, property: Any?): T = value\noperator fun <T> MutableState<T>.setValue(thisRef: Any?, property: Any?, v: T) { value = v }\nfun stringResource(id: Int): String = \"string_${\"$\"}id\"\nfun painterResource(id: Int): Any = js(\"({type:'resource',id:${\"$\"}id})\")\n\nobject Icons {\n object Default {\n val Add: Any = js(\"({icon:'add'})\")\n val Search: Any = js(\"({icon:'search'})\")\n val Close: Any = js(\"({icon:'close'})\")\n val Check: Any = js(\"({icon:'check'})\")\n val Delete: Any = js(\"({icon:'delete'})\")\n val Edit: Any = js(\"({icon:'edit'})\")\n val Home: Any = js(\"({icon:'home'})\")\n val Menu: Any = js(\"({icon:'menu'})\")\n val Settings: Any = js(\"({icon:'settings'})\")\n val ArrowBack: Any = js(\"({icon:'arrow_back'})\")\n val MoreVert: Any = js(\"({icon:'more_vert'})\")\n val Favorite: Any = js(\"({icon:'favorite'})\")\n val Share: Any = js(\"({icon:'share'})\")\n val Info: Any = js(\"({icon:'info'})\")\n val Person: Any = js(\"({icon:'person'})\")\n val Star: Any = js(\"({icon:'star'})\")\n val Notifications: Any = js(\"({icon:'notifications'})\")\n val Email: Any = js(\"({icon:'email'})\")\n val Phone: Any = js(\"({icon:'phone'})\")\n val Lock: Any = js(\"({icon:'lock'})\")\n val Visibility: Any = js(\"({icon:'visibility'})\")\n val VisibilityOff: Any = js(\"({icon:'visibility_off'})\")\n val ShoppingCart: Any = js(\"({icon:'shopping_cart'})\")\n val KeyboardArrowDown: Any = js(\"({icon:'keyboard_arrow_down'})\")\n val KeyboardArrowUp: Any = js(\"({icon:'keyboard_arrow_up'})\")\n }\n object Filled { val Add = Default.Add; val Search = Default.Search; val Edit = Default.Edit }\n object Outlined { val Add = Default.Add; val Search = Default.Search }\n object Rounded { val Add = Default.Add }\n}\n\n// ── Layout composables ────────────────────────────────────────────────────────\nfun Column(modifier: Modifier=Modifier(), verticalArrangement: Any=\"\", horizontalAlignment: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Column',children:[]})\")\n n.modifier=modifier.s; n.verticalArrangement=verticalArrangement.toString(); n.horizontalAlignment=horizontalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Row(modifier: Modifier=Modifier(), horizontalArrangement: Any=\"\", verticalAlignment: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Row',children:[]})\")\n n.modifier=modifier.s; n.horizontalArrangement=horizontalArrangement.toString(); n.verticalAlignment=verticalAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Box(modifier: Modifier=Modifier(), contentAlignment: Any=\"topStart\", content: ()->Unit={}) {\n val n: dynamic = js(\"({type:'Box',children:[]})\")\n n.modifier=modifier.s; n.contentAlignment=contentAlignment.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Text(text: String, modifier: Modifier=Modifier(), style: Any=\"\", color: Any=\"\", fontSize: Any=\"\", fontWeight: Any=\"\", textAlign: Any=\"\", maxLines: Int=Int.MAX_VALUE, overflow: Any=\"\") {\n val n: dynamic = js(\"({type:'Text',children:[]})\")\n n.text=text; n.modifier=modifier.s\n val styleStr = style.toString()\n n.style=if(styleStr.isEmpty()) \"bodyMedium\" else styleStr\n n.color=color.toString(); n.fontWeight=fontWeight.toString(); n.textAlign=textAlign.toString(); n.maxLines=maxLines\n ComposeTree.current().children.push(n)\n}\nfun Button(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, shape: Any=\"\", colors: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Button',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'OutlinedButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun TextButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'TextButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'ElevatedButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FilledTonalButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'FilledTonalButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun FloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), containerColor: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'FloatingActionButton',children:[]})\")\n n.modifier=modifier.s; n.containerColor=containerColor.toString()\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ExtendedFloatingActionButton(onClick: ()->Unit, modifier: Modifier=Modifier(), icon: ()->Unit={}, text: ()->Unit) {\n val n: dynamic = js(\"({type:'ExtendedFAB',children:[]})\")\n n.modifier=modifier.s\n ComposeTree.push(n); icon(); text(); ComposeTree.pop()\n}\nfun IconButton(onClick: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true, content: ()->Unit) {\n val n: dynamic = js(\"({type:'IconButton',children:[]})\")\n n.modifier=modifier.s; n.enabled=enabled\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Card(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any=\"\", colors: Any=\"\", elevation: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'Card',children:[]})\")\n n.modifier=modifier.s; n.clickable=(onClick!=null)\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun ElevatedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'ElevatedCard',children:[]})\")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun OutlinedCard(onClick: (()->Unit)?=null, modifier: Modifier=Modifier(), shape: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'OutlinedCard',children:[]})\")\n n.modifier=modifier.s\n ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Scaffold(modifier: Modifier=Modifier(), topBar: ()->Unit={}, bottomBar: ()->Unit={}, floatingActionButton: ()->Unit={}, snackbarHost: (Any)->Unit={}, containerColor: Any=\"\", content: (Any)->Unit) {\n val n: dynamic = js(\"({type:'Scaffold',children:[],topBar:[],bottomBar:[],fab:[]})\")\n n.modifier=modifier.s\n n.topBar = ComposeTree.captureSlot { topBar() }\n n.bottomBar = ComposeTree.captureSlot { bottomBar() }\n n.fab = ComposeTree.captureSlot { floatingActionButton() }\n ComposeTree.push(n); content(js(\"({})\")); ComposeTree.pop()\n}\nfun TopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") {\n val n: dynamic = js(\"({type:'TopAppBar',children:[],title:[],actions:[]})\")\n n.modifier=modifier.s\n n.title = ComposeTree.captureSlot { title() }\n n.actions = ComposeTree.captureSlot { actions() }\n ComposeTree.current().children.push(n)\n}\nfun CenterAlignedTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun LargeTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun MediumTopAppBar(title: ()->Unit, modifier: Modifier=Modifier(), navigationIcon: ()->Unit={}, actions: ()->Unit={}, colors: Any=\"\") = TopAppBar(title=title, modifier=modifier, navigationIcon=navigationIcon, actions=actions, colors=colors)\nfun NavigationBar(modifier: Modifier=Modifier(), containerColor: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'NavigationBar',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun NavigationBarItem(selected: Boolean, onClick: ()->Unit, icon: ()->Unit, label: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js(\"({type:'NavigationBarItem',children:[],label:[]})\")\n n.selected=selected; n.modifier=modifier.s\n n.children = ComposeTree.captureSlot { icon() }\n if (label != null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun OutlinedTextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, maxLines: Int=Int.MAX_VALUE, shape: Any=\"\", keyboardOptions: Any=\"\", keyboardActions: Any=\"\") {\n val n: dynamic = js(\"({type:'OutlinedTextField',children:[]})\")\n n.value=value; n.modifier=modifier.s; n.isError=isError; n.singleLine=singleLine\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n if (placeholder!=null) { n.placeholder = ComposeTree.captureSlot { placeholder() } }\n ComposeTree.current().children.push(n)\n}\nfun TextField(value: String, onValueChange: (String)->Unit, modifier: Modifier=Modifier(), label: (()->Unit)?=null, placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, isError: Boolean=false, singleLine: Boolean=false, shape: Any=\"\", keyboardOptions: Any=\"\", keyboardActions: Any=\"\") {\n val n: dynamic = js(\"({type:'TextField',children:[]})\")\n n.value=value; n.modifier=modifier.s; n.isError=isError\n if (label!=null) { n.label = ComposeTree.captureSlot { label() } }\n ComposeTree.current().children.push(n)\n}\nfun SearchBar(query: String, onQueryChange: (String)->Unit, onSearch: (String)->Unit, active: Boolean, onActiveChange: (Boolean)->Unit, modifier: Modifier=Modifier(), placeholder: (()->Unit)?=null, leadingIcon: (()->Unit)?=null, trailingIcon: (()->Unit)?=null, content: ()->Unit={}) {\n val n: dynamic = js(\"({type:'SearchBar',children:[]})\")\n n.query=query; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LazyColumn(modifier: Modifier=Modifier(), state: Any=\"\", contentPadding: Any=\"\", verticalArrangement: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyColumn',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyRow(modifier: Modifier=Modifier(), state: Any=\"\", contentPadding: Any=\"\", horizontalArrangement: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyRow',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any=\"\", verticalArrangement: Any=\"\", horizontalArrangement: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyVerticalGrid',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun LazyVerticalStaggeredGrid(columns: Any, modifier: Modifier=Modifier(), contentPadding: Any=\"\", content: ()->Unit) {\n val n: dynamic = js(\"({type:'LazyVerticalStaggeredGrid',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun Icon(imageVector: Any, contentDescription: String?, modifier: Modifier=Modifier(), tint: Any=\"\") {\n val n: dynamic = js(\"({type:'Icon',children:[]})\")\n val iv: dynamic = imageVector\n n.icon = if(iv != null && iv.icon != null) iv.icon.toString() else \"default\"\n n.contentDescription=contentDescription?:\"\"; n.modifier=modifier.s; n.tint=tint.toString()\n ComposeTree.current().children.push(n)\n}\nfun Image(painter: Any, contentDescription: String?, modifier: Modifier=Modifier(), contentScale: Any=ContentScale.Fit, alignment: Any=Alignment.Center) {\n val n: dynamic = js(\"({type:'Image',children:[]})\")\n n.contentDescription=contentDescription?:\"\"; n.modifier=modifier.s\n ComposeTree.current().children.push(n)\n}\nfun Spacer(modifier: Modifier=Modifier()) {\n val n: dynamic = js(\"({type:'Spacer',children:[]})\")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun Divider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any=\"\") {\n val n: dynamic = js(\"({type:'Divider',children:[]})\")\n n.modifier=modifier.s; n.thickness=thickness; ComposeTree.current().children.push(n)\n}\nfun HorizontalDivider(modifier: Modifier=Modifier(), thickness: Int=1, color: Any=\"\") = Divider(modifier, thickness, color)\nfun Switch(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Switch',children:[]})\")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun Checkbox(checked: Boolean, onCheckedChange: ((Boolean)->Unit)?, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Checkbox',children:[]})\")\n n.checked=checked; n.modifier=modifier.s; n.enabled=enabled; ComposeTree.current().children.push(n)\n}\nfun CircularProgressIndicator(modifier: Modifier=Modifier(), color: Any=\"\", strokeWidth: Int=4) {\n val n: dynamic = js(\"({type:'CircularProgressIndicator',children:[]})\")\n n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun LinearProgressIndicator(progress: Float=0f, modifier: Modifier=Modifier(), color: Any=\"\") {\n val n: dynamic = js(\"({type:'LinearProgressIndicator',children:[]})\")\n n.progress=progress; n.modifier=modifier.s; ComposeTree.current().children.push(n)\n}\nfun AssistChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Chip',chipType:'assist',children:[]})\")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun FilterChip(selected: Boolean, onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null, enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Chip',chipType:'filter',children:[]})\")\n n.selected=selected; n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun SuggestionChip(onClick: ()->Unit, label: ()->Unit, modifier: Modifier=Modifier(), enabled: Boolean=true) {\n val n: dynamic = js(\"({type:'Chip',chipType:'suggestion',children:[]})\")\n n.modifier=modifier.s\n n.label = ComposeTree.captureSlot { label() }\n ComposeTree.current().children.push(n)\n}\nfun AlertDialog(onDismissRequest: ()->Unit, title: (()->Unit)?=null, text: (()->Unit)?=null, confirmButton: ()->Unit, dismissButton: (()->Unit)?=null, modifier: Modifier=Modifier()) {\n val n: dynamic = js(\"({type:'AlertDialog',children:[],title:[],text:[],confirmButton:[],dismissButton:[]})\")\n n.modifier=modifier.s\n if (title!=null) { n.title = ComposeTree.captureSlot { title() } }\n if (text!=null) { n.text = ComposeTree.captureSlot { text() } }\n n.confirmButton = ComposeTree.captureSlot { confirmButton() }\n if (dismissButton!=null) { n.dismissButton = ComposeTree.captureSlot { dismissButton() } }\n ComposeTree.current().children.push(n)\n}\nfun DropdownMenu(expanded: Boolean, onDismissRequest: ()->Unit, modifier: Modifier=Modifier(), content: ()->Unit) {\n if (!expanded) return\n val n: dynamic = js(\"({type:'DropdownMenu',children:[]})\")\n n.modifier=modifier.s; ComposeTree.push(n); content(); ComposeTree.pop()\n}\nfun DropdownMenuItem(text: ()->Unit, onClick: ()->Unit, modifier: Modifier=Modifier(), leadingIcon: (()->Unit)?=null) {\n val n: dynamic = js(\"({type:'DropdownMenuItem',children:[]})\")\n n.modifier=modifier.s\n n.text = ComposeTree.captureSlot { text() }\n ComposeTree.current().children.push(n)\n}\nfun SnackbarHost(hostState: Any, modifier: Modifier=Modifier(), snackbar: (Any)->Unit={}) {}\nfun items(count: Int, key: ((Int)->Any)?=null, itemContent: (Int)->Unit) { for (i in 0 until minOf(count, 20)) { itemContent(i) } }\nfun <T> items(items: List<T>, key: ((T)->Any)?=null, itemContent: (T)->Unit) { items.take(20).forEach { itemContent(it) } }\nfun item(key: Any?=null, content: ()->Unit) { content() }\nfun rememberLazyListState(): Any = js(\"({})\")\nfun rememberScrollState(): Any = js(\"({})\")\nfun SnackbarHostState(): Any = js(\"({})\")\nfun rememberSnackbarHostState(): Any = js(\"({})\")\nfun rememberModalBottomSheetState(initialValue: Any?=null, skipPartiallyExpanded: Boolean=true): Any = js(\"({})\")\nobject KeyboardOptions { val Default = js(\"({})\") }\nobject KeyboardActions { val Default = js(\"({})\") }\nobject ImeAction { val Done=\"done\"; val Search=\"search\"; val Go=\"go\"; val Next=\"next\" }\nobject KeyboardType { val Text=\"text\"; val Number=\"number\"; val Email=\"email\"; val Password=\"password\" }\nfun PaddingValues(all: Int=0): Any = js(\"({})\")\nfun PaddingValues(horizontal: Int=0, vertical: Int=0): Any = js(\"({})\")\nfun PaddingValues(start: Int=0, top: Int=0, end: Int=0, bottom: Int=0): Any = js(\"({})\")\nobject StaggeredGridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\nobject GridCells { fun Fixed(n: Int) = n; fun Adaptive(minSize: Int) = minSize }\n\n// ── Entry point called by the browser ────────────────────────────────────────\n@JsExport\nfun renderScreen(content: ()->Unit): dynamic {\n ComposeTree.reset()\n content()\n return ComposeTree.root\n}";
|
|
421
|
+
//# sourceMappingURL=js-compiler-service.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kotlin Compiler Service
|
|
3
|
+
* Compiles Kotlin files to .class files for hot reload
|
|
4
|
+
*/
|
|
5
|
+
export interface CompileResult {
|
|
6
|
+
success: boolean;
|
|
7
|
+
classFiles: string[];
|
|
8
|
+
errors: string[];
|
|
9
|
+
outputDir: string;
|
|
10
|
+
}
|
|
11
|
+
export declare class KotlinCompiler {
|
|
12
|
+
private projectPath;
|
|
13
|
+
private static readonly TAG;
|
|
14
|
+
private kotlincPath;
|
|
15
|
+
private composeCompilerPath;
|
|
16
|
+
private staticClasspath;
|
|
17
|
+
constructor(projectPath: string);
|
|
18
|
+
/**
|
|
19
|
+
* Find Compose compiler plugin JAR
|
|
20
|
+
* For Kotlin 2.0+, the Compose compiler is bundled with kotlinc
|
|
21
|
+
*/
|
|
22
|
+
findComposeCompiler(): Promise<string | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Find kotlinc executable
|
|
25
|
+
*/
|
|
26
|
+
findKotlinc(): Promise<string | null>;
|
|
27
|
+
/**
|
|
28
|
+
* Build the classpath for compilation
|
|
29
|
+
* This needs to include Android SDK, Compose, and project dependencies
|
|
30
|
+
*/
|
|
31
|
+
buildClasspath(): Promise<string[]>;
|
|
32
|
+
/**
|
|
33
|
+
* Get project build output classpath entries.
|
|
34
|
+
* Always called fresh ΓÇö never cached ΓÇö so new class files from Gradle builds are always visible.
|
|
35
|
+
*/
|
|
36
|
+
private getProjectClasspathEntries;
|
|
37
|
+
/**
|
|
38
|
+
* Recursively find JAR files in a directory up to maxDepth
|
|
39
|
+
*/
|
|
40
|
+
private findJarsRecursive;
|
|
41
|
+
/**
|
|
42
|
+
* Compile a single Kotlin file
|
|
43
|
+
*/
|
|
44
|
+
compileFile(filePath: string): Promise<CompileResult>;
|
|
45
|
+
/**
|
|
46
|
+
* Find all .class files in a directory
|
|
47
|
+
*/
|
|
48
|
+
private findClassFiles;
|
|
49
|
+
/**
|
|
50
|
+
* Run a command and return result
|
|
51
|
+
*/
|
|
52
|
+
private runCommand;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=kotlin-compiler.d.ts.map
|