lecodes-cli 2.4.7 → 2.4.9

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/dist/index.js CHANGED
@@ -64,7 +64,7 @@ var package_default;
64
64
  var init_package = __esm(() => {
65
65
  package_default = {
66
66
  name: "lecodes-cli",
67
- version: "2.4.7",
67
+ version: "2.4.9",
68
68
  dependencies: {
69
69
  "@letary/chisel": "^0.15.0",
70
70
  jimp: "^1.6.1",
@@ -89,7 +89,7 @@ var init_package = __esm(() => {
89
89
  },
90
90
  peerDependencies: {
91
91
  "lecodes-design": "^0.11.0",
92
- "lecodes-headless": "^2.4.7",
92
+ "lecodes-headless": "^2.4.9",
93
93
  "lecodes-3d-editor": "^0.9.0",
94
94
  "lecodes-assets": "^0.2.1",
95
95
  "heic-decode": "^2.1.0",
@@ -159,7 +159,7 @@ var init_package = __esm(() => {
159
159
  });
160
160
 
161
161
  // ../../sdk/src/version.ts
162
- var SDK_VERSION = "2.4.7";
162
+ var SDK_VERSION = "2.4.9";
163
163
 
164
164
  // src/cli/args.ts
165
165
  var isNegativeNumber = (s) => /^-(\d|\.\d)/.test(s), isValue = (s) => !s.startsWith("-") || isNegativeNumber(s), parseArgv = (argv, booleans = new Set) => {
@@ -880,13 +880,7 @@ var init_resourceSlots = __esm(() => {
880
880
  "map",
881
881
  "shader",
882
882
  "build",
883
- "data",
884
- "light",
885
- "aux",
886
- "direct",
887
- "indirect",
888
- "probes",
889
- "volume"
883
+ "data"
890
884
  ]
891
885
  };
892
886
  });
@@ -1146,6 +1140,520 @@ var init_assetMacro = __esm(() => {
1146
1140
  SET_TARGET = /\.\s*([A-Za-z_$][\w$]*)\s*$/;
1147
1141
  });
1148
1142
 
1143
+ // ../../sdk/src/compile/assetIconMacro.ts
1144
+ var ICON_ID, isIdentStart = (c) => c >= "A" && c <= "Z" || c >= "a" && c <= "z" || c === "_" || c === "$", isIdentPart = (c) => isIdentStart(c) || c >= "0" && c <= "9", skipString = (src, start) => {
1145
+ const quote = src[start];
1146
+ const n = src.length;
1147
+ let i = start + 1;
1148
+ while (i < n) {
1149
+ const c = src[i];
1150
+ if (c === "\\") {
1151
+ i += 2;
1152
+ continue;
1153
+ }
1154
+ if (quote === "`" && c === "$" && src[i + 1] === "{") {
1155
+ i = skipBraces(src, i + 1);
1156
+ continue;
1157
+ }
1158
+ if (c === quote)
1159
+ return i + 1;
1160
+ i++;
1161
+ }
1162
+ return n;
1163
+ }, skipBraces = (src, open) => {
1164
+ const n = src.length;
1165
+ let depth = 0;
1166
+ let i = open;
1167
+ while (i < n) {
1168
+ const c = src[i];
1169
+ if (c === '"' || c === "'" || c === "`") {
1170
+ i = skipString(src, i);
1171
+ continue;
1172
+ }
1173
+ if (c === "{")
1174
+ depth++;
1175
+ else if (c === "}") {
1176
+ depth--;
1177
+ if (depth === 0)
1178
+ return i + 1;
1179
+ }
1180
+ i++;
1181
+ }
1182
+ return n;
1183
+ }, matchParen = (src, open) => {
1184
+ const n = src.length;
1185
+ let depth = 0;
1186
+ let i = open;
1187
+ while (i < n) {
1188
+ const c = src[i];
1189
+ if (c === '"' || c === "'" || c === "`") {
1190
+ i = skipString(src, i);
1191
+ continue;
1192
+ }
1193
+ if (c === "/" && src[i + 1] === "/") {
1194
+ i += 2;
1195
+ while (i < n && src[i] !== `
1196
+ `)
1197
+ i++;
1198
+ continue;
1199
+ }
1200
+ if (c === "/" && src[i + 1] === "*") {
1201
+ i += 2;
1202
+ while (i < n && !(src[i] === "*" && src[i + 1] === "/"))
1203
+ i++;
1204
+ i += 2;
1205
+ continue;
1206
+ }
1207
+ if (c === "(")
1208
+ depth++;
1209
+ else if (c === ")") {
1210
+ depth--;
1211
+ if (depth === 0)
1212
+ return i;
1213
+ }
1214
+ i++;
1215
+ }
1216
+ return -1;
1217
+ }, splitTopLevel = (text, delim) => {
1218
+ const parts = [];
1219
+ const n = text.length;
1220
+ let depth = 0;
1221
+ let last = 0;
1222
+ let i = 0;
1223
+ while (i < n) {
1224
+ const c = text[i];
1225
+ if (c === '"' || c === "'" || c === "`") {
1226
+ i = skipString(text, i);
1227
+ continue;
1228
+ }
1229
+ if (c === "(" || c === "[" || c === "{")
1230
+ depth++;
1231
+ else if (c === ")" || c === "]" || c === "}")
1232
+ depth = Math.max(0, depth - 1);
1233
+ else if (depth === 0 && c === delim) {
1234
+ parts.push(text.slice(last, i));
1235
+ last = i + 1;
1236
+ }
1237
+ i++;
1238
+ }
1239
+ parts.push(text.slice(last));
1240
+ return parts;
1241
+ }, stringLiteralValue = (text) => {
1242
+ const t = text.trim();
1243
+ if (t.length < 2)
1244
+ return null;
1245
+ const q = t[0];
1246
+ if (q !== '"' && q !== "'" && q !== "`" || t[t.length - 1] !== q)
1247
+ return null;
1248
+ const inner = t.slice(1, -1);
1249
+ if (q === "`" && inner.includes("${"))
1250
+ return null;
1251
+ if (inner.includes(q) && !inner.includes("\\" + q))
1252
+ return null;
1253
+ return inner.replace(/\\(.)/g, "$1");
1254
+ }, scanAssetIconCalls = (source) => {
1255
+ const calls = [];
1256
+ const n = source.length;
1257
+ let i = 0;
1258
+ while (i < n) {
1259
+ const c = source[i];
1260
+ if (c === '"' || c === "'" || c === "`") {
1261
+ i = skipString(source, i);
1262
+ continue;
1263
+ }
1264
+ if (c === "/" && source[i + 1] === "/") {
1265
+ i += 2;
1266
+ while (i < n && source[i] !== `
1267
+ `)
1268
+ i++;
1269
+ continue;
1270
+ }
1271
+ if (c === "/" && source[i + 1] === "*") {
1272
+ i += 2;
1273
+ while (i < n && !(source[i] === "*" && source[i + 1] === "/"))
1274
+ i++;
1275
+ i += 2;
1276
+ continue;
1277
+ }
1278
+ if (isIdentStart(c)) {
1279
+ let j = i + 1;
1280
+ while (j < n && isIdentPart(source[j]))
1281
+ j++;
1282
+ if (source.slice(i, j) === "assetIcon" && prevNonSpace(source, i) !== ".") {
1283
+ const call = parseCall(source, i, j);
1284
+ if (call) {
1285
+ calls.push(call);
1286
+ i = call.end;
1287
+ continue;
1288
+ }
1289
+ }
1290
+ i = j;
1291
+ continue;
1292
+ }
1293
+ i++;
1294
+ }
1295
+ return calls;
1296
+ }, prevNonSpace = (src, i) => {
1297
+ let k = i - 1;
1298
+ while (k >= 0 && /\s/.test(src[k]))
1299
+ k--;
1300
+ return k >= 0 ? src[k] : "";
1301
+ }, parseCall = (src, start, identEnd) => {
1302
+ const n = src.length;
1303
+ let i = identEnd;
1304
+ while (i < n && /\s/.test(src[i]))
1305
+ i++;
1306
+ if (src[i] !== "(")
1307
+ return null;
1308
+ const close = matchParen(src, i);
1309
+ if (close === -1)
1310
+ return null;
1311
+ const call = { start, end: close + 1, id: "", pack: "", name: "" };
1312
+ const args = splitTopLevel(src.slice(i + 1, close), ",");
1313
+ const id = stringLiteralValue(args[0] ?? "");
1314
+ if (id === null) {
1315
+ call.error = `assetIcon(...) needs a string-literal id like "lucide:bell" (got \`${(args[0] ?? "").trim() || "()"}\`).`;
1316
+ return call;
1317
+ }
1318
+ const m = ICON_ID.exec(id);
1319
+ if (!m) {
1320
+ call.error = `assetIcon("${id}"): id must be "pack:name", lowercase [a-z0-9-] (e.g. "lucide:bell").`;
1321
+ return call;
1322
+ }
1323
+ call.id = id;
1324
+ call.pack = m[1];
1325
+ call.name = m[2];
1326
+ const optsText = args[1]?.trim();
1327
+ if (optsText) {
1328
+ const color = extractColor(optsText);
1329
+ if (color.error) {
1330
+ call.error = color.error;
1331
+ return call;
1332
+ }
1333
+ if (color.bake !== undefined)
1334
+ call.bake = color.bake;
1335
+ else if (color.tint !== undefined)
1336
+ call.tint = color.tint;
1337
+ }
1338
+ return call;
1339
+ }, extractColor = (optsText) => {
1340
+ if (!optsText.startsWith("{") || !optsText.endsWith("}")) {
1341
+ return { error: `assetIcon(id, opts): opts must be an object literal like { color: "#5b8cff" }.` };
1342
+ }
1343
+ for (const prop of splitTopLevel(optsText.slice(1, -1), ",")) {
1344
+ const kv = splitTopLevel(prop, ":");
1345
+ if (kv.length < 2)
1346
+ continue;
1347
+ const key = kv[0].trim().replace(/^["'`]|["'`]$/g, "");
1348
+ if (key !== "color")
1349
+ continue;
1350
+ const raw = kv.slice(1).join(":").trim();
1351
+ if (!raw)
1352
+ continue;
1353
+ const literal = stringLiteralValue(raw);
1354
+ return literal !== null ? { bake: literal } : { tint: raw };
1355
+ }
1356
+ return {};
1357
+ }, rewriteAssetIcons = (source, calls, svgById) => {
1358
+ let out = source;
1359
+ for (const call of [...calls].sort((a, b) => b.start - a.start)) {
1360
+ const svg = svgById.get(call.id);
1361
+ if (svg === undefined)
1362
+ continue;
1363
+ out = out.slice(0, call.start) + iconExpr(svg, call) + out.slice(call.end);
1364
+ }
1365
+ return out;
1366
+ }, iconExpr = (svg, call) => {
1367
+ const body = call.bake ? svg.split("currentColor").join(call.bake) : svg;
1368
+ const src = `SvgSource(\`${escapeTemplate(body)}\`)`;
1369
+ return call.tint ? `Object.assign(${src}, { tintColor: (${call.tint}) })` : src;
1370
+ }, escapeTemplate = (s) => s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${"), applyAssetIconMacro = async (entries, resolveSvgs) => {
1371
+ const scanned = entries.filter((e) => e.type === "text" && typeof e.text === "string" && e.text.includes("assetIcon")).map((e) => ({ entry: e, calls: scanAssetIconCalls(e.text) })).filter((s) => s.calls.length > 0);
1372
+ if (scanned.length === 0)
1373
+ return [];
1374
+ const errors = scanned.flatMap((s) => s.calls.filter((c) => c.error).map((c) => c.error));
1375
+ if (errors.length)
1376
+ throw new Error([...new Set(errors)].join(`
1377
+ `));
1378
+ const ids = [...new Set(scanned.flatMap((s) => s.calls.map((c) => c.id)))];
1379
+ const svgById = await resolveSvgs(ids);
1380
+ const missing = ids.filter((id) => !svgById.has(id));
1381
+ if (missing.length)
1382
+ throw new Error(`assetIcon: could not resolve icon(s): ${missing.join(", ")}.`);
1383
+ for (const { entry, calls } of scanned)
1384
+ entry.text = rewriteAssetIcons(entry.text, calls, svgById);
1385
+ return ids;
1386
+ }, buildIconsHeaderValue = (ids) => [...new Set(ids.map((id) => id.split(":")[0]))].sort().join(";"), editDistance = (a, b, cap) => {
1387
+ if (Math.abs(a.length - b.length) > cap)
1388
+ return cap + 1;
1389
+ let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
1390
+ for (let i = 1;i <= a.length; i++) {
1391
+ const cur = [i];
1392
+ let rowMin = i;
1393
+ for (let j = 1;j <= b.length; j++) {
1394
+ cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
1395
+ if (cur[j] < rowMin)
1396
+ rowMin = cur[j];
1397
+ }
1398
+ if (rowMin > cap)
1399
+ return cap + 1;
1400
+ prev = cur;
1401
+ }
1402
+ return prev[b.length];
1403
+ }, suggestIconNames = (want, icons) => {
1404
+ const wantParts = new Set(want.split("-").filter(Boolean));
1405
+ const scored = [];
1406
+ for (const [name, info] of Object.entries(icons)) {
1407
+ const lenDiff = Math.abs(name.length - want.length) / 100;
1408
+ let rank;
1409
+ if (name.includes(want) || want.includes(name))
1410
+ rank = lenDiff;
1411
+ else {
1412
+ const d = editDistance(want, name, 2);
1413
+ if (d <= 2)
1414
+ rank = 1 + d;
1415
+ else if (info.kw?.some((k) => wantParts.has(k)))
1416
+ rank = 4 + lenDiff;
1417
+ else
1418
+ continue;
1419
+ }
1420
+ scored.push({ name, rank });
1421
+ }
1422
+ return scored.sort((a, b) => a.rank - b.rank || (a.name < b.name ? -1 : 1)).slice(0, 5).map((s) => s.name);
1423
+ }, iconNotFoundError = async (pack, name, read, registry) => {
1424
+ const metaRaw = await read(`${pack}/meta.json`);
1425
+ if (metaRaw !== null) {
1426
+ let icons = {};
1427
+ try {
1428
+ icons = JSON.parse(metaRaw).icons ?? {};
1429
+ } catch {}
1430
+ const similar = suggestIconNames(name, icons);
1431
+ const hint = similar.length ? ` Did you mean ${similar.map((s) => `"${pack}:${s}"`).join(", ")}?` : ` No similar name in the "${pack}" pack either.`;
1432
+ return new Error(`assetIcon: icon "${pack}:${name}" not found.${hint}`);
1433
+ }
1434
+ const licRaw = await read("licenses.json");
1435
+ if (licRaw !== null) {
1436
+ try {
1437
+ const packs = Object.keys(JSON.parse(licRaw)).sort();
1438
+ return new Error(`assetIcon: unknown icon pack "${pack}" (in "${pack}:${name}"). Available packs: ${packs.join(", ")}.`);
1439
+ } catch {}
1440
+ }
1441
+ return Object.assign(new Error(`assetIcon: can't reach the icon registry${registry ? ` (${registry})` : ""} to resolve "${pack}:${name}". ` + `Check your connection, or set LECODES_ICON_REGISTRY.`), { transient: true });
1442
+ }, VENDORED_ICON_PATH, collectVendoredIcons = (entries) => {
1443
+ const map = new Map;
1444
+ for (const e of entries) {
1445
+ if (e.type !== "text" || typeof e.text !== "string")
1446
+ continue;
1447
+ const m = VENDORED_ICON_PATH.exec(e.path);
1448
+ if (m)
1449
+ map.set(`${m[1]}:${m[2]}`, e.text);
1450
+ }
1451
+ return map;
1452
+ };
1453
+ var init_assetIconMacro = __esm(() => {
1454
+ ICON_ID = /^([a-z0-9]+(?:-[a-z0-9]+)*):([a-z0-9]+(?:-[a-z0-9]+)*)$/;
1455
+ VENDORED_ICON_PATH = /(?:^|\/)assets\/icons\/([a-z0-9]+(?:-[a-z0-9]+)*)\/([a-z0-9]+(?:-[a-z0-9]+)*)\.svg$/;
1456
+ });
1457
+
1458
+ // ../../sdk/src/compile/bakeMacro.ts
1459
+ var BAKE_CALL, DECLARES_BAKE, DEFINITION_TAIL, hasBakeMacro = (src) => src.includes("bake"), stringLiteralValue2 = (text) => {
1460
+ const t = text.trim();
1461
+ if (t.length < 2)
1462
+ return null;
1463
+ const q = t[0];
1464
+ if (q !== '"' && q !== "'" && q !== "`" || t[t.length - 1] !== q)
1465
+ return null;
1466
+ const inner = t.slice(1, -1);
1467
+ if (q === "`" && inner.includes("${"))
1468
+ return null;
1469
+ if (/[\r\n]/.test(inner))
1470
+ return null;
1471
+ for (let i = 0;i < inner.length; i++) {
1472
+ if (inner[i] === "\\") {
1473
+ i++;
1474
+ continue;
1475
+ }
1476
+ if (inner[i] === q)
1477
+ return null;
1478
+ }
1479
+ return inner.replace(/\\(.)/g, "$1");
1480
+ }, closeOf = (mask, open) => {
1481
+ let depth = 0;
1482
+ for (let i = open;i < mask.length; i++) {
1483
+ const c = mask[i];
1484
+ if (c === "(" || c === "[" || c === "{")
1485
+ depth++;
1486
+ else if (c === ")" || c === "]" || c === "}") {
1487
+ depth--;
1488
+ if (depth === 0)
1489
+ return i;
1490
+ }
1491
+ }
1492
+ return -1;
1493
+ }, argSpans = (mask, open, close) => {
1494
+ const spans = [];
1495
+ let depth = 0;
1496
+ let start = open + 1;
1497
+ for (let i = open + 1;i < close; i++) {
1498
+ const c = mask[i];
1499
+ if (c === "(" || c === "[" || c === "{")
1500
+ depth++;
1501
+ else if (c === ")" || c === "]" || c === "}")
1502
+ depth--;
1503
+ else if (c === "," && depth === 0) {
1504
+ spans.push([start, i]);
1505
+ start = i + 1;
1506
+ }
1507
+ }
1508
+ spans.push([start, close]);
1509
+ return spans;
1510
+ }, newlines = (s) => {
1511
+ let n = 0;
1512
+ for (let i = 0;i < s.length; i++)
1513
+ if (s[i] === `
1514
+ `)
1515
+ n++;
1516
+ return n;
1517
+ }, stringArray = (v) => Array.isArray(v) && v.every((x) => typeof x === "string") ? v : null, readManifest2 = (json) => {
1518
+ if (!json || typeof json !== "object")
1519
+ return null;
1520
+ const m = json;
1521
+ const light = stringArray(m.light), aux = stringArray(m.aux);
1522
+ if (!light || !aux)
1523
+ return null;
1524
+ const file = (v) => typeof v === "string" && v !== "" ? v : v && typeof v === "object" && typeof v.file === "string" ? v.file : undefined;
1525
+ return {
1526
+ light,
1527
+ aux,
1528
+ direct: stringArray(m.direct) ?? undefined,
1529
+ indirect: stringArray(m.indirect) ?? undefined,
1530
+ probes: file(m.probes),
1531
+ volume: file(m.volume),
1532
+ uv1: file(m.uv1)
1533
+ };
1534
+ }, expandBakeMacros = async (src, filePath, exists, read) => {
1535
+ if (!hasBakeMacro(src))
1536
+ return { src, bakes: [] };
1537
+ const strings = [];
1538
+ const mask = maskNonCode(src, (s, e) => {
1539
+ strings.push([s, e]);
1540
+ });
1541
+ if (DECLARES_BAKE.test(mask))
1542
+ return { src, bakes: [] };
1543
+ const stringsIn = ([a, b]) => strings.filter(([s, e]) => s >= a && e <= b);
1544
+ const blankArg = (span) => mask.slice(span[0], span[1]).trim() === "" && stringsIn(span).length === 0;
1545
+ const literalOf = (span) => {
1546
+ const inside = stringsIn(span);
1547
+ if (inside.length === 1 && mask.slice(span[0], span[1]).trim() === "")
1548
+ return src.slice(inside[0][0], inside[0][1]);
1549
+ return src.slice(span[0], span[1]).trim();
1550
+ };
1551
+ const dir = filePath.slice(0, filePath.lastIndexOf("/"));
1552
+ const at = (index) => `${filePath.replace(/^\//, "")}:${newlines(src.slice(0, index)) + 1}`;
1553
+ const bakes = [];
1554
+ const parts = [];
1555
+ let done = 0;
1556
+ for (const m of mask.matchAll(BAKE_CALL)) {
1557
+ const start = m.index ?? 0;
1558
+ if (start < done)
1559
+ continue;
1560
+ const open = start + m[0].length - 1;
1561
+ const close = closeOf(mask, open);
1562
+ if (close < 0)
1563
+ throw new Error(`bake(): unclosed call (${at(start)})`);
1564
+ const spans = argSpans(mask, open, close);
1565
+ const pathText = literalOf(spans[0]);
1566
+ const written = stringLiteralValue2(pathText);
1567
+ if (written === null && DEFINITION_TAIL.test(mask.slice(close + 1, close + 160)))
1568
+ continue;
1569
+ if (written === null)
1570
+ throw new Error(`bake() expects a string-literal path like bake('./assets/lightmap/level.bake'), found \`${pathText || "()"}\` (${at(start)})`);
1571
+ if (!/\.bake$/i.test(written))
1572
+ throw new Error(`bake('${written}'): the path names a .bake file — what \`lecodes lightmap bake\` writes (${at(start)})`);
1573
+ if (spans.slice(2).some((s) => !blankArg(s)))
1574
+ throw new Error(`bake('${written}'): one path and one options object at most (${at(start)})`);
1575
+ let options = null;
1576
+ if (spans.length > 1 && !blankArg(spans[1])) {
1577
+ const [a, b] = spans[1];
1578
+ const code = mask.slice(a, b);
1579
+ const lead = a + (code.length - code.trimStart().length);
1580
+ const tail = b - (code.length - code.trimEnd().length);
1581
+ if (mask[lead] !== "{" || closeOf(mask, lead) !== tail - 1) {
1582
+ throw new Error(`bake('${written}', options): options must be an object literal like { emissiveBake: false } (${at(start)})`);
1583
+ }
1584
+ options = [lead, tail];
1585
+ }
1586
+ const relative = written.startsWith("./") || written.startsWith("../");
1587
+ const rooted = written.startsWith("/");
1588
+ const candidates = rooted ? [normalizePath(written)] : relative ? [normalizePath(`${dir}/${written}`), normalizePath(written)] : [normalizePath(written), normalizePath(`${dir}/${written}`)];
1589
+ const found = candidates.find(exists);
1590
+ const target = found ?? candidates[0];
1591
+ const ref = { path: target.slice(1), exists: found !== undefined, line: newlines(src.slice(0, start)) + 1, written, missing: [] };
1592
+ let manifest = null;
1593
+ if (found !== undefined) {
1594
+ const body = read ? await read(found) : null;
1595
+ if (body === null)
1596
+ ref.unread = true;
1597
+ else {
1598
+ let json;
1599
+ try {
1600
+ json = JSON.parse(body.replace(/^\uFEFF/, ""));
1601
+ } catch (e) {
1602
+ throw new Error(`bake('${written}'): ${ref.path} is not a bake manifest (${e instanceof Error ? e.message : String(e)}) — rebake with \`lecodes lightmap bake\` (${at(start)})`);
1603
+ }
1604
+ manifest = readManifest2(json);
1605
+ if (!manifest)
1606
+ throw new Error(`bake('${written}'): ${ref.path} is not a bake manifest (no light / aux pages) — rebake with \`lecodes lightmap bake\` (${at(start)})`);
1607
+ }
1608
+ }
1609
+ const bakeDir = target.slice(0, target.lastIndexOf("/"));
1610
+ const ref1 = (name) => {
1611
+ const p = normalizePath(`${bakeDir}/${name}`);
1612
+ if (!exists(p) && !ref.missing.includes(p))
1613
+ ref.missing.push(p);
1614
+ return `asset(${JSON.stringify(p)})`;
1615
+ };
1616
+ const list = (names) => `[${names.map(ref1).join(", ")}]`;
1617
+ const fields = [`path: \`${escapeTemplate(ref.path)}\``];
1618
+ if (manifest) {
1619
+ fields.unshift(`data: asset(${JSON.stringify(target)})`);
1620
+ fields.push(`light: ${list(manifest.light)}`, `aux: ${list(manifest.aux)}`);
1621
+ if (manifest.direct)
1622
+ fields.push(`direct: ${list(manifest.direct)}`);
1623
+ if (manifest.indirect)
1624
+ fields.push(`indirect: ${list(manifest.indirect)}`);
1625
+ if (manifest.probes)
1626
+ fields.push(`probes: ${ref1(manifest.probes)}`);
1627
+ if (manifest.volume)
1628
+ fields.push(`volume: ${ref1(manifest.volume)}`);
1629
+ if (manifest.uv1)
1630
+ fields.push(`uv1: ${ref1(manifest.uv1)}`);
1631
+ } else {
1632
+ fields.unshift("data: null");
1633
+ fields.push("light: []", "aux: []");
1634
+ }
1635
+ const head = `{ ${fields.join(", ")}`;
1636
+ const expansion = options ? `${head}, ${`
1637
+ `.repeat(newlines(src.slice(start, options[0])))}...${src.slice(options[0], options[1])}${`
1638
+ `.repeat(newlines(src.slice(options[1], close + 1)))} }` : `${head}${`
1639
+ `.repeat(newlines(src.slice(start, close + 1)))} }`;
1640
+ parts.push(src.slice(done, start), expansion);
1641
+ done = close + 1;
1642
+ bakes.push(ref);
1643
+ }
1644
+ if (bakes.length === 0)
1645
+ return { src, bakes };
1646
+ parts.push(src.slice(done));
1647
+ return { src: parts.join(""), bakes };
1648
+ };
1649
+ var init_bakeMacro = __esm(() => {
1650
+ init_assetMacro();
1651
+ init_assetIconMacro();
1652
+ BAKE_CALL = /(?<![.\w$])bake\s*\(/g;
1653
+ DECLARES_BAKE = /\b(?:function|const|let|var|class)\s+bake\b|\bimport\s+(?:\{[^}]*\bbake\b[^}]*\}|bake)\s+from\b/;
1654
+ DEFINITION_TAIL = /^\s*(?::[^;{}=()\n]*)?(?:\{|=>)/;
1655
+ });
1656
+
1149
1657
  // ../../sdk/src/compile/detectEntry.ts
1150
1658
  var ENTRY_CONVENTION = "/main.ts", isCode = (path) => /\.[tj]s$/.test(path) && !path.endsWith(".d.ts"), isNonEntry = (path) => path.endsWith(".editor.ts") || /\.server\.[tj]s$/.test(path) || /^\/design\//.test(path), normalize = (path) => {
1151
1659
  const parts = [];
@@ -1242,7 +1750,7 @@ var HEAD_RE, hasAspectExtends = (src) => /\bextends\s+(?:Aspect|System)\s*</.tes
1242
1750
  for (;i < src.length && angle > 0; i++) {
1243
1751
  const c = src[i];
1244
1752
  if (c === '"' || c === "'" || c === "`") {
1245
- i = skipString(src, i) - 1;
1753
+ i = skipString2(src, i) - 1;
1246
1754
  continue;
1247
1755
  }
1248
1756
  if (c === "=" && src[i + 1] === ">") {
@@ -1269,7 +1777,7 @@ var HEAD_RE, hasAspectExtends = (src) => /\bextends\s+(?:Aspect|System)\s*</.tes
1269
1777
  return -1;
1270
1778
  }
1271
1779
  return -1;
1272
- }, skipString = (src, at) => {
1780
+ }, skipString2 = (src, at) => {
1273
1781
  const q = src[at];
1274
1782
  let i = at + 1;
1275
1783
  for (;i < src.length; i++) {
@@ -1646,11 +2154,13 @@ var resolveInject = () => {
1646
2154
  if (missing.length > 0)
1647
2155
  throw new Error(missing.join(`
1648
2156
  `));
1649
- }, bundleProjectChisel = async (entrypoint, files, format, sourcemap, minifyOverride, editor, libraries, inject = CLIENT_INJECT) => {
2157
+ }, bundleProjectChisel = async (entrypoint, files, format, sourcemap, minifyOverride, editor, libraries, inject = CLIENT_INJECT, readResource) => {
1650
2158
  const entry = normalize2(entrypoint);
1651
2159
  const slugs = new Set(Object.keys(libraries ?? {}));
2160
+ const rootKeys = new Map(Object.keys(files).map((k) => [normalize2(k), k]));
1652
2161
  const projFiles = {};
1653
2162
  const assetRefs = [];
2163
+ const bakeRefs = [];
1654
2164
  const scanSources = {};
1655
2165
  for (const [k, v] of Object.entries(files)) {
1656
2166
  const path = normalize2(k);
@@ -1659,6 +2169,12 @@ var resolveInject = () => {
1659
2169
  if (hasImportStatement(src))
1660
2170
  src = rewriteLibraryImports(src, slugs);
1661
2171
  scanSources[path] = src;
2172
+ if (hasBakeMacro(src)) {
2173
+ const expanded = await expandBakeMacros(src, path, (rootPath) => rootKeys.has(rootPath), readResource);
2174
+ for (const ref of expanded.bakes)
2175
+ bakeRefs.push({ file: path, ref });
2176
+ src = expanded.src;
2177
+ }
1662
2178
  const macros = hasAssetMacro(src) ? collectAssetMacros(src, path) : [];
1663
2179
  const paths = collectPathLiterals(src, path, (rootPath) => files[rootPath] !== undefined, macros);
1664
2180
  const found = macros.concat(paths).sort((a, b) => a.index - b.index);
@@ -1692,8 +2208,22 @@ var resolveInject = () => {
1692
2208
  projFiles[path] = src;
1693
2209
  }
1694
2210
  }
1695
- if (assetRefs.length > 0) {
2211
+ const bakes = [];
2212
+ const notes = [];
2213
+ if (assetRefs.length > 0 || bakeRefs.length > 0) {
1696
2214
  const reachable = reachableFrom(entry, scanSources);
2215
+ const reached = bakeRefs.filter(({ file }) => reachable.has(file));
2216
+ const incomplete = reached.filter(({ ref }) => ref.missing.length > 0);
2217
+ if (incomplete.length > 0) {
2218
+ throw new Error(incomplete.map(({ file, ref }) => `bake('${ref.written}') (${file.replace(/^\//, "")}:${ref.line}): ${ref.path} names files that are not there: ${ref.missing.map((p) => p.replace(/^\//, "")).join(", ")} — rebake with \`lecodes lightmap bake\``).join(`
2219
+ `));
2220
+ }
2221
+ for (const { file, ref } of reached) {
2222
+ if (!bakes.some((b) => b.path === ref.path))
2223
+ bakes.push({ path: ref.path, exists: ref.exists });
2224
+ if (ref.unread)
2225
+ notes.push(`${file.replace(/^\//, "")}:${ref.line}: bake('${ref.written}'): this compile cannot read ${ref.path} — the level runs unbaked`);
2226
+ }
1697
2227
  assertAssetsExist(assetRefs.filter(({ file }) => reachable.has(file)), projFiles);
1698
2228
  }
1699
2229
  const sdkFiles = collectSdkFiles(resolveSdkSrc());
@@ -1727,13 +2257,14 @@ var resolveInject = () => {
1727
2257
  throw new Error(`chisel: ${parsed.error}`);
1728
2258
  if (parsed.code == null)
1729
2259
  throw new Error("chisel produced no code");
1730
- const diagnostics = parsed.diagnostics ?? [];
2260
+ const diagnostics = [...notes, ...parsed.diagnostics ?? []];
1731
2261
  for (const d of diagnostics)
1732
2262
  console.warn(`warning: ${d}`);
1733
- return { code: parsed.code, map: parsed.map ?? null, diagnostics };
1734
- }, bundleProjectWithMap = async (entrypoint, files, format = "esm", minify, editor, libraries, sourcemap = true, inject) => bundleProjectChisel(entrypoint, files, format, sourcemap, minify, editor, libraries, inject);
2263
+ return { code: parsed.code, map: parsed.map ?? null, diagnostics, bakes };
2264
+ }, bundleProjectWithMap = async (entrypoint, files, format = "esm", minify, editor, libraries, sourcemap = true, inject, readResource) => bundleProjectChisel(entrypoint, files, format, sourcemap, minify, editor, libraries, inject, readResource);
1735
2265
  var init_bundler = __esm(() => {
1736
2266
  init_assetMacro();
2267
+ init_bakeMacro();
1737
2268
  init_detectEntry();
1738
2269
  init_aspectMacro();
1739
2270
  init_libraryImports();
@@ -170839,6 +171370,18 @@ var init_shaderSchema = __esm(() => {
170839
171370
  COLOR_NAME = /colou?r|tint|albedo|emissive|glare|glow/i;
170840
171371
  });
170841
171372
 
171373
+ // ../../sdk/src/compile/sourcemap.ts
171374
+ var offsetSourceMap = (mapJson, lines) => {
171375
+ if (lines <= 0)
171376
+ return mapJson;
171377
+ const map = JSON.parse(mapJson);
171378
+ map.mappings = ";".repeat(lines) + map.mappings;
171379
+ return JSON.stringify(map);
171380
+ }, inlineSourceMapComment = (mapJson) => {
171381
+ const b64 = Buffer.from(mapJson, "utf8").toString("base64");
171382
+ return `//# lecodesMap=data:application/json;charset=utf-8;base64,${b64}`;
171383
+ };
171384
+
170842
171385
  // ../../sdk/src/compile/header.ts
170843
171386
  var ENGINE_SIGNALS, buildHeader = (name, code, opts = {}) => {
170844
171387
  let header = `// sdk: ${SDK_VERSION}
@@ -170904,333 +171447,6 @@ var init_header = __esm(() => {
170904
171447
  ];
170905
171448
  });
170906
171449
 
170907
- // ../../sdk/src/compile/sourcemap.ts
170908
- var offsetSourceMap = (mapJson, lines) => {
170909
- if (lines <= 0)
170910
- return mapJson;
170911
- const map = JSON.parse(mapJson);
170912
- map.mappings = ";".repeat(lines) + map.mappings;
170913
- return JSON.stringify(map);
170914
- }, inlineSourceMapComment = (mapJson) => {
170915
- const b64 = Buffer.from(mapJson, "utf8").toString("base64");
170916
- return `//# lecodesMap=data:application/json;charset=utf-8;base64,${b64}`;
170917
- };
170918
-
170919
- // ../../sdk/src/compile/assetIconMacro.ts
170920
- var ICON_ID, isIdentStart = (c) => c >= "A" && c <= "Z" || c >= "a" && c <= "z" || c === "_" || c === "$", isIdentPart = (c) => isIdentStart(c) || c >= "0" && c <= "9", skipString2 = (src, start) => {
170921
- const quote = src[start];
170922
- const n = src.length;
170923
- let i = start + 1;
170924
- while (i < n) {
170925
- const c = src[i];
170926
- if (c === "\\") {
170927
- i += 2;
170928
- continue;
170929
- }
170930
- if (quote === "`" && c === "$" && src[i + 1] === "{") {
170931
- i = skipBraces(src, i + 1);
170932
- continue;
170933
- }
170934
- if (c === quote)
170935
- return i + 1;
170936
- i++;
170937
- }
170938
- return n;
170939
- }, skipBraces = (src, open) => {
170940
- const n = src.length;
170941
- let depth = 0;
170942
- let i = open;
170943
- while (i < n) {
170944
- const c = src[i];
170945
- if (c === '"' || c === "'" || c === "`") {
170946
- i = skipString2(src, i);
170947
- continue;
170948
- }
170949
- if (c === "{")
170950
- depth++;
170951
- else if (c === "}") {
170952
- depth--;
170953
- if (depth === 0)
170954
- return i + 1;
170955
- }
170956
- i++;
170957
- }
170958
- return n;
170959
- }, matchParen = (src, open) => {
170960
- const n = src.length;
170961
- let depth = 0;
170962
- let i = open;
170963
- while (i < n) {
170964
- const c = src[i];
170965
- if (c === '"' || c === "'" || c === "`") {
170966
- i = skipString2(src, i);
170967
- continue;
170968
- }
170969
- if (c === "/" && src[i + 1] === "/") {
170970
- i += 2;
170971
- while (i < n && src[i] !== `
170972
- `)
170973
- i++;
170974
- continue;
170975
- }
170976
- if (c === "/" && src[i + 1] === "*") {
170977
- i += 2;
170978
- while (i < n && !(src[i] === "*" && src[i + 1] === "/"))
170979
- i++;
170980
- i += 2;
170981
- continue;
170982
- }
170983
- if (c === "(")
170984
- depth++;
170985
- else if (c === ")") {
170986
- depth--;
170987
- if (depth === 0)
170988
- return i;
170989
- }
170990
- i++;
170991
- }
170992
- return -1;
170993
- }, splitTopLevel = (text, delim) => {
170994
- const parts = [];
170995
- const n = text.length;
170996
- let depth = 0;
170997
- let last = 0;
170998
- let i = 0;
170999
- while (i < n) {
171000
- const c = text[i];
171001
- if (c === '"' || c === "'" || c === "`") {
171002
- i = skipString2(text, i);
171003
- continue;
171004
- }
171005
- if (c === "(" || c === "[" || c === "{")
171006
- depth++;
171007
- else if (c === ")" || c === "]" || c === "}")
171008
- depth = Math.max(0, depth - 1);
171009
- else if (depth === 0 && c === delim) {
171010
- parts.push(text.slice(last, i));
171011
- last = i + 1;
171012
- }
171013
- i++;
171014
- }
171015
- parts.push(text.slice(last));
171016
- return parts;
171017
- }, stringLiteralValue = (text) => {
171018
- const t = text.trim();
171019
- if (t.length < 2)
171020
- return null;
171021
- const q = t[0];
171022
- if (q !== '"' && q !== "'" && q !== "`" || t[t.length - 1] !== q)
171023
- return null;
171024
- const inner = t.slice(1, -1);
171025
- if (q === "`" && inner.includes("${"))
171026
- return null;
171027
- if (inner.includes(q) && !inner.includes("\\" + q))
171028
- return null;
171029
- return inner.replace(/\\(.)/g, "$1");
171030
- }, scanAssetIconCalls = (source) => {
171031
- const calls = [];
171032
- const n = source.length;
171033
- let i = 0;
171034
- while (i < n) {
171035
- const c = source[i];
171036
- if (c === '"' || c === "'" || c === "`") {
171037
- i = skipString2(source, i);
171038
- continue;
171039
- }
171040
- if (c === "/" && source[i + 1] === "/") {
171041
- i += 2;
171042
- while (i < n && source[i] !== `
171043
- `)
171044
- i++;
171045
- continue;
171046
- }
171047
- if (c === "/" && source[i + 1] === "*") {
171048
- i += 2;
171049
- while (i < n && !(source[i] === "*" && source[i + 1] === "/"))
171050
- i++;
171051
- i += 2;
171052
- continue;
171053
- }
171054
- if (isIdentStart(c)) {
171055
- let j = i + 1;
171056
- while (j < n && isIdentPart(source[j]))
171057
- j++;
171058
- if (source.slice(i, j) === "assetIcon" && prevNonSpace(source, i) !== ".") {
171059
- const call = parseCall(source, i, j);
171060
- if (call) {
171061
- calls.push(call);
171062
- i = call.end;
171063
- continue;
171064
- }
171065
- }
171066
- i = j;
171067
- continue;
171068
- }
171069
- i++;
171070
- }
171071
- return calls;
171072
- }, prevNonSpace = (src, i) => {
171073
- let k = i - 1;
171074
- while (k >= 0 && /\s/.test(src[k]))
171075
- k--;
171076
- return k >= 0 ? src[k] : "";
171077
- }, parseCall = (src, start, identEnd) => {
171078
- const n = src.length;
171079
- let i = identEnd;
171080
- while (i < n && /\s/.test(src[i]))
171081
- i++;
171082
- if (src[i] !== "(")
171083
- return null;
171084
- const close = matchParen(src, i);
171085
- if (close === -1)
171086
- return null;
171087
- const call = { start, end: close + 1, id: "", pack: "", name: "" };
171088
- const args = splitTopLevel(src.slice(i + 1, close), ",");
171089
- const id = stringLiteralValue(args[0] ?? "");
171090
- if (id === null) {
171091
- call.error = `assetIcon(...) needs a string-literal id like "lucide:bell" (got \`${(args[0] ?? "").trim() || "()"}\`).`;
171092
- return call;
171093
- }
171094
- const m = ICON_ID.exec(id);
171095
- if (!m) {
171096
- call.error = `assetIcon("${id}"): id must be "pack:name", lowercase [a-z0-9-] (e.g. "lucide:bell").`;
171097
- return call;
171098
- }
171099
- call.id = id;
171100
- call.pack = m[1];
171101
- call.name = m[2];
171102
- const optsText = args[1]?.trim();
171103
- if (optsText) {
171104
- const color = extractColor(optsText);
171105
- if (color.error) {
171106
- call.error = color.error;
171107
- return call;
171108
- }
171109
- if (color.bake !== undefined)
171110
- call.bake = color.bake;
171111
- else if (color.tint !== undefined)
171112
- call.tint = color.tint;
171113
- }
171114
- return call;
171115
- }, extractColor = (optsText) => {
171116
- if (!optsText.startsWith("{") || !optsText.endsWith("}")) {
171117
- return { error: `assetIcon(id, opts): opts must be an object literal like { color: "#5b8cff" }.` };
171118
- }
171119
- for (const prop of splitTopLevel(optsText.slice(1, -1), ",")) {
171120
- const kv = splitTopLevel(prop, ":");
171121
- if (kv.length < 2)
171122
- continue;
171123
- const key = kv[0].trim().replace(/^["'`]|["'`]$/g, "");
171124
- if (key !== "color")
171125
- continue;
171126
- const raw = kv.slice(1).join(":").trim();
171127
- if (!raw)
171128
- continue;
171129
- const literal = stringLiteralValue(raw);
171130
- return literal !== null ? { bake: literal } : { tint: raw };
171131
- }
171132
- return {};
171133
- }, rewriteAssetIcons = (source, calls, svgById) => {
171134
- let out = source;
171135
- for (const call of [...calls].sort((a, b) => b.start - a.start)) {
171136
- const svg = svgById.get(call.id);
171137
- if (svg === undefined)
171138
- continue;
171139
- out = out.slice(0, call.start) + iconExpr(svg, call) + out.slice(call.end);
171140
- }
171141
- return out;
171142
- }, iconExpr = (svg, call) => {
171143
- const body = call.bake ? svg.split("currentColor").join(call.bake) : svg;
171144
- const src = `SvgSource(\`${escapeTemplate(body)}\`)`;
171145
- return call.tint ? `Object.assign(${src}, { tintColor: (${call.tint}) })` : src;
171146
- }, escapeTemplate = (s) => s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${"), applyAssetIconMacro = async (entries, resolveSvgs) => {
171147
- const scanned = entries.filter((e) => e.type === "text" && typeof e.text === "string" && e.text.includes("assetIcon")).map((e) => ({ entry: e, calls: scanAssetIconCalls(e.text) })).filter((s) => s.calls.length > 0);
171148
- if (scanned.length === 0)
171149
- return [];
171150
- const errors = scanned.flatMap((s) => s.calls.filter((c) => c.error).map((c) => c.error));
171151
- if (errors.length)
171152
- throw new Error([...new Set(errors)].join(`
171153
- `));
171154
- const ids = [...new Set(scanned.flatMap((s) => s.calls.map((c) => c.id)))];
171155
- const svgById = await resolveSvgs(ids);
171156
- const missing = ids.filter((id) => !svgById.has(id));
171157
- if (missing.length)
171158
- throw new Error(`assetIcon: could not resolve icon(s): ${missing.join(", ")}.`);
171159
- for (const { entry, calls } of scanned)
171160
- entry.text = rewriteAssetIcons(entry.text, calls, svgById);
171161
- return ids;
171162
- }, buildIconsHeaderValue = (ids) => [...new Set(ids.map((id) => id.split(":")[0]))].sort().join(";"), editDistance = (a, b, cap) => {
171163
- if (Math.abs(a.length - b.length) > cap)
171164
- return cap + 1;
171165
- let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
171166
- for (let i = 1;i <= a.length; i++) {
171167
- const cur = [i];
171168
- let rowMin = i;
171169
- for (let j = 1;j <= b.length; j++) {
171170
- cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
171171
- if (cur[j] < rowMin)
171172
- rowMin = cur[j];
171173
- }
171174
- if (rowMin > cap)
171175
- return cap + 1;
171176
- prev = cur;
171177
- }
171178
- return prev[b.length];
171179
- }, suggestIconNames = (want, icons) => {
171180
- const wantParts = new Set(want.split("-").filter(Boolean));
171181
- const scored = [];
171182
- for (const [name, info] of Object.entries(icons)) {
171183
- const lenDiff = Math.abs(name.length - want.length) / 100;
171184
- let rank;
171185
- if (name.includes(want) || want.includes(name))
171186
- rank = lenDiff;
171187
- else {
171188
- const d = editDistance(want, name, 2);
171189
- if (d <= 2)
171190
- rank = 1 + d;
171191
- else if (info.kw?.some((k) => wantParts.has(k)))
171192
- rank = 4 + lenDiff;
171193
- else
171194
- continue;
171195
- }
171196
- scored.push({ name, rank });
171197
- }
171198
- return scored.sort((a, b) => a.rank - b.rank || (a.name < b.name ? -1 : 1)).slice(0, 5).map((s) => s.name);
171199
- }, iconNotFoundError = async (pack, name, read, registry) => {
171200
- const metaRaw = await read(`${pack}/meta.json`);
171201
- if (metaRaw !== null) {
171202
- let icons = {};
171203
- try {
171204
- icons = JSON.parse(metaRaw).icons ?? {};
171205
- } catch {}
171206
- const similar = suggestIconNames(name, icons);
171207
- const hint = similar.length ? ` Did you mean ${similar.map((s) => `"${pack}:${s}"`).join(", ")}?` : ` No similar name in the "${pack}" pack either.`;
171208
- return new Error(`assetIcon: icon "${pack}:${name}" not found.${hint}`);
171209
- }
171210
- const licRaw = await read("licenses.json");
171211
- if (licRaw !== null) {
171212
- try {
171213
- const packs = Object.keys(JSON.parse(licRaw)).sort();
171214
- return new Error(`assetIcon: unknown icon pack "${pack}" (in "${pack}:${name}"). Available packs: ${packs.join(", ")}.`);
171215
- } catch {}
171216
- }
171217
- return Object.assign(new Error(`assetIcon: can't reach the icon registry${registry ? ` (${registry})` : ""} to resolve "${pack}:${name}". ` + `Check your connection, or set LECODES_ICON_REGISTRY.`), { transient: true });
171218
- }, VENDORED_ICON_PATH, collectVendoredIcons = (entries) => {
171219
- const map = new Map;
171220
- for (const e of entries) {
171221
- if (e.type !== "text" || typeof e.text !== "string")
171222
- continue;
171223
- const m = VENDORED_ICON_PATH.exec(e.path);
171224
- if (m)
171225
- map.set(`${m[1]}:${m[2]}`, e.text);
171226
- }
171227
- return map;
171228
- };
171229
- var init_assetIconMacro = __esm(() => {
171230
- ICON_ID = /^([a-z0-9]+(?:-[a-z0-9]+)*):([a-z0-9]+(?:-[a-z0-9]+)*)$/;
171231
- VENDORED_ICON_PATH = /(?:^|\/)assets\/icons\/([a-z0-9]+(?:-[a-z0-9]+)*)\/([a-z0-9]+(?:-[a-z0-9]+)*)\.svg$/;
171232
- });
171233
-
171234
171450
  // ../../sdk/src/compile/compileProject.ts
171235
171451
  var localAssetBase = (path) => path.split(/[\\/]/).pop() ?? path, localAssetNames = (entries) => {
171236
171452
  const seen = new Map;
@@ -171328,7 +171544,7 @@ var localAssetBase = (path) => path.split(/[\\/]/).pop() ?? path, localAssetName
171328
171544
  throw new Error("Couldn't find an entrypoint (no .ts/.js project file — name yours main.ts). Pass entryOverride.");
171329
171545
  }
171330
171546
  const files = entriesToFiles(entries, opts);
171331
- const { code, map } = await bundleProjectWithMap(entry, files, opts.format ?? "esm", opts.minify, opts.editor, opts.libraries, opts.sourcemap ?? true);
171547
+ const { code, map, bakes } = await bundleProjectWithMap(entry, files, opts.format ?? "esm", opts.minify, opts.editor, opts.libraries, opts.sourcemap ?? true, undefined, opts.readResource);
171332
171548
  const plugins = opts.plugins ?? pluginsOfAppJson(entries);
171333
171549
  if (map && opts.onWarning) {
171334
171550
  const missing = pluginsOfBundle(map).filter((id) => !plugins.includes(id));
@@ -171337,14 +171553,10 @@ var localAssetBase = (path) => path.split(/[\\/]/).pop() ?? path, localAssetName
171337
171553
  }
171338
171554
  }
171339
171555
  if (opts.header === false)
171340
- return { code, map, entry };
171556
+ return { code, map, entry, bakes };
171341
171557
  const header = buildHeader(opts.name, code, { icon: opts.icon, fonts: opts.fontsHeader, icons: opts.iconsHeader, plugins });
171342
171558
  const headerLines = (header.match(/\n/g) ?? []).length;
171343
- return { code: header + code, map: map ? offsetSourceMap(map, headerLines) : null, entry };
171344
- }, compileProject = async (opts) => {
171345
- const { code, map } = await compileProjectWithMap(opts);
171346
- return map ? `${code}
171347
- ${inlineSourceMapComment(map)}` : code;
171559
+ return { code: header + code, map: map ? offsetSourceMap(map, headerLines) : null, entry, bakes };
171348
171560
  };
171349
171561
  var init_compileProject = __esm(() => {
171350
171562
  init_bundler();
@@ -171811,7 +172023,7 @@ var FONT_ID, CODE_FILE, DECLARES_FONT, isIdentStart2 = (c) => c >= "A" && c <= "
171811
172023
  }
171812
172024
  parts.push(text.slice(last));
171813
172025
  return parts;
171814
- }, stringLiteralValue2 = (text) => {
172026
+ }, stringLiteralValue3 = (text) => {
171815
172027
  const t = text.trim();
171816
172028
  if (t.length < 2)
171817
172029
  return null;
@@ -171883,7 +172095,7 @@ var FONT_ID, CODE_FILE, DECLARES_FONT, isIdentStart2 = (c) => c >= "A" && c <= "
171883
172095
  return null;
171884
172096
  const call = { start, end: close + 1, arg: "", kind: "registry" };
171885
172097
  const args = splitTopLevel2(src.slice(i + 1, close), ",");
171886
- const arg = stringLiteralValue2(args[0] ?? "");
172098
+ const arg = stringLiteralValue3(args[0] ?? "");
171887
172099
  if (arg === null) {
171888
172100
  call.error = `font(...) needs a string-literal argument — a registry id ("manrope") or a project path ("./fonts/Brand.ttf"). Got \`${(args[0] ?? "").trim() || "()"}\`.`;
171889
172101
  return call;
@@ -172138,7 +172350,7 @@ var init_compile = __esm(() => {
172138
172350
  });
172139
172351
 
172140
172352
  // src/compile/projectCompile.ts
172141
- import { existsSync as existsSync6 } from "node:fs";
172353
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
172142
172354
  import { createRequire as createRequire4 } from "node:module";
172143
172355
  import { dirname as dirname6, join as join7 } from "node:path";
172144
172356
  var resolveSdkInject = () => {
@@ -172162,22 +172374,39 @@ var resolveSdkInject = () => {
172162
172374
  if (!entry || roots.length < 2 || roots.includes(ENTRY_CONVENTION))
172163
172375
  return null;
172164
172376
  return `Ambiguous entrypoint: nothing imports ${roots.join(", ")} — picked ${entry}. Rename your entry to main.ts (or pass --entry).`;
172165
- }, compileProject2 = (opts) => compileProject({
172166
- entries: opts.entries.map((e) => ({ path: e.path, type: e.type, text: e.text, fileSrc: e.fileSrc, remote: e.remote, shaders: e.shaders, shadersLocal: e.shadersLocal })),
172167
- name: opts.name,
172168
- publicUrl: opts.publicUrl,
172169
- entryOverride: opts.entryOverride,
172170
- header: opts.header,
172171
- fontsHeader: opts.fontsHeader,
172172
- iconsHeader: opts.iconsHeader,
172173
- format: opts.format,
172174
- localAssets: opts.localAssets,
172175
- minify: opts.minify,
172176
- sourcemap: opts.sourcemap,
172177
- editor: opts.editor,
172178
- onWarning: opts.onWarning,
172179
- serverUrl: opts.serverUrl
172180
- }), hasServerFiles = (entries) => entries.some((e) => e.type === "text" && isServerPath(e.path)), findTypescript = async (root) => {
172377
+ }, resourceReader = (entries) => {
172378
+ const byRoot = new Map(entries.map((e) => ["/" + e.path.replace(/\\/g, "/").replace(/^[./]+/, ""), e]));
172379
+ return (rootPath) => {
172380
+ const e = byRoot.get(rootPath);
172381
+ if (e?.text !== undefined)
172382
+ return e.text;
172383
+ try {
172384
+ return e?.absPath ? readFileSync5(e.absPath, "utf8") : null;
172385
+ } catch {
172386
+ return null;
172387
+ }
172388
+ };
172389
+ }, compileProjectBundle = async (opts) => {
172390
+ const { code, map, bakes } = await compileProjectWithMap({
172391
+ entries: opts.entries.map((e) => ({ path: e.path, type: e.type, text: e.text, fileSrc: e.fileSrc, remote: e.remote, shaders: e.shaders, shadersLocal: e.shadersLocal })),
172392
+ readResource: resourceReader(opts.entries),
172393
+ name: opts.name,
172394
+ publicUrl: opts.publicUrl,
172395
+ entryOverride: opts.entryOverride,
172396
+ header: opts.header,
172397
+ fontsHeader: opts.fontsHeader,
172398
+ iconsHeader: opts.iconsHeader,
172399
+ format: opts.format,
172400
+ localAssets: opts.localAssets,
172401
+ minify: opts.minify,
172402
+ sourcemap: opts.sourcemap,
172403
+ editor: opts.editor,
172404
+ onWarning: opts.onWarning,
172405
+ serverUrl: opts.serverUrl
172406
+ });
172407
+ return { js: map ? `${code}
172408
+ ${inlineSourceMapComment(map)}` : code, bakes };
172409
+ }, compileProject2 = async (opts) => (await compileProjectBundle(opts)).js, hasServerFiles = (entries) => entries.some((e) => e.type === "text" && isServerPath(e.path)), findTypescript = async (root) => {
172181
172410
  const api = (mod) => mod?.createProgram ? mod : mod?.default?.createProgram ? mod.default : null;
172182
172411
  const from = (at) => {
172183
172412
  try {
@@ -172397,7 +172626,7 @@ var init_paths = __esm(() => {
172397
172626
  });
172398
172627
 
172399
172628
  // src/project/ignore.ts
172400
- import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
172629
+ import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "node:fs";
172401
172630
  import { join as join10 } from "node:path";
172402
172631
  var BUILTIN_PATTERNS, DEFAULT_IGNORE_FILE = `# Files matched here are NOT pushed to LeCodes.
172403
172632
  # Only untracked files are affected — assets already in the project stay tracked
@@ -172482,7 +172711,7 @@ design/comments/
172482
172711
  const file = join10(root, ".lecodesignore");
172483
172712
  if (existsSync9(file)) {
172484
172713
  try {
172485
- lines.push(...readFileSync7(file, "utf8").split(/\r?\n/));
172714
+ lines.push(...readFileSync8(file, "utf8").split(/\r?\n/));
172486
172715
  } catch {}
172487
172716
  }
172488
172717
  const rules = lines.map(compile).filter((r) => r !== null);
@@ -172510,7 +172739,7 @@ dist/
172510
172739
  writeFileSync3(file, DEFAULT_GITIGNORE);
172511
172740
  return "created";
172512
172741
  }
172513
- const text = readFileSync7(file, "utf8");
172742
+ const text = readFileSync8(file, "utf8");
172514
172743
  const covered = text.split(/\r?\n/).some((l) => /^\/?\.lecodes\/?\s*$/.test(l.trim()));
172515
172744
  if (covered)
172516
172745
  return null;
@@ -172528,7 +172757,7 @@ var init_ignore = __esm(() => {
172528
172757
  });
172529
172758
 
172530
172759
  // src/project/localFiles.ts
172531
- import { existsSync as existsSync10, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "node:fs";
172760
+ import { existsSync as existsSync10, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "node:fs";
172532
172761
  import { basename, dirname as dirname7, join as join11, relative as relative3 } from "node:path";
172533
172762
  var SKIP_DIRS, SKIP_FILES, TEXT_EXTS, ext = (name) => {
172534
172763
  const i = name.lastIndexOf(".");
@@ -172547,7 +172776,7 @@ var SKIP_DIRS, SKIP_FILES, TEXT_EXTS, ext = (name) => {
172547
172776
  continue;
172548
172777
  let folder;
172549
172778
  try {
172550
- folder = JSON.parse(readFileSync8(file.absPath, "utf8").replace(/^\uFEFF/, "")).web?.static;
172779
+ folder = JSON.parse(readFileSync9(file.absPath, "utf8").replace(/^\uFEFF/, "")).web?.static;
172551
172780
  } catch {
172552
172781
  continue;
172553
172782
  }
@@ -172595,7 +172824,7 @@ var SKIP_DIRS, SKIP_FILES, TEXT_EXTS, ext = (name) => {
172595
172824
  ignored.push(key);
172596
172825
  continue;
172597
172826
  }
172598
- found.push({ path: key, absPath: abs, sha: sha256(readFileSync8(abs)) });
172827
+ found.push({ path: key, absPath: abs, sha: sha256(readFileSync9(abs)) });
172599
172828
  }
172600
172829
  }
172601
172830
  };
@@ -172676,7 +172905,7 @@ var init_localFiles = __esm(() => {
172676
172905
 
172677
172906
  // src/compile/fonts.ts
172678
172907
  import { createHash as createHash2 } from "node:crypto";
172679
- import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "node:fs";
172908
+ import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "node:fs";
172680
172909
  import { dirname as dirname9, join as join13 } from "node:path";
172681
172910
  var applyProjectFonts = async (entries, opts) => {
172682
172911
  const registry = (opts.registry ?? fontRegistryBase()).replace(/\/+$/, "");
@@ -172688,7 +172917,7 @@ var applyProjectFonts = async (entries, opts) => {
172688
172917
  const entry = byPath().get(entryPath);
172689
172918
  const abs = entry?.absPath ?? join13(opts.root, entryPath);
172690
172919
  try {
172691
- return new Uint8Array(readFileSync11(abs));
172920
+ return new Uint8Array(readFileSync12(abs));
172692
172921
  } catch {
172693
172922
  return null;
172694
172923
  }
@@ -172720,13 +172949,13 @@ var applyProjectFonts = async (entries, opts) => {
172720
172949
  return res.ok ? new Uint8Array(await res.arrayBuffer()) : null;
172721
172950
  }
172722
172951
  try {
172723
- return new Uint8Array(readFileSync11(join13(registry, rel)));
172952
+ return new Uint8Array(readFileSync12(join13(registry, rel)));
172724
172953
  } catch {
172725
172954
  return null;
172726
172955
  }
172727
172956
  }, readLock = (path) => {
172728
172957
  try {
172729
- const lock = JSON.parse(readFileSync11(path, "utf8"));
172958
+ const lock = JSON.parse(readFileSync12(path, "utf8"));
172730
172959
  if (lock && typeof lock === "object" && lock.faces && lock.families)
172731
172960
  return lock;
172732
172961
  } catch {}
@@ -172758,7 +172987,7 @@ var applyProjectFonts = async (entries, opts) => {
172758
172987
  `);
172759
172988
  }, writeIfChanged = (path, text) => {
172760
172989
  try {
172761
- if (readFileSync11(path, "utf8") === text)
172990
+ if (readFileSync12(path, "utf8") === text)
172762
172991
  return;
172763
172992
  } catch {}
172764
172993
  writeFileSync5(path, text);
@@ -172789,7 +173018,7 @@ var init_fonts = __esm(() => {
172789
173018
 
172790
173019
  // src/compile/assetIcons.ts
172791
173020
  import { createHash as createHash5 } from "node:crypto";
172792
- import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "node:fs";
173021
+ import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
172793
173022
  import { homedir as homedir4 } from "node:os";
172794
173023
  import { dirname as dirname10, join as join17 } from "node:path";
172795
173024
  var DEFAULT_REGISTRY, isHttp2 = (s) => /^https?:/i.test(s), sha122 = (s) => createHash5("sha256").update(s).digest("hex").slice(0, 12), readText = async (registry, rel) => {
@@ -172802,13 +173031,13 @@ var DEFAULT_REGISTRY, isHttp2 = (s) => /^https?:/i.test(s), sha122 = (s) => crea
172802
173031
  }
172803
173032
  }
172804
173033
  try {
172805
- return readFileSync14(join17(registry, rel), "utf8");
173034
+ return readFileSync15(join17(registry, rel), "utf8");
172806
173035
  } catch {
172807
173036
  return null;
172808
173037
  }
172809
173038
  }, safeRead = (path) => {
172810
173039
  try {
172811
- return readFileSync14(path, "utf8");
173040
+ return readFileSync15(path, "utf8");
172812
173041
  } catch {
172813
173042
  return null;
172814
173043
  }
@@ -172830,7 +173059,7 @@ var DEFAULT_REGISTRY, isHttp2 = (s) => /^https?:/i.test(s), sha122 = (s) => crea
172830
173059
  return JSON.parse(raw);
172831
173060
  }, readLock2 = (path) => {
172832
173061
  try {
172833
- const lock = JSON.parse(readFileSync14(path, "utf8"));
173062
+ const lock = JSON.parse(readFileSync15(path, "utf8"));
172834
173063
  if (lock && typeof lock === "object" && lock.icons && lock.packs)
172835
173064
  return lock;
172836
173065
  } catch {}
@@ -172937,11 +173166,11 @@ var init_assetIcons = __esm(() => {
172937
173166
  // src/platform/config.ts
172938
173167
  import { homedir as homedir5 } from "node:os";
172939
173168
  import { join as join18 } from "node:path";
172940
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync9 } from "node:fs";
173169
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "node:fs";
172941
173170
  var DEFAULT_API = "https://le.codes", CONFIG_DIR, CONFIG_FILE, loadConfig = () => {
172942
173171
  let stored = {};
172943
173172
  try {
172944
- stored = JSON.parse(readFileSync15(CONFIG_FILE, "utf8"));
173173
+ stored = JSON.parse(readFileSync16(CONFIG_FILE, "utf8"));
172945
173174
  } catch {}
172946
173175
  return {
172947
173176
  apiUrl: process.env.LECODES_API ?? stored.apiUrl,
@@ -206398,7 +206627,7 @@ var init_esm29 = __esm(() => {
206398
206627
 
206399
206628
  // src/project/types.ts
206400
206629
  import { dirname as dirname18, join as join29, relative as relative5, sep as sep3 } from "node:path";
206401
- import { existsSync as existsSync27, mkdirSync as mkdirSync17, readdirSync as readdirSync12, readFileSync as readFileSync24, rmSync as rmSync9, statSync as statSync10, writeFileSync as writeFileSync20 } from "node:fs";
206630
+ import { existsSync as existsSync27, mkdirSync as mkdirSync17, readdirSync as readdirSync12, readFileSync as readFileSync25, rmSync as rmSync9, statSync as statSync10, writeFileSync as writeFileSync20 } from "node:fs";
206402
206631
  import { createRequire as createRequire5 } from "node:module";
206403
206632
  var TSCONFIG, listBundleFiles = (dir, base = dir) => {
206404
206633
  if (!existsSync27(dir))
@@ -206427,7 +206656,7 @@ var TSCONFIG, listBundleFiles = (dir, base = dir) => {
206427
206656
  const dest = join29(typesDir, path);
206428
206657
  if (!before.has(path))
206429
206658
  stats.added++;
206430
- else if (readFileSync24(dest, "utf8") !== text)
206659
+ else if (readFileSync25(dest, "utf8") !== text)
206431
206660
  stats.updated++;
206432
206661
  else
206433
206662
  continue;
@@ -206446,7 +206675,7 @@ var TSCONFIG, listBundleFiles = (dir, base = dir) => {
206446
206675
  const typesDir = join29(root, LECODES_DIR, "types");
206447
206676
  const bundle = {};
206448
206677
  for (const path of listBundleFiles(typesDir))
206449
- bundle[path] = readFileSync24(join29(typesDir, path), "utf8");
206678
+ bundle[path] = readFileSync25(join29(typesDir, path), "utf8");
206450
206679
  return bundle;
206451
206680
  }, materializeTypes = async (root, apiUrl) => writeTypeBundle(root, await getTypes(apiUrl)), readLocalTypeBundle = () => {
206452
206681
  const fromPackage = (() => {
@@ -206464,7 +206693,7 @@ var TSCONFIG, listBundleFiles = (dir, base = dir) => {
206464
206693
  for (const path of candidates) {
206465
206694
  if (existsSync27(path)) {
206466
206695
  try {
206467
- return JSON.parse(readFileSync24(path, "utf8"));
206696
+ return JSON.parse(readFileSync25(path, "utf8"));
206468
206697
  } catch {}
206469
206698
  }
206470
206699
  }
@@ -206502,7 +206731,7 @@ var init_types4 = __esm(() => {
206502
206731
  });
206503
206732
 
206504
206733
  // src/compile/designCompile.ts
206505
- import { readFileSync as readFileSync25 } from "node:fs";
206734
+ import { readFileSync as readFileSync26 } from "node:fs";
206506
206735
  var LOCAL_ASSET_SCHEME = "lecodesfile://", compileDesignScreen = async (opts) => {
206507
206736
  const { root, manifest, designDir, designKey, screenId, state } = opts;
206508
206737
  const entries = [];
@@ -206519,7 +206748,7 @@ var LOCAL_ASSET_SCHEME = "lecodesfile://", compileDesignScreen = async (opts) =>
206519
206748
  } else if (type === "shader") {
206520
206749
  entries.push({ path: file.path, type, shaders: [] });
206521
206750
  } else {
206522
- entries.push({ path: file.path, type, text: readFileSync25(file.absPath, "utf8") });
206751
+ entries.push({ path: file.path, type, text: readFileSync26(file.absPath, "utf8") });
206523
206752
  }
206524
206753
  }
206525
206754
  const invoke = state !== undefined ? `screen(${JSON.stringify(state)})` : "screen()";
@@ -206641,11 +206870,11 @@ var init_browserAuth = __esm(() => {
206641
206870
  });
206642
206871
 
206643
206872
  // src/project/designMeta.ts
206644
- import { readFileSync as readFileSync26 } from "node:fs";
206873
+ import { readFileSync as readFileSync27 } from "node:fs";
206645
206874
  import { join as join31 } from "node:path";
206646
206875
  var PHONE_SIZE, readMetaFrames = (designDir) => {
206647
206876
  try {
206648
- const meta = JSON.parse(readFileSync26(join31(designDir, "meta.json"), "utf8"));
206877
+ const meta = JSON.parse(readFileSync27(join31(designDir, "meta.json"), "utf8"));
206649
206878
  const d = meta?.defaultSize;
206650
206879
  const valid = Array.isArray(d) && d.length === 2 && d.every((n) => typeof n === "number" && Number.isFinite(n) && n >= 64 && n <= 8192);
206651
206880
  return {
@@ -206996,7 +207225,7 @@ var init_comments = __esm(() => {
206996
207225
  });
206997
207226
 
206998
207227
  // src/commands/design/snapshot.ts
206999
- import { existsSync as existsSync32, mkdirSync as mkdirSync20, readdirSync as readdirSync15, readFileSync as readFileSync27, writeFileSync as writeFileSync22 } from "node:fs";
207228
+ import { existsSync as existsSync32, mkdirSync as mkdirSync20, readdirSync as readdirSync15, readFileSync as readFileSync28, writeFileSync as writeFileSync22 } from "node:fs";
207000
207229
  import { join as join33 } from "node:path";
207001
207230
  var listScreens = (designDir) => {
207002
207231
  const dir = join33(designDir, "screens");
@@ -207068,7 +207297,7 @@ var init_snapshot = __esm(() => {
207068
207297
  const [width, height] = frames.screens[id]?.size ?? frames.defaultSize;
207069
207298
  let plan = [{ state: undefined, bases: [id] }];
207070
207299
  if (discover) {
207071
- const { states, canonical } = discover(readFileSync27(join33(ctx.designDir, "screens", `${id}.ts`), "utf8"));
207300
+ const { states, canonical } = discover(readFileSync28(join33(ctx.designDir, "screens", `${id}.ts`), "utf8"));
207072
207301
  if (stateFlag !== undefined) {
207073
207302
  if (!states.includes(stateFlag)) {
207074
207303
  if (args.length === 1)
@@ -207598,7 +207827,7 @@ var init_templates2 = __esm(() => {
207598
207827
  });
207599
207828
 
207600
207829
  // src/commands/design/index.ts
207601
- import { appendFileSync as appendFileSync2, existsSync as existsSync33, mkdirSync as mkdirSync21, readFileSync as readFileSync28, writeFileSync as writeFileSync23 } from "node:fs";
207830
+ import { appendFileSync as appendFileSync2, existsSync as existsSync33, mkdirSync as mkdirSync21, readFileSync as readFileSync29, writeFileSync as writeFileSync23 } from "node:fs";
207602
207831
  import { join as join34 } from "node:path";
207603
207832
  var firstLine = (body, max = 64) => {
207604
207833
  const line = body.split(`
@@ -207673,7 +207902,7 @@ var firstLine = (body, max = 64) => {
207673
207902
  let config = {};
207674
207903
  if (existsSync33(path)) {
207675
207904
  try {
207676
- config = JSON.parse(readFileSync28(path, "utf8"));
207905
+ config = JSON.parse(readFileSync29(path, "utf8"));
207677
207906
  } catch {
207678
207907
  warnErr(".mcp.json exists but isn't valid JSON — left it untouched. Add the lecodes-design server by hand.");
207679
207908
  return;
@@ -207753,7 +207982,7 @@ var init_design = __esm(() => {
207753
207982
  }
207754
207983
  const ignorePath = join34(ctx.root, ".lecodesignore");
207755
207984
  const ignoreLines = [`${ctx.dirName}/.snapshots/`, `${ctx.dirName}/comments/`];
207756
- const current = existsSync33(ignorePath) ? readFileSync28(ignorePath, "utf8") : "";
207985
+ const current = existsSync33(ignorePath) ? readFileSync29(ignorePath, "utf8") : "";
207757
207986
  const present = new Set(current.split(/\r?\n/));
207758
207987
  const missing = ignoreLines.filter((line) => !present.has(line));
207759
207988
  if (missing.length) {
@@ -227112,17 +227341,17 @@ import { delimiter as delimiter3, dirname as dirname31, join as join63 } from "n
227112
227341
  // src/commands/app/index.ts
227113
227342
  init_manifest();
227114
227343
  import { execFileSync as execFileSync6 } from "node:child_process";
227115
- import { cpSync as cpSync4, existsSync as existsSync25, mkdirSync as mkdirSync16, readFileSync as readFileSync23, readdirSync as readdirSync10, rmSync as rmSync8, statSync as statSync9, writeFileSync as writeFileSync19 } from "node:fs";
227344
+ import { cpSync as cpSync4, existsSync as existsSync25, mkdirSync as mkdirSync16, readFileSync as readFileSync24, readdirSync as readdirSync10, rmSync as rmSync8, statSync as statSync9, writeFileSync as writeFileSync19 } from "node:fs";
227116
227345
  import { homedir as homedir10 } from "node:os";
227117
227346
  import { basename as basename10, dirname as dirname17, join as join27, resolve as resolve14 } from "node:path";
227118
227347
 
227119
227348
  // src/plugin/manifest.ts
227120
- import { existsSync as existsSync8, readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
227349
+ import { existsSync as existsSync8, readFileSync as readFileSync7, statSync as statSync2 } from "node:fs";
227121
227350
  import { isAbsolute as isAbsolute2, join as join9 } from "node:path";
227122
227351
 
227123
227352
  // src/plugin/firstParty.ts
227124
227353
  init_projectCompile();
227125
- import { existsSync as existsSync7, readFileSync as readFileSync5, readdirSync as readdirSync2 } from "node:fs";
227354
+ import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync2 } from "node:fs";
227126
227355
  import { join as join8 } from "node:path";
227127
227356
  var ID = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
227128
227357
  var firstPartyContractFile = (id) => {
@@ -227137,7 +227366,7 @@ var firstPartyIds = () => {
227137
227366
  const gen = join8(sdkSourceDir, "plugins", "gen");
227138
227367
  return existsSync7(gen) ? readdirSync2(gen).filter((id) => existsSync7(join8(gen, id, "contract.d.ts"))).sort() : [];
227139
227368
  };
227140
- var firstPartyContractText = (file) => readFileSync5(file, "utf8").replace(/from "(?:\.\.\/)+plugin"/g, `from "lecodes-sdk/plugin"`);
227369
+ var firstPartyContractText = (file) => readFileSync6(file, "utf8").replace(/from "(?:\.\.\/)+plugin"/g, `from "lecodes-sdk/plugin"`);
227141
227370
 
227142
227371
  // src/plugin/manifest.ts
227143
227372
  var MANIFEST_FILE = "lecodes-plugin.json";
@@ -227174,11 +227403,11 @@ var isFolder = (path) => existsSync8(path) && statSync2(path).isDirectory();
227174
227403
  var pascal = (id) => id.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
227175
227404
  var XCFRAMEWORK = ".xcframework";
227176
227405
  var binaryName = (path) => path.split(/[\\/]/).pop().slice(0, -XCFRAMEWORK.length);
227177
- var readManifest2 = (dir, id) => {
227406
+ var readManifest3 = (dir, id) => {
227178
227407
  const file = join9(dir, MANIFEST_FILE);
227179
227408
  let raw;
227180
227409
  try {
227181
- raw = JSON.parse(readFileSync6(file, "utf8").replace(/^/, ""));
227410
+ raw = JSON.parse(readFileSync7(file, "utf8").replace(/^/, ""));
227182
227411
  } catch (e) {
227183
227412
  throw new ManifestError(file, [`not JSON: ${e instanceof Error ? e.message : String(e)}`]);
227184
227413
  }
@@ -227346,7 +227575,7 @@ var readManifest2 = (dir, id) => {
227346
227575
  throw new ManifestError(file, problems);
227347
227576
  return raw;
227348
227577
  };
227349
- var readManifestIfAny = (dir, id) => existsSync8(join9(dir, MANIFEST_FILE)) ? readManifest2(dir, id) : null;
227578
+ var readManifestIfAny = (dir, id) => existsSync8(join9(dir, MANIFEST_FILE)) ? readManifest3(dir, id) : null;
227350
227579
 
227351
227580
  // src/commands/compile.ts
227352
227581
  init_api();
@@ -227357,7 +227586,7 @@ import { basename as basename6, resolve as resolve6 } from "node:path";
227357
227586
  // src/compile/collect.ts
227358
227587
  init_localFiles();
227359
227588
  init_ignore();
227360
- import { readFileSync as readFileSync9 } from "node:fs";
227589
+ import { readFileSync as readFileSync10 } from "node:fs";
227361
227590
 
227362
227591
  // src/project/materialize.ts
227363
227592
  init_api();
@@ -227430,7 +227659,7 @@ var collectEntries = (root, manifest, project, opts = {}) => {
227430
227659
  warnings.push(`No compiled shader for ${file.path} — push it to compile the shader.`);
227431
227660
  entries.push({ path: file.path, type, shaders, absPath: file.absPath });
227432
227661
  } else {
227433
- entries.push({ path: file.path, type, text: readFileSync9(file.absPath, "utf8") });
227662
+ entries.push({ path: file.path, type, text: readFileSync10(file.absPath, "utf8") });
227434
227663
  }
227435
227664
  }
227436
227665
  return { entries, warnings };
@@ -227439,11 +227668,11 @@ var collectEntries = (root, manifest, project, opts = {}) => {
227439
227668
  // src/compile/collectLocal.ts
227440
227669
  init_localFiles();
227441
227670
  init_ignore();
227442
- import { readFileSync as readFileSync10 } from "node:fs";
227671
+ import { readFileSync as readFileSync11 } from "node:fs";
227443
227672
  import { basename as basename2 } from "node:path";
227444
227673
  var isHalfManifest = (abs) => {
227445
227674
  try {
227446
- return typeof JSON.parse(readFileSync10(abs, "utf8").replace(/^/, "")).implements === "string";
227675
+ return typeof JSON.parse(readFileSync11(abs, "utf8").replace(/^/, "")).implements === "string";
227447
227676
  } catch {
227448
227677
  return false;
227449
227678
  }
@@ -227465,7 +227694,7 @@ var collectLocalEntries = (root, manifest, assetBase, opts = {}) => {
227465
227694
  }
227466
227695
  entries.push({ path: file.path, type, shaders: [], absPath: file.absPath });
227467
227696
  } else {
227468
- entries.push({ path: file.path, type, text: readFileSync10(file.absPath, "utf8") });
227697
+ entries.push({ path: file.path, type, text: readFileSync11(file.absPath, "utf8") });
227469
227698
  }
227470
227699
  }
227471
227700
  return { entries, warnings };
@@ -227479,7 +227708,7 @@ init_fonts();
227479
227708
  init_compile();
227480
227709
  import { execFile } from "node:child_process";
227481
227710
  import { createHash as createHash4, randomBytes } from "node:crypto";
227482
- import { existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync13, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync7 } from "node:fs";
227711
+ import { existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync14, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync7 } from "node:fs";
227483
227712
  import { tmpdir } from "node:os";
227484
227713
  import { basename as basename3, join as join16 } from "node:path";
227485
227714
 
@@ -227491,14 +227720,14 @@ import { join as join15, resolve as resolve4 } from "node:path";
227491
227720
  // src/hosts/releases.ts
227492
227721
  init_cli();
227493
227722
  import { createHash as createHash3 } from "node:crypto";
227494
- import { existsSync as existsSync12, mkdirSync as mkdirSync5, mkdtempSync, readFileSync as readFileSync12, renameSync, rmSync, writeFileSync as writeFileSync6 } from "node:fs";
227723
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, mkdtempSync, readFileSync as readFileSync13, renameSync, rmSync, writeFileSync as writeFileSync6 } from "node:fs";
227495
227724
  import { join as join14 } from "node:path";
227496
227725
  import { spawnSync as spawnSync4 } from "node:child_process";
227497
227726
  var MANIFEST_URL = "https://storage.yandexcloud.net/le-codes/releases/versions.json";
227498
227727
  var ARTIFACT_STAMP = ".lecodes-artifact.json";
227499
227728
  var cachedArtifactSha = (dir) => {
227500
227729
  try {
227501
- return JSON.parse(readFileSync12(join14(dir, ARTIFACT_STAMP), "utf8")).sha256 ?? null;
227730
+ return JSON.parse(readFileSync13(join14(dir, ARTIFACT_STAMP), "utf8")).sha256 ?? null;
227502
227731
  } catch {
227503
227732
  return null;
227504
227733
  }
@@ -227729,7 +227958,7 @@ var applyLocalShaders = async (entries, opts) => {
227729
227958
  mkdirSync6(cacheDir, { recursive: true });
227730
227959
  const artifacts = [];
227731
227960
  for (const entry of shaderEntries) {
227732
- const text = readFileSync13(entry.absPath, "utf8");
227961
+ const text = readFileSync14(entry.absPath, "utf8");
227733
227962
  const hash = shaderHash(text, matcId);
227734
227963
  const family = artifactFamily();
227735
227964
  const artifact = (platform) => `${hash}_${platform}.filamat`;
@@ -227787,7 +228016,7 @@ init_config();
227787
228016
  init_ignore();
227788
228017
  init_cli();
227789
228018
  import { execFileSync } from "node:child_process";
227790
- import { existsSync as existsSync16, mkdirSync as mkdirSync9, readdirSync as readdirSync5, readFileSync as readFileSync16, statSync as statSync4, writeFileSync as writeFileSync10 } from "node:fs";
228019
+ import { existsSync as existsSync16, mkdirSync as mkdirSync9, readdirSync as readdirSync5, readFileSync as readFileSync17, statSync as statSync4, writeFileSync as writeFileSync10 } from "node:fs";
227791
228020
  import { homedir as homedir6 } from "node:os";
227792
228021
  import { basename as basename4, dirname as dirname11, join as join19, resolve as resolve5 } from "node:path";
227793
228022
  init_compile();
@@ -228036,7 +228265,7 @@ var readJson2 = (path) => {
228036
228265
  if (!existsSync16(path))
228037
228266
  return null;
228038
228267
  try {
228039
- return JSON.parse(readFileSync16(path, "utf8").replace(/^\uFEFF/, ""));
228268
+ return JSON.parse(readFileSync17(path, "utf8").replace(/^\uFEFF/, ""));
228040
228269
  } catch {
228041
228270
  throw new CliError(`Invalid JSON in ${path}`);
228042
228271
  }
@@ -228068,7 +228297,7 @@ ${tail}`);
228068
228297
  };
228069
228298
  var ensureShellIgnored = (root, dir) => {
228070
228299
  const ignorePath = join19(root, ".lecodesignore");
228071
- const ignore = existsSync16(ignorePath) ? readFileSync16(ignorePath, "utf8") : DEFAULT_IGNORE_FILE;
228300
+ const ignore = existsSync16(ignorePath) ? readFileSync17(ignorePath, "utf8") : DEFAULT_IGNORE_FILE;
228072
228301
  if (new RegExp(`^${dir}\\/$`, "m").test(ignore))
228073
228302
  return;
228074
228303
  const comment = ignore.includes("# Native app shells") ? "" : "# Native app shells — generated by `lecodes app init`, never pushed to LeCodes.\n";
@@ -228201,7 +228430,7 @@ var localPlugin = (root, id, spec) => {
228201
228430
  if (!existsSync16(dir) || !statSync4(dir).isDirectory())
228202
228431
  throw new CliError(`Plugin "${id}": ${spec} is not a folder (${dir})`);
228203
228432
  const folder = basename4(dir);
228204
- const manifest = readManifest2(dir, folder);
228433
+ const manifest = readManifest3(dir, folder);
228205
228434
  if (manifest.implements ? manifest.implements !== id : folder !== id) {
228206
228435
  throw new CliError(manifest.implements ? `Plugin "${id}": ${spec} implements "${manifest.implements}" — app.json names it under that plugin: "${manifest.implements}": { "<platform>": "${spec}" }` : `Plugin "${id}": ${spec} is the plugin "${folder}" — app.json names a plugin of the project by its folder's name`);
228207
228436
  }
@@ -228212,7 +228441,7 @@ var localPluginKey = (root, spec) => {
228212
228441
  const dir = resolve5(root, spec.startsWith("file:") ? spec.slice("file:".length) : spec);
228213
228442
  if (!existsSync16(join19(dir, "lecodes-plugin.json")))
228214
228443
  return basename4(dir);
228215
- return readManifest2(dir, basename4(dir)).implements ?? basename4(dir);
228444
+ return readManifest3(dir, basename4(dir)).implements ?? basename4(dir);
228216
228445
  };
228217
228446
  function resolvePlugin(id, entry, lock, platform, opts) {
228218
228447
  const spec = pluginSpecOf(entry, platform);
@@ -228244,7 +228473,7 @@ function resolvePlugin(id, entry, lock, platform, opts) {
228244
228473
  const dir = subdir ? join19(checkout.dir, subdir) : checkout.dir;
228245
228474
  if (!existsSync16(join19(dir, "lecodes-plugin.json")))
228246
228475
  throw new CliError(`Plugin "${id}": no lecodes-plugin.json in ${dir}`);
228247
- const manifest = readManifest2(dir, id);
228476
+ const manifest = readManifest3(dir, id);
228248
228477
  fillManifestDefaults(manifest, id);
228249
228478
  if (!manifest[platform]) {
228250
228479
  if (opts?.optional)
@@ -228435,7 +228664,7 @@ var buildBundle = async (bundle, opts = {}) => {
228435
228664
  warnErr(ambiguous);
228436
228665
  }
228437
228666
  const serverUrl = await opts.backend?.start(entries, (w) => warnings.push(w));
228438
- const js = await compileProject2({
228667
+ const { js, bakes } = await compileProjectBundle({
228439
228668
  entries,
228440
228669
  name,
228441
228670
  publicUrl,
@@ -228456,7 +228685,7 @@ var buildBundle = async (bundle, opts = {}) => {
228456
228685
  const resources = entries.filter((e) => e.type === "resource").map((e) => ({ path: e.path.replace(/^\//, ""), name: localNames.get(e.path) ?? e.path.split(/[\\/]/).pop() ?? e.path, absPath: e.absPath, remote: !!e.remote }));
228457
228686
  for (const artifact of stagedShaders)
228458
228687
  resources.push({ path: artifact.name, name: artifact.name, absPath: artifact.absPath, remote: false, shader: true });
228459
- return { js, resources };
228688
+ return { js, resources, bakes: bakes.map((b) => ({ path: b.path, absPath: resolve6(root, b.path), exists: b.exists })) };
228460
228689
  };
228461
228690
 
228462
228691
  // src/commands/app/index.ts
@@ -229431,13 +229660,13 @@ init_compile();
229431
229660
  init_manifest();
229432
229661
  import { execFileSync as execFileSync2, spawn, spawnSync as spawnSync5 } from "node:child_process";
229433
229662
  import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
229434
- import { chmodSync, cpSync, existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync21, readdirSync as readdirSync6, renameSync as renameSync2, rmSync as rmSync4, statSync as statSync5, writeFileSync as writeFileSync15 } from "node:fs";
229663
+ import { chmodSync, cpSync, existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync22, readdirSync as readdirSync6, renameSync as renameSync2, rmSync as rmSync4, statSync as statSync5, writeFileSync as writeFileSync15 } from "node:fs";
229435
229664
  import { homedir as homedir8 } from "node:os";
229436
229665
  import { basename as basename7, dirname as dirname15, join as join23, relative as relative4, resolve as resolve10 } from "node:path";
229437
229666
  init_cli();
229438
229667
 
229439
229668
  // src/project/env.ts
229440
- import { appendFileSync, existsSync as existsSync17, readFileSync as readFileSync17, writeFileSync as writeFileSync12 } from "node:fs";
229669
+ import { appendFileSync, existsSync as existsSync17, readFileSync as readFileSync18, writeFileSync as writeFileSync12 } from "node:fs";
229441
229670
  import { homedir as homedir7 } from "node:os";
229442
229671
  import { dirname as dirname12, join as join20, resolve as resolve7 } from "node:path";
229443
229672
  var KEY_RE = /^(LECODES|CREATOR)_[A-Z0-9_]+$/;
@@ -229505,7 +229734,7 @@ var loadProjectEnv = (cwd = process.cwd()) => {
229505
229734
  const file = join20(root, ".env");
229506
229735
  if (!existsSync17(file))
229507
229736
  return [];
229508
- const added = applyDotEnv(parseDotEnv(readFileSync17(file, "utf8")), root, process.env);
229737
+ const added = applyDotEnv(parseDotEnv(readFileSync18(file, "utf8")), root, process.env);
229509
229738
  for (const [k, v] of Object.entries(added))
229510
229739
  process.env[k] = v;
229511
229740
  return Object.keys(added);
@@ -229516,7 +229745,7 @@ var loadProjectEnv = (cwd = process.cwd()) => {
229516
229745
  var upsertDotEnv = (root, entries) => {
229517
229746
  const touched = [];
229518
229747
  const envPath = join20(root, ".env");
229519
- let content = existsSync17(envPath) ? readFileSync17(envPath, "utf8") : "";
229748
+ let content = existsSync17(envPath) ? readFileSync18(envPath, "utf8") : "";
229520
229749
  for (const [key, value] of Object.entries(entries)) {
229521
229750
  const line = `${key}=${value}`;
229522
229751
  const re = new RegExp(`^(?:export\\s+)?${key}\\s*=.*$`, "m");
@@ -229531,7 +229760,7 @@ var upsertDotEnv = (root, entries) => {
229531
229760
  writeFileSync12(envPath, content);
229532
229761
  touched.push(".env");
229533
229762
  const ignorePath = join20(root, ".gitignore");
229534
- const ignore = existsSync17(ignorePath) ? readFileSync17(ignorePath, "utf8") : "";
229763
+ const ignore = existsSync17(ignorePath) ? readFileSync18(ignorePath, "utf8") : "";
229535
229764
  if (!ignore.split(`
229536
229765
  `).some((l) => l.trim() === ".env")) {
229537
229766
  appendFileSync(ignorePath, `${!ignore || ignore.endsWith(`
@@ -229544,7 +229773,7 @@ var upsertDotEnv = (root, entries) => {
229544
229773
  };
229545
229774
 
229546
229775
  // src/commands/app/icon.ts
229547
- import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync18, writeFileSync as writeFileSync13 } from "node:fs";
229776
+ import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "node:fs";
229548
229777
  import { dirname as dirname13, join as join21, resolve as resolve8 } from "node:path";
229549
229778
 
229550
229779
  // ../../sdk/src/canvas/gen/cssColor.ts
@@ -230088,7 +230317,7 @@ var restampManifestIcon = (androidDir) => {
230088
230317
  const path = join21(androidDir, "app", "src", "main", "AndroidManifest.xml");
230089
230318
  if (!existsSync19(path))
230090
230319
  return;
230091
- const content = readFileSync18(path, "utf8");
230320
+ const content = readFileSync19(path, "utf8");
230092
230321
  if (content.includes(`android:icon="@mipmap/ic_launcher"`))
230093
230322
  return;
230094
230323
  if (!/android:icon="[^"]*"/.test(content)) {
@@ -230100,7 +230329,7 @@ var restampManifestIcon = (androidDir) => {
230100
230329
  };
230101
230330
 
230102
230331
  // src/commands/app/splash.ts
230103
- import { existsSync as existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync19, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "node:fs";
230332
+ import { existsSync as existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync20, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "node:fs";
230104
230333
  import { dirname as dirname14, join as join22, resolve as resolve9 } from "node:path";
230105
230334
  init_cli();
230106
230335
 
@@ -230978,7 +231207,7 @@ var androidSplashThemeHint = (androidDir) => {
230978
231207
  const path = join22(androidDir, "app", "src", "main", "res", "values", "themes.xml");
230979
231208
  if (!existsSync20(path))
230980
231209
  return null;
230981
- const xml = readFileSync19(path, "utf8");
231210
+ const xml = readFileSync20(path, "utf8");
230982
231211
  const style = /<style\b[^>]*\bname="Theme\.LeCodesApp"[^>]*?(?:\/>|>([\s\S]*?)<\/style>)/.exec(xml);
230983
231212
  if (!style) {
230984
231213
  return /\bparent="Theme\.LeCodesRuntime"/.test(xml) ? null : `app/src/main/res/values/themes.xml: the app's theme needs parent="Theme.LeCodesRuntime" and no android:windowBackground of its own for the splash to show — see "Splash screen" in android/README.md`;
@@ -231346,13 +231575,13 @@ if "%OS%"=="Windows_NT" endlocal
231346
231575
  // src/commands/app/play.ts
231347
231576
  init_cli();
231348
231577
  import { sign } from "node:crypto";
231349
- import { readFileSync as readFileSync20 } from "node:fs";
231578
+ import { readFileSync as readFileSync21 } from "node:fs";
231350
231579
  var SCOPE = "https://www.googleapis.com/auth/androidpublisher";
231351
231580
  var apiBase = () => process.env.LECODES_PLAY_API_URL ?? "https://androidpublisher.googleapis.com";
231352
231581
  var readServiceAccount = (path) => {
231353
231582
  let key;
231354
231583
  try {
231355
- key = JSON.parse(readFileSync20(path, "utf8"));
231584
+ key = JSON.parse(readFileSync21(path, "utf8"));
231356
231585
  } catch {
231357
231586
  throw new CliError(`${path} is not a JSON key file.`);
231358
231587
  }
@@ -231432,7 +231661,7 @@ var shellSdkVersionAndroid = (androidDir) => {
231432
231661
  const path = join23(androidDir, "lecodes-runtime", "build.gradle.kts");
231433
231662
  if (!existsSync21(path))
231434
231663
  return null;
231435
- const m = /io\.letary:lecodes-sdk(?:-(?:core|2d|3d))?:(\d+\.\d+\.\d+)@aar/.exec(readFileSync21(path, "utf8"));
231664
+ const m = /io\.letary:lecodes-sdk(?:-(?:core|2d|3d))?:(\d+\.\d+\.\d+)@aar/.exec(readFileSync22(path, "utf8"));
231436
231665
  return m ? m[1] : null;
231437
231666
  };
231438
231667
  var GRADLE_WRAPPER_JAR_URL = "https://raw.githubusercontent.com/gradle/gradle/v8.13.0/gradle/wrapper/gradle-wrapper.jar";
@@ -231440,7 +231669,7 @@ var GRADLE_WRAPPER_JAR_SHA256 = "81a82aaea5abcc8ff68b3dfcb58b3c3c429378efd98e743
231440
231669
  var ensureGradleWrapperJar = async () => {
231441
231670
  const cached = join23(homedir8(), ".lecodes", "gradle-wrapper", GRADLE_VERSION, "gradle-wrapper.jar");
231442
231671
  if (existsSync21(cached))
231443
- return new Uint8Array(readFileSync21(cached));
231672
+ return new Uint8Array(readFileSync22(cached));
231444
231673
  try {
231445
231674
  const res = await fetch(GRADLE_WRAPPER_JAR_URL);
231446
231675
  if (!res.ok)
@@ -231571,7 +231800,7 @@ var syncAndroid = async (root, config, prior) => {
231571
231800
  resources = prior.resources;
231572
231801
  } else {
231573
231802
  resources = await compileShell(join23(assetsDir, "app.js"), { remotePatterns });
231574
- bundleJs = readFileSync21(join23(assetsDir, "app.js"), "utf8");
231803
+ bundleJs = readFileSync22(join23(assetsDir, "app.js"), "utf8");
231575
231804
  }
231576
231805
  const requestedVariant = config.android?.variant ?? "auto";
231577
231806
  if (requestedVariant !== "auto" && !ANDROID_SDK_VARIANTS.includes(requestedVariant)) {
@@ -231716,7 +231945,7 @@ var restampManifestBlock = (androidDir, tag, block, what, missing, after = []) =
231716
231945
  const path = join23(androidDir, "app", "src", "main", "AndroidManifest.xml");
231717
231946
  if (!existsSync21(path))
231718
231947
  return;
231719
- const content = readFileSync21(path, "utf8");
231948
+ const content = readFileSync22(path, "utf8");
231720
231949
  const tagged = new RegExp(`\\n[ \\t]*<!-- lecodes:${tag}[\\s\\S]*?<!-- \\/lecodes:${tag} -->`);
231721
231950
  let next;
231722
231951
  if (tagged.test(content))
@@ -231745,7 +231974,7 @@ var restampAppBuildGradle = (androidDir, config, gradlePlugins) => {
231745
231974
  warn("android/app/build.gradle.kts is missing — run `lecodes app init android` on a clean tree to regenerate the shell.");
231746
231975
  return;
231747
231976
  }
231748
- let content = readFileSync21(path, "utf8");
231977
+ let content = readFileSync22(path, "utf8");
231749
231978
  const before = content;
231750
231979
  const versionName = config.version ?? "1.0.0";
231751
231980
  const versionCode = config.android?.versionCode ?? 1;
@@ -231958,7 +232187,7 @@ var uploadAndroid = async (flags) => {
231958
232187
  const token = await serviceAccountToken(key);
231959
232188
  const { versionCode } = await uploadToPlay(token, {
231960
232189
  packageName,
231961
- aab: readFileSync21(aabPath),
232190
+ aab: readFileSync22(aabPath),
231962
232191
  track: flags.track,
231963
232192
  status: flags.draft ? "draft" : "completed"
231964
232193
  });
@@ -231987,7 +232216,7 @@ var ANDROID_SIGNING_ENV = {
231987
232216
  };
231988
232217
  var readProperties = (path) => {
231989
232218
  const out = {};
231990
- for (const raw of readFileSync21(path, "utf8").split(/\r?\n/)) {
232219
+ for (const raw of readFileSync22(path, "utf8").split(/\r?\n/)) {
231991
232220
  const line = raw.trim();
231992
232221
  if (!line || line.startsWith("#") || line.startsWith("!"))
231993
232222
  continue;
@@ -232087,7 +232316,7 @@ var writeSigningConfig = (root, config, storeFile, alias, storePassword, keyPass
232087
232316
  const rel = relative4(root, storeFile);
232088
232317
  if (rel && !rel.startsWith("..") && !rel.includes(":") && !rel.replace(/\\/g, "/").startsWith("android/")) {
232089
232318
  const ignorePath = join23(root, ".gitignore");
232090
- const ignore = existsSync21(ignorePath) ? readFileSync21(ignorePath, "utf8") : "";
232319
+ const ignore = existsSync21(ignorePath) ? readFileSync22(ignorePath, "utf8") : "";
232091
232320
  if (!ignore.split(`
232092
232321
  `).some((l) => l.trim() === "*.keystore")) {
232093
232322
  writeFileSync15(ignorePath, ignore + (!ignore || ignore.endsWith(`
@@ -232325,10 +232554,10 @@ var doctorAndroidChecks = async (root, config, check) => {
232325
232554
  }
232326
232555
  if (config.update) {
232327
232556
  const manifest = join23(androidDir, "lecodes-runtime", "src", "main", "AndroidManifest.xml");
232328
- check(existsSync21(manifest) && readFileSync21(manifest, "utf8").includes("io.letary.lecodes.UPDATE_URL"), "OTA update URL in the runtime manifest", "run `lecodes app sync`", false);
232557
+ check(existsSync21(manifest) && readFileSync22(manifest, "utf8").includes("io.letary.lecodes.UPDATE_URL"), "OTA update URL in the runtime manifest", "run `lecodes app sync`", false);
232329
232558
  const appSrc = join23(androidDir, "app", "src", "main", "java");
232330
232559
  const kt = existsSync21(appSrc) ? readdirSync6(appSrc, { recursive: true }).map(String).filter((f) => f.endsWith(".kt")) : [];
232331
- const sources = kt.map((f) => readFileSync21(join23(appSrc, f), "utf8"));
232560
+ const sources = kt.map((f) => readFileSync22(join23(appSrc, f), "utf8"));
232332
232561
  const boots = sources.some((src) => src.includes("LeCodesShell.boot"));
232333
232562
  check(boots, "App code launches via the updater (LeCodesShell.boot)", 'no app/src/main/java/**/*.kt calls LeCodesShell.boot(engine, activity) — downloaded updates would never run; see "Over-the-air updates" in android/README.md for the AppViewModel snippet', false);
232334
232563
  const waits = sources.some((src) => /LeCodesShell\.boot\(\s*engine\s*,\s*activity\b/.test(src));
@@ -232348,7 +232577,7 @@ var doctorAndroidChecks = async (root, config, check) => {
232348
232577
  }
232349
232578
  check(!!findKeytool(), "keytool", "install a JDK or set JAVA_HOME (`lecodes app keystore …` needs it)", false);
232350
232579
  const appGradle = join23(androidDir, "app", "build.gradle.kts");
232351
- const gradleText = existsSync21(appGradle) ? readFileSync21(appGradle, "utf8") : "";
232580
+ const gradleText = existsSync21(appGradle) ? readFileSync22(appGradle, "utf8") : "";
232352
232581
  check(gradleText.includes("abiFilters"), "abiFilters in app/build.gradle.kts", 'add `ndk { abiFilters += listOf("arm64-v8a", "armeabi-v7a") }` to defaultConfig — x86_64 devices crash at launch without it', false);
232353
232582
  check(["isMinifyEnabled", "isShrinkResources"].every((k) => new RegExp(`${k} = (true|false)`).test(gradleText)), "R8 block in app/build.gradle.kts", 'shell predates R8-by-default — see android/README.md "Building"', false);
232354
232583
  if (Object.keys(config.plugins ?? {}).includes("push")) {
@@ -232363,7 +232592,7 @@ import { basename as basename9, dirname as dirname16, join as join26, resolve as
232363
232592
 
232364
232593
  // src/hosts/desktopRenderer.ts
232365
232594
  import { execFileSync as execFileSync3, spawn as spawn2, spawnSync as spawnSync6 } from "node:child_process";
232366
- import { copyFileSync, existsSync as existsSync22, mkdirSync as mkdirSync13, mkdtempSync as mkdtempSync2, readdirSync as readdirSync7, readFileSync as readFileSync22, rmSync as rmSync5, statSync as statSync6, writeFileSync as writeFileSync16, linkSync } from "node:fs";
232595
+ import { copyFileSync, existsSync as existsSync22, mkdirSync as mkdirSync13, mkdtempSync as mkdtempSync2, readdirSync as readdirSync7, readFileSync as readFileSync23, rmSync as rmSync5, statSync as statSync6, writeFileSync as writeFileSync16, linkSync } from "node:fs";
232367
232596
  import { homedir as homedir9, tmpdir as tmpdir2 } from "node:os";
232368
232597
  import { delimiter, join as join24, resolve as resolve11 } from "node:path";
232369
232598
  init_cli();
@@ -232508,7 +232737,7 @@ var installedDesktopVersions = () => {
232508
232737
  };
232509
232738
  var selectedDesktopVersion = () => {
232510
232739
  try {
232511
- const saved = JSON.parse(readFileSync22(currentFile(), "utf8")).version;
232740
+ const saved = JSON.parse(readFileSync23(currentFile(), "utf8")).version;
232512
232741
  if (saved && /^\d+\.\d+\.\d+/.test(saved) && semverNewer(DESKTOP_RENDERER_VERSION, saved) > 0)
232513
232742
  return saved;
232514
232743
  } catch {}
@@ -233630,7 +233859,7 @@ var shellSdkVersion = (iosDir) => {
233630
233859
  const path = join27(iosDir, "LeCodesRuntime", "Package.swift");
233631
233860
  if (!existsSync25(path))
233632
233861
  return null;
233633
- const m = /lecodes-ios-sdk\.git",\s*(?:exact|from):\s*"(\d+\.\d+\.\d+)(?:-(?:core|2d|3d))?"/.exec(readFileSync23(path, "utf8"));
233862
+ const m = /lecodes-ios-sdk\.git",\s*(?:exact|from):\s*"(\d+\.\d+\.\d+)(?:-(?:core|2d|3d))?"/.exec(readFileSync24(path, "utf8"));
233634
233863
  return m && VERSION_RE.test(m[1]) ? m[1] : null;
233635
233864
  };
233636
233865
  var IOS_SDK_OPTS = { repo: SDK_REPO, minVersion: SDK_MIN_VERSION, configKey: "ios.sdk" };
@@ -233836,7 +234065,7 @@ var syncIos = async (root, config) => {
233836
234065
  rmSync8(resourcesDir, { recursive: true, force: true });
233837
234066
  mkdirSync16(resourcesDir, { recursive: true });
233838
234067
  const resources = await compileShell(join27(resourcesDir, "app.js"), { remotePatterns });
233839
- const bundleJs = readFileSync23(join27(resourcesDir, "app.js"), "utf8");
234068
+ const bundleJs = readFileSync24(join27(resourcesDir, "app.js"), "utf8");
233840
234069
  const { variant, why } = resolveVariant(requestedVariant, bundleJs);
233841
234070
  log(`${c.green("variant")} ${variant}${requestedVariant === "auto" ? ` (auto: ${why})` : " (forced by ios.variant)"}`);
233842
234071
  const sdkWhy = sdkPin === "latest" ? "latest" : config.ios.sdk ? "pinned by ios.sdk" : "from the shell — set ios.sdk or run `lecodes app update`";
@@ -233902,7 +234131,7 @@ var syncIos = async (root, config) => {
233902
234131
  if (pluginSpecs.length > 0 || existsSync25(lockPath))
233903
234132
  writeJson(lockPath, { plugins: newLock });
233904
234133
  const pbxPath = join27(iosDir, "App.xcodeproj", "project.pbxproj");
233905
- let pbx = readFileSync23(pbxPath, "utf8").replace(/MARKETING_VERSION = [^;]+;/g, `MARKETING_VERSION = ${config.version ?? "1.0.0"};`).replace(/CURRENT_PROJECT_VERSION = [^;]+;/g, `CURRENT_PROJECT_VERSION = ${config.ios.buildNumber ?? 1};`);
234134
+ let pbx = readFileSync24(pbxPath, "utf8").replace(/MARKETING_VERSION = [^;]+;/g, `MARKETING_VERSION = ${config.version ?? "1.0.0"};`).replace(/CURRENT_PROJECT_VERSION = [^;]+;/g, `CURRENT_PROJECT_VERSION = ${config.ios.buildNumber ?? 1};`);
233906
234135
  const mergedEntitlements = {};
233907
234136
  const appLinks = appLinksOf(config);
233908
234137
  if (appLinks.length) {
@@ -234194,8 +234423,8 @@ var doctorIosChecks = async (root, config, check) => {
234194
234423
  const validVariant = requestedVariant === "auto" || SDK_VARIANTS.includes(requestedVariant);
234195
234424
  check(validVariant, `ios.variant "${requestedVariant}"`, `use "auto" or one of ${SDK_VARIANTS.join(", ")}`);
234196
234425
  if (existsSync25(bundle) && validVariant) {
234197
- const { variant } = resolveVariant(requestedVariant, readFileSync23(bundle, "utf8"));
234198
- const pkg = readFileSync23(join27(root, "ios", "LeCodesRuntime", "Package.swift"), "utf8");
234426
+ const { variant } = resolveVariant(requestedVariant, readFileSync24(bundle, "utf8"));
234427
+ const pkg = readFileSync24(join27(root, "ios", "LeCodesRuntime", "Package.swift"), "utf8");
234199
234428
  const tag = /lecodes-ios-sdk\.git",\s*exact:\s*"([^"]+)"/.exec(pkg)?.[1] ?? "";
234200
234429
  const tagVariant = /-(core|2d|3d)$/.exec(tag)?.[1] ?? "full";
234201
234430
  check(tagVariant === variant, `SDK variant ${variant}`, "run `lecodes app sync`");
@@ -234217,25 +234446,25 @@ var doctorIosChecks = async (root, config, check) => {
234217
234446
  const inSync = wanted.every((id) => lock[id]) && Object.keys(lock).every((id) => wanted.includes(id));
234218
234447
  check(wanted.length === 0 || inSync, "Plugins synced", "run `lecodes app sync`");
234219
234448
  const pbxFile = join27(root, "ios", "App.xcodeproj", "project.pbxproj");
234220
- if (existsSync25(pbxFile) && readFileSync23(pbxFile, "utf8").includes("CODE_SIGN_ENTITLEMENTS")) {
234449
+ if (existsSync25(pbxFile) && readFileSync24(pbxFile, "utf8").includes("CODE_SIGN_ENTITLEMENTS")) {
234221
234450
  check(existsSync25(join27(root, "ios", "App", "App.entitlements")), "App/App.entitlements present", "run `lecodes app sync`");
234222
234451
  }
234223
234452
  if (wanted.includes("push")) {
234224
234453
  const runtimeSwift = join27(root, "ios", "LeCodesRuntime", "Sources", "LeCodesRuntime", "LeCodesRuntime.swift");
234225
- const baked = existsSync25(runtimeSwift) && readFileSync23(runtimeSwift, "utf8").includes("bootProjectUuid");
234454
+ const baked = existsSync25(runtimeSwift) && readFileSync24(runtimeSwift, "utf8").includes("bootProjectUuid");
234226
234455
  check(baked, "Push world identity baked", `without it Push.register() rejects "unavailable" — needs a pushed project (uuid), lecodes-ios-sdk ${SDK_IDENTITY_VERSION}+ and \`lecodes app sync\``, false);
234227
234456
  }
234228
234457
  if (config.update) {
234229
- const plist = readFileSync23(join27(root, "ios", "App", "Info.plist"), "utf8");
234458
+ const plist = readFileSync24(join27(root, "ios", "App", "Info.plist"), "utf8");
234230
234459
  check(plist.includes("<key>LeCodesUpdateURL</key>"), "OTA update URL in Info.plist", "run `lecodes app sync`", false);
234231
234460
  const appDir = join27(root, "ios", "App");
234232
- const swift = readdirSync10(appDir).filter((f) => f.endsWith(".swift")).map((f) => readFileSync23(join27(appDir, f), "utf8"));
234461
+ const swift = readdirSync10(appDir).filter((f) => f.endsWith(".swift")).map((f) => readFileSync24(join27(appDir, f), "utf8"));
234233
234462
  const launchesViaUpdater = swift.some((t) => t.includes("LeCodesAppViewController") || t.includes("LeCodesUpdater.activeBundle"));
234234
234463
  check(launchesViaUpdater, "App code launches via the updater", `no App/*.swift subclasses LeCodesAppViewController or calls LeCodesUpdater.activeBundle() — downloaded updates would never run; see "Over-the-air updates" in ios/README.md`, false);
234235
234464
  }
234236
234465
  {
234237
234466
  const vc = join27(root, "ios", "App", "ViewController.swift");
234238
- const is2x = !existsSync25(vc) || readFileSync23(vc, "utf8").includes("LeCodesAppViewController");
234467
+ const is2x = !existsSync25(vc) || readFileSync24(vc, "utf8").includes("LeCodesAppViewController");
234239
234468
  check(is2x, "App/ViewController.swift is the 2.x shell (LeCodesAppViewController)", "a 1.x shell's App/*.swift: replace AppDelegate.swift, SceneDelegate.swift and ViewController.swift with the ones `lecodes app init ios` writes today (your own code goes into ViewController.configure(_:))", false);
234240
234469
  }
234241
234470
  const asc = readJson2(join27(homedir10(), ".lecodes", "asc.json"));
@@ -234652,6 +234881,7 @@ lecodes render --sim --script <file> # the WINDOWLESS native host (lecodes-
234652
234881
  # engines, no GPU, no pixels, no UI hit-test — expect.log /
234653
234882
  # expect.paint only; runs on CI and beside an open desktop window
234654
234883
  lecodes test # all tests/*.flow.json + design-derived flows; run before "done"
234884
+ bunx -p typescript tsc --noEmit # typecheck against .lecodes/types (the SDK's globals); also before "done"
234655
234885
  lecodes test --desktop # the file flows on the NATIVE host (design flows stay headless)
234656
234886
  lecodes test --sim # the windowless twin: coordinates + expect.log / expect.paint only
234657
234887
  lecodes dev --android --time 12 --png phone.png # the REAL phone over adb (USB): launches the app on
@@ -234775,9 +235005,9 @@ fetch the docs — they are served as raw markdown for AI tools:
234775
235005
  with a COOKBOOK: read the matching recipe BEFORE you design the structure.** It has the file
234776
235006
  layout, a skeleton per part, how to verify, and the traps already paid for (start with
234777
235007
  "Project architecture for a game" for any game bigger than one file).
234778
- - https://le.codes/en/docs/api/<area>/<page>.md — one reference page as raw markdown
235008
+ - https://le.codes/docs/api/<area>/<page>.md — one reference page as raw markdown
234779
235009
  (e.g. \`ui/styling.md\`, \`2d/scene2d.md\`, \`3d/scene.md\`, \`runtime/storage.md\`)
234780
- - https://le.codes/en/docs/guide/<page>.md — guide pages (project anatomy, CLI, tracks)
235010
+ - https://le.codes/docs/guide/<page>.md — guide pages (project anatomy, CLI, tracks)
234781
235011
  - https://le.codes/llms-full.txt — everything in one file (large; prefer per-page)
234782
235012
 
234783
235013
  Always fetch the \`.md\` URLs — the HTML routes are a JS app and return an empty shell.
@@ -235575,7 +235805,7 @@ var desktop_default = defineCommand({
235575
235805
 
235576
235806
  // src/commands/dev.ts
235577
235807
  init_manifest();
235578
- import { existsSync as existsSync38, mkdirSync as mkdirSync22, readFileSync as readFileSync32, writeFileSync as writeFileSync25 } from "node:fs";
235808
+ import { existsSync as existsSync38, mkdirSync as mkdirSync22, readFileSync as readFileSync33, writeFileSync as writeFileSync25 } from "node:fs";
235579
235809
  import { randomBytes as randomBytes5 } from "node:crypto";
235580
235810
  import { networkInterfaces } from "node:os";
235581
235811
  import { basename as basename15, join as join43 } from "node:path";
@@ -235583,7 +235813,7 @@ import { basename as basename15, join as join43 } from "node:path";
235583
235813
  // src/dev/devServer.ts
235584
235814
  init_localFiles();
235585
235815
  import { createHash as createHash8 } from "node:crypto";
235586
- import { createReadStream as createReadStream2, readFileSync as readFileSync30, statSync as statSync11 } from "node:fs";
235816
+ import { createReadStream as createReadStream2, readFileSync as readFileSync31, statSync as statSync11 } from "node:fs";
235587
235817
  import { join as join39 } from "node:path";
235588
235818
  import { Readable as Readable3 } from "node:stream";
235589
235819
  init_projectCompile();
@@ -235718,7 +235948,7 @@ init_paths();
235718
235948
  // src/web/halves.ts
235719
235949
  init_compile();
235720
235950
  init_cli();
235721
- import { cpSync as cpSync5, existsSync as existsSync34, readFileSync as readFileSync29 } from "node:fs";
235951
+ import { cpSync as cpSync5, existsSync as existsSync34, readFileSync as readFileSync30 } from "node:fs";
235722
235952
  import { join as join38, sep as sep4 } from "node:path";
235723
235953
  var halfModule = (half) => `plugins/${half.folder}/index.js`;
235724
235954
  var asCliError = (e) => new CliError(e instanceof Error ? e.message : String(e));
@@ -235729,7 +235959,7 @@ var projectHalves = (root) => {
235729
235959
  const folder = dir.split(sep4).pop();
235730
235960
  let web;
235731
235961
  try {
235732
- web = webHalfManifestOf(readFileSync29(join38(dir, MANIFEST_FILE), "utf8"), folder, id);
235962
+ web = webHalfManifestOf(readFileSync30(join38(dir, MANIFEST_FILE), "utf8"), folder, id);
235733
235963
  } catch (e) {
235734
235964
  throw asCliError(e);
235735
235965
  }
@@ -235870,7 +236100,7 @@ var startDevServer = async (options) => {
235870
236100
  const liteTable = () => {
235871
236101
  let plugins = {};
235872
236102
  try {
235873
- plugins = JSON.parse(readFileSync30(join39(lite, "plugins.json"), "utf8")).plugins ?? {};
236103
+ plugins = JSON.parse(readFileSync31(join39(lite, "plugins.json"), "utf8")).plugins ?? {};
235874
236104
  } catch {}
235875
236105
  const table = { ...plugins };
235876
236106
  for (const { half, hash } of halves) {
@@ -236361,7 +236591,7 @@ var androidCrashLines = (session) => {
236361
236591
 
236362
236592
  // src/dev/iosDev.ts
236363
236593
  init_cli();
236364
- import { existsSync as existsSync37, readdirSync as readdirSync17, readFileSync as readFileSync31, statSync as statSync13, unlinkSync } from "node:fs";
236594
+ import { existsSync as existsSync37, readdirSync as readdirSync17, readFileSync as readFileSync32, statSync as statSync13, unlinkSync } from "node:fs";
236365
236595
  import { homedir as homedir12 } from "node:os";
236366
236596
  import { join as join42 } from "node:path";
236367
236597
  import { spawnSync as spawnSync10 } from "node:child_process";
@@ -236394,7 +236624,7 @@ var listDevices2 = () => {
236394
236624
  if (!ok || !existsSync37(tmp))
236395
236625
  return [];
236396
236626
  try {
236397
- const json = JSON.parse(readFileSync31(tmp, "utf8"));
236627
+ const json = JSON.parse(readFileSync32(tmp, "utf8"));
236398
236628
  return (json.result?.devices ?? []).map((d) => ({
236399
236629
  udid: d.identifier,
236400
236630
  name: d.deviceProperties?.name ?? d.identifier,
@@ -236441,7 +236671,7 @@ var deviceHasApp = (udid, bundleId) => {
236441
236671
  if (!ok || !existsSync37(tmp))
236442
236672
  return false;
236443
236673
  try {
236444
- const json = JSON.parse(readFileSync31(tmp, "utf8"));
236674
+ const json = JSON.parse(readFileSync32(tmp, "utf8"));
236445
236675
  return (json.result?.apps ?? []).some((a) => a.bundleIdentifier === bundleId);
236446
236676
  } catch {
236447
236677
  return false;
@@ -236481,7 +236711,7 @@ var launchOnIos = (opts) => {
236481
236711
  if (!start.ok)
236482
236712
  throw new CliError(`Could not launch ${bundleId} on ${target.name}: ${start.out}`);
236483
236713
  try {
236484
- const json = JSON.parse(readFileSync31(tmp, "utf8"));
236714
+ const json = JSON.parse(readFileSync32(tmp, "utf8"));
236485
236715
  pid = json.result?.process?.processIdentifier?.toString() ?? null;
236486
236716
  } catch {
236487
236717
  pid = null;
@@ -236553,7 +236783,7 @@ var lanCandidates = () => {
236553
236783
  var loadDevToken = (root) => {
236554
236784
  const path = join43(root, LECODES_DIR, "dev.json");
236555
236785
  try {
236556
- const token = JSON.parse(readFileSync32(path, "utf8")).token;
236786
+ const token = JSON.parse(readFileSync33(path, "utf8")).token;
236557
236787
  if (token && /^[a-z0-9]{8,}$/.test(token))
236558
236788
  return token;
236559
236789
  } catch {}
@@ -236788,7 +237018,7 @@ init_api();
236788
237018
 
236789
237019
  // src/platform/serverDiff.ts
236790
237020
  init_localFiles();
236791
- import { readFileSync as readFileSync33 } from "node:fs";
237021
+ import { readFileSync as readFileSync34 } from "node:fs";
236792
237022
  var isTextAsset = (t) => t === "text" || t === "shader" || t === "scene";
236793
237023
  var computeServerDiff = (project, scan) => {
236794
237024
  const pathById = buildPaths(project.assets);
@@ -236806,7 +237036,7 @@ var computeServerDiff = (project, scan) => {
236806
237036
  const local = localByPath.get(path);
236807
237037
  if (local && !server2) {
236808
237038
  const text = isTextKind(local.kind);
236809
- entries.push({ path, status: "added", binary: !text, serverText: "", localText: text ? readFileSync33(local.absPath, "utf8") : undefined });
237039
+ entries.push({ path, status: "added", binary: !text, serverText: "", localText: text ? readFileSync34(local.absPath, "utf8") : undefined });
236810
237040
  continue;
236811
237041
  }
236812
237042
  if (server2 && !local) {
@@ -236817,12 +237047,12 @@ var computeServerDiff = (project, scan) => {
236817
237047
  if (server2 && local) {
236818
237048
  if (isTextAsset(server2.type)) {
236819
237049
  const serverText = server2.text ?? "";
236820
- const localText = readFileSync33(local.absPath, "utf8");
237050
+ const localText = readFileSync34(local.absPath, "utf8");
236821
237051
  if (serverText !== localText)
236822
237052
  entries.push({ path, status: "modified", binary: false, serverText, localText });
236823
237053
  } else {
236824
237054
  const serverSize = server2.file?.size;
236825
- const localSize = Buffer.byteLength(readFileSync33(local.absPath));
237055
+ const localSize = Buffer.byteLength(readFileSync34(local.absPath));
236826
237056
  if (serverSize !== undefined && serverSize !== localSize) {
236827
237057
  entries.push({ path, status: "modified", binary: true });
236828
237058
  }
@@ -237154,7 +237384,7 @@ var init3 = (dir, forDesktop) => {
237154
237384
  // src/commands/install.ts
237155
237385
  init_cli();
237156
237386
  import { execFileSync as execFileSync7 } from "node:child_process";
237157
- import { appendFileSync as appendFileSync3, chmodSync as chmodSync2, cpSync as cpSync6, existsSync as existsSync40, mkdirSync as mkdirSync24, readFileSync as readFileSync34, realpathSync as realpathSync2, rmSync as rmSync11, symlinkSync as symlinkSync2 } from "node:fs";
237387
+ import { appendFileSync as appendFileSync3, chmodSync as chmodSync2, cpSync as cpSync6, existsSync as existsSync40, mkdirSync as mkdirSync24, readFileSync as readFileSync35, realpathSync as realpathSync2, rmSync as rmSync11, symlinkSync as symlinkSync2 } from "node:fs";
237158
237388
  import { homedir as homedir13 } from "node:os";
237159
237389
  import { basename as basename17, delimiter as delimiter2, dirname as dirname22, join as join45, resolve as resolve21 } from "node:path";
237160
237390
  var isWin = process.platform === "win32";
@@ -237208,7 +237438,7 @@ var appendPathToShellRc = (binDir) => {
237208
237438
  const { file, name } = shellRc();
237209
237439
  const line = `export PATH="${binDir}:$PATH"`;
237210
237440
  try {
237211
- const existing = existsSync40(file) ? readFileSync34(file, "utf8") : "";
237441
+ const existing = existsSync40(file) ? readFileSync35(file, "utf8") : "";
237212
237442
  if (existing.includes(line))
237213
237443
  return name;
237214
237444
  appendFileSync3(file, `${existing && !existing.endsWith(`
@@ -237330,9 +237560,9 @@ var install_default = defineCommand({
237330
237560
  });
237331
237561
 
237332
237562
  // src/commands/lightmap.ts
237333
- import { closeSync, existsSync as existsSync41, mkdirSync as mkdirSync25, mkdtempSync as mkdtempSync4, openSync, readFileSync as readFileSync35, readSync, rmSync as rmSync12, statSync as statSync14, writeFileSync as writeFileSync27 } from "node:fs";
237563
+ import { closeSync, existsSync as existsSync41, mkdirSync as mkdirSync25, mkdtempSync as mkdtempSync4, openSync, readdirSync as readdirSync18, readFileSync as readFileSync36, readSync, rmSync as rmSync12, writeFileSync as writeFileSync27 } from "node:fs";
237334
237564
  import { tmpdir as tmpdir4 } from "node:os";
237335
- import { basename as basename18, join as join46, relative as relative7, resolve as resolve22 } from "node:path";
237565
+ import { basename as basename18, dirname as dirname23, join as join46, relative as relative7, resolve as resolve22, sep as sep5 } from "node:path";
237336
237566
  import { deflateSync } from "node:zlib";
237337
237567
  init_peerInstall();
237338
237568
  init_cli();
@@ -237345,7 +237575,7 @@ var range = (name, value, min, max) => {
237345
237575
  var readBake = (file) => {
237346
237576
  if (!existsSync41(file))
237347
237577
  throw new CliError(`No bake at ${file} — run \`lecodes lightmap bake\` first.`);
237348
- const data = JSON.parse(readFileSync35(file, "utf8"));
237578
+ const data = JSON.parse(readFileSync36(file, "utf8"));
237349
237579
  if (data.version !== 4)
237350
237580
  throw new CliError(`${file} is not a creator-bake file (version ${String(data.version)}) — rebake.`);
237351
237581
  return data;
@@ -237381,54 +237611,47 @@ var cubePatternsOf = (argv) => {
237381
237611
  }
237382
237612
  return out.filter((p) => p !== "");
237383
237613
  };
237384
- var placeholderBake = (stem) => JSON.stringify({ version: 4, placeholder: true, size: 4, pages: 0, texel: 0, lightScale: 0, light: [`${stem}-light.ktx2`], aux: [`${stem}-aux.ktx2`], instances: [] }) + `
237385
- `;
237386
- var ktx2Placeholder = (vkFormat) => {
237387
- const raw = Buffer.alloc(vkFormat === 143 ? 16 : 64);
237388
- const dfdSize = 44;
237389
- const dfdOffset = 80 + 24;
237390
- const b = Buffer.alloc(dfdOffset + dfdSize + raw.length);
237391
- Buffer.from([171, 75, 84, 88, 32, 50, 48, 187, 13, 10, 26, 10]).copy(b, 0);
237392
- let o = 12;
237393
- for (const v of [vkFormat, 1, 4, 4, 0, 0, 1, 1, 0]) {
237394
- b.writeUInt32LE(v, o);
237395
- o += 4;
237396
- }
237397
- b.writeUInt32LE(dfdOffset, o);
237398
- b.writeUInt32LE(dfdSize, o + 4);
237399
- o += 8;
237400
- b.writeUInt32LE(0, o);
237401
- b.writeUInt32LE(0, o + 4);
237402
- o += 8;
237403
- b.writeBigUInt64LE(0n, o);
237404
- b.writeBigUInt64LE(0n, o + 8);
237405
- o += 16;
237406
- b.writeBigUInt64LE(BigInt(dfdOffset + dfdSize), o);
237407
- b.writeBigUInt64LE(BigInt(raw.length), o + 8);
237408
- b.writeBigUInt64LE(BigInt(raw.length), o + 16);
237409
- o += 24;
237410
- b.writeUInt32LE(dfdSize, o);
237411
- raw.copy(b, dfdOffset + dfdSize);
237412
- return b;
237614
+ var stemOf = (path) => basename18(path).replace(/\.bake$/i, "");
237615
+ var pickTarget2 = (bakes, name, entry) => {
237616
+ if (bakes.length === 0)
237617
+ throw new CliError(`${entry} reaches no bake(): the level's scene file says where its bake goes — add \`env: { lightmap: bake('./assets/lightmap/level.bake') }\` (a hand-built scene: \`Lightmap.load(scene, bake('./assets/lightmap/level.bake'))\`).`);
237618
+ const listed = bakes.map((b) => `${stemOf(b.path)} (${b.path})`).join(", ");
237619
+ if (name === undefined) {
237620
+ if (bakes.length > 1)
237621
+ throw new CliError(`${entry} reaches ${bakes.length} bake() calls: ${listed} — pass --name <stem> to pick the level to bake.`);
237622
+ return bakes[0];
237623
+ }
237624
+ const named = bakes.filter((b) => stemOf(b.path) === name);
237625
+ if (named.length === 0)
237626
+ throw new CliError(`--name ${name}: ${entry} reaches no bake() of that stem — it reaches ${listed}.`);
237627
+ if (named.length > 1)
237628
+ throw new CliError(`--name ${name}: two bake() targets share that stem (${named.map((b) => b.path).join(", ")}) — rename one.`);
237629
+ return named[0];
237413
237630
  };
237414
- var ensurePlaceholders = (outDir, stem) => {
237415
- const missing = (f) => !existsSync41(f) || statSync14(f).size === 0;
237416
- const bake = join46(outDir, `${stem}.bake`);
237417
- if (missing(bake))
237418
- writeFileSync27(bake, placeholderBake(stem));
237419
- const light = join46(outDir, `${stem}-light.ktx2`), aux = join46(outDir, `${stem}-aux.ktx2`), probes = join46(outDir, `${stem}-probes.ktx2`);
237420
- if (missing(light))
237421
- writeFileSync27(light, ktx2Placeholder(143));
237422
- if (missing(aux))
237423
- writeFileSync27(aux, ktx2Placeholder(37));
237424
- if (missing(probes))
237425
- writeFileSync27(probes, ktx2Placeholder(143));
237426
- const grid = join46(outDir, `${stem}.lgrid`);
237427
- if (missing(grid))
237428
- writeFileSync27(grid, "NONE");
237429
- const uv1 = join46(outDir, `${stem}-uv1.bin`);
237430
- if (missing(uv1))
237431
- writeFileSync27(uv1, "NONE");
237631
+ var findBakes = (root) => {
237632
+ const SKIP = new Set(["node_modules", ".lightmap", ".git", ".lecodes"]);
237633
+ const out = [];
237634
+ const walk = (dir) => {
237635
+ for (const e of readdirSync18(dir, { withFileTypes: true })) {
237636
+ if (e.isDirectory()) {
237637
+ if (!SKIP.has(e.name))
237638
+ walk(join46(dir, e.name));
237639
+ } else if (/\.bake$/i.test(e.name))
237640
+ out.push(relative7(root, join46(dir, e.name)).replace(/\\/g, "/"));
237641
+ }
237642
+ };
237643
+ walk(root);
237644
+ return out.sort();
237645
+ };
237646
+ var bakeFileOf = (root, flag) => {
237647
+ if (flag)
237648
+ return resolve22(root, flag);
237649
+ const found = findBakes(root);
237650
+ if (found.length === 0)
237651
+ throw new CliError("No .bake in the project — run `lecodes lightmap bake` first.");
237652
+ if (found.length > 1)
237653
+ throw new CliError(`The project has ${found.length} bakes (${found.join(", ")}) — pass --bake <file>.`);
237654
+ return resolve22(root, found[0]);
237432
237655
  };
237433
237656
  var readGlbJson = (file) => {
237434
237657
  let fd;
@@ -237491,25 +237714,27 @@ var unwrapReferenced = async (js, resources, root, texel) => {
237491
237714
  var bake = defineCommand({
237492
237715
  name: "bake",
237493
237716
  summary: "Bake the sun, the sky and the lamps into a lux atlas for the level's statics",
237494
- description: `Runs the app in bake mode in lecodes-bake — the windowless host with the bake built in (no physics, 2D, audio, net or navmesh), downloaded on first use like the desktop host; LECODES_BAKE_EXE points at a local build (hosts/desktop build.ps1 -Bake). The statics are what the app registers with Lightmap.add (a scene file does it for every node no Physics aspect moves), the bake fires at its Lightmap.load call. Writes <out>/<name>.bake + <name>-light[_n].ktx2 (irradiance, BC6H) + <name>-aux[_n].ktx2 (sky visibility, the directional share and direction of the light) + <name>-probes.ktx2 (the reflection probes: a cubemap of the level's radiance per probe, prefiltered per roughness; --probe-spacing 0 = none). Needs a GPU with hardware ray tracing (--tracer).
237717
+ description: `Runs the app in bake mode in lecodes-bake — the windowless host with the bake built in (no physics, 2D, audio, net or navmesh), downloaded on first use like the desktop host; LECODES_BAKE_EXE points at a local build (hosts/desktop build.ps1 -Bake). The statics are what the app registers with Lightmap.add (a scene file does it for every node no Physics aspect moves), the bake fires at its Lightmap.load call. Needs a GPU with hardware ray tracing (--tracer).
237718
+
237719
+ Where: the level's own bake('<dir>/<stem>.bake') — env: { lightmap: bake('./assets/lightmap/level.bake') } in a scene file, the path relative to that file — names the target; nothing has to exist before the first bake. Writes into <dir>: <stem>.bake (the manifest: it names every file below, and the next compile reads it — nothing to paste into the scene) + <stem>-light[_n].ktx2 (irradiance, BC6H, a page per _n) + <stem>-aux[_n].ktx2 (sky visibility, the directional share and direction of the light) + <stem>-probes.ktx2 (the reflection probes: a cubemap of the level's radiance per probe, prefiltered per roughness; --probe-spacing 0 = none) + <stem>.lgrid (the light grid the movers read; --volume 0 = none) + <stem>-uv1.bin (the packed lightmap uv1 the rects map) + with --split <stem>-direct[_n].ktx2 / <stem>-indirect[_n].ktx2.
237495
237720
 
237496
237721
  Lightmap UVs: the GLBs the app references that have none (TEXCOORD_1) are unwrapped first, IN PLACE — the same as lecodes assets unwrap, run on just those files; a file that has them is left alone, --no-unwrap skips the step. A whole level in one GLB that wants --groups / --zone is unwrapped by hand first (lecodes assets unwrap --help).`,
237497
237722
  flags: {
237498
- out: str("where the atlas lands", { value: "<dir>", default: "assets/lightmap" }),
237499
- name: str("file stem — one per baked level", { value: "<stem>", default: "lightmap" }),
237723
+ out: str("write the files into this folder instead of next to the level's .bake (a second seed for `lightmap diff`)", { value: "<dir>" }),
237724
+ name: str("bake the level whose bake() names <stem>.bake — when the entry reaches several", { value: "<stem>" }),
237500
237725
  ...bundleFlags,
237501
- entry: str("the file that builds the scene and calls Lightmap.load", { value: "<file>", default: "main.ts" }),
237726
+ entry: str("the file that opens the level (it reaches the bake() that names the target)", { value: "<file>", default: "main.ts" }),
237502
237727
  size: num("atlas page edge (texels)", { default: 4096 }),
237503
237728
  texel: num("texel size (m); the bake coarsens only when the pages are full", { default: 0.05 }),
237504
237729
  pages: num("atlas pages to fill before the bake coarsens the texel", { default: 4 }),
237505
237730
  "sun-rays": num("shadow rays into the sun's disc per texel", { default: 64 }),
237506
- "sky-rays": num("hemisphere rays per texel (the sky's irradiance and visibility). 2048 (2026-09-16): shadow rays are cheap (the school's direct pass 2 s a page) and a texel deep in a corridor sees the sky through a far window with 1 ray in 256 — every hit was 410 lux", { default: 2048 }),
237507
- "light-rays": num("shadow rays per lamp per texel (64: at 32 the school's lamp layer read 43 % noise between two seeds, the ceilings and doors next to a lamp 48 %)", { default: 64 }),
237508
- "bounce-rays": num("bounce paths per texel; 0 = direct light only. 128 (2026-09-16): a dark room's floor in the school reads 240 % noise at 64, 160 % at 128, 130 % at 256 — the last doubling costs 2x the bake time for little", { default: 128 }),
237731
+ "sky-rays": num("hemisphere rays per texel (the sky's irradiance and visibility). 2048 (2026-09-16): shadow rays are cheap (an interior level's direct pass: 2 s a page) and a texel deep in a corridor sees the sky through a far window with 1 ray in 256 — every hit was 410 lux", { default: 2048 }),
237732
+ "light-rays": num("shadow rays per lamp per texel (64: at 32 an interior level's lamp layer read 43 % noise between two seeds, the ceilings and doors next to a lamp 48 %)", { default: 64 }),
237733
+ "bounce-rays": num("bounce paths per texel; 0 = direct light only. 128 (2026-09-16): a dark room's floor of an interior level reads 240 % noise at 64, 160 % at 128, 130 % at 256 — the last doubling costs 2x the bake time for little", { default: 128 }),
237509
237734
  bounces: num("path vertices at most (Russian roulette from the third); 0 = direct light only", { default: 3 }),
237510
237735
  "patch-samples": num("next-event samples per texel over the BRIGHT PATCHES of a first direct pass (sunlit floors, the ceiling around a lamp) — what keeps the bounce in dark rooms from arriving as impulses (every path vertex takes one more); 0 = the cosine paths alone", { default: 64 }),
237511
- split: bool('also write <name>-direct[_n].ktx2 / <name>-indirect[_n].ktx2 — the pages the "direct" / "indirect" views bind (list them as env.lightmap.direct / .indirect)'),
237512
- denoise: str("the AI denoiser (--denoiser) over: indirect = the bounce alone (the range 2026-09-16: bounce noise 57 % -> 23 % of the mean, per-instance means unchanged), no-sun = + the sky and the lamps (the sky through a small opening: 0..3 hits of 256 rays, a texel is one of four values), all = + the sun (its shadow edges too: a sunlit pillar's mean moved 6 %), none. School, two seeds: no-sun took the sky 1.8 % -> 0.5 %, the lamps 43 % -> 21 %", { value: "<none|indirect|no-sun|all>", default: "no-sun" }),
237736
+ split: bool('also write <stem>-direct[_n].ktx2 / <stem>-indirect[_n].ktx2 — the pages the "direct" / "indirect" views bind (the manifest names them)'),
237737
+ denoise: str("the AI denoiser (--denoiser) over: indirect = the bounce alone (an interior level, 2026-09-16: bounce noise 57 % -> 23 % of the mean, per-instance means unchanged), no-sun = + the sky and the lamps (the sky through a small opening: 0..3 hits of 256 rays, a texel is one of four values), all = + the sun (its shadow edges too: a sunlit pillar's mean moved 6 %), none. An interior level, two seeds: no-sun took the sky 1.8 % -> 0.5 %, the lamps 43 % -> 21 %", { value: "<none|indirect|no-sun|all>", default: "no-sun" }),
237513
237738
  tracer: str("which GPU API traces: auto = OptiX where an NVIDIA driver with OptiX opens, else Vulkan ray query — any card with hardware ray tracing (RTX, Radeon RX 6000+, Arc) with its driver alone; optix / vulkan by name (the bake fails when it cannot open)", { value: "<auto|optix|vulkan>", default: "auto" }),
237514
237739
  denoiser: str("which AI denoiser --denoise runs: auto = OptiX's on an NVIDIA card, else Intel Open Image Denoise (its CUDA / HIP / CPU device, the DLLs next to the host); optix / oidn by name. Independent of --tracer; one that cannot open is a warning and the pages stay as traced", { value: "<auto|optix|oidn>", default: "auto" }),
237515
237740
  "light-radius": num("lamps: the emitter's radius (m) — soft shadows, no 1/d² singularity", { default: 0.05 }),
@@ -237523,13 +237748,13 @@ Lightmap UVs: the GLBs the app references that have none (TEXCOORD_1) are unwrap
237523
237748
  "probe-indoor": bool("keep only the probes under a roof — those whose open share of the sphere (rays from the point that meet nothing) is at most 15 %, holes in the geometry or not: outdoor statics reflect the sky through their baked sky visibility instead — an environment map without the ground, but no outdoor probe can reach indoors and the atlas shrinks"),
237524
237749
  "probe-layout": str("rooms = every indoor point of the grid slides to the middle of its room and the points of one room merge: one probe per room, far from the walls, its box the room (stations every ~8 m along a room over 12 m), outdoor points stay on the grid; grid = everything stays on the grid. Either way the pages carry which probes each block of texels sees (rays at bake time)", { value: "<rooms|grid>", default: "rooms" }),
237525
237750
  "min-texels": num("instances smaller than this many ATLAS TEXELS along every axis (x --texel: 12 at 5 cm = 0.6 m) get no place in the atlas: they still cast their baked shadow and bounce, their own light is baked where they stand (six sides, no atlas texels) — cartridges, boxes, pipe elbows, a fire extinguisher: a baked shadow does not read on them and their tight charts are where the seams are. 0 = everything bakes; --dry-run lists what falls under it", { default: 12 }),
237526
- "thin-texels": num("ROUND AND NARROW objects leave the atlas like --min-texels: an instance none of whose lightmap charts is wider than this many texels across its short side (at --texel) — a railing's tubes, a cable, a pipe run, blinds, a gun barrel. A strip of 1-3 texels around a tube cannot hold the light's change around it and its uv seam shows as a step (the range's railing: two texels 5 mm apart on the seam, one lit, one shadowed); the ambient cube baked where it stands is smooth. A flat object has a wide chart somewhere and stays. 0 = off; --dry-run lists what falls under it", { default: 4 }),
237527
- cube: str(`BAKE AS CUBES BY NAME: a regular expression (ECMAScript, case-sensitive) searched in every record's key — <node path>@<ordinal>, one record per mesh of a model, so SM_pipe takes every pipe node and @1$ the second mesh of each. A record that matches leaves the atlas like a --min-texels / --thin-texels one, whatever its size or charts: an occluder whose own light is baked where it stands as ambient cubes (in slabs along a long one, --object-slab) — a pipe run, a fence, a prop whose lightmap seams show. Repeat the flag (--cube SM_pipe --cube SM_fence) or join with | in one (--cube "SM_pipe|SM_fence", quoted for the shell); a pattern that starts with - goes as --cube=<regex>. The scene's env.lightmap.cube adds its own patterns, a node's lightmap: 'cube' marks one node by hand; --dry-run lists what matches`, { value: "<regex>" }),
237751
+ "thin-texels": num("ROUND AND NARROW objects leave the atlas like --min-texels: an instance none of whose lightmap charts is wider than this many texels across its short side (at --texel) — a railing's tubes, a cable, a pipe run, blinds, a gun barrel. A strip of 1-3 texels around a tube cannot hold the light's change around it and its uv seam shows as a step (a railing: two texels 5 mm apart on the seam, one lit, one shadowed); the ambient cube baked where it stands is smooth. A flat object has a wide chart somewhere and stays. 0 = off; --dry-run lists what falls under it", { default: 4 }),
237752
+ cube: str(`BAKE AS CUBES BY NAME: a regular expression (ECMAScript, case-sensitive) searched in every record's key — <node path>@<ordinal>, one record per mesh of a model, so SM_pipe takes every pipe node and @1$ the second mesh of each. A record that matches leaves the atlas like a --min-texels / --thin-texels one, whatever its size or charts: an occluder whose own light is baked where it stands as ambient cubes (in slabs along a long one, --object-slab) — a pipe run, a fence, a prop whose lightmap seams show. Repeat the flag (--cube SM_pipe --cube SM_fence) or join with | in one (--cube "SM_pipe|SM_fence", quoted for the shell); a pattern that starts with - goes as --cube=<regex>. The level's bake() option cube adds its own patterns, a node's lightmap: 'cube' marks one node by hand; --dry-run lists what matches`, { value: "<regex>" }),
237528
237753
  "object-slab": num("A LONG object the atlas left out (--min-texels, --thin-texels, --cube) takes its light in SLABS of this many metres along the longest axis of its bounds — round(length / this) ambient cubes, 16 at most — a 6 m pipe run, a railing along a wall: one end under a lamp, the other in the dark, where one cube gave all of it the mean. The shader blends the two slabs around each pixel along that axis. 0 = one cube an object", { value: "<m>", default: 1 }),
237529
237754
  "emissive-nits": num("EMISSIVE SURFACES LIGHT THE LEVEL: cd/m² of emission 1.0 (emissive factor x strength x map) — every lit region of an emissive surface (a panel drawn in a ceiling's texture, a screen, a sign) bakes as a rectangle lamp. Default: what the level's camera makes of 1.0, 1.2 x 2^EV100, so a panel lights as brightly as it looks. 0 = emissive surfaces light nothing"),
237530
237755
  "detail-texels": num("A SMALL SURFACE GETS A FINER TEXEL: an instance whose surface is under this many atlas texels (area / texel²) is laid out denser by sqrt(this / its texels), 3x at most — a pipe of 1 m is ~300 texels at 5 cm, its charts a few texels wide and every chart edge a step; at 2000 it bakes at ~2 cm. 0 = off", { default: 2000 }),
237531
- volume: num("THE LIGHT GRID for movers: the cell size in metres (0.75 of it tall) — per cell in free space the baked ambient light (sky, lamps, every bounce; not the direct sun, which stays real-time on movers), links between cells a ray connects so nothing blends through a wall; written as <name>.lgrid, list it as env.lightmap.volume. 0 = none", { default: 1 }),
237532
- "probe-box": str("only grid cells whose centre lies inside this world box get a probe: x0,y0,z0,x1,y1,z1 in metres — keeps probes off a roof top the player never sees (the range: 79 probes above a ceiling with holes where the fixtures were bled the sky onto the walls). Default: the receivers' bounds", { value: "<x0,y0,z0,x1,y1,z1>" }),
237756
+ volume: num("THE LIGHT GRID for movers: the cell size in metres (0.75 of it tall) — per cell in free space the baked ambient light (sky, lamps, every bounce; not the direct sun, which stays real-time on movers), links between cells a ray connects so nothing blends through a wall; written as <stem>.lgrid. 0 = none", { default: 1 }),
237757
+ "probe-box": str("only grid cells whose centre lies inside this world box get a probe: x0,y0,z0,x1,y1,z1 in metres — keeps probes off a roof top the player never sees (79 probes above a ceiling with holes where the fixtures were bled the sky onto the walls of a level). Default: the receivers' bounds", { value: "<x0,y0,z0,x1,y1,z1>" }),
237533
237758
  "debug-dir": str("keep the float layers per page (PFM) here for `lightmap inspect` — gigabytes per level; a `.lightmap/` dir inside the project is skipped by the compile scan, anywhere else must be outside the project", { value: "<dir>" }),
237534
237759
  "dry-run": bool("only the atlas LAYOUT: pages, texel size, the largest rects — no tracing, nothing written (seconds, not minutes; the host still loads the level)"),
237535
237760
  frames: num("host frames to run before giving up — load headroom", { default: 900 }),
@@ -237540,14 +237765,16 @@ Lightmap UVs: the GLBs the app references that have none (TEXCOORD_1) are unwrap
237540
237765
  logs: bool("forward the host's output"),
237541
237766
  "no-unwrap": bool("leave the referenced GLBs as they are: no lightmap unwrap of those without TEXCOORD_1 (they bake without lightmap uvs)")
237542
237767
  },
237543
- examples: ["lecodes lightmap bake", "lecodes lightmap bake --entry school.ts --name school --texel 0.04 --pages 4 --debug-dir .lightmap/school --logs"],
237768
+ examples: [
237769
+ "lecodes lightmap bake",
237770
+ "lecodes lightmap bake --dry-run --logs",
237771
+ "lecodes lightmap bake --entry level2.ts --texel 0.04 --debug-dir .lightmap/level2 --logs"
237772
+ ],
237544
237773
  run: async ({ flags, argv }) => {
237545
237774
  const { root } = localProject();
237546
- const outDir = resolve22(root, flags.out);
237547
- const stem = flags.name;
237548
237775
  const cube = cubePatternsOf(argv);
237549
- if (!/^[A-Za-z0-9_-]+$/.test(stem))
237550
- throw new CliError(`--name wants a plain file stem (letters, digits, - and _), got "${stem}".`);
237776
+ if (flags.name !== undefined && !/^[A-Za-z0-9_.-]+$/.test(flags.name))
237777
+ throw new CliError(`--name wants the stem of a .bake (its file name without .bake), got "${flags.name}".`);
237551
237778
  const entryFile = flags.entry;
237552
237779
  if (!existsSync41(resolve22(root, entryFile))) {
237553
237780
  throw new CliError(`No ${entryFile} in ${root} — pass --entry <file> (the file that builds the scene and calls Lightmap.load).`);
@@ -237560,8 +237787,9 @@ Lightmap UVs: the GLBs the app references that have none (TEXCOORD_1) are unwrap
237560
237787
  if (!["auto", "optix", "oidn"].includes(flags.denoiser))
237561
237788
  throw new CliError(`--denoiser wants auto, optix or oidn, got "${flags.denoiser}".`);
237562
237789
  const cfg = {
237563
- outDir: outDir.replace(/\\/g, "/"),
237564
- stem,
237790
+ root: root.replace(/\\/g, "/"),
237791
+ ...flags.out ? { outDir: resolve22(root, flags.out).replace(/\\/g, "/") } : {},
237792
+ ...flags.name !== undefined ? { stem: flags.name } : {},
237565
237793
  size: range("size", flags.size, 256, 16384),
237566
237794
  texel: range("texel", flags.texel, 0.001, 2),
237567
237795
  pages: range("pages", flags.pages, 1, 16),
@@ -237604,23 +237832,26 @@ Lightmap UVs: the GLBs the app references that have none (TEXCOORD_1) are unwrap
237604
237832
  const entryText = `globalThis.__lecodesLightmap = ${JSON.stringify(cfg)}
237605
237833
  import ${JSON.stringify(importPath)}
237606
237834
  `;
237607
- mkdirSync25(outDir, { recursive: true });
237608
- ensurePlaceholders(outDir, stem);
237609
- if (debugDir)
237610
- mkdirSync25(debugDir, { recursive: true });
237611
237835
  const desktopRenderer = "gl";
237612
- const { js, resources } = await buildBundle(bundleOptions(flags), {
237836
+ const { js, resources, bakes } = await buildBundle(bundleOptions(flags), {
237613
237837
  localAssets: true,
237614
237838
  desktopRenderer,
237615
237839
  extraEntries: [{ path: entryPath, type: "text", text: entryText }],
237616
237840
  entryOverride: entryPath
237617
237841
  });
237842
+ const target = pickTarget2(bakes, flags.name, entryFile);
237843
+ const stem = stemOf(target.path);
237844
+ const outDir = flags.out ? resolve22(root, flags.out) : dirname23(target.absPath);
237845
+ mkdirSync25(outDir, { recursive: true });
237846
+ if (debugDir)
237847
+ mkdirSync25(debugDir, { recursive: true });
237848
+ note(`Target: ${relative7(root, join46(outDir, `${stem}.bake`)).replace(/\\/g, "/")}${target.exists ? "" : " (the first bake: nothing there yet)"}${flags.out ? " (--out)" : ""}`);
237618
237849
  if (!flags["no-unwrap"])
237619
237850
  await unwrapReferenced(js, resources, root, cfg.texel);
237620
237851
  const scratch = mkdtempSync4(join46(tmpdir4(), "lecodes-lightmap-"));
237621
237852
  const outPng = join46(scratch, "frame.png");
237622
237853
  const bounce = cfg.bounceRays > 0 && cfg.bounces > 0 ? `, ${cfg.bounceRays} bounce paths of ${cfg.bounces}${cfg.patchSamples > 0 ? ` + ${cfg.patchSamples} patch samples` : ""}${cfg.denoise !== "none" ? `, denoised (${cfg.denoise})` : ", raw"}` : ", no bounce";
237623
- note(`Baking up to ${cfg.pages} × ${cfg.size}² at ${(cfg.texel * 100).toFixed(1)} cm texels (${cfg.sunRays} sun + ${cfg.skyRays} sky + ${cfg.lightRays} lamp rays${bounce}) → ${relative7(root, outDir) || "."}/${stem}.*…`);
237854
+ note(`Baking up to ${cfg.pages} × ${cfg.size}² at ${(cfg.texel * 100).toFixed(1)} cm texels (${cfg.sunRays} sun + ${cfg.skyRays} sky + ${cfg.lightRays} lamp rays${bounce}) → ${relative7(root, outDir).split(sep5).join("/") || "."}/${stem}.*…`);
237624
237855
  let output = "";
237625
237856
  if (flags["dry-run"])
237626
237857
  process.env.CBK_LAYOUT_ONLY = "1";
@@ -237664,16 +237895,12 @@ import ${JSON.stringify(importPath)}
237664
237895
  }
237665
237896
  if (/\[lightmap\] done/.test(output)) {
237666
237897
  const data = readBake(join46(outDir, `${stem}.bake`));
237667
- const rel = (f) => relative7(root, join46(outDir, f)).replace(/\\/g, "/");
237668
- const assets = (files) => files.length === 1 ? `asset('<rel>/${rel(files[0])}')` : `[${files.map((f) => `asset('<rel>/${rel(f)}')`).join(", ")}]`;
237669
237898
  const splitFiles = data.direct && data.indirect ? ` + ${data.direct.join(", ")} + ${data.indirect.join(", ")}` : "";
237670
- const splitDef = data.direct && data.indirect ? `, direct: ${assets(data.direct)}, indirect: ${assets(data.indirect)}` : "";
237671
237899
  const probeFiles = data.probes ? ` + ${data.probes.file} (${data.probes.count} probe(s) of ${data.probes.size}² at ${data.probes.spacing} m)` : "";
237672
237900
  const gridFile = data.volume ? ` + ${data.volume.file} (light grid ${data.volume.n.join(" x ")}, ${data.volume.valid} cell(s) in free space)` : "";
237673
- const probeDef = (data.probes ? `, probes: asset('<rel>/${rel(data.probes.file)}')` : "") + (data.volume ? `, volume: asset('<rel>/${rel(data.volume.file)}')` : "") + (data.uv1 ? `, uv1: asset('<rel>/${rel(data.uv1)}')` : "");
237674
- const uv1File = data.uv1 ? ` + ${data.uv1} (the packed lightmap uv1 — list it as env.lightmap.uv1, the rects map it)` : "";
237675
- logErr(c.green(`✓ baked → ${relative7(root, outDir) || "."}/${stem}.bake + ${data.light.join(", ")} + ${data.aux.join(", ")}${splitFiles}${probeFiles}${gridFile}${uv1File}`));
237676
- logErr(c.dim(` scene file: env.lightmap: { data: asset('<rel>/${rel(`${stem}.bake`)}'), light: ${assets(data.light)}, aux: ${assets(data.aux)}${splitDef}${probeDef} } — asset() paths are relative to the scene file`));
237901
+ const uv1File = data.uv1 ? ` + ${data.uv1} (the packed lightmap uv1)` : "";
237902
+ logErr(c.green(`✓ baked → ${relative7(root, outDir).replace(/\\/g, "/") || "."}/${stem}.bake + ${data.light.join(", ")} + ${data.aux.join(", ")}${splitFiles}${probeFiles}${gridFile}${uv1File}`));
237903
+ logErr(c.dim(flags.out ? ` written to --out: the level's bake() still reads ${target.path}` : ` the manifest names them: the next compile of ${entryFile} stages them (bake('…') in the scene reads it)`));
237677
237904
  if (debugDir)
237678
237905
  logErr(c.dim(` debug layers: ${relative7(root, debugDir)} (lecodes lightmap inspect --bake ${relative7(root, join46(outDir, `${stem}.bake`)).replace(/\\/g, "/")} --debug-dir ${relative7(root, debugDir).replace(/\\/g, "/")})`));
237679
237906
  return;
@@ -237693,15 +237920,16 @@ var stats = defineCommand({
237693
237920
  summary: "The per-instance numbers a .bake carries: mean lux, sun / sky visibility, texels, rect",
237694
237921
  description: "Reads the .bake alone (no debug layers needed). One row per receiver, sorted by mean lux unless --sort says otherwise; --key filters by a regular expression over the keys.",
237695
237922
  flags: {
237696
- bake: str("the .bake file", { value: "<file>", default: "assets/lightmap/lightmap.bake" }),
237923
+ bake: str("the .bake file (default: the project's one .bake)", { value: "<file>" }),
237697
237924
  key: str("only instances whose key matches this regular expression", { value: "<regex>" }),
237698
237925
  sort: str("row order", { value: "<lux|key|texels|sun|sky>", default: "lux" }),
237699
237926
  limit: num("rows to print (0 = all)", { default: 60 })
237700
237927
  },
237701
- examples: ["lecodes lightmap stats --bake assets/school/lightmap/school.bake --key Wall", "lecodes lightmap stats --sort sky --limit 0"],
237928
+ examples: ["lecodes lightmap stats --key Wall", "lecodes lightmap stats --bake assets/level2/lightmap/level2.bake --sort sky --limit 0"],
237702
237929
  run: ({ flags }) => {
237703
237930
  const { root } = localProject();
237704
- const data = readBake(resolve22(root, flags.bake));
237931
+ const bakeFile = bakeFileOf(root, flags.bake);
237932
+ const data = readBake(bakeFile);
237705
237933
  const re = flags.key ? new RegExp(flags.key) : null;
237706
237934
  let rows = data.instances.filter((i) => i.receiver && (!re || re.test(i.key)));
237707
237935
  const sorters = {
@@ -237718,7 +237946,7 @@ var stats = defineCommand({
237718
237946
  const total = rows.length;
237719
237947
  if (flags.limit > 0)
237720
237948
  rows = rows.slice(0, flags.limit);
237721
- log(`${basename18(flags.bake)}: ${data.pages} page(s) of ${data.size}, ${(data.texel * 100).toFixed(2)} cm texels, ${data.texels ?? "?"} texels, lightScale ${data.lightScale}`);
237949
+ log(`${basename18(bakeFile)}: ${data.pages} page(s) of ${data.size}, ${(data.texel * 100).toFixed(2)} cm texels, ${data.texels ?? "?"} texels, lightScale ${data.lightScale}`);
237722
237950
  log(`${"key".padEnd(48)} ${"page".padStart(4)} ${"rect".padStart(20)} ${"texels".padStart(8)} ${"lux".padStart(9)} ${"sun".padStart(6)} ${"sky".padStart(6)} ${"dir".padStart(6)}`);
237723
237951
  for (const r of rows) {
237724
237952
  const rect = r.rect ? `${r.rect[0]},${r.rect[1]} ${r.rect[2]}x${r.rect[3]}` : "";
@@ -237733,7 +237961,7 @@ var diff = defineCommand({
237733
237961
  summary: "The noise of a bake, per instance: two bakes that differ only by --seed, compared texel by texel",
237734
237962
  description: "Bake the level twice with different --seed (and --out / --debug-dir, so the second does not overwrite the first — a `.lightmap/` dir inside the project is skipped by the compile scan). Both must share the layout (same instances, rects and pages — a seed changes nothing else). Per instance: the mean of the layer, the noise as the RMS of (a − b) / √2 over the mean, in %, and the share of texels that differ by more than half their mean. Sorted by noise unless --sort says otherwise.",
237735
237963
  flags: {
237736
- bake: str("the first .bake", { value: "<file>", default: "assets/lightmap/lightmap.bake" }),
237964
+ bake: str("the first .bake (default: the project's one .bake)", { value: "<file>" }),
237737
237965
  "debug-dir": str("where the first bake kept its PFM layers", { value: "<dir>", default: ".lightmap" }),
237738
237966
  bake2: str("the second .bake (another --seed)", { value: "<file>" }),
237739
237967
  "debug-dir2": str("where the second bake kept its PFM layers", { value: "<dir>" }),
@@ -237744,21 +237972,23 @@ var diff = defineCommand({
237744
237972
  limit: num("rows to print (0 = all)", { default: 40 })
237745
237973
  },
237746
237974
  examples: [
237747
- "lecodes lightmap bake --entry range.ts --name range --seed 2 --out .lightmap/range-seed2/bake --debug-dir .lightmap/range-seed2 --logs",
237748
- "lecodes lightmap diff --bake assets/range/lightmap/range.bake --debug-dir .lightmap/range --bake2 .lightmap/range-seed2/bake/range.bake --debug-dir2 .lightmap/range-seed2 --layer bounce"
237975
+ "lecodes lightmap bake --debug-dir .lightmap/seed1 --logs",
237976
+ "lecodes lightmap bake --seed 2 --out .lightmap/seed2/bake --debug-dir .lightmap/seed2 --logs",
237977
+ "lecodes lightmap diff --debug-dir .lightmap/seed1 --bake2 .lightmap/seed2/bake/level.bake --debug-dir2 .lightmap/seed2 --layer bounce"
237749
237978
  ],
237750
237979
  run: ({ flags }) => {
237751
237980
  const { root } = localProject();
237752
237981
  if (!flags.bake2 || !flags["debug-dir2"])
237753
237982
  throw new CliError("--bake2 and --debug-dir2 name the second bake (another --seed).");
237754
- const a = readBake(resolve22(root, flags.bake)), b = readBake(resolve22(root, flags.bake2));
237983
+ const bakeFile = bakeFileOf(root, flags.bake);
237984
+ const a = readBake(bakeFile), b = readBake(resolve22(root, flags.bake2));
237755
237985
  if (a.size !== b.size || a.pages !== b.pages)
237756
237986
  throw new CliError(`The bakes differ in layout (${a.pages} × ${a.size} vs ${b.pages} × ${b.size}) — a diff needs the same scene baked twice with another --seed.`);
237757
237987
  const ra = a.instances.filter((i) => i.receiver), rb = b.instances.filter((i) => i.receiver);
237758
237988
  if (ra.length !== rb.length || ra.some((i, k) => i.key !== rb[k].key || i.page !== rb[k].page || String(i.rect) !== String(rb[k].rect))) {
237759
237989
  throw new CliError("The bakes list different instances or rects — a diff needs the same scene baked twice with another --seed.");
237760
237990
  }
237761
- const stemA = basename18(flags.bake, ".bake"), stemB = basename18(flags.bake2, ".bake");
237991
+ const stemA = basename18(bakeFile, ".bake"), stemB = basename18(flags.bake2, ".bake");
237762
237992
  const dirA = resolve22(root, flags["debug-dir"]), dirB = resolve22(root, flags["debug-dir2"]);
237763
237993
  const layer = flags.layer;
237764
237994
  if (!["irradiance", "bounce", "sun", "sky", "lamps"].includes(layer))
@@ -237831,7 +238061,7 @@ var diff = defineCommand({
237831
238061
  }
237832
238062
  });
237833
238063
  var readPfm = (file) => {
237834
- const buf = readFileSync35(file);
238064
+ const buf = readFileSync36(file);
237835
238065
  let o = 0;
237836
238066
  const token = () => {
237837
238067
  while (o < buf.length && /\s/.test(String.fromCharCode(buf[o])))
@@ -237911,7 +238141,7 @@ var inspect = defineCommand({
237911
238141
  summary: "PNGs from a bake's debug layers: whole pages, or one instance's rect enlarged",
237912
238142
  description: "Reads the PFM float layers `bake --debug-dir` kept next to the .bake's rects. Without --key: one PNG per page and layer with every rect outlined. With --key: the matching instances' rects (plus a margin) enlarged by --scale, one PNG per instance and layer. Lux layers are shown as log10 over --lux-range (or linear over --white); vectors as x0.5+0.5.",
237913
238143
  flags: {
237914
- bake: str("the .bake file", { value: "<file>", default: "assets/lightmap/lightmap.bake" }),
238144
+ bake: str("the .bake file (default: the project's one .bake)", { value: "<file>" }),
237915
238145
  "debug-dir": str("where the bake kept its PFM layers", { value: "<dir>", default: ".lightmap" }),
237916
238146
  out: str("where the PNGs go", { value: "<dir>", default: ".lightmap/png" }),
237917
238147
  key: str("instances whose key matches this regular expression (default: whole pages)", { value: "<regex>" }),
@@ -237922,10 +238152,10 @@ var inspect = defineCommand({
237922
238152
  white: num("linear mode: the lux shown as white (0 = log mode)", { default: 0 }),
237923
238153
  limit: num("instances to render with --key (0 = all)", { default: 24 })
237924
238154
  },
237925
- examples: ["lecodes lightmap inspect --bake assets/school/lightmap/school.bake --debug-dir .lightmap/school", "lecodes lightmap inspect --key 'Wall_05' --layer irradiance,sun,sky,lamps --scale 6"],
238155
+ examples: ["lecodes lightmap inspect --debug-dir .lightmap/level", "lecodes lightmap inspect --key 'Wall_05' --layer irradiance,sun,sky,lamps --scale 6"],
237926
238156
  run: ({ flags }) => {
237927
238157
  const { root } = localProject();
237928
- const bakeFile = resolve22(root, flags.bake);
238158
+ const bakeFile = bakeFileOf(root, flags.bake);
237929
238159
  const data = readBake(bakeFile);
237930
238160
  const stem = basename18(bakeFile).replace(/\.bake$/, "");
237931
238161
  const dbg = resolve22(root, flags["debug-dir"]);
@@ -238097,7 +238327,7 @@ init_types4();
238097
238327
  // src/commands/push.ts
238098
238328
  init_cli();
238099
238329
  init_api();
238100
- import { readFileSync as readFileSync37 } from "node:fs";
238330
+ import { readFileSync as readFileSync38 } from "node:fs";
238101
238331
  import { basename as basename19 } from "node:path";
238102
238332
  init_localFiles();
238103
238333
  init_manifest();
@@ -238106,7 +238336,7 @@ init_manifest();
238106
238336
  init_cli();
238107
238337
  init_api();
238108
238338
  init_localFiles();
238109
- import { existsSync as existsSync42, readFileSync as readFileSync36 } from "node:fs";
238339
+ import { existsSync as existsSync42, readFileSync as readFileSync37 } from "node:fs";
238110
238340
  import { resolve as resolve23 } from "node:path";
238111
238341
  init_shared();
238112
238342
  var compareVersions = (a, b) => {
@@ -238193,11 +238423,11 @@ var upload2 = defineCommand({
238193
238423
  const path = resolve23(file);
238194
238424
  if (!existsSync42(path))
238195
238425
  throw new CliError(`No such file: ${file}`);
238196
- const code = readFileSync36(path, "utf8");
238426
+ const code = readFileSync37(path, "utf8");
238197
238427
  const sdk = /^\/\/ sdk: (\S+)/m.exec(code)?.[1];
238198
238428
  if (!sdk)
238199
238429
  throw new CliError(`${file} is not a LeCodes bundle: no \`// sdk:\` header line. \`lecodes compile --remote-assets -o app.js\` writes one.`);
238200
- const map = existsSync42(`${path}.map`) ? readFileSync36(`${path}.map`, "utf8") : null;
238430
+ const map = existsSync42(`${path}.map`) ? readFileSync37(`${path}.map`, "utf8") : null;
238201
238431
  const { root, manifest, apiUrl, token } = linkedProject();
238202
238432
  if (hasChanges(diffLocal(manifest, scanProject(root, manifest))))
238203
238433
  await runPush({ message: flags.message || "Release" });
@@ -238302,9 +238532,9 @@ var runPush = async (opts) => {
238302
238532
  if (source)
238303
238533
  entry.assetId = source.id;
238304
238534
  if (isTextKind(kind))
238305
- entry.text = readFileSync37(file.absPath, "utf8");
238535
+ entry.text = readFileSync38(file.absPath, "utf8");
238306
238536
  else if (!source || source.sha !== file.sha) {
238307
- const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename19(file.path), readFileSync37(file.absPath));
238537
+ const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename19(file.path), readFileSync38(file.absPath));
238308
238538
  entry.blobFileId = blob.id;
238309
238539
  uploaded++;
238310
238540
  }
@@ -238504,7 +238734,7 @@ var login_default = defineCommand({
238504
238734
  });
238505
238735
 
238506
238736
  // src/commands/navmesh.ts
238507
- import { existsSync as existsSync44, mkdirSync as mkdirSync26, mkdtempSync as mkdtempSync5, readdirSync as readdirSync18, readFileSync as readFileSync38, rmSync as rmSync13, statSync as statSync15, unlinkSync as unlinkSync2, writeFileSync as writeFileSync28 } from "node:fs";
238737
+ import { existsSync as existsSync44, mkdirSync as mkdirSync26, mkdtempSync as mkdtempSync5, readdirSync as readdirSync19, readFileSync as readFileSync39, rmSync as rmSync13, statSync as statSync14, unlinkSync as unlinkSync2, writeFileSync as writeFileSync28 } from "node:fs";
238508
238738
  import { tmpdir as tmpdir5 } from "node:os";
238509
238739
  import { basename as basename21, join as join48, relative as relative8, resolve as resolve25 } from "node:path";
238510
238740
  init_cli();
@@ -239307,7 +239537,7 @@ var guessName = (root) => {
239307
239537
  continue;
239308
239538
  let entries;
239309
239539
  try {
239310
- entries = readdirSync18(dir);
239540
+ entries = readdirSync19(dir);
239311
239541
  } catch {
239312
239542
  continue;
239313
239543
  }
@@ -239315,7 +239545,7 @@ var guessName = (root) => {
239315
239545
  if (!f.endsWith(".scene.ts"))
239316
239546
  continue;
239317
239547
  try {
239318
- if (/\bnavmesh\s*:/.test(readFileSync38(join48(dir, f), "utf8")))
239548
+ if (/\bnavmesh\s*:/.test(readFileSync39(join48(dir, f), "utf8")))
239319
239549
  return f.slice(0, -".scene.ts".length);
239320
239550
  } catch {}
239321
239551
  }
@@ -239465,7 +239695,7 @@ import ${JSON.stringify(importPath)}
239465
239695
  const geoPath = join48(outDir, `${name}.navgeo`);
239466
239696
  if (!existsSync44(geoPath))
239467
239697
  throw new CliError(`The host reported a dump but ${geoPath} is missing.`);
239468
- const geo = readNgeo(new Uint8Array(readFileSync38(geoPath)));
239698
+ const geo = readNgeo(new Uint8Array(readFileSync39(geoPath)));
239469
239699
  const params = {
239470
239700
  agentRadius: range2("radius", flags.radius, config.agent.radius, 0.05, 10),
239471
239701
  agentHeight: range2("height", flags.height, config.agent.height, 0.1, 20),
@@ -239519,7 +239749,7 @@ import ${JSON.stringify(importPath)}
239519
239749
  }
239520
239750
  if (!flags["keep-geometry"])
239521
239751
  unlinkSync2(geoPath);
239522
- const kb = Math.round(statSync15(navmeshPath).size / 1024);
239752
+ const kb = Math.round(statSync14(navmeshPath).size / 1024);
239523
239753
  const b = result.bounds;
239524
239754
  logErr(c.dim(` ${result.tiles} tiles, ${result.polys} polygons, ${(b.max[0] - b.min[0]).toFixed(0)} × ${(b.max[2] - b.min[2]).toFixed(0)} m, ${result.time.toFixed(1)} s`));
239525
239755
  logErr(c.green(`✓ baked → ${relative8(root, navmeshPath).replace(/\\/g, "/")} (${kb} KB)`));
@@ -239539,9 +239769,9 @@ init_cli();
239539
239769
 
239540
239770
  // src/plugin/generate.ts
239541
239771
  init_cli();
239542
- import { existsSync as existsSync45, mkdirSync as mkdirSync27, readFileSync as readFileSync39, readdirSync as readdirSync19, statSync as statSync16, writeFileSync as writeFileSync29 } from "node:fs";
239772
+ import { existsSync as existsSync45, mkdirSync as mkdirSync27, readFileSync as readFileSync40, readdirSync as readdirSync20, statSync as statSync15, writeFileSync as writeFileSync29 } from "node:fs";
239543
239773
  import { createRequire as createRequire7 } from "node:module";
239544
- import { basename as basename22, join as join49, relative as relative9, resolve as resolve26, sep as sep5 } from "node:path";
239774
+ import { basename as basename22, join as join49, relative as relative9, resolve as resolve26, sep as sep6 } from "node:path";
239545
239775
 
239546
239776
  // src/plugin/permissions.ts
239547
239777
  var PERMISSION_KINDS = ["camera", "microphone", "location", "speech"];
@@ -242410,10 +242640,10 @@ const wire = {
242410
242640
  `;
242411
242641
 
242412
242642
  // src/plugin/generate.ts
242413
- var shown = (from, to) => relative9(from, to).split(sep5).join("/");
242643
+ var shown = (from, to) => relative9(from, to).split(sep6).join("/");
242414
242644
  var implementsOf = (dir) => {
242415
242645
  try {
242416
- const raw = JSON.parse(readFileSync39(join49(dir, MANIFEST_FILE), "utf8").replace(/^/, ""));
242646
+ const raw = JSON.parse(readFileSync40(join49(dir, MANIFEST_FILE), "utf8").replace(/^/, ""));
242417
242647
  return typeof raw.implements === "string" ? raw.implements : null;
242418
242648
  } catch {
242419
242649
  return null;
@@ -242427,7 +242657,7 @@ var findPlugins = (path) => {
242427
242657
  const holder = join49(root, "plugins");
242428
242658
  if (!existsSync45(holder))
242429
242659
  return [];
242430
- return readdirSync19(holder).sort().map((id) => ({ id, dir: join49(holder, id) })).filter((p) => statSync16(p.dir).isDirectory() && isPluginDir(p.dir));
242660
+ return readdirSync20(holder).sort().map((id) => ({ id, dir: join49(holder, id) })).filter((p) => statSync15(p.dir).isDirectory() && isPluginDir(p.dir));
242431
242661
  };
242432
242662
  var loadTypescript = (dir) => {
242433
242663
  for (const from of [join49(dir, "package.json"), import.meta.url]) {
@@ -242444,7 +242674,7 @@ var generatePlugin = (ts, plugin) => {
242444
242674
  const manifest = readManifestIfAny(plugin.dir, plugin.id);
242445
242675
  const implemented = manifest?.implements;
242446
242676
  const file = implemented ? firstPartyContractFile(implemented) : join49(plugin.dir, CONTRACT_FILE);
242447
- const contract = readContract(ts, file, implemented ? firstPartyContractText(file) : readFileSync39(file, "utf8"));
242677
+ const contract = readContract(ts, file, implemented ? firstPartyContractText(file) : readFileSync40(file, "utf8"));
242448
242678
  checkPermissions(plugin, contract, manifest);
242449
242679
  const id = implemented ?? plugin.id;
242450
242680
  const handWritten = existsSync45(join49(plugin.dir, "sdk", `${id}.ts`));
@@ -242484,7 +242714,7 @@ var checkPermissions = (plugin, contract, manifest) => {
242484
242714
  if (kinds.length === 0)
242485
242715
  return;
242486
242716
  const androidManifest = join49(plugin.dir, "android", "src", "main", "AndroidManifest.xml");
242487
- const androidXml = manifest.android ? existsSync45(androidManifest) ? readFileSync39(androidManifest, "utf8") : "" : null;
242717
+ const androidXml = manifest.android ? existsSync45(androidManifest) ? readFileSync40(androidManifest, "utf8") : "" : null;
242488
242718
  const missing = missingDeclarations(kinds, manifest.ios ? manifest.ios.infoPlist ?? {} : null, androidXml === null ? null : usesPermissions(androidXml), androidXml === null ? null : usesFeatures(androidXml));
242489
242719
  const problems = [];
242490
242720
  if (missing.infoPlist.length)
@@ -242499,16 +242729,16 @@ var checkPermissions = (plugin, contract, manifest) => {
242499
242729
  var hostImports = (web) => {
242500
242730
  const found = [];
242501
242731
  const walk = (dir) => {
242502
- for (const entry of readdirSync19(dir).sort()) {
242732
+ for (const entry of readdirSync20(dir).sort()) {
242503
242733
  const path = join49(dir, entry);
242504
- if (statSync16(path).isDirectory()) {
242734
+ if (statSync15(path).isDirectory()) {
242505
242735
  if (entry !== "node_modules" && entry !== "dist")
242506
242736
  walk(path);
242507
242737
  continue;
242508
242738
  }
242509
242739
  if (!/\.(ts|tsx|js|mjs)$/.test(entry))
242510
242740
  continue;
242511
- readFileSync39(path, "utf8").split(`
242741
+ readFileSync40(path, "utf8").split(`
242512
242742
  `).forEach((line, i) => {
242513
242743
  const m = /(?:from\s+|import\s*\(\s*|import\s+)["'](lecodes-[^"']*)["']/.exec(line);
242514
242744
  if (m && !/^\s*\/\//.test(line))
@@ -242529,8 +242759,8 @@ var kotlinPackage = (android, register) => {
242529
242759
  }
242530
242760
  let dir = root;
242531
242761
  while (existsSync45(dir)) {
242532
- const entries = readdirSync19(dir).filter((e) => !e.startsWith("."));
242533
- const dirs = entries.filter((e) => statSync16(join49(dir, e)).isDirectory());
242762
+ const entries = readdirSync20(dir).filter((e) => !e.startsWith("."));
242763
+ const dirs = entries.filter((e) => statSync15(join49(dir, e)).isDirectory());
242534
242764
  if (dirs.length !== 1 || entries.length !== 1)
242535
242765
  break;
242536
242766
  dir = join49(dir, dirs[0]);
@@ -242545,7 +242775,7 @@ var repoSdkPath = (root) => {
242545
242775
  if (!existsSync45(file))
242546
242776
  return DEFAULT_SDK_PATH;
242547
242777
  try {
242548
- const pkg = JSON.parse(readFileSync39(file, "utf8"));
242778
+ const pkg = JSON.parse(readFileSync40(file, "utf8"));
242549
242779
  const path = pkg.lecodes?.iosSdkPath;
242550
242780
  if (path !== undefined && typeof path !== "string")
242551
242781
  throw new PackageError(`package.json lecodes.iosSdkPath: a path, as a string`);
@@ -242565,7 +242795,7 @@ var generate = (path, opts = {}) => {
242565
242795
  const report = { plugins: [], repo: { written: [], stale: [], fresh: [] }, failures: [] };
242566
242796
  const place = (f, entry) => {
242567
242797
  const name = shown(root, f.file) || basename22(f.file);
242568
- const current = existsSync45(f.file) ? readFileSync39(f.file, "utf8") : null;
242798
+ const current = existsSync45(f.file) ? readFileSync40(f.file, "utf8") : null;
242569
242799
  if (current === f.text) {
242570
242800
  entry.fresh.push(name);
242571
242801
  return;
@@ -243002,7 +243232,7 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
243002
243232
  };
243003
243233
 
243004
243234
  // src/commands/render.ts
243005
- import { mkdirSync as mkdirSync30, readFileSync as readFileSync41, writeFileSync as writeFileSync32 } from "node:fs";
243235
+ import { mkdirSync as mkdirSync30, readFileSync as readFileSync42, writeFileSync as writeFileSync32 } from "node:fs";
243006
243236
  import { dirname as dirname25, resolve as resolve30 } from "node:path";
243007
243237
 
243008
243238
  // src/compile/headlessBundle.ts
@@ -243090,7 +243320,7 @@ var compileHeadlessBundle = async (opts = {}) => {
243090
243320
  init_designMeta();
243091
243321
  init_cli();
243092
243322
  init_paths();
243093
- import { existsSync as existsSync47, readFileSync as readFileSync40, statSync as statSync17 } from "node:fs";
243323
+ import { existsSync as existsSync47, readFileSync as readFileSync41, statSync as statSync16 } from "node:fs";
243094
243324
  import { isAbsolute as isAbsolute3, join as join51, relative as relative10, resolve as resolve27 } from "node:path";
243095
243325
  var SCREEN_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs"];
243096
243326
  var SCREEN_ENTRY_PATH = "/.screen-entry.ts";
@@ -243102,7 +243332,7 @@ var findScreenFile = (root, file, dirFlag) => {
243102
243332
  candidates.push(join51(root, (dirFlag ?? DEFAULT_DESIGN_DIR).replace(/[/\\]+$/, ""), "screens", file));
243103
243333
  }
243104
243334
  for (const base of candidates) {
243105
- if (existsSync47(base) && statSync17(base).isFile())
243335
+ if (existsSync47(base) && statSync16(base).isFile())
243106
243336
  return base;
243107
243337
  for (const ext of SCREEN_EXTS)
243108
243338
  if (existsSync47(base + ext))
@@ -243124,7 +243354,7 @@ var resolveScreenTarget = (root, file, opts = {}) => {
243124
243354
  throw new CliError(`"${file}" is outside the project (${root}) — a screen renders as part of its project, so its ` + `imports and asset() files resolve from the same tree.`);
243125
243355
  }
243126
243356
  const key = toKey(rel);
243127
- if (!/^\s*export\s+default\b/m.test(readFileSync40(abs, "utf8"))) {
243357
+ if (!/^\s*export\s+default\b/m.test(readFileSync41(abs, "utf8"))) {
243128
243358
  throw new CliError(`${key.slice(1)} has no default export. A screen file ends with ` + `\`export default UIScreen([...])\` — or \`export default (state) => UIScreen([...])\` for screen states.`);
243129
243359
  }
243130
243360
  const invoke = opts.state !== undefined ? `screen(${JSON.stringify(opts.state)})` : "screen()";
@@ -243921,7 +244151,7 @@ var resolveFixtures = (value, baseDir) => {
243921
244151
  if (typeof value === "string") {
243922
244152
  const abs = resolve30(baseDir, value);
243923
244153
  try {
243924
- map = JSON.parse(readFileSync41(abs, "utf8"));
244154
+ map = JSON.parse(readFileSync42(abs, "utf8"));
243925
244155
  } catch (e) {
243926
244156
  throw new CliError(`Can't read fixtures "${value}": ${e instanceof Error ? e.message : String(e)}`);
243927
244157
  }
@@ -243934,7 +244164,7 @@ var resolveFixtures = (value, baseDir) => {
243934
244164
  var readScenarioFile = (path) => {
243935
244165
  let raw;
243936
244166
  try {
243937
- raw = JSON.parse(readFileSync41(resolve30(path), "utf8"));
244167
+ raw = JSON.parse(readFileSync42(resolve30(path), "utf8"));
243938
244168
  } catch (e) {
243939
244169
  throw new CliError(`Can't read scenario "${path}": ${e instanceof Error ? e.message : String(e)}`);
243940
244170
  }
@@ -244279,7 +244509,7 @@ var renderHeadless = async (flags, file) => {
244279
244509
 
244280
244510
  // src/commands/scene.ts
244281
244511
  init_cli();
244282
- import { existsSync as existsSync50, readFileSync as readFileSync43, readdirSync as readdirSync20, statSync as statSync18, writeFileSync as writeFileSync34 } from "node:fs";
244512
+ import { existsSync as existsSync50, readFileSync as readFileSync44, readdirSync as readdirSync21, statSync as statSync17, writeFileSync as writeFileSync34 } from "node:fs";
244283
244513
  import { basename as basename23, dirname as dirname26, join as join54, relative as relative11, resolve as resolve31 } from "node:path";
244284
244514
 
244285
244515
  // src/compile/sceneCompile.ts
@@ -244288,7 +244518,7 @@ init_ignore();
244288
244518
  init_cli();
244289
244519
  init_projectCompile();
244290
244520
  import { createHash as createHash9 } from "node:crypto";
244291
- import { existsSync as existsSync49, mkdirSync as mkdirSync31, readFileSync as readFileSync42, writeFileSync as writeFileSync33 } from "node:fs";
244521
+ import { existsSync as existsSync49, mkdirSync as mkdirSync31, readFileSync as readFileSync43, writeFileSync as writeFileSync33 } from "node:fs";
244292
244522
  import { join as join53 } from "node:path";
244293
244523
  var TEXT_EXT = /\.(ts|js|json|svg|txt|md)$/i;
244294
244524
  var rel = (key) => key.replace(/^\//, "");
@@ -244300,11 +244530,11 @@ var compileSceneEditor = async (opts) => {
244300
244530
  for (const file of scan.files) {
244301
244531
  const path = "./" + rel(file.path);
244302
244532
  if (TEXT_EXT.test(path)) {
244303
- entries.push({ path, type: "text", text: readFileSync42(file.absPath, "utf8") });
244533
+ entries.push({ path, type: "text", text: readFileSync43(file.absPath, "utf8") });
244304
244534
  } else if (/\.mat$/i.test(path)) {
244305
- entries.push({ path, type: "shader", text: readFileSync42(file.absPath, "utf8"), absPath: file.absPath });
244535
+ entries.push({ path, type: "shader", text: readFileSync43(file.absPath, "utf8"), absPath: file.absPath });
244306
244536
  } else {
244307
- entries.push({ path, type: "resource", fileSrc: "/project/" + rel(file.path) });
244537
+ entries.push({ path, type: "resource", fileSrc: "/project/" + rel(file.path), absPath: file.absPath });
244308
244538
  }
244309
244539
  }
244310
244540
  const shaderEntries = entries.filter((e) => e.type === "shader");
@@ -244384,7 +244614,7 @@ var getContext2 = (pathArg) => {
244384
244614
  manifest = null;
244385
244615
  if (pathArg) {
244386
244616
  const abs = resolve31(process.cwd(), pathArg);
244387
- if (existsSync50(abs) && statSync18(abs).isDirectory())
244617
+ if (existsSync50(abs) && statSync17(abs).isDirectory())
244388
244618
  root = abs;
244389
244619
  else if (abs.endsWith(".scene.ts") && existsSync50(abs))
244390
244620
  root = findAppRoot(dirname26(abs)) ?? dirname26(abs);
@@ -244409,7 +244639,7 @@ var writeMcpConfig2 = (root, port) => {
244409
244639
  let config = {};
244410
244640
  if (existsSync50(path)) {
244411
244641
  try {
244412
- config = JSON.parse(readFileSync43(path, "utf8"));
244642
+ config = JSON.parse(readFileSync44(path, "utf8"));
244413
244643
  } catch {
244414
244644
  warnErr(".mcp.json exists but isn't valid JSON — left it untouched. Add the lecodes-scene server by hand.");
244415
244645
  return;
@@ -244428,11 +244658,11 @@ var writeMcpConfig2 = (root, port) => {
244428
244658
  };
244429
244659
  var countScenes = (dir) => {
244430
244660
  let n = 0;
244431
- for (const name of readdirSync20(dir)) {
244661
+ for (const name of readdirSync21(dir)) {
244432
244662
  if (name === "node_modules" || name.startsWith("."))
244433
244663
  continue;
244434
244664
  const full = join54(dir, name);
244435
- if (statSync18(full).isDirectory())
244665
+ if (statSync17(full).isDirectory())
244436
244666
  n += countScenes(full);
244437
244667
  else if (name.endsWith(".scene.ts"))
244438
244668
  n++;
@@ -244524,7 +244754,7 @@ var serve_default = defineCommand({
244524
244754
  });
244525
244755
 
244526
244756
  // src/commands/shaders/index.ts
244527
- import { readFileSync as readFileSync45 } from "node:fs";
244757
+ import { readFileSync as readFileSync46 } from "node:fs";
244528
244758
  import { relative as relative13 } from "node:path";
244529
244759
  init_cli();
244530
244760
  init_shared();
@@ -244533,7 +244763,7 @@ init_shared();
244533
244763
  init_compile();
244534
244764
  init_distRoot();
244535
244765
  init_cli();
244536
- import { existsSync as existsSync51, mkdirSync as mkdirSync32, readdirSync as readdirSync21, readFileSync as readFileSync44, writeFileSync as writeFileSync35 } from "node:fs";
244766
+ import { existsSync as existsSync51, mkdirSync as mkdirSync32, readdirSync as readdirSync22, readFileSync as readFileSync45, writeFileSync as writeFileSync35 } from "node:fs";
244537
244767
  import { basename as basename25, join as join55, relative as relative12, resolve as resolve32 } from "node:path";
244538
244768
  var engineMaterialsDir = () => {
244539
244769
  const candidates = [
@@ -244549,7 +244779,7 @@ var engineMaterialNames = () => {
244549
244779
  const dir = engineMaterialsDir();
244550
244780
  if (!dir)
244551
244781
  return [];
244552
- return readdirSync21(dir).filter((f) => f.endsWith(".mat") && !f.startsWith("camera") && f !== "shader360.mat").map((f) => f.slice(0, -4)).sort();
244782
+ return readdirSync22(dir).filter((f) => f.endsWith(".mat") && !f.startsWith("camera") && f !== "shader360.mat").map((f) => f.slice(0, -4)).sort();
244553
244783
  };
244554
244784
  var NAME_RE = /^[a-z][a-z0-9_-]*$/i;
244555
244785
  var matName = (name) => name.replace(/-/g, "_");
@@ -244640,7 +244870,7 @@ var writeNewShader = (o) => {
244640
244870
  const known = engineMaterialNames();
244641
244871
  if (!known.includes(ref))
244642
244872
  throw new CliError(`no engine material "${ref}". Known: ${known.join(", ")}`);
244643
- mat = refMat(name, ref, readFileSync44(join55(srcDir, `${ref}.mat`), "utf8"));
244873
+ mat = refMat(name, ref, readFileSync45(join55(srcDir, `${ref}.mat`), "utf8"));
244644
244874
  } else
244645
244875
  mat = starterMat(name);
244646
244876
  mkdirSync32(dir, { recursive: true });
@@ -244672,7 +244902,7 @@ var status2 = async () => {
244672
244902
  if (matcId === null)
244673
244903
  state = c.dim("unknown (matc not downloaded)");
244674
244904
  else {
244675
- const text = readFileSync45(shader.absPath, "utf8");
244905
+ const text = readFileSync46(shader.absPath, "utf8");
244676
244906
  if (shaderIsCompiled(root, text, matcId))
244677
244907
  state = "compiled";
244678
244908
  else {
@@ -244681,7 +244911,7 @@ var status2 = async () => {
244681
244911
  }
244682
244912
  }
244683
244913
  log(` ${shader.path.padEnd(width)} ${state}`);
244684
- const schema = parseShaderSchema(readFileSync45(shader.absPath, "utf8"));
244914
+ const schema = parseShaderSchema(readFileSync46(shader.absPath, "utf8"));
244685
244915
  for (const p of schema.params) {
244686
244916
  const range = p.min !== undefined ? ` [${p.min}..${p.max}]` : "";
244687
244917
  const def = p.default !== null && p.default !== undefined ? ` = ${JSON.stringify(p.default)}` : "";
@@ -244722,7 +244952,7 @@ var compileShaders = async (platforms) => {
244722
244952
  }
244723
244953
  const matcExe = await resolveMatcExe();
244724
244954
  const matcId = matcIdentity(matcExe);
244725
- const wasCompiled = new Map(shaders.map((s) => [s.path, shaderIsCompiled(root, readFileSync45(s.absPath, "utf8"), matcId)]));
244955
+ const wasCompiled = new Map(shaders.map((s) => [s.path, shaderIsCompiled(root, readFileSync46(s.absPath, "utf8"), matcId)]));
244726
244956
  const errors = [];
244727
244957
  for (const shader of shaders) {
244728
244958
  try {
@@ -244824,7 +245054,7 @@ var status_default = defineCommand({
244824
245054
  });
244825
245055
 
244826
245056
  // src/commands/test.ts
244827
- import { existsSync as existsSync53, mkdirSync as mkdirSync34, readFileSync as readFileSync46, readdirSync as readdirSync22 } from "node:fs";
245057
+ import { existsSync as existsSync53, mkdirSync as mkdirSync34, readFileSync as readFileSync47, readdirSync as readdirSync23 } from "node:fs";
244828
245058
  import { dirname as dirname28, resolve as resolve33 } from "node:path";
244829
245059
  init_manifest();
244830
245060
 
@@ -245341,7 +245571,7 @@ var slug = (s) => s.replace(/[^a-zA-Z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").sli
245341
245571
  var readFlowFile = (abs, displayName) => {
245342
245572
  let raw;
245343
245573
  try {
245344
- raw = JSON.parse(readFileSync46(abs, "utf8"));
245574
+ raw = JSON.parse(readFileSync47(abs, "utf8"));
245345
245575
  } catch (e) {
245346
245576
  throw new CliError(`Can't read "${displayName}": ${e instanceof Error ? e.message : String(e)}`);
245347
245577
  }
@@ -245449,7 +245679,7 @@ var runTests = async (args, flags) => {
245449
245679
  cases.push(readFlowFile(abs, given));
245450
245680
  }
245451
245681
  } else if (existsSync53(testsDir)) {
245452
- for (const f of readdirSync22(testsDir).filter((f) => f.endsWith(".flow.json")).sort()) {
245682
+ for (const f of readdirSync23(testsDir).filter((f) => f.endsWith(".flow.json")).sort()) {
245453
245683
  cases.push(readFlowFile(resolve33(testsDir, f), f));
245454
245684
  }
245455
245685
  }
@@ -245467,7 +245697,7 @@ var runTests = async (args, flags) => {
245467
245697
  if (!desktop && !lite && (wantFlows || flowsOnly) && existsSync53(designMetaPath)) {
245468
245698
  let meta;
245469
245699
  try {
245470
- meta = JSON.parse(readFileSync46(designMetaPath, "utf8"));
245700
+ meta = JSON.parse(readFileSync47(designMetaPath, "utf8"));
245471
245701
  } catch (e) {
245472
245702
  throw new CliError(`Can't read ${designMetaPath}: ${e instanceof Error ? e.message : String(e)}`);
245473
245703
  }
@@ -245540,7 +245770,7 @@ var runTests = async (args, flags) => {
245540
245770
  script: plan.text,
245541
245771
  fixedDtMs: nativeFixedDt
245542
245772
  });
245543
- const r = evaluateDesktopRun(plan, output, (path) => existsSync53(path) ? readFileSync46(path, "utf8") : null);
245773
+ const r = evaluateDesktopRun(plan, output, (path) => existsSync53(path) ? readFileSync47(path, "utf8") : null);
245544
245774
  if (!r.loaded)
245545
245775
  throw new CliError("This desktop host doesn't support scripted input (CREATOR_SCRIPT). Update it: lecodes desktop update (or point LECODES_DESKTOP_EXE at a newer build).");
245546
245776
  return {
@@ -245579,7 +245809,7 @@ var runTests = async (args, flags) => {
245579
245809
  height: flags.height || kase.height || projectViewport?.height || 844,
245580
245810
  onConsole
245581
245811
  });
245582
- const r = evaluateDesktopRun(plan, output, (path) => existsSync53(path) ? readFileSync46(path, "utf8") : null);
245812
+ const r = evaluateDesktopRun(plan, output, (path) => existsSync53(path) ? readFileSync47(path, "utf8") : null);
245583
245813
  return {
245584
245814
  name: kase.name,
245585
245815
  kind: kase.kind,
@@ -245688,7 +245918,7 @@ var runTests = async (args, flags) => {
245688
245918
  init_peerInstall();
245689
245919
  import { createRequire as createRequire8 } from "node:module";
245690
245920
  import { execFileSync as execFileSync8 } from "node:child_process";
245691
- import { existsSync as existsSync54, mkdtempSync as mkdtempSync7, readdirSync as readdirSync23, rmSync as rmSync17, statSync as statSync19 } from "node:fs";
245921
+ import { existsSync as existsSync54, mkdtempSync as mkdtempSync7, readdirSync as readdirSync24, rmSync as rmSync17, statSync as statSync18 } from "node:fs";
245692
245922
  import { tmpdir as tmpdir7 } from "node:os";
245693
245923
  import { extname, join as join57, relative as relative14, resolve as resolve34 } from "node:path";
245694
245924
  init_cli();
@@ -245746,11 +245976,11 @@ var ktxToPng = (ktx, file, out) => {
245746
245976
  };
245747
245977
  var collect = (dir) => {
245748
245978
  const out = [];
245749
- for (const name of readdirSync23(dir)) {
245979
+ for (const name of readdirSync24(dir)) {
245750
245980
  if (name === "node_modules" || name.startsWith("."))
245751
245981
  continue;
245752
245982
  const full = join57(dir, name);
245753
- if (statSync19(full).isDirectory())
245983
+ if (statSync18(full).isDirectory())
245754
245984
  out.push(...collect(full));
245755
245985
  else if (IMAGE_EXT.test(name))
245756
245986
  out.push(full);
@@ -245778,7 +246008,7 @@ var thumbs = async (targets, size, force) => {
245778
246008
  const abs = resolve34(process.cwd(), arg);
245779
246009
  if (!existsSync54(abs))
245780
246010
  throw new CliError(`No such file or folder: ${arg}`);
245781
- if (statSync19(abs).isDirectory())
246011
+ if (statSync18(abs).isDirectory())
245782
246012
  files.push(...collect(abs));
245783
246013
  else if (IMAGE_EXT.test(abs))
245784
246014
  files.push(abs);
@@ -245855,7 +246085,7 @@ init_peerInstall();
245855
246085
  init_archives();
245856
246086
  init_packageManager();
245857
246087
  import { spawnSync as spawnSync11 } from "node:child_process";
245858
- import { existsSync as existsSync55, readdirSync as readdirSync24 } from "node:fs";
246088
+ import { existsSync as existsSync55, readdirSync as readdirSync25 } from "node:fs";
245859
246089
  import { homedir as homedir14 } from "node:os";
245860
246090
  import { dirname as dirname29, join as join58, resolve as resolve36 } from "node:path";
245861
246091
 
@@ -246026,7 +246256,7 @@ ${c.bold(name)} ${c.dim(what)}`);
246026
246256
  return ["skipped", "not inside a project"];
246027
246257
  if (flags["no-shells"])
246028
246258
  return ["skipped", "--no-shells"];
246029
- const present = ["ios", "android"].filter((d) => existsSync55(join58(root, d)) && readdirSync24(join58(root, d)).length > 0);
246259
+ const present = ["ios", "android"].filter((d) => existsSync55(join58(root, d)) && readdirSync25(join58(root, d)).length > 0);
246030
246260
  if (present.length === 0)
246031
246261
  return ["skipped", "no ios/ or android/ shell in this project (`lecodes app init ios|android`)"];
246032
246262
  await runSubcommand(app_default, "update");
@@ -246105,7 +246335,7 @@ init_cli();
246105
246335
  // src/web/build.ts
246106
246336
  init_compile();
246107
246337
  import { createHash as createHash10 } from "node:crypto";
246108
- import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync56, mkdirSync as mkdirSync35, readFileSync as readFileSync47, readdirSync as readdirSync25, rmSync as rmSync18, statSync as statSync20, writeFileSync as writeFileSync37 } from "node:fs";
246338
+ import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync56, mkdirSync as mkdirSync35, readFileSync as readFileSync48, readdirSync as readdirSync26, rmSync as rmSync18, statSync as statSync19, writeFileSync as writeFileSync37 } from "node:fs";
246109
246339
  import { dirname as dirname30, extname as extname2, join as join59, resolve as resolve37 } from "node:path";
246110
246340
  init_fonts();
246111
246341
  init_assetIcons();
@@ -246118,9 +246348,9 @@ var normalBase = (base) => {
246118
246348
  };
246119
246349
  var walk = (dir, prefix = "") => {
246120
246350
  const out = [];
246121
- for (const name of readdirSync25(dir)) {
246351
+ for (const name of readdirSync26(dir)) {
246122
246352
  const abs = join59(dir, name);
246123
- const stat = statSync20(abs);
246353
+ const stat = statSync19(abs);
246124
246354
  if (stat.isDirectory())
246125
246355
  out.push(...walk(abs, `${prefix}${name}/`));
246126
246356
  else
@@ -246149,7 +246379,7 @@ var buildWeb = async (options) => {
246149
246379
  for (const entry of entries) {
246150
246380
  if (entry.type !== "shader" || !entry.absPath)
246151
246381
  continue;
246152
- const text = readFileSync47(entry.absPath, "utf8");
246382
+ const text = readFileSync48(entry.absPath, "utf8");
246153
246383
  const hash = createHash10("sha256").update(text).digest("hex").slice(0, 16);
246154
246384
  shaders.set(hash, JSON.stringify(buildLiteMaterialArtifact(text)));
246155
246385
  entry.shaders = [{ platform: "opengl", src: `shaders/${hash}_opengl.filamat` }];
@@ -246196,7 +246426,7 @@ var buildWeb = async (options) => {
246196
246426
  cpSync7(lite, out, { recursive: true });
246197
246427
  const table = join59(out, "features.json");
246198
246428
  if (existsSync56(table)) {
246199
- const { features, fonts } = JSON.parse(readFileSync47(table, "utf8"));
246429
+ const { features, fonts } = JSON.parse(readFileSync48(table, "utf8"));
246200
246430
  if (config.font === "system")
246201
246431
  for (const file of fonts ?? [])
246202
246432
  rmSync18(join59(out, file), { force: true });
@@ -246210,7 +246440,7 @@ var buildWeb = async (options) => {
246210
246440
  }
246211
246441
  const pluginsTable = join59(out, "plugins.json");
246212
246442
  if (existsSync56(pluginsTable) || halves.length > 0) {
246213
- const { plugins } = existsSync56(pluginsTable) ? JSON.parse(readFileSync47(pluginsTable, "utf8")) : { plugins: {} };
246443
+ const { plugins } = existsSync56(pluginsTable) ? JSON.parse(readFileSync48(pluginsTable, "utf8")) : { plugins: {} };
246214
246444
  const kept = {};
246215
246445
  for (const id of Object.keys(plugins)) {
246216
246446
  if (listed.has(id))
@@ -246260,7 +246490,7 @@ var buildWeb = async (options) => {
246260
246490
  writeFileSync37(join59(out, "shaders", `${hash}_webgl-lite.filamat`), artifact);
246261
246491
  }
246262
246492
  const app = readAppConfigAt(root);
246263
- let html = readFileSync47(join59(lite, "index.html"), "utf8");
246493
+ let html = readFileSync48(join59(lite, "index.html"), "utf8");
246264
246494
  const head = [];
246265
246495
  head.push(`<meta name="theme-color" content="${escapeHtml(config.themeColor ?? "#000000")}" />`);
246266
246496
  if (app?.icon) {
@@ -246300,7 +246530,7 @@ var buildWeb = async (options) => {
246300
246530
  };
246301
246531
 
246302
246532
  // src/web/preview.ts
246303
- import { createReadStream as createReadStream3, existsSync as existsSync57, statSync as statSync21 } from "node:fs";
246533
+ import { createReadStream as createReadStream3, existsSync as existsSync57, statSync as statSync20 } from "node:fs";
246304
246534
  import { createServer as createServer4 } from "node:http";
246305
246535
  import { join as join60, resolve as resolve38 } from "node:path";
246306
246536
  var startPreview = async (dir, port) => {
@@ -246322,10 +246552,10 @@ var startPreview = async (dir, port) => {
246322
246552
  return;
246323
246553
  }
246324
246554
  let file = join60(root, ...parts);
246325
- if (existsSync57(file) && statSync21(file).isDirectory())
246555
+ if (existsSync57(file) && statSync20(file).isDirectory())
246326
246556
  file = join60(file, "index.html");
246327
246557
  let status = 200;
246328
- if (!existsSync57(file) || !statSync21(file).isFile()) {
246558
+ if (!existsSync57(file) || !statSync20(file).isFile()) {
246329
246559
  if (!fallback || /\.[a-z0-9]+$/i.test(parts[parts.length - 1] ?? "")) {
246330
246560
  res.writeHead(404, { "content-type": "text/plain" });
246331
246561
  res.end("Not found");
@@ -246355,7 +246585,7 @@ var startPreview = async (dir, port) => {
246355
246585
  init_shared();
246356
246586
 
246357
246587
  // src/dev/https.ts
246358
- import { existsSync as existsSync58, mkdirSync as mkdirSync36, readFileSync as readFileSync48, writeFileSync as writeFileSync38 } from "node:fs";
246588
+ import { existsSync as existsSync58, mkdirSync as mkdirSync36, readFileSync as readFileSync49, writeFileSync as writeFileSync38 } from "node:fs";
246359
246589
  import { homedir as homedir15 } from "node:os";
246360
246590
  import { join as join61 } from "node:path";
246361
246591
  var DIR = () => join61(homedir15(), ".lecodes", "dev-https");
@@ -246365,12 +246595,12 @@ var devCertificate = async (names, dir = DIR()) => {
246365
246595
  const files = { cert: join61(dir, "cert.pem"), key: join61(dir, "key.pem"), meta: join61(dir, "names.json") };
246366
246596
  let kept = null;
246367
246597
  try {
246368
- kept = JSON.parse(readFileSync48(files.meta, "utf8"));
246598
+ kept = JSON.parse(readFileSync49(files.meta, "utf8"));
246369
246599
  } catch {}
246370
246600
  const wanted = [...new Set(["localhost", "127.0.0.1", ...names])];
246371
246601
  const fresh = kept && Date.now() - kept.made < RENEW_DAYS * 86400000 && wanted.every((n) => kept.names.includes(n));
246372
246602
  if (fresh && existsSync58(files.cert) && existsSync58(files.key)) {
246373
- return { cert: readFileSync48(files.cert, "utf8"), key: readFileSync48(files.key, "utf8") };
246603
+ return { cert: readFileSync49(files.cert, "utf8"), key: readFileSync49(files.key, "utf8") };
246374
246604
  }
246375
246605
  const { generate } = await Promise.resolve().then(() => __toESM(require_selfsigned(), 1));
246376
246606
  const all = [...new Set([...kept?.names ?? [], ...wanted])];