lecodes-cli 2.4.8 → 2.4.10

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.8",
67
+ version: "2.4.10",
68
68
  dependencies: {
69
69
  "@letary/chisel": "^0.15.0",
70
70
  jimp: "^1.6.1",
@@ -89,9 +89,9 @@ var init_package = __esm(() => {
89
89
  },
90
90
  peerDependencies: {
91
91
  "lecodes-design": "^0.11.0",
92
- "lecodes-headless": "^2.4.8",
92
+ "lecodes-headless": "^2.4.10",
93
93
  "lecodes-3d-editor": "^0.9.0",
94
- "lecodes-assets": "^0.2.1",
94
+ "lecodes-assets": "^2.4.10",
95
95
  "heic-decode": "^2.1.0",
96
96
  "marcidb-embedded": "^0.15.0",
97
97
  sharp: "^0.35.2",
@@ -159,7 +159,7 @@ var init_package = __esm(() => {
159
159
  });
160
160
 
161
161
  // ../../sdk/src/version.ts
162
- var SDK_VERSION = "2.4.8";
162
+ var SDK_VERSION = "2.4.10";
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) => {
@@ -651,6 +651,94 @@ var init_peers = __esm(() => {
651
651
  ];
652
652
  });
653
653
 
654
+ // src/hosts/releases.ts
655
+ import { createHash } from "node:crypto";
656
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, mkdtempSync, readFileSync as readFileSync3, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
657
+ import { join as join4 } from "node:path";
658
+ import { spawnSync } from "node:child_process";
659
+ var MANIFEST_URL = "https://storage.yandexcloud.net/le-codes/releases/versions.json", ARTIFACT_STAMP = ".lecodes-artifact.json", cachedArtifactSha = (dir) => {
660
+ try {
661
+ return JSON.parse(readFileSync3(join4(dir, ARTIFACT_STAMP), "utf8")).sha256 ?? null;
662
+ } catch {
663
+ return null;
664
+ }
665
+ }, macPlatformKey = () => process.arch === "arm64" ? "macos-arm64" : "macos-x64", semverNewer = (a, b) => {
666
+ const pa = a.split(/[.-]/).map(Number), pb = b.split(/[.-]/).map(Number);
667
+ for (let i = 0;i < 3; i++)
668
+ if ((pb[i] ?? 0) !== (pa[i] ?? 0))
669
+ return (pb[i] ?? 0) - (pa[i] ?? 0);
670
+ return 0;
671
+ }, newestForPlatform = (versions, key) => {
672
+ const carrying = versions.filter((v) => v.files[key]).sort((a, b) => semverNewer(a.version, b.version));
673
+ const newest = carrying[0];
674
+ return newest ? { version: newest.version, file: newest.files[key] } : null;
675
+ }, fetchReleaseProduct = async (product, label) => {
676
+ let manifest;
677
+ try {
678
+ const res = await fetch(MANIFEST_URL);
679
+ if (!res.ok)
680
+ throw new Error(`HTTP ${res.status}`);
681
+ manifest = await res.json();
682
+ } catch (e) {
683
+ throw new CliError(`Can't fetch the release manifest (${MANIFEST_URL}): ${e instanceof Error ? e.message : String(e)}`);
684
+ }
685
+ const entry = manifest.products?.[product];
686
+ if (!entry)
687
+ throw new CliError(`The release manifest has no ${label} product.`);
688
+ return entry;
689
+ }, downloadReleaseArchive = async (file, opts) => {
690
+ let bytes;
691
+ try {
692
+ const res = await fetch(file.url);
693
+ if (!res.ok)
694
+ throw new Error(`HTTP ${res.status}`);
695
+ bytes = Buffer.from(await res.arrayBuffer());
696
+ } catch (e) {
697
+ throw new CliError(`Download failed (${file.url}): ${e instanceof Error ? e.message : String(e)}`);
698
+ }
699
+ const sha = createHash("sha256").update(bytes).digest("hex");
700
+ if (sha !== file.sha256)
701
+ throw new CliError(`Downloaded archive is corrupt (sha256 mismatch) — try again.`);
702
+ mkdirSync2(opts.cacheRoot, { recursive: true });
703
+ const stage = mkdtempSync(join4(opts.cacheRoot, "dl-"));
704
+ try {
705
+ const archiveName = file.url.split("/").pop() ?? "archive";
706
+ const archive = join4(stage, archiveName);
707
+ writeFileSync2(archive, bytes);
708
+ if (opts.extract) {
709
+ opts.extract(archive, stage);
710
+ } else {
711
+ const winTar = join4(process.env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe");
712
+ const tarExe = process.platform === "win32" && existsSync4(winTar) ? winTar : "tar";
713
+ const tar = spawnSync(tarExe, ["-xf", archiveName], { cwd: stage, stdio: ["ignore", "ignore", "pipe"] });
714
+ if (tar.status !== 0)
715
+ throw new CliError(`Couldn't extract the archive: ${tar.stderr?.toString() ?? "tar failed"}`);
716
+ }
717
+ rmSync(archive);
718
+ const bad = opts.validate(stage);
719
+ if (bad)
720
+ throw new CliError(bad);
721
+ writeFileSync2(join4(stage, ARTIFACT_STAMP), JSON.stringify({ sha256: file.sha256, url: file.url }, null, 2));
722
+ try {
723
+ renameSync(stage, opts.dest);
724
+ } catch {
725
+ let destBad;
726
+ try {
727
+ destBad = opts.validate(opts.dest);
728
+ } catch {
729
+ destBad = "missing";
730
+ }
731
+ if (destBad)
732
+ throw new CliError(`Couldn't move the download into ${opts.dest}.`);
733
+ }
734
+ } finally {
735
+ rmSync(stage, { recursive: true, force: true });
736
+ }
737
+ };
738
+ var init_releases = __esm(() => {
739
+ init_cli();
740
+ });
741
+
654
742
  // src/hosts/archives.ts
655
743
  var ARCHIVED_PEERS, BASE = "https://storage.yandexcloud.net/le-codes/releases/packages", packagesBase = () => (process.env.LECODES_PACKAGES_URL || BASE).replace(/\/$/, ""), archiveUrl = (name, version) => `${packagesBase()}/${name}/${name}-${version}.tgz`, numbers = (version) => version.split(/[.-]/).slice(0, 3).map(Number), byVersion = (a, b) => {
656
744
  const pa = numbers(a), pb = numbers(b);
@@ -699,17 +787,17 @@ var init_archives = __esm(() => {
699
787
  });
700
788
 
701
789
  // src/hosts/packageManager.ts
702
- import { spawnSync } from "node:child_process";
790
+ import { spawnSync as spawnSync2 } from "node:child_process";
703
791
  import { homedir as homedir2 } from "node:os";
704
- import { join as join4, resolve as resolve2, sep } from "node:path";
792
+ import { join as join5, resolve as resolve2, sep } from "node:path";
705
793
  import { fileURLToPath as fileURLToPath2 } from "node:url";
706
794
  var onPath = (cmd) => {
707
795
  try {
708
- return spawnSync(cmd, ["--version"], { stdio: "ignore", shell: process.platform === "win32" }).status === 0;
796
+ return spawnSync2(cmd, ["--version"], { stdio: "ignore", shell: process.platform === "win32" }).status === 0;
709
797
  } catch {
710
798
  return false;
711
799
  }
712
- }, bunGlobalDir = () => join4(process.env.BUN_INSTALL ? resolve2(process.env.BUN_INSTALL) : join4(homedir2(), ".bun"), "install", "global"), ownManager = (file = fileURLToPath2(import.meta.url)) => {
800
+ }, bunGlobalDir = () => join5(process.env.BUN_INSTALL ? resolve2(process.env.BUN_INSTALL) : join5(homedir2(), ".bun"), "install", "global"), ownManager = (file = fileURLToPath2(import.meta.url)) => {
713
801
  const norm = (p) => process.platform === "win32" ? p.toLowerCase() : p;
714
802
  return norm(resolve2(file)).startsWith(norm(bunGlobalDir()) + sep) ? "bun" : "npm";
715
803
  }, pickManager = () => {
@@ -720,12 +808,16 @@ var onPath = (cmd) => {
720
808
  var init_packageManager = () => {};
721
809
 
722
810
  // src/hosts/peerInstall.ts
723
- import { spawnSync as spawnSync2 } from "node:child_process";
724
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
725
- import { join as join5 } from "node:path";
811
+ import { spawnSync as spawnSync3 } from "node:child_process";
812
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
813
+ import { join as join6 } from "node:path";
726
814
  import { pathToFileURL } from "node:url";
727
815
  async function loadPeer(name, subpath, opts) {
728
816
  const spec = subpath ? `${name}/${subpath}` : name;
817
+ const byHand = peerVersionGlobal(name, spec);
818
+ if (olderThanCli(name, byHand)) {
819
+ throw new CliError(`${name} ${byHand} (installed by hand) is older than this CLI (${package_default.version}) needs — update it: ${commandLine(ownManager(), installGlobalArgv(ownManager(), [await peerSpec(name)]))}`);
820
+ }
729
821
  try {
730
822
  return await import(spec);
731
823
  } catch (e) {
@@ -733,18 +825,22 @@ async function loadPeer(name, subpath, opts) {
733
825
  throw e;
734
826
  }
735
827
  const dir = peerDirInPrefix(name);
736
- if (!existsSync4(join5(dir, "package.json"))) {
828
+ const installed = peerVersionPrefix(name);
829
+ const stale = olderThanCli(name, installed);
830
+ if (installed === null || stale) {
737
831
  if (opts.install === false)
738
832
  return null;
739
- if (peerVersionGlobal(name, spec) !== null)
833
+ if (installed === null && peerVersionGlobal(name, spec) !== null)
740
834
  throw new CliError(`${name} is installed but "${spec}" can't be loaded from it — reinstall it: ${commandLine(ownManager(), installGlobalArgv(ownManager(), [await peerSpec(name)]))}`);
835
+ if (stale)
836
+ note(`${name} ${installed} in ${peersDir()} is older than this CLI (${package_default.version}) — installing the one it needs`);
741
837
  await installPeers(companions(name), opts.for);
742
838
  }
743
839
  return await import(pathToFileURL(resolveEntry(dir, subpath)).href);
744
840
  }
745
841
  var CANVAS_RANGE_FALLBACK = "^0.1.65", readJson = (path) => {
746
842
  try {
747
- return JSON.parse(readFileSync3(path, "utf8"));
843
+ return JSON.parse(readFileSync4(path, "utf8"));
748
844
  } catch {
749
845
  return null;
750
846
  }
@@ -753,7 +849,7 @@ var CANVAS_RANGE_FALLBACK = "^0.1.65", readJson = (path) => {
753
849
  if (archive)
754
850
  return archive;
755
851
  if (name === "@napi-rs/canvas") {
756
- const renderer = readJson(join5(peerDirInPrefix("lecodes-headless"), "package.json"));
852
+ const renderer = readJson(join6(peerDirInPrefix("lecodes-headless"), "package.json"));
757
853
  const range = renderer?.peerDependencies?.["@napi-rs/canvas"] ?? renderer?.optionalDependencies?.["@napi-rs/canvas"] ?? CANVAS_RANGE_FALLBACK;
758
854
  return `@napi-rs/canvas@${range}`;
759
855
  }
@@ -761,10 +857,10 @@ var CANVAS_RANGE_FALLBACK = "^0.1.65", readJson = (path) => {
761
857
  return range ? `${name}@${range}` : `${name}@latest`;
762
858
  }, companions = (name) => name === "lecodes-headless" ? ["lecodes-headless", "@napi-rs/canvas"] : name === "sharp" ? ["sharp", "heic-decode"] : [name], manualCommand = (dir, specs) => commandLine(ownManager(), installIntoArgv(ownManager(), dir, specs)), installPeers = async (names, why, opts = {}) => {
763
859
  const dir = peersDir();
764
- mkdirSync2(dir, { recursive: true });
765
- const manifest = join5(dir, "package.json");
766
- if (!existsSync4(manifest)) {
767
- writeFileSync2(manifest, JSON.stringify({
860
+ mkdirSync3(dir, { recursive: true });
861
+ const manifest = join6(dir, "package.json");
862
+ if (!existsSync5(manifest)) {
863
+ writeFileSync3(manifest, JSON.stringify({
768
864
  name: "lecodes-peers",
769
865
  private: true,
770
866
  description: "Optional lecodes-cli peers, installed on first use. Safe to delete — the CLI re-installs what a command needs."
@@ -792,16 +888,16 @@ var CANVAS_RANGE_FALLBACK = "^0.1.65", readJson = (path) => {
792
888
  const spec = await peerSpec(name);
793
889
  const argv = installIntoArgv(tool, dir, [spec]);
794
890
  note(`$ ${tool} ${argv.join(" ")}`);
795
- const r = spawnSync2(tool, argv, { stdio: "inherit", shell: process.platform === "win32", timeout: 10 * 60000 });
891
+ const r = spawnSync3(tool, argv, { stdio: "inherit", shell: process.platform === "win32", timeout: 10 * 60000 });
796
892
  if (r.error)
797
893
  throw new CliError(`${tool} could not run: ${r.error.message}`);
798
894
  if (r.status !== 0)
799
895
  throw new CliError(`${tool} exited with ${r.status ?? "a signal"} while installing ${spec}. By hand: ${manualCommand(dir, [spec])}`);
800
- if (!existsSync4(join5(peerDirInPrefix(name), "package.json")))
896
+ if (!existsSync5(join6(peerDirInPrefix(name), "package.json")))
801
897
  throw new CliError(`${name} did not land in ${dir} — the installer finished but the package is missing`);
802
898
  }
803
899
  }, resolveEntry = (pkgDir, subpath) => {
804
- const manifest = readJson(join5(pkgDir, "package.json"));
900
+ const manifest = readJson(join6(pkgDir, "package.json"));
805
901
  if (!manifest)
806
902
  throw new CliError(`${pkgDir} holds no package.json`);
807
903
  const key = subpath ? `./${subpath}` : ".";
@@ -829,8 +925,8 @@ var CANVAS_RANGE_FALLBACK = "^0.1.65", readJson = (path) => {
829
925
  rel = typeof manifest.main === "string" ? manifest.main : "index.js";
830
926
  if (!rel)
831
927
  throw new CliError(`${manifest.name} has no export "${key}"`);
832
- return join5(pkgDir, rel);
833
- }, isNotFound = (e, spec) => {
928
+ return join6(pkgDir, rel);
929
+ }, SET_ON_NPM, olderThanCli = (name, version) => SET_ON_NPM.has(name) && version !== null && semverNewer(version, package_default.version) > 0, isNotFound = (e, spec) => {
834
930
  const code = e?.code;
835
931
  const msg = e instanceof Error ? e.message : String(e);
836
932
  return (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || /Cannot find (module|package)/i.test(msg)) && msg.includes(spec.split("/")[0]);
@@ -838,9 +934,11 @@ var CANVAS_RANGE_FALLBACK = "^0.1.65", readJson = (path) => {
838
934
  var init_peerInstall = __esm(() => {
839
935
  init_package();
840
936
  init_peers();
937
+ init_releases();
841
938
  init_archives();
842
939
  init_packageManager();
843
940
  init_cli();
941
+ SET_ON_NPM = new Set(["lecodes-assets"]);
844
942
  });
845
943
 
846
944
  // ../../sdk/src/compile/resourceSlots.ts
@@ -880,13 +978,7 @@ var init_resourceSlots = __esm(() => {
880
978
  "map",
881
979
  "shader",
882
980
  "build",
883
- "data",
884
- "light",
885
- "aux",
886
- "direct",
887
- "indirect",
888
- "probes",
889
- "volume"
981
+ "data"
890
982
  ]
891
983
  };
892
984
  });
@@ -1146,6 +1238,520 @@ var init_assetMacro = __esm(() => {
1146
1238
  SET_TARGET = /\.\s*([A-Za-z_$][\w$]*)\s*$/;
1147
1239
  });
1148
1240
 
1241
+ // ../../sdk/src/compile/assetIconMacro.ts
1242
+ 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) => {
1243
+ const quote = src[start];
1244
+ const n = src.length;
1245
+ let i = start + 1;
1246
+ while (i < n) {
1247
+ const c = src[i];
1248
+ if (c === "\\") {
1249
+ i += 2;
1250
+ continue;
1251
+ }
1252
+ if (quote === "`" && c === "$" && src[i + 1] === "{") {
1253
+ i = skipBraces(src, i + 1);
1254
+ continue;
1255
+ }
1256
+ if (c === quote)
1257
+ return i + 1;
1258
+ i++;
1259
+ }
1260
+ return n;
1261
+ }, skipBraces = (src, open) => {
1262
+ const n = src.length;
1263
+ let depth = 0;
1264
+ let i = open;
1265
+ while (i < n) {
1266
+ const c = src[i];
1267
+ if (c === '"' || c === "'" || c === "`") {
1268
+ i = skipString(src, i);
1269
+ continue;
1270
+ }
1271
+ if (c === "{")
1272
+ depth++;
1273
+ else if (c === "}") {
1274
+ depth--;
1275
+ if (depth === 0)
1276
+ return i + 1;
1277
+ }
1278
+ i++;
1279
+ }
1280
+ return n;
1281
+ }, matchParen = (src, open) => {
1282
+ const n = src.length;
1283
+ let depth = 0;
1284
+ let i = open;
1285
+ while (i < n) {
1286
+ const c = src[i];
1287
+ if (c === '"' || c === "'" || c === "`") {
1288
+ i = skipString(src, i);
1289
+ continue;
1290
+ }
1291
+ if (c === "/" && src[i + 1] === "/") {
1292
+ i += 2;
1293
+ while (i < n && src[i] !== `
1294
+ `)
1295
+ i++;
1296
+ continue;
1297
+ }
1298
+ if (c === "/" && src[i + 1] === "*") {
1299
+ i += 2;
1300
+ while (i < n && !(src[i] === "*" && src[i + 1] === "/"))
1301
+ i++;
1302
+ i += 2;
1303
+ continue;
1304
+ }
1305
+ if (c === "(")
1306
+ depth++;
1307
+ else if (c === ")") {
1308
+ depth--;
1309
+ if (depth === 0)
1310
+ return i;
1311
+ }
1312
+ i++;
1313
+ }
1314
+ return -1;
1315
+ }, splitTopLevel = (text, delim) => {
1316
+ const parts = [];
1317
+ const n = text.length;
1318
+ let depth = 0;
1319
+ let last = 0;
1320
+ let i = 0;
1321
+ while (i < n) {
1322
+ const c = text[i];
1323
+ if (c === '"' || c === "'" || c === "`") {
1324
+ i = skipString(text, i);
1325
+ continue;
1326
+ }
1327
+ if (c === "(" || c === "[" || c === "{")
1328
+ depth++;
1329
+ else if (c === ")" || c === "]" || c === "}")
1330
+ depth = Math.max(0, depth - 1);
1331
+ else if (depth === 0 && c === delim) {
1332
+ parts.push(text.slice(last, i));
1333
+ last = i + 1;
1334
+ }
1335
+ i++;
1336
+ }
1337
+ parts.push(text.slice(last));
1338
+ return parts;
1339
+ }, stringLiteralValue = (text) => {
1340
+ const t = text.trim();
1341
+ if (t.length < 2)
1342
+ return null;
1343
+ const q = t[0];
1344
+ if (q !== '"' && q !== "'" && q !== "`" || t[t.length - 1] !== q)
1345
+ return null;
1346
+ const inner = t.slice(1, -1);
1347
+ if (q === "`" && inner.includes("${"))
1348
+ return null;
1349
+ if (inner.includes(q) && !inner.includes("\\" + q))
1350
+ return null;
1351
+ return inner.replace(/\\(.)/g, "$1");
1352
+ }, scanAssetIconCalls = (source) => {
1353
+ const calls = [];
1354
+ const n = source.length;
1355
+ let i = 0;
1356
+ while (i < n) {
1357
+ const c = source[i];
1358
+ if (c === '"' || c === "'" || c === "`") {
1359
+ i = skipString(source, i);
1360
+ continue;
1361
+ }
1362
+ if (c === "/" && source[i + 1] === "/") {
1363
+ i += 2;
1364
+ while (i < n && source[i] !== `
1365
+ `)
1366
+ i++;
1367
+ continue;
1368
+ }
1369
+ if (c === "/" && source[i + 1] === "*") {
1370
+ i += 2;
1371
+ while (i < n && !(source[i] === "*" && source[i + 1] === "/"))
1372
+ i++;
1373
+ i += 2;
1374
+ continue;
1375
+ }
1376
+ if (isIdentStart(c)) {
1377
+ let j = i + 1;
1378
+ while (j < n && isIdentPart(source[j]))
1379
+ j++;
1380
+ if (source.slice(i, j) === "assetIcon" && prevNonSpace(source, i) !== ".") {
1381
+ const call = parseCall(source, i, j);
1382
+ if (call) {
1383
+ calls.push(call);
1384
+ i = call.end;
1385
+ continue;
1386
+ }
1387
+ }
1388
+ i = j;
1389
+ continue;
1390
+ }
1391
+ i++;
1392
+ }
1393
+ return calls;
1394
+ }, prevNonSpace = (src, i) => {
1395
+ let k = i - 1;
1396
+ while (k >= 0 && /\s/.test(src[k]))
1397
+ k--;
1398
+ return k >= 0 ? src[k] : "";
1399
+ }, parseCall = (src, start, identEnd) => {
1400
+ const n = src.length;
1401
+ let i = identEnd;
1402
+ while (i < n && /\s/.test(src[i]))
1403
+ i++;
1404
+ if (src[i] !== "(")
1405
+ return null;
1406
+ const close = matchParen(src, i);
1407
+ if (close === -1)
1408
+ return null;
1409
+ const call = { start, end: close + 1, id: "", pack: "", name: "" };
1410
+ const args = splitTopLevel(src.slice(i + 1, close), ",");
1411
+ const id = stringLiteralValue(args[0] ?? "");
1412
+ if (id === null) {
1413
+ call.error = `assetIcon(...) needs a string-literal id like "lucide:bell" (got \`${(args[0] ?? "").trim() || "()"}\`).`;
1414
+ return call;
1415
+ }
1416
+ const m = ICON_ID.exec(id);
1417
+ if (!m) {
1418
+ call.error = `assetIcon("${id}"): id must be "pack:name", lowercase [a-z0-9-] (e.g. "lucide:bell").`;
1419
+ return call;
1420
+ }
1421
+ call.id = id;
1422
+ call.pack = m[1];
1423
+ call.name = m[2];
1424
+ const optsText = args[1]?.trim();
1425
+ if (optsText) {
1426
+ const color = extractColor(optsText);
1427
+ if (color.error) {
1428
+ call.error = color.error;
1429
+ return call;
1430
+ }
1431
+ if (color.bake !== undefined)
1432
+ call.bake = color.bake;
1433
+ else if (color.tint !== undefined)
1434
+ call.tint = color.tint;
1435
+ }
1436
+ return call;
1437
+ }, extractColor = (optsText) => {
1438
+ if (!optsText.startsWith("{") || !optsText.endsWith("}")) {
1439
+ return { error: `assetIcon(id, opts): opts must be an object literal like { color: "#5b8cff" }.` };
1440
+ }
1441
+ for (const prop of splitTopLevel(optsText.slice(1, -1), ",")) {
1442
+ const kv = splitTopLevel(prop, ":");
1443
+ if (kv.length < 2)
1444
+ continue;
1445
+ const key = kv[0].trim().replace(/^["'`]|["'`]$/g, "");
1446
+ if (key !== "color")
1447
+ continue;
1448
+ const raw = kv.slice(1).join(":").trim();
1449
+ if (!raw)
1450
+ continue;
1451
+ const literal = stringLiteralValue(raw);
1452
+ return literal !== null ? { bake: literal } : { tint: raw };
1453
+ }
1454
+ return {};
1455
+ }, rewriteAssetIcons = (source, calls, svgById) => {
1456
+ let out = source;
1457
+ for (const call of [...calls].sort((a, b) => b.start - a.start)) {
1458
+ const svg = svgById.get(call.id);
1459
+ if (svg === undefined)
1460
+ continue;
1461
+ out = out.slice(0, call.start) + iconExpr(svg, call) + out.slice(call.end);
1462
+ }
1463
+ return out;
1464
+ }, iconExpr = (svg, call) => {
1465
+ const body = call.bake ? svg.split("currentColor").join(call.bake) : svg;
1466
+ const src = `SvgSource(\`${escapeTemplate(body)}\`)`;
1467
+ return call.tint ? `Object.assign(${src}, { tintColor: (${call.tint}) })` : src;
1468
+ }, escapeTemplate = (s) => s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${"), applyAssetIconMacro = async (entries, resolveSvgs) => {
1469
+ 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);
1470
+ if (scanned.length === 0)
1471
+ return [];
1472
+ const errors = scanned.flatMap((s) => s.calls.filter((c) => c.error).map((c) => c.error));
1473
+ if (errors.length)
1474
+ throw new Error([...new Set(errors)].join(`
1475
+ `));
1476
+ const ids = [...new Set(scanned.flatMap((s) => s.calls.map((c) => c.id)))];
1477
+ const svgById = await resolveSvgs(ids);
1478
+ const missing = ids.filter((id) => !svgById.has(id));
1479
+ if (missing.length)
1480
+ throw new Error(`assetIcon: could not resolve icon(s): ${missing.join(", ")}.`);
1481
+ for (const { entry, calls } of scanned)
1482
+ entry.text = rewriteAssetIcons(entry.text, calls, svgById);
1483
+ return ids;
1484
+ }, buildIconsHeaderValue = (ids) => [...new Set(ids.map((id) => id.split(":")[0]))].sort().join(";"), editDistance = (a, b, cap) => {
1485
+ if (Math.abs(a.length - b.length) > cap)
1486
+ return cap + 1;
1487
+ let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
1488
+ for (let i = 1;i <= a.length; i++) {
1489
+ const cur = [i];
1490
+ let rowMin = i;
1491
+ for (let j = 1;j <= b.length; j++) {
1492
+ cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
1493
+ if (cur[j] < rowMin)
1494
+ rowMin = cur[j];
1495
+ }
1496
+ if (rowMin > cap)
1497
+ return cap + 1;
1498
+ prev = cur;
1499
+ }
1500
+ return prev[b.length];
1501
+ }, suggestIconNames = (want, icons) => {
1502
+ const wantParts = new Set(want.split("-").filter(Boolean));
1503
+ const scored = [];
1504
+ for (const [name, info] of Object.entries(icons)) {
1505
+ const lenDiff = Math.abs(name.length - want.length) / 100;
1506
+ let rank;
1507
+ if (name.includes(want) || want.includes(name))
1508
+ rank = lenDiff;
1509
+ else {
1510
+ const d = editDistance(want, name, 2);
1511
+ if (d <= 2)
1512
+ rank = 1 + d;
1513
+ else if (info.kw?.some((k) => wantParts.has(k)))
1514
+ rank = 4 + lenDiff;
1515
+ else
1516
+ continue;
1517
+ }
1518
+ scored.push({ name, rank });
1519
+ }
1520
+ return scored.sort((a, b) => a.rank - b.rank || (a.name < b.name ? -1 : 1)).slice(0, 5).map((s) => s.name);
1521
+ }, iconNotFoundError = async (pack, name, read, registry) => {
1522
+ const metaRaw = await read(`${pack}/meta.json`);
1523
+ if (metaRaw !== null) {
1524
+ let icons = {};
1525
+ try {
1526
+ icons = JSON.parse(metaRaw).icons ?? {};
1527
+ } catch {}
1528
+ const similar = suggestIconNames(name, icons);
1529
+ const hint = similar.length ? ` Did you mean ${similar.map((s) => `"${pack}:${s}"`).join(", ")}?` : ` No similar name in the "${pack}" pack either.`;
1530
+ return new Error(`assetIcon: icon "${pack}:${name}" not found.${hint}`);
1531
+ }
1532
+ const licRaw = await read("licenses.json");
1533
+ if (licRaw !== null) {
1534
+ try {
1535
+ const packs = Object.keys(JSON.parse(licRaw)).sort();
1536
+ return new Error(`assetIcon: unknown icon pack "${pack}" (in "${pack}:${name}"). Available packs: ${packs.join(", ")}.`);
1537
+ } catch {}
1538
+ }
1539
+ 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 });
1540
+ }, VENDORED_ICON_PATH, collectVendoredIcons = (entries) => {
1541
+ const map = new Map;
1542
+ for (const e of entries) {
1543
+ if (e.type !== "text" || typeof e.text !== "string")
1544
+ continue;
1545
+ const m = VENDORED_ICON_PATH.exec(e.path);
1546
+ if (m)
1547
+ map.set(`${m[1]}:${m[2]}`, e.text);
1548
+ }
1549
+ return map;
1550
+ };
1551
+ var init_assetIconMacro = __esm(() => {
1552
+ ICON_ID = /^([a-z0-9]+(?:-[a-z0-9]+)*):([a-z0-9]+(?:-[a-z0-9]+)*)$/;
1553
+ VENDORED_ICON_PATH = /(?:^|\/)assets\/icons\/([a-z0-9]+(?:-[a-z0-9]+)*)\/([a-z0-9]+(?:-[a-z0-9]+)*)\.svg$/;
1554
+ });
1555
+
1556
+ // ../../sdk/src/compile/bakeMacro.ts
1557
+ var BAKE_CALL, DECLARES_BAKE, DEFINITION_TAIL, hasBakeMacro = (src) => src.includes("bake"), stringLiteralValue2 = (text) => {
1558
+ const t = text.trim();
1559
+ if (t.length < 2)
1560
+ return null;
1561
+ const q = t[0];
1562
+ if (q !== '"' && q !== "'" && q !== "`" || t[t.length - 1] !== q)
1563
+ return null;
1564
+ const inner = t.slice(1, -1);
1565
+ if (q === "`" && inner.includes("${"))
1566
+ return null;
1567
+ if (/[\r\n]/.test(inner))
1568
+ return null;
1569
+ for (let i = 0;i < inner.length; i++) {
1570
+ if (inner[i] === "\\") {
1571
+ i++;
1572
+ continue;
1573
+ }
1574
+ if (inner[i] === q)
1575
+ return null;
1576
+ }
1577
+ return inner.replace(/\\(.)/g, "$1");
1578
+ }, closeOf = (mask, open) => {
1579
+ let depth = 0;
1580
+ for (let i = open;i < mask.length; i++) {
1581
+ const c = mask[i];
1582
+ if (c === "(" || c === "[" || c === "{")
1583
+ depth++;
1584
+ else if (c === ")" || c === "]" || c === "}") {
1585
+ depth--;
1586
+ if (depth === 0)
1587
+ return i;
1588
+ }
1589
+ }
1590
+ return -1;
1591
+ }, argSpans = (mask, open, close) => {
1592
+ const spans = [];
1593
+ let depth = 0;
1594
+ let start = open + 1;
1595
+ for (let i = open + 1;i < close; i++) {
1596
+ const c = mask[i];
1597
+ if (c === "(" || c === "[" || c === "{")
1598
+ depth++;
1599
+ else if (c === ")" || c === "]" || c === "}")
1600
+ depth--;
1601
+ else if (c === "," && depth === 0) {
1602
+ spans.push([start, i]);
1603
+ start = i + 1;
1604
+ }
1605
+ }
1606
+ spans.push([start, close]);
1607
+ return spans;
1608
+ }, newlines = (s) => {
1609
+ let n = 0;
1610
+ for (let i = 0;i < s.length; i++)
1611
+ if (s[i] === `
1612
+ `)
1613
+ n++;
1614
+ return n;
1615
+ }, stringArray = (v) => Array.isArray(v) && v.every((x) => typeof x === "string") ? v : null, readManifest2 = (json) => {
1616
+ if (!json || typeof json !== "object")
1617
+ return null;
1618
+ const m = json;
1619
+ const light = stringArray(m.light), aux = stringArray(m.aux);
1620
+ if (!light || !aux)
1621
+ return null;
1622
+ const file = (v) => typeof v === "string" && v !== "" ? v : v && typeof v === "object" && typeof v.file === "string" ? v.file : undefined;
1623
+ return {
1624
+ light,
1625
+ aux,
1626
+ direct: stringArray(m.direct) ?? undefined,
1627
+ indirect: stringArray(m.indirect) ?? undefined,
1628
+ probes: file(m.probes),
1629
+ volume: file(m.volume),
1630
+ uv1: file(m.uv1)
1631
+ };
1632
+ }, expandBakeMacros = async (src, filePath, exists, read) => {
1633
+ if (!hasBakeMacro(src))
1634
+ return { src, bakes: [] };
1635
+ const strings = [];
1636
+ const mask = maskNonCode(src, (s, e) => {
1637
+ strings.push([s, e]);
1638
+ });
1639
+ if (DECLARES_BAKE.test(mask))
1640
+ return { src, bakes: [] };
1641
+ const stringsIn = ([a, b]) => strings.filter(([s, e]) => s >= a && e <= b);
1642
+ const blankArg = (span) => mask.slice(span[0], span[1]).trim() === "" && stringsIn(span).length === 0;
1643
+ const literalOf = (span) => {
1644
+ const inside = stringsIn(span);
1645
+ if (inside.length === 1 && mask.slice(span[0], span[1]).trim() === "")
1646
+ return src.slice(inside[0][0], inside[0][1]);
1647
+ return src.slice(span[0], span[1]).trim();
1648
+ };
1649
+ const dir = filePath.slice(0, filePath.lastIndexOf("/"));
1650
+ const at = (index) => `${filePath.replace(/^\//, "")}:${newlines(src.slice(0, index)) + 1}`;
1651
+ const bakes = [];
1652
+ const parts = [];
1653
+ let done = 0;
1654
+ for (const m of mask.matchAll(BAKE_CALL)) {
1655
+ const start = m.index ?? 0;
1656
+ if (start < done)
1657
+ continue;
1658
+ const open = start + m[0].length - 1;
1659
+ const close = closeOf(mask, open);
1660
+ if (close < 0)
1661
+ throw new Error(`bake(): unclosed call (${at(start)})`);
1662
+ const spans = argSpans(mask, open, close);
1663
+ const pathText = literalOf(spans[0]);
1664
+ const written = stringLiteralValue2(pathText);
1665
+ if (written === null && DEFINITION_TAIL.test(mask.slice(close + 1, close + 160)))
1666
+ continue;
1667
+ if (written === null)
1668
+ throw new Error(`bake() expects a string-literal path like bake('./assets/lightmap/level.bake'), found \`${pathText || "()"}\` (${at(start)})`);
1669
+ if (!/\.bake$/i.test(written))
1670
+ throw new Error(`bake('${written}'): the path names a .bake file — what \`lecodes lightmap bake\` writes (${at(start)})`);
1671
+ if (spans.slice(2).some((s) => !blankArg(s)))
1672
+ throw new Error(`bake('${written}'): one path and one options object at most (${at(start)})`);
1673
+ let options = null;
1674
+ if (spans.length > 1 && !blankArg(spans[1])) {
1675
+ const [a, b] = spans[1];
1676
+ const code = mask.slice(a, b);
1677
+ const lead = a + (code.length - code.trimStart().length);
1678
+ const tail = b - (code.length - code.trimEnd().length);
1679
+ if (mask[lead] !== "{" || closeOf(mask, lead) !== tail - 1) {
1680
+ throw new Error(`bake('${written}', options): options must be an object literal like { emissiveBake: false } (${at(start)})`);
1681
+ }
1682
+ options = [lead, tail];
1683
+ }
1684
+ const relative = written.startsWith("./") || written.startsWith("../");
1685
+ const rooted = written.startsWith("/");
1686
+ const candidates = rooted ? [normalizePath(written)] : relative ? [normalizePath(`${dir}/${written}`), normalizePath(written)] : [normalizePath(written), normalizePath(`${dir}/${written}`)];
1687
+ const found = candidates.find(exists);
1688
+ const target = found ?? candidates[0];
1689
+ const ref = { path: target.slice(1), exists: found !== undefined, line: newlines(src.slice(0, start)) + 1, written, missing: [] };
1690
+ let manifest = null;
1691
+ if (found !== undefined) {
1692
+ const body = read ? await read(found) : null;
1693
+ if (body === null)
1694
+ ref.unread = true;
1695
+ else {
1696
+ let json;
1697
+ try {
1698
+ json = JSON.parse(body.replace(/^\uFEFF/, ""));
1699
+ } catch (e) {
1700
+ 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)})`);
1701
+ }
1702
+ manifest = readManifest2(json);
1703
+ if (!manifest)
1704
+ throw new Error(`bake('${written}'): ${ref.path} is not a bake manifest (no light / aux pages) — rebake with \`lecodes lightmap bake\` (${at(start)})`);
1705
+ }
1706
+ }
1707
+ const bakeDir = target.slice(0, target.lastIndexOf("/"));
1708
+ const ref1 = (name) => {
1709
+ const p = normalizePath(`${bakeDir}/${name}`);
1710
+ if (!exists(p) && !ref.missing.includes(p))
1711
+ ref.missing.push(p);
1712
+ return `asset(${JSON.stringify(p)})`;
1713
+ };
1714
+ const list = (names) => `[${names.map(ref1).join(", ")}]`;
1715
+ const fields = [`path: \`${escapeTemplate(ref.path)}\``];
1716
+ if (manifest) {
1717
+ fields.unshift(`data: asset(${JSON.stringify(target)})`);
1718
+ fields.push(`light: ${list(manifest.light)}`, `aux: ${list(manifest.aux)}`);
1719
+ if (manifest.direct)
1720
+ fields.push(`direct: ${list(manifest.direct)}`);
1721
+ if (manifest.indirect)
1722
+ fields.push(`indirect: ${list(manifest.indirect)}`);
1723
+ if (manifest.probes)
1724
+ fields.push(`probes: ${ref1(manifest.probes)}`);
1725
+ if (manifest.volume)
1726
+ fields.push(`volume: ${ref1(manifest.volume)}`);
1727
+ if (manifest.uv1)
1728
+ fields.push(`uv1: ${ref1(manifest.uv1)}`);
1729
+ } else {
1730
+ fields.unshift("data: null");
1731
+ fields.push("light: []", "aux: []");
1732
+ }
1733
+ const head = `{ ${fields.join(", ")}`;
1734
+ const expansion = options ? `${head}, ${`
1735
+ `.repeat(newlines(src.slice(start, options[0])))}...${src.slice(options[0], options[1])}${`
1736
+ `.repeat(newlines(src.slice(options[1], close + 1)))} }` : `${head}${`
1737
+ `.repeat(newlines(src.slice(start, close + 1)))} }`;
1738
+ parts.push(src.slice(done, start), expansion);
1739
+ done = close + 1;
1740
+ bakes.push(ref);
1741
+ }
1742
+ if (bakes.length === 0)
1743
+ return { src, bakes };
1744
+ parts.push(src.slice(done));
1745
+ return { src: parts.join(""), bakes };
1746
+ };
1747
+ var init_bakeMacro = __esm(() => {
1748
+ init_assetMacro();
1749
+ init_assetIconMacro();
1750
+ BAKE_CALL = /(?<![.\w$])bake\s*\(/g;
1751
+ DECLARES_BAKE = /\b(?:function|const|let|var|class)\s+bake\b|\bimport\s+(?:\{[^}]*\bbake\b[^}]*\}|bake)\s+from\b/;
1752
+ DEFINITION_TAIL = /^\s*(?::[^;{}=()\n]*)?(?:\{|=>)/;
1753
+ });
1754
+
1149
1755
  // ../../sdk/src/compile/detectEntry.ts
1150
1756
  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
1757
  const parts = [];
@@ -1242,7 +1848,7 @@ var HEAD_RE, hasAspectExtends = (src) => /\bextends\s+(?:Aspect|System)\s*</.tes
1242
1848
  for (;i < src.length && angle > 0; i++) {
1243
1849
  const c = src[i];
1244
1850
  if (c === '"' || c === "'" || c === "`") {
1245
- i = skipString(src, i) - 1;
1851
+ i = skipString2(src, i) - 1;
1246
1852
  continue;
1247
1853
  }
1248
1854
  if (c === "=" && src[i + 1] === ">") {
@@ -1269,7 +1875,7 @@ var HEAD_RE, hasAspectExtends = (src) => /\bextends\s+(?:Aspect|System)\s*</.tes
1269
1875
  return -1;
1270
1876
  }
1271
1877
  return -1;
1272
- }, skipString = (src, at) => {
1878
+ }, skipString2 = (src, at) => {
1273
1879
  const q = src[at];
1274
1880
  let i = at + 1;
1275
1881
  for (;i < src.length; i++) {
@@ -1533,18 +2139,18 @@ var init_chisel = __esm(() => {
1533
2139
  });
1534
2140
 
1535
2141
  // ../../sdk/src/compile/bundler.ts
1536
- import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync4, statSync } from "node:fs";
1537
- import { spawnSync as spawnSync3 } from "node:child_process";
2142
+ import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync5, statSync } from "node:fs";
2143
+ import { spawnSync as spawnSync4 } from "node:child_process";
1538
2144
  import { createRequire as createRequire3 } from "node:module";
1539
- import { dirname as dirname4, join as join6, relative } from "node:path";
2145
+ import { dirname as dirname4, join as join7, relative } from "node:path";
1540
2146
  import { fileURLToPath as fileURLToPath3 } from "node:url";
1541
2147
  var resolveInject = () => {
1542
2148
  const override = process.env.LECODES_SDK_INJECT;
1543
- if (override && existsSync5(override))
2149
+ if (override && existsSync6(override))
1544
2150
  return override;
1545
2151
  const here = dirname4(fileURLToPath3(import.meta.url));
1546
- const src = join6(here, "..", "inject.ts");
1547
- if (existsSync5(src))
2152
+ const src = join7(here, "..", "inject.ts");
2153
+ if (existsSync6(src))
1548
2154
  return src;
1549
2155
  throw new Error("sdk: could not locate the inject source (LECODES_SDK_INJECT / src/inject.ts)");
1550
2156
  }, normalize2 = (path) => {
@@ -1560,7 +2166,7 @@ var resolveInject = () => {
1560
2166
  return "/" + parts.join("/");
1561
2167
  }, CHISEL_DEFINE, CHISEL_KEEP, CLIENT_INJECT = "/__sdk/inject.ts", SERVER_INJECT = "/__sdk/server/inject.ts", EDITOR_INJECT = "/__sdk/inject.editor.ts", readInjectSources = () => {
1562
2168
  const client = resolveInject();
1563
- return { client: readFileSync4(client, "utf8"), server: readFileSync4(join6(dirname4(client), "server", "inject.ts"), "utf8") };
2169
+ return { client: readFileSync5(client, "utf8"), server: readFileSync5(join7(dirname4(client), "server", "inject.ts"), "utf8") };
1564
2170
  }, resolveSdkSrc = () => {
1565
2171
  const inject = resolveInject();
1566
2172
  if (!inject.endsWith("inject.ts")) {
@@ -1574,11 +2180,11 @@ var resolveInject = () => {
1574
2180
  const out = {};
1575
2181
  const walk = (dir) => {
1576
2182
  for (const name of readdirSync(dir)) {
1577
- const p = join6(dir, name);
2183
+ const p = join7(dir, name);
1578
2184
  if (statSync(p).isDirectory())
1579
2185
  walk(p);
1580
2186
  else if (isSdkSource(p))
1581
- out["/__sdk/" + relative(sdkSrc, p).split("\\").join("/")] = readFileSync4(p, "utf8");
2187
+ out["/__sdk/" + relative(sdkSrc, p).split("\\").join("/")] = readFileSync5(p, "utf8");
1582
2188
  }
1583
2189
  };
1584
2190
  walk(sdkSrc);
@@ -1587,13 +2193,13 @@ var resolveInject = () => {
1587
2193
  }, resolveChiselBin = () => {
1588
2194
  const override = process.env.LECODES_CHISEL_BIN;
1589
2195
  if (override) {
1590
- if (existsSync5(override))
2196
+ if (existsSync6(override))
1591
2197
  return override;
1592
2198
  throw new Error(`LECODES_CHISEL_BIN points at a missing file: ${override}`);
1593
2199
  }
1594
2200
  try {
1595
2201
  const bin = createRequire3(import.meta.url)("@letary/chisel").binaryPath();
1596
- if (bin && existsSync5(bin))
2202
+ if (bin && existsSync6(bin))
1597
2203
  return bin;
1598
2204
  } catch {}
1599
2205
  throw new Error("chisel binary not found. Install @letary/chisel, or set LECODES_CHISEL_BIN.");
@@ -1605,7 +2211,7 @@ var resolveInject = () => {
1605
2211
  return null;
1606
2212
  }
1607
2213
  const input = JSON.stringify({ scan: true, files });
1608
- const r = spawnSync3(bin, [], { input, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
2214
+ const r = spawnSync4(bin, [], { input, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
1609
2215
  if (r.status !== 0 || r.error)
1610
2216
  return null;
1611
2217
  try {
@@ -1646,11 +2252,13 @@ var resolveInject = () => {
1646
2252
  if (missing.length > 0)
1647
2253
  throw new Error(missing.join(`
1648
2254
  `));
1649
- }, bundleProjectChisel = async (entrypoint, files, format, sourcemap, minifyOverride, editor, libraries, inject = CLIENT_INJECT) => {
2255
+ }, bundleProjectChisel = async (entrypoint, files, format, sourcemap, minifyOverride, editor, libraries, inject = CLIENT_INJECT, readResource) => {
1650
2256
  const entry = normalize2(entrypoint);
1651
2257
  const slugs = new Set(Object.keys(libraries ?? {}));
2258
+ const rootKeys = new Map(Object.keys(files).map((k) => [normalize2(k), k]));
1652
2259
  const projFiles = {};
1653
2260
  const assetRefs = [];
2261
+ const bakeRefs = [];
1654
2262
  const scanSources = {};
1655
2263
  for (const [k, v] of Object.entries(files)) {
1656
2264
  const path = normalize2(k);
@@ -1659,6 +2267,12 @@ var resolveInject = () => {
1659
2267
  if (hasImportStatement(src))
1660
2268
  src = rewriteLibraryImports(src, slugs);
1661
2269
  scanSources[path] = src;
2270
+ if (hasBakeMacro(src)) {
2271
+ const expanded = await expandBakeMacros(src, path, (rootPath) => rootKeys.has(rootPath), readResource);
2272
+ for (const ref of expanded.bakes)
2273
+ bakeRefs.push({ file: path, ref });
2274
+ src = expanded.src;
2275
+ }
1662
2276
  const macros = hasAssetMacro(src) ? collectAssetMacros(src, path) : [];
1663
2277
  const paths = collectPathLiterals(src, path, (rootPath) => files[rootPath] !== undefined, macros);
1664
2278
  const found = macros.concat(paths).sort((a, b) => a.index - b.index);
@@ -1692,8 +2306,22 @@ var resolveInject = () => {
1692
2306
  projFiles[path] = src;
1693
2307
  }
1694
2308
  }
1695
- if (assetRefs.length > 0) {
2309
+ const bakes = [];
2310
+ const notes = [];
2311
+ if (assetRefs.length > 0 || bakeRefs.length > 0) {
1696
2312
  const reachable = reachableFrom(entry, scanSources);
2313
+ const reached = bakeRefs.filter(({ file }) => reachable.has(file));
2314
+ const incomplete = reached.filter(({ ref }) => ref.missing.length > 0);
2315
+ if (incomplete.length > 0) {
2316
+ 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(`
2317
+ `));
2318
+ }
2319
+ for (const { file, ref } of reached) {
2320
+ if (!bakes.some((b) => b.path === ref.path))
2321
+ bakes.push({ path: ref.path, exists: ref.exists });
2322
+ if (ref.unread)
2323
+ notes.push(`${file.replace(/^\//, "")}:${ref.line}: bake('${ref.written}'): this compile cannot read ${ref.path} — the level runs unbaked`);
2324
+ }
1697
2325
  assertAssetsExist(assetRefs.filter(({ file }) => reachable.has(file)), projFiles);
1698
2326
  }
1699
2327
  const sdkFiles = collectSdkFiles(resolveSdkSrc());
@@ -1713,7 +2341,7 @@ var resolveInject = () => {
1713
2341
  vocab: chiselVocab
1714
2342
  });
1715
2343
  const bin = resolveChiselBin();
1716
- const r = spawnSync3(bin, [], { input, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
2344
+ const r = spawnSync4(bin, [], { input, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
1717
2345
  if (r.status === 2 || r.error) {
1718
2346
  throw new Error(`chisel failed: ${r.error?.message ?? r.stderr ?? "unknown error"}`);
1719
2347
  }
@@ -1727,13 +2355,14 @@ var resolveInject = () => {
1727
2355
  throw new Error(`chisel: ${parsed.error}`);
1728
2356
  if (parsed.code == null)
1729
2357
  throw new Error("chisel produced no code");
1730
- const diagnostics = parsed.diagnostics ?? [];
2358
+ const diagnostics = [...notes, ...parsed.diagnostics ?? []];
1731
2359
  for (const d of diagnostics)
1732
2360
  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);
2361
+ return { code: parsed.code, map: parsed.map ?? null, diagnostics, bakes };
2362
+ }, bundleProjectWithMap = async (entrypoint, files, format = "esm", minify, editor, libraries, sourcemap = true, inject, readResource) => bundleProjectChisel(entrypoint, files, format, sourcemap, minify, editor, libraries, inject, readResource);
1735
2363
  var init_bundler = __esm(() => {
1736
2364
  init_assetMacro();
2365
+ init_bakeMacro();
1737
2366
  init_detectEntry();
1738
2367
  init_aspectMacro();
1739
2368
  init_libraryImports();
@@ -170839,6 +171468,18 @@ var init_shaderSchema = __esm(() => {
170839
171468
  COLOR_NAME = /colou?r|tint|albedo|emissive|glare|glow/i;
170840
171469
  });
170841
171470
 
171471
+ // ../../sdk/src/compile/sourcemap.ts
171472
+ var offsetSourceMap = (mapJson, lines) => {
171473
+ if (lines <= 0)
171474
+ return mapJson;
171475
+ const map = JSON.parse(mapJson);
171476
+ map.mappings = ";".repeat(lines) + map.mappings;
171477
+ return JSON.stringify(map);
171478
+ }, inlineSourceMapComment = (mapJson) => {
171479
+ const b64 = Buffer.from(mapJson, "utf8").toString("base64");
171480
+ return `//# lecodesMap=data:application/json;charset=utf-8;base64,${b64}`;
171481
+ };
171482
+
170842
171483
  // ../../sdk/src/compile/header.ts
170843
171484
  var ENGINE_SIGNALS, buildHeader = (name, code, opts = {}) => {
170844
171485
  let header = `// sdk: ${SDK_VERSION}
@@ -170904,333 +171545,6 @@ var init_header = __esm(() => {
170904
171545
  ];
170905
171546
  });
170906
171547
 
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
171548
  // ../../sdk/src/compile/compileProject.ts
171235
171549
  var localAssetBase = (path) => path.split(/[\\/]/).pop() ?? path, localAssetNames = (entries) => {
171236
171550
  const seen = new Map;
@@ -171328,7 +171642,7 @@ var localAssetBase = (path) => path.split(/[\\/]/).pop() ?? path, localAssetName
171328
171642
  throw new Error("Couldn't find an entrypoint (no .ts/.js project file — name yours main.ts). Pass entryOverride.");
171329
171643
  }
171330
171644
  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);
171645
+ const { code, map, bakes } = await bundleProjectWithMap(entry, files, opts.format ?? "esm", opts.minify, opts.editor, opts.libraries, opts.sourcemap ?? true, undefined, opts.readResource);
171332
171646
  const plugins = opts.plugins ?? pluginsOfAppJson(entries);
171333
171647
  if (map && opts.onWarning) {
171334
171648
  const missing = pluginsOfBundle(map).filter((id) => !plugins.includes(id));
@@ -171337,14 +171651,10 @@ var localAssetBase = (path) => path.split(/[\\/]/).pop() ?? path, localAssetName
171337
171651
  }
171338
171652
  }
171339
171653
  if (opts.header === false)
171340
- return { code, map, entry };
171654
+ return { code, map, entry, bakes };
171341
171655
  const header = buildHeader(opts.name, code, { icon: opts.icon, fonts: opts.fontsHeader, icons: opts.iconsHeader, plugins });
171342
171656
  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;
171657
+ return { code: header + code, map: map ? offsetSourceMap(map, headerLines) : null, entry, bakes };
171348
171658
  };
171349
171659
  var init_compileProject = __esm(() => {
171350
171660
  init_bundler();
@@ -171811,7 +172121,7 @@ var FONT_ID, CODE_FILE, DECLARES_FONT, isIdentStart2 = (c) => c >= "A" && c <= "
171811
172121
  }
171812
172122
  parts.push(text.slice(last));
171813
172123
  return parts;
171814
- }, stringLiteralValue2 = (text) => {
172124
+ }, stringLiteralValue3 = (text) => {
171815
172125
  const t = text.trim();
171816
172126
  if (t.length < 2)
171817
172127
  return null;
@@ -171883,7 +172193,7 @@ var FONT_ID, CODE_FILE, DECLARES_FONT, isIdentStart2 = (c) => c >= "A" && c <= "
171883
172193
  return null;
171884
172194
  const call = { start, end: close + 1, arg: "", kind: "registry" };
171885
172195
  const args = splitTopLevel2(src.slice(i + 1, close), ",");
171886
- const arg = stringLiteralValue2(args[0] ?? "");
172196
+ const arg = stringLiteralValue3(args[0] ?? "");
171887
172197
  if (arg === null) {
171888
172198
  call.error = `font(...) needs a string-literal argument — a registry id ("manrope") or a project path ("./fonts/Brand.ttf"). Got \`${(args[0] ?? "").trim() || "()"}\`.`;
171889
172199
  return call;
@@ -172138,15 +172448,15 @@ var init_compile = __esm(() => {
172138
172448
  });
172139
172449
 
172140
172450
  // src/compile/projectCompile.ts
172141
- import { existsSync as existsSync6 } from "node:fs";
172451
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
172142
172452
  import { createRequire as createRequire4 } from "node:module";
172143
- import { dirname as dirname6, join as join7 } from "node:path";
172453
+ import { dirname as dirname6, join as join8 } from "node:path";
172144
172454
  var resolveSdkInject = () => {
172145
172455
  try {
172146
172456
  return createRequire4(import.meta.url).resolve("lecodes-sdk/inject");
172147
172457
  } catch {}
172148
- const vendored = join7(distRoot, "runtime", "sdk", "inject.ts");
172149
- return existsSync6(vendored) ? vendored : null;
172458
+ const vendored = join8(distRoot, "runtime", "sdk", "inject.ts");
172459
+ return existsSync7(vendored) ? vendored : null;
172150
172460
  }, sdkInject, sdkSourceDir, resolveChiselBin2 = () => {
172151
172461
  if (process.env.LECODES_CHISEL_BIN)
172152
172462
  return process.env.LECODES_CHISEL_BIN;
@@ -172155,29 +172465,46 @@ var resolveSdkInject = () => {
172155
172465
  if (fromPkg)
172156
172466
  return fromPkg;
172157
172467
  } catch {}
172158
- const local = join7(dirname6(process.execPath), process.platform === "win32" ? "chisel.exe" : "chisel");
172159
- return existsSync6(local) ? local : null;
172468
+ const local = join8(dirname6(process.execPath), process.platform === "win32" ? "chisel.exe" : "chisel");
172469
+ return existsSync7(local) ? local : null;
172160
172470
  }, chiselBin, ambiguousEntryWarning = (entries) => {
172161
172471
  const { entry, roots } = detectEntry(entries, scanModulesChisel);
172162
172472
  if (!entry || roots.length < 2 || roots.includes(ENTRY_CONVENTION))
172163
172473
  return null;
172164
172474
  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) => {
172475
+ }, resourceReader = (entries) => {
172476
+ const byRoot = new Map(entries.map((e) => ["/" + e.path.replace(/\\/g, "/").replace(/^[./]+/, ""), e]));
172477
+ return (rootPath) => {
172478
+ const e = byRoot.get(rootPath);
172479
+ if (e?.text !== undefined)
172480
+ return e.text;
172481
+ try {
172482
+ return e?.absPath ? readFileSync6(e.absPath, "utf8") : null;
172483
+ } catch {
172484
+ return null;
172485
+ }
172486
+ };
172487
+ }, compileProjectBundle = async (opts) => {
172488
+ const { code, map, bakes } = await compileProjectWithMap({
172489
+ 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 })),
172490
+ readResource: resourceReader(opts.entries),
172491
+ name: opts.name,
172492
+ publicUrl: opts.publicUrl,
172493
+ entryOverride: opts.entryOverride,
172494
+ header: opts.header,
172495
+ fontsHeader: opts.fontsHeader,
172496
+ iconsHeader: opts.iconsHeader,
172497
+ format: opts.format,
172498
+ localAssets: opts.localAssets,
172499
+ minify: opts.minify,
172500
+ sourcemap: opts.sourcemap,
172501
+ editor: opts.editor,
172502
+ onWarning: opts.onWarning,
172503
+ serverUrl: opts.serverUrl
172504
+ });
172505
+ return { js: map ? `${code}
172506
+ ${inlineSourceMapComment(map)}` : code, bakes };
172507
+ }, compileProject2 = async (opts) => (await compileProjectBundle(opts)).js, hasServerFiles = (entries) => entries.some((e) => e.type === "text" && isServerPath(e.path)), findTypescript = async (root) => {
172181
172508
  const api = (mod) => mod?.createProgram ? mod : mod?.default?.createProgram ? mod.default : null;
172182
172509
  const from = (at) => {
172183
172510
  try {
@@ -172188,15 +172515,15 @@ var resolveSdkInject = () => {
172188
172515
  };
172189
172516
  const dir = peerDirInPrefix("typescript");
172190
172517
  const peer = () => {
172191
- if (!existsSync6(join7(dir, "package.json")))
172518
+ if (!existsSync7(join8(dir, "package.json")))
172192
172519
  return null;
172193
172520
  try {
172194
- return api(createRequire4(import.meta.url)(join7(dir, "lib", "typescript.js")));
172521
+ return api(createRequire4(import.meta.url)(join8(dir, "lib", "typescript.js")));
172195
172522
  } catch {
172196
172523
  return null;
172197
172524
  }
172198
172525
  };
172199
- const found = from(join7(root, "package.json")) ?? from(import.meta.url) ?? peer();
172526
+ const found = from(join8(root, "package.json")) ?? from(import.meta.url) ?? peer();
172200
172527
  if (found)
172201
172528
  return found;
172202
172529
  await installPeers(["typescript"], "The project's server code (*.server.ts)");
@@ -172381,8 +172708,8 @@ var init_assetName = __esm(() => {
172381
172708
  });
172382
172709
 
172383
172710
  // src/project/paths.ts
172384
- import { createHash } from "node:crypto";
172385
- var sha256 = (data) => createHash("sha256").update(data).digest("hex"), toKey = (path) => {
172711
+ import { createHash as createHash2 } from "node:crypto";
172712
+ var sha256 = (data) => createHash2("sha256").update(data).digest("hex"), toKey = (path) => {
172386
172713
  const parts = path.replace(/\\/g, "/").split("/").map((p) => p.trim()).filter((p) => p && p !== ".");
172387
172714
  if (parts.some((p) => p === ".."))
172388
172715
  throw new CliError(`Unsafe path: ${path}`);
@@ -172397,8 +172724,8 @@ var init_paths = __esm(() => {
172397
172724
  });
172398
172725
 
172399
172726
  // src/project/ignore.ts
172400
- import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
172401
- import { join as join10 } from "node:path";
172727
+ import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "node:fs";
172728
+ import { join as join11 } from "node:path";
172402
172729
  var BUILTIN_PATTERNS, DEFAULT_IGNORE_FILE = `# Files matched here are NOT pushed to LeCodes.
172403
172730
  # Only untracked files are affected — assets already in the project stay tracked
172404
172731
  # even if they match a pattern. Built-in defaults (node_modules, _*, *.log, .artifacts)
@@ -172479,18 +172806,18 @@ design/comments/
172479
172806
  return (path) => match(path.startsWith("/") ? path : "/" + path, false);
172480
172807
  }, loadIgnore = (root) => {
172481
172808
  const lines = [...BUILTIN_PATTERNS];
172482
- const file = join10(root, ".lecodesignore");
172483
- if (existsSync9(file)) {
172809
+ const file = join11(root, ".lecodesignore");
172810
+ if (existsSync10(file)) {
172484
172811
  try {
172485
- lines.push(...readFileSync7(file, "utf8").split(/\r?\n/));
172812
+ lines.push(...readFileSync9(file, "utf8").split(/\r?\n/));
172486
172813
  } catch {}
172487
172814
  }
172488
172815
  const rules = lines.map(compile).filter((r) => r !== null);
172489
172816
  return buildMatcher(rules);
172490
172817
  }, writeDefaultIgnore = (root) => {
172491
- const file = join10(root, ".lecodesignore");
172492
- if (!existsSync9(file))
172493
- writeFileSync3(file, DEFAULT_IGNORE_FILE);
172818
+ const file = join11(root, ".lecodesignore");
172819
+ if (!existsSync10(file))
172820
+ writeFileSync4(file, DEFAULT_IGNORE_FILE);
172494
172821
  }, DEFAULT_GITIGNORE = `# LeCodes per-checkout state: generated IDE types + this machine's sync cursor
172495
172822
  # (.lecodes/manifest.json). Regenerate with \`lecodes update\` / re-attach with \`lecodes link\`.
172496
172823
  .lecodes/
@@ -172505,19 +172832,19 @@ node_modules/
172505
172832
 
172506
172833
  dist/
172507
172834
  `, ensureGitignore = (root) => {
172508
- const file = join10(root, ".gitignore");
172509
- if (!existsSync9(file)) {
172510
- writeFileSync3(file, DEFAULT_GITIGNORE);
172835
+ const file = join11(root, ".gitignore");
172836
+ if (!existsSync10(file)) {
172837
+ writeFileSync4(file, DEFAULT_GITIGNORE);
172511
172838
  return "created";
172512
172839
  }
172513
- const text = readFileSync7(file, "utf8");
172840
+ const text = readFileSync9(file, "utf8");
172514
172841
  const covered = text.split(/\r?\n/).some((l) => /^\/?\.lecodes\/?\s*$/.test(l.trim()));
172515
172842
  if (covered)
172516
172843
  return null;
172517
172844
  const sep = text.length === 0 || text.endsWith(`
172518
172845
  `) ? "" : `
172519
172846
  `;
172520
- writeFileSync3(file, `${text}${sep}
172847
+ writeFileSync4(file, `${text}${sep}
172521
172848
  # LeCodes per-checkout state (generated types + sync cursor) — not source
172522
172849
  .lecodes/
172523
172850
  `);
@@ -172528,8 +172855,8 @@ var init_ignore = __esm(() => {
172528
172855
  });
172529
172856
 
172530
172857
  // src/project/localFiles.ts
172531
- import { existsSync as existsSync10, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "node:fs";
172532
- import { basename, dirname as dirname7, join as join11, relative as relative3 } from "node:path";
172858
+ import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync10 } from "node:fs";
172859
+ import { basename, dirname as dirname7, join as join12, relative as relative3 } from "node:path";
172533
172860
  var SKIP_DIRS, SKIP_FILES, TEXT_EXTS, ext = (name) => {
172534
172861
  const i = name.lastIndexOf(".");
172535
172862
  return i < 0 ? "" : name.slice(i + 1).toLowerCase();
@@ -172547,14 +172874,14 @@ var SKIP_DIRS, SKIP_FILES, TEXT_EXTS, ext = (name) => {
172547
172874
  continue;
172548
172875
  let folder;
172549
172876
  try {
172550
- folder = JSON.parse(readFileSync8(file.absPath, "utf8").replace(/^\uFEFF/, "")).web?.static;
172877
+ folder = JSON.parse(readFileSync10(file.absPath, "utf8").replace(/^\uFEFF/, "")).web?.static;
172551
172878
  } catch {
172552
172879
  continue;
172553
172880
  }
172554
172881
  if (typeof folder !== "string" || folder.trim() === "")
172555
172882
  continue;
172556
172883
  try {
172557
- folders.push(toKey(relative3(root, join11(dirname7(file.absPath), folder))) + "/");
172884
+ folders.push(toKey(relative3(root, join12(dirname7(file.absPath), folder))) + "/");
172558
172885
  } catch {}
172559
172886
  }
172560
172887
  return folders;
@@ -172575,7 +172902,7 @@ var SKIP_DIRS, SKIP_FILES, TEXT_EXTS, ext = (name) => {
172575
172902
  };
172576
172903
  const walk = (dir, parentIgnored) => {
172577
172904
  for (const entry of readdirSync3(dir, { withFileTypes: true })) {
172578
- const abs = join11(dir, entry.name);
172905
+ const abs = join12(dir, entry.name);
172579
172906
  const key = toKey(relative3(root, abs));
172580
172907
  if (entry.isDirectory()) {
172581
172908
  if (SKIP_DIRS.has(entry.name))
@@ -172595,7 +172922,7 @@ var SKIP_DIRS, SKIP_FILES, TEXT_EXTS, ext = (name) => {
172595
172922
  ignored.push(key);
172596
172923
  continue;
172597
172924
  }
172598
- found.push({ path: key, absPath: abs, sha: sha256(readFileSync8(abs)) });
172925
+ found.push({ path: key, absPath: abs, sha: sha256(readFileSync10(abs)) });
172599
172926
  }
172600
172927
  }
172601
172928
  };
@@ -172607,8 +172934,8 @@ var SKIP_DIRS, SKIP_FILES, TEXT_EXTS, ext = (name) => {
172607
172934
  }));
172608
172935
  return { files, dirs, ignored };
172609
172936
  }, resolveLocalPath = (root, key) => {
172610
- const direct = join11(root, key.slice(1));
172611
- if (existsSync10(direct))
172937
+ const direct = join12(root, key.slice(1));
172938
+ if (existsSync11(direct))
172612
172939
  return direct;
172613
172940
  let dir = root;
172614
172941
  try {
@@ -172616,7 +172943,7 @@ var SKIP_DIRS, SKIP_FILES, TEXT_EXTS, ext = (name) => {
172616
172943
  const match = readdirSync3(dir).find((entry) => entry === name || sanitizeAssetName2(entry) === name);
172617
172944
  if (!match)
172618
172945
  return null;
172619
- dir = join11(dir, match);
172946
+ dir = join12(dir, match);
172620
172947
  }
172621
172948
  } catch {
172622
172949
  return null;
@@ -172675,9 +173002,9 @@ var init_localFiles = __esm(() => {
172675
173002
  });
172676
173003
 
172677
173004
  // src/compile/fonts.ts
172678
- import { createHash as createHash2 } from "node:crypto";
172679
- import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "node:fs";
172680
- import { dirname as dirname9, join as join13 } from "node:path";
173005
+ import { createHash as createHash3 } from "node:crypto";
173006
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "node:fs";
173007
+ import { dirname as dirname9, join as join14 } from "node:path";
172681
173008
  var applyProjectFonts = async (entries, opts) => {
172682
173009
  const registry = (opts.registry ?? fontRegistryBase()).replace(/\/+$/, "");
172683
173010
  const byPath = () => new Map(entries.map((e) => [e.path.replace(/^\//, ""), e]));
@@ -172686,9 +173013,9 @@ var applyProjectFonts = async (entries, opts) => {
172686
173013
  registryBase: registry,
172687
173014
  readBytes: (entryPath) => {
172688
173015
  const entry = byPath().get(entryPath);
172689
- const abs = entry?.absPath ?? join13(opts.root, entryPath);
173016
+ const abs = entry?.absPath ?? join14(opts.root, entryPath);
172690
173017
  try {
172691
- return new Uint8Array(readFileSync11(abs));
173018
+ return new Uint8Array(readFileSync13(abs));
172692
173019
  } catch {
172693
173020
  return null;
172694
173021
  }
@@ -172696,9 +173023,9 @@ var applyProjectFonts = async (entries, opts) => {
172696
173023
  vendorFace: opts.mode === "vendor" ? async (face) => {
172697
173024
  const vendorDir = opts.vendorDir ?? opts.root;
172698
173025
  const rel = `assets/fonts/${face.fileName}`;
172699
- const abs = join13(vendorDir, rel);
173026
+ const abs = join14(vendorDir, rel);
172700
173027
  const entryPath = `${opts.vendorPrefix ?? ""}/${rel}`;
172701
- if (!existsSync11(abs))
173028
+ if (!existsSync12(abs))
172702
173029
  await downloadFace(registry, vendorDir, face, rel, abs);
172703
173030
  if (!byPath().has(entryPath.replace(/^\//, ""))) {
172704
173031
  entries.push(opts.makeResourceEntry?.(entryPath, abs) ?? { path: entryPath, type: "resource", absPath: abs });
@@ -172714,19 +173041,19 @@ var applyProjectFonts = async (entries, opts) => {
172714
173041
  const fileSrc = byPath().get(path.replace(/^\//, ""))?.fileSrc;
172715
173042
  return fileSrc ? (opts.publicUrl ?? "") + fileSrc : null;
172716
173043
  }) || undefined;
172717
- }, isHttp = (s) => /^https?:/i.test(s), sha12 = (b) => createHash2("sha256").update(b).digest("hex").slice(0, 12), registryHome = (registry) => registry.replace(/\/v\d+$/, ""), readRegistry = async (registry, rel) => {
173044
+ }, isHttp = (s) => /^https?:/i.test(s), sha12 = (b) => createHash3("sha256").update(b).digest("hex").slice(0, 12), registryHome = (registry) => registry.replace(/\/v\d+$/, ""), readRegistry = async (registry, rel) => {
172718
173045
  if (isHttp(registry)) {
172719
173046
  const res = await fetch(`${registry}/${rel}`);
172720
173047
  return res.ok ? new Uint8Array(await res.arrayBuffer()) : null;
172721
173048
  }
172722
173049
  try {
172723
- return new Uint8Array(readFileSync11(join13(registry, rel)));
173050
+ return new Uint8Array(readFileSync13(join14(registry, rel)));
172724
173051
  } catch {
172725
173052
  return null;
172726
173053
  }
172727
173054
  }, readLock = (path) => {
172728
173055
  try {
172729
- const lock = JSON.parse(readFileSync11(path, "utf8"));
173056
+ const lock = JSON.parse(readFileSync13(path, "utf8"));
172730
173057
  if (lock && typeof lock === "object" && lock.faces && lock.families)
172731
173058
  return lock;
172732
173059
  } catch {}
@@ -172758,10 +173085,10 @@ var applyProjectFonts = async (entries, opts) => {
172758
173085
  `);
172759
173086
  }, writeIfChanged = (path, text) => {
172760
173087
  try {
172761
- if (readFileSync11(path, "utf8") === text)
173088
+ if (readFileSync13(path, "utf8") === text)
172762
173089
  return;
172763
173090
  } catch {}
172764
- writeFileSync5(path, text);
173091
+ writeFileSync6(path, text);
172765
173092
  }, downloadFace = async (registry, vendorDir, face, rel, abs) => {
172766
173093
  const licRaw = await readRegistry(registry, "licenses.json");
172767
173094
  if (!licRaw)
@@ -172773,15 +173100,15 @@ var applyProjectFonts = async (entries, opts) => {
172773
173100
  const bytes = await readRegistry(registry, `${face.id}/${face.fileName}`);
172774
173101
  if (!bytes)
172775
173102
  throw new Error(`font("${face.id}"): face "${face.fileName}" not found in the registry (${registryHome(registry)}).`);
172776
- mkdirSync4(dirname9(abs), { recursive: true });
172777
- writeFileSync5(abs, bytes);
172778
- const lockPath = join13(vendorDir, "fonts.lock.json");
173103
+ mkdirSync5(dirname9(abs), { recursive: true });
173104
+ writeFileSync6(abs, bytes);
173105
+ const lockPath = join14(vendorDir, "fonts.lock.json");
172779
173106
  const lock = readLock(lockPath) ?? { registry, faces: {}, families: {} };
172780
173107
  lock.registry = registry;
172781
173108
  lock.faces[`${face.id}@${face.weight}${face.italic ? "i" : ""}`] = { id: face.id, family: face.family, weight: face.weight, italic: face.italic, hash: sha12(bytes), spdx: lic.spdx, path: rel };
172782
173109
  lock.families[face.id] = lic;
172783
173110
  writeIfChanged(lockPath, serializeLock(lock));
172784
- writeIfChanged(join13(vendorDir, "FONT-NOTICES.md"), renderNotices(lock));
173111
+ writeIfChanged(join14(vendorDir, "FONT-NOTICES.md"), renderNotices(lock));
172785
173112
  };
172786
173113
  var init_fonts = __esm(() => {
172787
173114
  init_compile();
@@ -172789,7 +173116,7 @@ var init_fonts = __esm(() => {
172789
173116
 
172790
173117
  // src/compile/assetIcons.ts
172791
173118
  import { createHash as createHash5 } from "node:crypto";
172792
- import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "node:fs";
173119
+ import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
172793
173120
  import { homedir as homedir4 } from "node:os";
172794
173121
  import { dirname as dirname10, join as join17 } from "node:path";
172795
173122
  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 +173129,13 @@ var DEFAULT_REGISTRY, isHttp2 = (s) => /^https?:/i.test(s), sha122 = (s) => crea
172802
173129
  }
172803
173130
  }
172804
173131
  try {
172805
- return readFileSync14(join17(registry, rel), "utf8");
173132
+ return readFileSync15(join17(registry, rel), "utf8");
172806
173133
  } catch {
172807
173134
  return null;
172808
173135
  }
172809
173136
  }, safeRead = (path) => {
172810
173137
  try {
172811
- return readFileSync14(path, "utf8");
173138
+ return readFileSync15(path, "utf8");
172812
173139
  } catch {
172813
173140
  return null;
172814
173141
  }
@@ -172830,7 +173157,7 @@ var DEFAULT_REGISTRY, isHttp2 = (s) => /^https?:/i.test(s), sha122 = (s) => crea
172830
173157
  return JSON.parse(raw);
172831
173158
  }, readLock2 = (path) => {
172832
173159
  try {
172833
- const lock = JSON.parse(readFileSync14(path, "utf8"));
173160
+ const lock = JSON.parse(readFileSync15(path, "utf8"));
172834
173161
  if (lock && typeof lock === "object" && lock.icons && lock.packs)
172835
173162
  return lock;
172836
173163
  } catch {}
@@ -172937,11 +173264,11 @@ var init_assetIcons = __esm(() => {
172937
173264
  // src/platform/config.ts
172938
173265
  import { homedir as homedir5 } from "node:os";
172939
173266
  import { join as join18 } from "node:path";
172940
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync9 } from "node:fs";
173267
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "node:fs";
172941
173268
  var DEFAULT_API = "https://le.codes", CONFIG_DIR, CONFIG_FILE, loadConfig = () => {
172942
173269
  let stored = {};
172943
173270
  try {
172944
- stored = JSON.parse(readFileSync15(CONFIG_FILE, "utf8"));
173271
+ stored = JSON.parse(readFileSync16(CONFIG_FILE, "utf8"));
172945
173272
  } catch {}
172946
173273
  return {
172947
173274
  apiUrl: process.env.LECODES_API ?? stored.apiUrl,
@@ -206398,7 +206725,7 @@ var init_esm29 = __esm(() => {
206398
206725
 
206399
206726
  // src/project/types.ts
206400
206727
  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";
206728
+ 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
206729
  import { createRequire as createRequire5 } from "node:module";
206403
206730
  var TSCONFIG, listBundleFiles = (dir, base = dir) => {
206404
206731
  if (!existsSync27(dir))
@@ -206427,7 +206754,7 @@ var TSCONFIG, listBundleFiles = (dir, base = dir) => {
206427
206754
  const dest = join29(typesDir, path);
206428
206755
  if (!before.has(path))
206429
206756
  stats.added++;
206430
- else if (readFileSync24(dest, "utf8") !== text)
206757
+ else if (readFileSync25(dest, "utf8") !== text)
206431
206758
  stats.updated++;
206432
206759
  else
206433
206760
  continue;
@@ -206446,7 +206773,7 @@ var TSCONFIG, listBundleFiles = (dir, base = dir) => {
206446
206773
  const typesDir = join29(root, LECODES_DIR, "types");
206447
206774
  const bundle = {};
206448
206775
  for (const path of listBundleFiles(typesDir))
206449
- bundle[path] = readFileSync24(join29(typesDir, path), "utf8");
206776
+ bundle[path] = readFileSync25(join29(typesDir, path), "utf8");
206450
206777
  return bundle;
206451
206778
  }, materializeTypes = async (root, apiUrl) => writeTypeBundle(root, await getTypes(apiUrl)), readLocalTypeBundle = () => {
206452
206779
  const fromPackage = (() => {
@@ -206464,7 +206791,7 @@ var TSCONFIG, listBundleFiles = (dir, base = dir) => {
206464
206791
  for (const path of candidates) {
206465
206792
  if (existsSync27(path)) {
206466
206793
  try {
206467
- return JSON.parse(readFileSync24(path, "utf8"));
206794
+ return JSON.parse(readFileSync25(path, "utf8"));
206468
206795
  } catch {}
206469
206796
  }
206470
206797
  }
@@ -206502,7 +206829,7 @@ var init_types4 = __esm(() => {
206502
206829
  });
206503
206830
 
206504
206831
  // src/compile/designCompile.ts
206505
- import { readFileSync as readFileSync25 } from "node:fs";
206832
+ import { readFileSync as readFileSync26 } from "node:fs";
206506
206833
  var LOCAL_ASSET_SCHEME = "lecodesfile://", compileDesignScreen = async (opts) => {
206507
206834
  const { root, manifest, designDir, designKey, screenId, state } = opts;
206508
206835
  const entries = [];
@@ -206519,7 +206846,7 @@ var LOCAL_ASSET_SCHEME = "lecodesfile://", compileDesignScreen = async (opts) =>
206519
206846
  } else if (type === "shader") {
206520
206847
  entries.push({ path: file.path, type, shaders: [] });
206521
206848
  } else {
206522
- entries.push({ path: file.path, type, text: readFileSync25(file.absPath, "utf8") });
206849
+ entries.push({ path: file.path, type, text: readFileSync26(file.absPath, "utf8") });
206523
206850
  }
206524
206851
  }
206525
206852
  const invoke = state !== undefined ? `screen(${JSON.stringify(state)})` : "screen()";
@@ -206641,11 +206968,11 @@ var init_browserAuth = __esm(() => {
206641
206968
  });
206642
206969
 
206643
206970
  // src/project/designMeta.ts
206644
- import { readFileSync as readFileSync26 } from "node:fs";
206971
+ import { readFileSync as readFileSync27 } from "node:fs";
206645
206972
  import { join as join31 } from "node:path";
206646
206973
  var PHONE_SIZE, readMetaFrames = (designDir) => {
206647
206974
  try {
206648
- const meta = JSON.parse(readFileSync26(join31(designDir, "meta.json"), "utf8"));
206975
+ const meta = JSON.parse(readFileSync27(join31(designDir, "meta.json"), "utf8"));
206649
206976
  const d = meta?.defaultSize;
206650
206977
  const valid = Array.isArray(d) && d.length === 2 && d.every((n) => typeof n === "number" && Number.isFinite(n) && n >= 64 && n <= 8192);
206651
206978
  return {
@@ -206996,7 +207323,7 @@ var init_comments = __esm(() => {
206996
207323
  });
206997
207324
 
206998
207325
  // 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";
207326
+ import { existsSync as existsSync32, mkdirSync as mkdirSync20, readdirSync as readdirSync15, readFileSync as readFileSync28, writeFileSync as writeFileSync22 } from "node:fs";
207000
207327
  import { join as join33 } from "node:path";
207001
207328
  var listScreens = (designDir) => {
207002
207329
  const dir = join33(designDir, "screens");
@@ -207068,7 +207395,7 @@ var init_snapshot = __esm(() => {
207068
207395
  const [width, height] = frames.screens[id]?.size ?? frames.defaultSize;
207069
207396
  let plan = [{ state: undefined, bases: [id] }];
207070
207397
  if (discover) {
207071
- const { states, canonical } = discover(readFileSync27(join33(ctx.designDir, "screens", `${id}.ts`), "utf8"));
207398
+ const { states, canonical } = discover(readFileSync28(join33(ctx.designDir, "screens", `${id}.ts`), "utf8"));
207072
207399
  if (stateFlag !== undefined) {
207073
207400
  if (!states.includes(stateFlag)) {
207074
207401
  if (args.length === 1)
@@ -207598,7 +207925,7 @@ var init_templates2 = __esm(() => {
207598
207925
  });
207599
207926
 
207600
207927
  // 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";
207928
+ import { appendFileSync as appendFileSync2, existsSync as existsSync33, mkdirSync as mkdirSync21, readFileSync as readFileSync29, writeFileSync as writeFileSync23 } from "node:fs";
207602
207929
  import { join as join34 } from "node:path";
207603
207930
  var firstLine = (body, max = 64) => {
207604
207931
  const line = body.split(`
@@ -207673,7 +208000,7 @@ var firstLine = (body, max = 64) => {
207673
208000
  let config = {};
207674
208001
  if (existsSync33(path)) {
207675
208002
  try {
207676
- config = JSON.parse(readFileSync28(path, "utf8"));
208003
+ config = JSON.parse(readFileSync29(path, "utf8"));
207677
208004
  } catch {
207678
208005
  warnErr(".mcp.json exists but isn't valid JSON — left it untouched. Add the lecodes-design server by hand.");
207679
208006
  return;
@@ -207753,7 +208080,7 @@ var init_design = __esm(() => {
207753
208080
  }
207754
208081
  const ignorePath = join34(ctx.root, ".lecodesignore");
207755
208082
  const ignoreLines = [`${ctx.dirName}/.snapshots/`, `${ctx.dirName}/comments/`];
207756
- const current = existsSync33(ignorePath) ? readFileSync28(ignorePath, "utf8") : "";
208083
+ const current = existsSync33(ignorePath) ? readFileSync29(ignorePath, "utf8") : "";
207757
208084
  const present = new Set(current.split(/\r?\n/));
207758
208085
  const missing = ignoreLines.filter((line) => !present.has(line));
207759
208086
  if (missing.length) {
@@ -227112,32 +227439,32 @@ import { delimiter as delimiter3, dirname as dirname31, join as join63 } from "n
227112
227439
  // src/commands/app/index.ts
227113
227440
  init_manifest();
227114
227441
  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";
227442
+ 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
227443
  import { homedir as homedir10 } from "node:os";
227117
227444
  import { basename as basename10, dirname as dirname17, join as join27, resolve as resolve14 } from "node:path";
227118
227445
 
227119
227446
  // src/plugin/manifest.ts
227120
- import { existsSync as existsSync8, readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
227121
- import { isAbsolute as isAbsolute2, join as join9 } from "node:path";
227447
+ import { existsSync as existsSync9, readFileSync as readFileSync8, statSync as statSync2 } from "node:fs";
227448
+ import { isAbsolute as isAbsolute2, join as join10 } from "node:path";
227122
227449
 
227123
227450
  // src/plugin/firstParty.ts
227124
227451
  init_projectCompile();
227125
- import { existsSync as existsSync7, readFileSync as readFileSync5, readdirSync as readdirSync2 } from "node:fs";
227126
- import { join as join8 } from "node:path";
227452
+ import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync as readdirSync2 } from "node:fs";
227453
+ import { join as join9 } from "node:path";
227127
227454
  var ID = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
227128
227455
  var firstPartyContractFile = (id) => {
227129
227456
  if (!sdkSourceDir || !ID.test(id))
227130
227457
  return null;
227131
- const file = join8(sdkSourceDir, "plugins", "gen", id, "contract.d.ts");
227132
- return existsSync7(file) ? file : null;
227458
+ const file = join9(sdkSourceDir, "plugins", "gen", id, "contract.d.ts");
227459
+ return existsSync8(file) ? file : null;
227133
227460
  };
227134
227461
  var firstPartyIds = () => {
227135
227462
  if (!sdkSourceDir)
227136
227463
  return [];
227137
- const gen = join8(sdkSourceDir, "plugins", "gen");
227138
- return existsSync7(gen) ? readdirSync2(gen).filter((id) => existsSync7(join8(gen, id, "contract.d.ts"))).sort() : [];
227464
+ const gen = join9(sdkSourceDir, "plugins", "gen");
227465
+ return existsSync8(gen) ? readdirSync2(gen).filter((id) => existsSync8(join9(gen, id, "contract.d.ts"))).sort() : [];
227139
227466
  };
227140
- var firstPartyContractText = (file) => readFileSync5(file, "utf8").replace(/from "(?:\.\.\/)+plugin"/g, `from "lecodes-sdk/plugin"`);
227467
+ var firstPartyContractText = (file) => readFileSync7(file, "utf8").replace(/from "(?:\.\.\/)+plugin"/g, `from "lecodes-sdk/plugin"`);
227141
227468
 
227142
227469
  // src/plugin/manifest.ts
227143
227470
  var MANIFEST_FILE = "lecodes-plugin.json";
@@ -227170,15 +227497,15 @@ var BRANCH = {
227170
227497
  web: new Set(["register", "dependencies", "resources", "static"])
227171
227498
  };
227172
227499
  var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
227173
- var isFolder = (path) => existsSync8(path) && statSync2(path).isDirectory();
227500
+ var isFolder = (path) => existsSync9(path) && statSync2(path).isDirectory();
227174
227501
  var pascal = (id) => id.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
227175
227502
  var XCFRAMEWORK = ".xcframework";
227176
227503
  var binaryName = (path) => path.split(/[\\/]/).pop().slice(0, -XCFRAMEWORK.length);
227177
- var readManifest2 = (dir, id) => {
227178
- const file = join9(dir, MANIFEST_FILE);
227504
+ var readManifest3 = (dir, id) => {
227505
+ const file = join10(dir, MANIFEST_FILE);
227179
227506
  let raw;
227180
227507
  try {
227181
- raw = JSON.parse(readFileSync6(file, "utf8").replace(/^/, ""));
227508
+ raw = JSON.parse(readFileSync8(file, "utf8").replace(/^/, ""));
227182
227509
  } catch (e) {
227183
227510
  throw new ManifestError(file, [`not JSON: ${e instanceof Error ? e.message : String(e)}`]);
227184
227511
  }
@@ -227217,14 +227544,14 @@ var readManifest2 = (dir, id) => {
227217
227544
  problems.push(`implements: the id of the first-party plugin whose contract the folder implements ("ar")`);
227218
227545
  else if (!firstPartyContractFile(impl))
227219
227546
  problems.push(`implements: "${impl}" is no first-party plugin of this SDK (${firstPartyIds().join(", ") || "it carries none"})`);
227220
- if (existsSync8(join9(dir, CONTRACT_FILE)))
227547
+ if (existsSync9(join10(dir, CONTRACT_FILE)))
227221
227548
  problems.push(`implements: the folder implements another plugin's contract — it has no ${CONTRACT_FILE} of its own`);
227222
- if (isFolder(join9(dir, "sdk")))
227549
+ if (isFolder(join10(dir, "sdk")))
227223
227550
  problems.push("implements: the folder implements another plugin — its app side is the SDK's, it has no sdk/ of its own");
227224
227551
  }
227225
227552
  for (const platform of PLATFORMS) {
227226
227553
  const branch = raw[platform];
227227
- const folder = isFolder(join9(dir, platform));
227554
+ const folder = isFolder(join10(dir, platform));
227228
227555
  if (branch === undefined) {
227229
227556
  problems.push(`${platform}: missing — an object, or null where the plugin has no ${platform} half`);
227230
227557
  continue;
@@ -227255,7 +227582,7 @@ var readManifest2 = (dir, id) => {
227255
227582
  if (platform === "web" && register !== undefined) {
227256
227583
  if (!register.startsWith("web/"))
227257
227584
  problems.push(`web.register: "${register}" — a module of the web/ folder`);
227258
- else if (!existsSync8(join9(dir, register)))
227585
+ else if (!existsSync9(join10(dir, register)))
227259
227586
  problems.push(`web.register: ${register} is not there`);
227260
227587
  }
227261
227588
  }
@@ -227295,7 +227622,7 @@ var readManifest2 = (dir, id) => {
227295
227622
  problems.push(`ios.binaries[${i}]: ${path} is in ios/, the half's sources — a binary stands beside them ("binaries/ios/X.xcframework")`);
227296
227623
  else if (!/^[A-Za-z_]\w*$/.test(binaryName(path)))
227297
227624
  problems.push(`ios.binaries[${i}]: "${path}" — the folder's name is its binary target's: a Swift name before ${XCFRAMEWORK}`);
227298
- else if (!isFolder(join9(dir, path)))
227625
+ else if (!isFolder(join10(dir, path)))
227299
227626
  problems.push(`ios.binaries[${i}]: ${path} is not a folder of the plugin`);
227300
227627
  else if (named.has(binaryName(path)))
227301
227628
  problems.push(`ios.binaries[${i}]: ${path} is named "${binaryName(path)}" like ${named.get(binaryName(path))} — a name is one binary target`);
@@ -227336,7 +227663,7 @@ var readManifest2 = (dir, id) => {
227336
227663
  const segments = typeof folder === "string" ? folder.split(/[\\/]/) : [];
227337
227664
  if (typeof folder !== "string" || folder.trim() === "" || isAbsolute2(folder) || segments.some((s) => s === ".." || s === "." || s === "")) {
227338
227665
  problems.push(`web.static: a folder of the plugin, by its path in it ("xr-runtime")`);
227339
- } else if (!isFolder(join9(dir, folder)))
227666
+ } else if (!isFolder(join10(dir, folder)))
227340
227667
  problems.push(`web.static: ${folder} is not a folder of the plugin`);
227341
227668
  else if (folder === "index.js")
227342
227669
  problems.push(`web.static: "index.js" is the built module's name`);
@@ -227346,7 +227673,7 @@ var readManifest2 = (dir, id) => {
227346
227673
  throw new ManifestError(file, problems);
227347
227674
  return raw;
227348
227675
  };
227349
- var readManifestIfAny = (dir, id) => existsSync8(join9(dir, MANIFEST_FILE)) ? readManifest2(dir, id) : null;
227676
+ var readManifestIfAny = (dir, id) => existsSync9(join10(dir, MANIFEST_FILE)) ? readManifest3(dir, id) : null;
227350
227677
 
227351
227678
  // src/commands/compile.ts
227352
227679
  init_api();
@@ -227357,15 +227684,15 @@ import { basename as basename6, resolve as resolve6 } from "node:path";
227357
227684
  // src/compile/collect.ts
227358
227685
  init_localFiles();
227359
227686
  init_ignore();
227360
- import { readFileSync as readFileSync9 } from "node:fs";
227687
+ import { readFileSync as readFileSync11 } from "node:fs";
227361
227688
 
227362
227689
  // src/project/materialize.ts
227363
227690
  init_api();
227364
227691
  init_paths();
227365
- import { dirname as dirname8, join as join12 } from "node:path";
227366
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "node:fs";
227692
+ import { dirname as dirname8, join as join13 } from "node:path";
227693
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "node:fs";
227367
227694
  var kindOf = (type) => type === "file" ? "resource" : type;
227368
- var absOf = (root, key) => join12(root, ...key.replace(/^\//, "").split("/"));
227695
+ var absOf = (root, key) => join13(root, ...key.replace(/^\//, "").split("/"));
227369
227696
  var buildPaths = (assets) => {
227370
227697
  const pathById = new Map;
227371
227698
  for (const asset of assets) {
@@ -227383,20 +227710,20 @@ var materializeAssets = async (root, project, apiUrl, token, onProgress) => {
227383
227710
  const key = pathById.get(asset.id);
227384
227711
  const abs = absOf(root, key);
227385
227712
  if (asset.type === "folder") {
227386
- mkdirSync3(abs, { recursive: true });
227713
+ mkdirSync4(abs, { recursive: true });
227387
227714
  continue;
227388
227715
  }
227389
- mkdirSync3(dirname8(abs), { recursive: true });
227716
+ mkdirSync4(dirname8(abs), { recursive: true });
227390
227717
  let sha;
227391
227718
  if (asset.type === "file") {
227392
227719
  if (!asset.file?.src)
227393
227720
  continue;
227394
227721
  const bytes = await downloadBlob(apiUrl, asset.file.src);
227395
- writeFileSync4(abs, bytes);
227722
+ writeFileSync5(abs, bytes);
227396
227723
  sha = sha256(bytes);
227397
227724
  } else {
227398
227725
  const text = asset.text ?? "";
227399
- writeFileSync4(abs, text);
227726
+ writeFileSync5(abs, text);
227400
227727
  sha = sha256(text);
227401
227728
  }
227402
227729
  files[key] = { id: asset.id, type: kindOf(asset.type), sha };
@@ -227430,7 +227757,7 @@ var collectEntries = (root, manifest, project, opts = {}) => {
227430
227757
  warnings.push(`No compiled shader for ${file.path} — push it to compile the shader.`);
227431
227758
  entries.push({ path: file.path, type, shaders, absPath: file.absPath });
227432
227759
  } else {
227433
- entries.push({ path: file.path, type, text: readFileSync9(file.absPath, "utf8") });
227760
+ entries.push({ path: file.path, type, text: readFileSync11(file.absPath, "utf8") });
227434
227761
  }
227435
227762
  }
227436
227763
  return { entries, warnings };
@@ -227439,11 +227766,11 @@ var collectEntries = (root, manifest, project, opts = {}) => {
227439
227766
  // src/compile/collectLocal.ts
227440
227767
  init_localFiles();
227441
227768
  init_ignore();
227442
- import { readFileSync as readFileSync10 } from "node:fs";
227769
+ import { readFileSync as readFileSync12 } from "node:fs";
227443
227770
  import { basename as basename2 } from "node:path";
227444
227771
  var isHalfManifest = (abs) => {
227445
227772
  try {
227446
- return typeof JSON.parse(readFileSync10(abs, "utf8").replace(/^/, "")).implements === "string";
227773
+ return typeof JSON.parse(readFileSync12(abs, "utf8").replace(/^/, "")).implements === "string";
227447
227774
  } catch {
227448
227775
  return false;
227449
227776
  }
@@ -227465,7 +227792,7 @@ var collectLocalEntries = (root, manifest, assetBase, opts = {}) => {
227465
227792
  }
227466
227793
  entries.push({ path: file.path, type, shaders: [], absPath: file.absPath });
227467
227794
  } else {
227468
- entries.push({ path: file.path, type, text: readFileSync10(file.absPath, "utf8") });
227795
+ entries.push({ path: file.path, type, text: readFileSync12(file.absPath, "utf8") });
227469
227796
  }
227470
227797
  }
227471
227798
  return { entries, warnings };
@@ -227479,110 +227806,16 @@ init_fonts();
227479
227806
  init_compile();
227480
227807
  import { execFile } from "node:child_process";
227481
227808
  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";
227809
+ import { existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync14, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync7 } from "node:fs";
227483
227810
  import { tmpdir } from "node:os";
227484
227811
  import { basename as basename3, join as join16 } from "node:path";
227485
227812
 
227486
227813
  // src/hosts/matcTool.ts
227814
+ init_releases();
227815
+ init_cli();
227487
227816
  import { existsSync as existsSync13, readdirSync as readdirSync4 } from "node:fs";
227488
227817
  import { homedir as homedir3 } from "node:os";
227489
227818
  import { join as join15, resolve as resolve4 } from "node:path";
227490
-
227491
- // src/hosts/releases.ts
227492
- init_cli();
227493
- 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";
227495
- import { join as join14 } from "node:path";
227496
- import { spawnSync as spawnSync4 } from "node:child_process";
227497
- var MANIFEST_URL = "https://storage.yandexcloud.net/le-codes/releases/versions.json";
227498
- var ARTIFACT_STAMP = ".lecodes-artifact.json";
227499
- var cachedArtifactSha = (dir) => {
227500
- try {
227501
- return JSON.parse(readFileSync12(join14(dir, ARTIFACT_STAMP), "utf8")).sha256 ?? null;
227502
- } catch {
227503
- return null;
227504
- }
227505
- };
227506
- var macPlatformKey = () => process.arch === "arm64" ? "macos-arm64" : "macos-x64";
227507
- var semverNewer = (a, b) => {
227508
- const pa = a.split(/[.-]/).map(Number), pb = b.split(/[.-]/).map(Number);
227509
- for (let i = 0;i < 3; i++)
227510
- if ((pb[i] ?? 0) !== (pa[i] ?? 0))
227511
- return (pb[i] ?? 0) - (pa[i] ?? 0);
227512
- return 0;
227513
- };
227514
- var newestForPlatform = (versions, key) => {
227515
- const carrying = versions.filter((v) => v.files[key]).sort((a, b) => semverNewer(a.version, b.version));
227516
- const newest = carrying[0];
227517
- return newest ? { version: newest.version, file: newest.files[key] } : null;
227518
- };
227519
- var fetchReleaseProduct = async (product, label) => {
227520
- let manifest;
227521
- try {
227522
- const res = await fetch(MANIFEST_URL);
227523
- if (!res.ok)
227524
- throw new Error(`HTTP ${res.status}`);
227525
- manifest = await res.json();
227526
- } catch (e) {
227527
- throw new CliError(`Can't fetch the release manifest (${MANIFEST_URL}): ${e instanceof Error ? e.message : String(e)}`);
227528
- }
227529
- const entry = manifest.products?.[product];
227530
- if (!entry)
227531
- throw new CliError(`The release manifest has no ${label} product.`);
227532
- return entry;
227533
- };
227534
- var downloadReleaseArchive = async (file, opts) => {
227535
- let bytes;
227536
- try {
227537
- const res = await fetch(file.url);
227538
- if (!res.ok)
227539
- throw new Error(`HTTP ${res.status}`);
227540
- bytes = Buffer.from(await res.arrayBuffer());
227541
- } catch (e) {
227542
- throw new CliError(`Download failed (${file.url}): ${e instanceof Error ? e.message : String(e)}`);
227543
- }
227544
- const sha = createHash3("sha256").update(bytes).digest("hex");
227545
- if (sha !== file.sha256)
227546
- throw new CliError(`Downloaded archive is corrupt (sha256 mismatch) — try again.`);
227547
- mkdirSync5(opts.cacheRoot, { recursive: true });
227548
- const stage = mkdtempSync(join14(opts.cacheRoot, "dl-"));
227549
- try {
227550
- const archiveName = file.url.split("/").pop() ?? "archive";
227551
- const archive = join14(stage, archiveName);
227552
- writeFileSync6(archive, bytes);
227553
- if (opts.extract) {
227554
- opts.extract(archive, stage);
227555
- } else {
227556
- const winTar = join14(process.env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe");
227557
- const tarExe = process.platform === "win32" && existsSync12(winTar) ? winTar : "tar";
227558
- const tar = spawnSync4(tarExe, ["-xf", archiveName], { cwd: stage, stdio: ["ignore", "ignore", "pipe"] });
227559
- if (tar.status !== 0)
227560
- throw new CliError(`Couldn't extract the archive: ${tar.stderr?.toString() ?? "tar failed"}`);
227561
- }
227562
- rmSync(archive);
227563
- const bad = opts.validate(stage);
227564
- if (bad)
227565
- throw new CliError(bad);
227566
- writeFileSync6(join14(stage, ARTIFACT_STAMP), JSON.stringify({ sha256: file.sha256, url: file.url }, null, 2));
227567
- try {
227568
- renameSync(stage, opts.dest);
227569
- } catch {
227570
- let destBad;
227571
- try {
227572
- destBad = opts.validate(opts.dest);
227573
- } catch {
227574
- destBad = "missing";
227575
- }
227576
- if (destBad)
227577
- throw new CliError(`Couldn't move the download into ${opts.dest}.`);
227578
- }
227579
- } finally {
227580
- rmSync(stage, { recursive: true, force: true });
227581
- }
227582
- };
227583
-
227584
- // src/hosts/matcTool.ts
227585
- init_cli();
227586
227819
  var matcName = () => process.platform === "win32" ? "matc.exe" : "matc";
227587
227820
  var platformKey = () => {
227588
227821
  if (process.platform === "win32")
@@ -227729,7 +227962,7 @@ var applyLocalShaders = async (entries, opts) => {
227729
227962
  mkdirSync6(cacheDir, { recursive: true });
227730
227963
  const artifacts = [];
227731
227964
  for (const entry of shaderEntries) {
227732
- const text = readFileSync13(entry.absPath, "utf8");
227965
+ const text = readFileSync14(entry.absPath, "utf8");
227733
227966
  const hash = shaderHash(text, matcId);
227734
227967
  const family = artifactFamily();
227735
227968
  const artifact = (platform) => `${hash}_${platform}.filamat`;
@@ -227787,7 +228020,7 @@ init_config();
227787
228020
  init_ignore();
227788
228021
  init_cli();
227789
228022
  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";
228023
+ import { existsSync as existsSync16, mkdirSync as mkdirSync9, readdirSync as readdirSync5, readFileSync as readFileSync17, statSync as statSync4, writeFileSync as writeFileSync10 } from "node:fs";
227791
228024
  import { homedir as homedir6 } from "node:os";
227792
228025
  import { basename as basename4, dirname as dirname11, join as join19, resolve as resolve5 } from "node:path";
227793
228026
  init_compile();
@@ -228036,7 +228269,7 @@ var readJson2 = (path) => {
228036
228269
  if (!existsSync16(path))
228037
228270
  return null;
228038
228271
  try {
228039
- return JSON.parse(readFileSync16(path, "utf8").replace(/^\uFEFF/, ""));
228272
+ return JSON.parse(readFileSync17(path, "utf8").replace(/^\uFEFF/, ""));
228040
228273
  } catch {
228041
228274
  throw new CliError(`Invalid JSON in ${path}`);
228042
228275
  }
@@ -228068,7 +228301,7 @@ ${tail}`);
228068
228301
  };
228069
228302
  var ensureShellIgnored = (root, dir) => {
228070
228303
  const ignorePath = join19(root, ".lecodesignore");
228071
- const ignore = existsSync16(ignorePath) ? readFileSync16(ignorePath, "utf8") : DEFAULT_IGNORE_FILE;
228304
+ const ignore = existsSync16(ignorePath) ? readFileSync17(ignorePath, "utf8") : DEFAULT_IGNORE_FILE;
228072
228305
  if (new RegExp(`^${dir}\\/$`, "m").test(ignore))
228073
228306
  return;
228074
228307
  const comment = ignore.includes("# Native app shells") ? "" : "# Native app shells — generated by `lecodes app init`, never pushed to LeCodes.\n";
@@ -228201,7 +228434,7 @@ var localPlugin = (root, id, spec) => {
228201
228434
  if (!existsSync16(dir) || !statSync4(dir).isDirectory())
228202
228435
  throw new CliError(`Plugin "${id}": ${spec} is not a folder (${dir})`);
228203
228436
  const folder = basename4(dir);
228204
- const manifest = readManifest2(dir, folder);
228437
+ const manifest = readManifest3(dir, folder);
228205
228438
  if (manifest.implements ? manifest.implements !== id : folder !== id) {
228206
228439
  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
228440
  }
@@ -228212,7 +228445,7 @@ var localPluginKey = (root, spec) => {
228212
228445
  const dir = resolve5(root, spec.startsWith("file:") ? spec.slice("file:".length) : spec);
228213
228446
  if (!existsSync16(join19(dir, "lecodes-plugin.json")))
228214
228447
  return basename4(dir);
228215
- return readManifest2(dir, basename4(dir)).implements ?? basename4(dir);
228448
+ return readManifest3(dir, basename4(dir)).implements ?? basename4(dir);
228216
228449
  };
228217
228450
  function resolvePlugin(id, entry, lock, platform, opts) {
228218
228451
  const spec = pluginSpecOf(entry, platform);
@@ -228244,7 +228477,7 @@ function resolvePlugin(id, entry, lock, platform, opts) {
228244
228477
  const dir = subdir ? join19(checkout.dir, subdir) : checkout.dir;
228245
228478
  if (!existsSync16(join19(dir, "lecodes-plugin.json")))
228246
228479
  throw new CliError(`Plugin "${id}": no lecodes-plugin.json in ${dir}`);
228247
- const manifest = readManifest2(dir, id);
228480
+ const manifest = readManifest3(dir, id);
228248
228481
  fillManifestDefaults(manifest, id);
228249
228482
  if (!manifest[platform]) {
228250
228483
  if (opts?.optional)
@@ -228435,7 +228668,7 @@ var buildBundle = async (bundle, opts = {}) => {
228435
228668
  warnErr(ambiguous);
228436
228669
  }
228437
228670
  const serverUrl = await opts.backend?.start(entries, (w) => warnings.push(w));
228438
- const js = await compileProject2({
228671
+ const { js, bakes } = await compileProjectBundle({
228439
228672
  entries,
228440
228673
  name,
228441
228674
  publicUrl,
@@ -228456,7 +228689,7 @@ var buildBundle = async (bundle, opts = {}) => {
228456
228689
  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
228690
  for (const artifact of stagedShaders)
228458
228691
  resources.push({ path: artifact.name, name: artifact.name, absPath: artifact.absPath, remote: false, shader: true });
228459
- return { js, resources };
228692
+ return { js, resources, bakes: bakes.map((b) => ({ path: b.path, absPath: resolve6(root, b.path), exists: b.exists })) };
228460
228693
  };
228461
228694
 
228462
228695
  // src/commands/app/index.ts
@@ -229431,13 +229664,13 @@ init_compile();
229431
229664
  init_manifest();
229432
229665
  import { execFileSync as execFileSync2, spawn, spawnSync as spawnSync5 } from "node:child_process";
229433
229666
  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";
229667
+ 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
229668
  import { homedir as homedir8 } from "node:os";
229436
229669
  import { basename as basename7, dirname as dirname15, join as join23, relative as relative4, resolve as resolve10 } from "node:path";
229437
229670
  init_cli();
229438
229671
 
229439
229672
  // src/project/env.ts
229440
- import { appendFileSync, existsSync as existsSync17, readFileSync as readFileSync17, writeFileSync as writeFileSync12 } from "node:fs";
229673
+ import { appendFileSync, existsSync as existsSync17, readFileSync as readFileSync18, writeFileSync as writeFileSync12 } from "node:fs";
229441
229674
  import { homedir as homedir7 } from "node:os";
229442
229675
  import { dirname as dirname12, join as join20, resolve as resolve7 } from "node:path";
229443
229676
  var KEY_RE = /^(LECODES|CREATOR)_[A-Z0-9_]+$/;
@@ -229505,7 +229738,7 @@ var loadProjectEnv = (cwd = process.cwd()) => {
229505
229738
  const file = join20(root, ".env");
229506
229739
  if (!existsSync17(file))
229507
229740
  return [];
229508
- const added = applyDotEnv(parseDotEnv(readFileSync17(file, "utf8")), root, process.env);
229741
+ const added = applyDotEnv(parseDotEnv(readFileSync18(file, "utf8")), root, process.env);
229509
229742
  for (const [k, v] of Object.entries(added))
229510
229743
  process.env[k] = v;
229511
229744
  return Object.keys(added);
@@ -229516,7 +229749,7 @@ var loadProjectEnv = (cwd = process.cwd()) => {
229516
229749
  var upsertDotEnv = (root, entries) => {
229517
229750
  const touched = [];
229518
229751
  const envPath = join20(root, ".env");
229519
- let content = existsSync17(envPath) ? readFileSync17(envPath, "utf8") : "";
229752
+ let content = existsSync17(envPath) ? readFileSync18(envPath, "utf8") : "";
229520
229753
  for (const [key, value] of Object.entries(entries)) {
229521
229754
  const line = `${key}=${value}`;
229522
229755
  const re = new RegExp(`^(?:export\\s+)?${key}\\s*=.*$`, "m");
@@ -229531,7 +229764,7 @@ var upsertDotEnv = (root, entries) => {
229531
229764
  writeFileSync12(envPath, content);
229532
229765
  touched.push(".env");
229533
229766
  const ignorePath = join20(root, ".gitignore");
229534
- const ignore = existsSync17(ignorePath) ? readFileSync17(ignorePath, "utf8") : "";
229767
+ const ignore = existsSync17(ignorePath) ? readFileSync18(ignorePath, "utf8") : "";
229535
229768
  if (!ignore.split(`
229536
229769
  `).some((l) => l.trim() === ".env")) {
229537
229770
  appendFileSync(ignorePath, `${!ignore || ignore.endsWith(`
@@ -229544,7 +229777,7 @@ var upsertDotEnv = (root, entries) => {
229544
229777
  };
229545
229778
 
229546
229779
  // src/commands/app/icon.ts
229547
- import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync18, writeFileSync as writeFileSync13 } from "node:fs";
229780
+ import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync19, writeFileSync as writeFileSync13 } from "node:fs";
229548
229781
  import { dirname as dirname13, join as join21, resolve as resolve8 } from "node:path";
229549
229782
 
229550
229783
  // ../../sdk/src/canvas/gen/cssColor.ts
@@ -230088,7 +230321,7 @@ var restampManifestIcon = (androidDir) => {
230088
230321
  const path = join21(androidDir, "app", "src", "main", "AndroidManifest.xml");
230089
230322
  if (!existsSync19(path))
230090
230323
  return;
230091
- const content = readFileSync18(path, "utf8");
230324
+ const content = readFileSync19(path, "utf8");
230092
230325
  if (content.includes(`android:icon="@mipmap/ic_launcher"`))
230093
230326
  return;
230094
230327
  if (!/android:icon="[^"]*"/.test(content)) {
@@ -230100,7 +230333,7 @@ var restampManifestIcon = (androidDir) => {
230100
230333
  };
230101
230334
 
230102
230335
  // 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";
230336
+ import { existsSync as existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync20, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "node:fs";
230104
230337
  import { dirname as dirname14, join as join22, resolve as resolve9 } from "node:path";
230105
230338
  init_cli();
230106
230339
 
@@ -230978,7 +231211,7 @@ var androidSplashThemeHint = (androidDir) => {
230978
231211
  const path = join22(androidDir, "app", "src", "main", "res", "values", "themes.xml");
230979
231212
  if (!existsSync20(path))
230980
231213
  return null;
230981
- const xml = readFileSync19(path, "utf8");
231214
+ const xml = readFileSync20(path, "utf8");
230982
231215
  const style = /<style\b[^>]*\bname="Theme\.LeCodesApp"[^>]*?(?:\/>|>([\s\S]*?)<\/style>)/.exec(xml);
230983
231216
  if (!style) {
230984
231217
  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 +231579,13 @@ if "%OS%"=="Windows_NT" endlocal
231346
231579
  // src/commands/app/play.ts
231347
231580
  init_cli();
231348
231581
  import { sign } from "node:crypto";
231349
- import { readFileSync as readFileSync20 } from "node:fs";
231582
+ import { readFileSync as readFileSync21 } from "node:fs";
231350
231583
  var SCOPE = "https://www.googleapis.com/auth/androidpublisher";
231351
231584
  var apiBase = () => process.env.LECODES_PLAY_API_URL ?? "https://androidpublisher.googleapis.com";
231352
231585
  var readServiceAccount = (path) => {
231353
231586
  let key;
231354
231587
  try {
231355
- key = JSON.parse(readFileSync20(path, "utf8"));
231588
+ key = JSON.parse(readFileSync21(path, "utf8"));
231356
231589
  } catch {
231357
231590
  throw new CliError(`${path} is not a JSON key file.`);
231358
231591
  }
@@ -231432,7 +231665,7 @@ var shellSdkVersionAndroid = (androidDir) => {
231432
231665
  const path = join23(androidDir, "lecodes-runtime", "build.gradle.kts");
231433
231666
  if (!existsSync21(path))
231434
231667
  return null;
231435
- const m = /io\.letary:lecodes-sdk(?:-(?:core|2d|3d))?:(\d+\.\d+\.\d+)@aar/.exec(readFileSync21(path, "utf8"));
231668
+ const m = /io\.letary:lecodes-sdk(?:-(?:core|2d|3d))?:(\d+\.\d+\.\d+)@aar/.exec(readFileSync22(path, "utf8"));
231436
231669
  return m ? m[1] : null;
231437
231670
  };
231438
231671
  var GRADLE_WRAPPER_JAR_URL = "https://raw.githubusercontent.com/gradle/gradle/v8.13.0/gradle/wrapper/gradle-wrapper.jar";
@@ -231440,7 +231673,7 @@ var GRADLE_WRAPPER_JAR_SHA256 = "81a82aaea5abcc8ff68b3dfcb58b3c3c429378efd98e743
231440
231673
  var ensureGradleWrapperJar = async () => {
231441
231674
  const cached = join23(homedir8(), ".lecodes", "gradle-wrapper", GRADLE_VERSION, "gradle-wrapper.jar");
231442
231675
  if (existsSync21(cached))
231443
- return new Uint8Array(readFileSync21(cached));
231676
+ return new Uint8Array(readFileSync22(cached));
231444
231677
  try {
231445
231678
  const res = await fetch(GRADLE_WRAPPER_JAR_URL);
231446
231679
  if (!res.ok)
@@ -231571,7 +231804,7 @@ var syncAndroid = async (root, config, prior) => {
231571
231804
  resources = prior.resources;
231572
231805
  } else {
231573
231806
  resources = await compileShell(join23(assetsDir, "app.js"), { remotePatterns });
231574
- bundleJs = readFileSync21(join23(assetsDir, "app.js"), "utf8");
231807
+ bundleJs = readFileSync22(join23(assetsDir, "app.js"), "utf8");
231575
231808
  }
231576
231809
  const requestedVariant = config.android?.variant ?? "auto";
231577
231810
  if (requestedVariant !== "auto" && !ANDROID_SDK_VARIANTS.includes(requestedVariant)) {
@@ -231716,7 +231949,7 @@ var restampManifestBlock = (androidDir, tag, block, what, missing, after = []) =
231716
231949
  const path = join23(androidDir, "app", "src", "main", "AndroidManifest.xml");
231717
231950
  if (!existsSync21(path))
231718
231951
  return;
231719
- const content = readFileSync21(path, "utf8");
231952
+ const content = readFileSync22(path, "utf8");
231720
231953
  const tagged = new RegExp(`\\n[ \\t]*<!-- lecodes:${tag}[\\s\\S]*?<!-- \\/lecodes:${tag} -->`);
231721
231954
  let next;
231722
231955
  if (tagged.test(content))
@@ -231745,7 +231978,7 @@ var restampAppBuildGradle = (androidDir, config, gradlePlugins) => {
231745
231978
  warn("android/app/build.gradle.kts is missing — run `lecodes app init android` on a clean tree to regenerate the shell.");
231746
231979
  return;
231747
231980
  }
231748
- let content = readFileSync21(path, "utf8");
231981
+ let content = readFileSync22(path, "utf8");
231749
231982
  const before = content;
231750
231983
  const versionName = config.version ?? "1.0.0";
231751
231984
  const versionCode = config.android?.versionCode ?? 1;
@@ -231958,7 +232191,7 @@ var uploadAndroid = async (flags) => {
231958
232191
  const token = await serviceAccountToken(key);
231959
232192
  const { versionCode } = await uploadToPlay(token, {
231960
232193
  packageName,
231961
- aab: readFileSync21(aabPath),
232194
+ aab: readFileSync22(aabPath),
231962
232195
  track: flags.track,
231963
232196
  status: flags.draft ? "draft" : "completed"
231964
232197
  });
@@ -231987,7 +232220,7 @@ var ANDROID_SIGNING_ENV = {
231987
232220
  };
231988
232221
  var readProperties = (path) => {
231989
232222
  const out = {};
231990
- for (const raw of readFileSync21(path, "utf8").split(/\r?\n/)) {
232223
+ for (const raw of readFileSync22(path, "utf8").split(/\r?\n/)) {
231991
232224
  const line = raw.trim();
231992
232225
  if (!line || line.startsWith("#") || line.startsWith("!"))
231993
232226
  continue;
@@ -232087,7 +232320,7 @@ var writeSigningConfig = (root, config, storeFile, alias, storePassword, keyPass
232087
232320
  const rel = relative4(root, storeFile);
232088
232321
  if (rel && !rel.startsWith("..") && !rel.includes(":") && !rel.replace(/\\/g, "/").startsWith("android/")) {
232089
232322
  const ignorePath = join23(root, ".gitignore");
232090
- const ignore = existsSync21(ignorePath) ? readFileSync21(ignorePath, "utf8") : "";
232323
+ const ignore = existsSync21(ignorePath) ? readFileSync22(ignorePath, "utf8") : "";
232091
232324
  if (!ignore.split(`
232092
232325
  `).some((l) => l.trim() === "*.keystore")) {
232093
232326
  writeFileSync15(ignorePath, ignore + (!ignore || ignore.endsWith(`
@@ -232325,10 +232558,10 @@ var doctorAndroidChecks = async (root, config, check) => {
232325
232558
  }
232326
232559
  if (config.update) {
232327
232560
  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);
232561
+ 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
232562
  const appSrc = join23(androidDir, "app", "src", "main", "java");
232330
232563
  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"));
232564
+ const sources = kt.map((f) => readFileSync22(join23(appSrc, f), "utf8"));
232332
232565
  const boots = sources.some((src) => src.includes("LeCodesShell.boot"));
232333
232566
  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
232567
  const waits = sources.some((src) => /LeCodesShell\.boot\(\s*engine\s*,\s*activity\b/.test(src));
@@ -232348,7 +232581,7 @@ var doctorAndroidChecks = async (root, config, check) => {
232348
232581
  }
232349
232582
  check(!!findKeytool(), "keytool", "install a JDK or set JAVA_HOME (`lecodes app keystore …` needs it)", false);
232350
232583
  const appGradle = join23(androidDir, "app", "build.gradle.kts");
232351
- const gradleText = existsSync21(appGradle) ? readFileSync21(appGradle, "utf8") : "";
232584
+ const gradleText = existsSync21(appGradle) ? readFileSync22(appGradle, "utf8") : "";
232352
232585
  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
232586
  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
232587
  if (Object.keys(config.plugins ?? {}).includes("push")) {
@@ -232362,11 +232595,12 @@ import { cpSync as cpSync3, existsSync as existsSync24, mkdirSync as mkdirSync15
232362
232595
  import { basename as basename9, dirname as dirname16, join as join26, resolve as resolve13 } from "node:path";
232363
232596
 
232364
232597
  // src/hosts/desktopRenderer.ts
232598
+ init_releases();
232599
+ init_cli();
232365
232600
  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";
232601
+ 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
232602
  import { homedir as homedir9, tmpdir as tmpdir2 } from "node:os";
232368
232603
  import { delimiter, join as join24, resolve as resolve11 } from "node:path";
232369
- init_cli();
232370
232604
 
232371
232605
  // ../../renderers/web-shared/src/sourcemapError.ts
232372
232606
  var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -232508,7 +232742,7 @@ var installedDesktopVersions = () => {
232508
232742
  };
232509
232743
  var selectedDesktopVersion = () => {
232510
232744
  try {
232511
- const saved = JSON.parse(readFileSync22(currentFile(), "utf8")).version;
232745
+ const saved = JSON.parse(readFileSync23(currentFile(), "utf8")).version;
232512
232746
  if (saved && /^\d+\.\d+\.\d+/.test(saved) && semverNewer(DESKTOP_RENDERER_VERSION, saved) > 0)
232513
232747
  return saved;
232514
232748
  } catch {}
@@ -233630,7 +233864,7 @@ var shellSdkVersion = (iosDir) => {
233630
233864
  const path = join27(iosDir, "LeCodesRuntime", "Package.swift");
233631
233865
  if (!existsSync25(path))
233632
233866
  return null;
233633
- const m = /lecodes-ios-sdk\.git",\s*(?:exact|from):\s*"(\d+\.\d+\.\d+)(?:-(?:core|2d|3d))?"/.exec(readFileSync23(path, "utf8"));
233867
+ const m = /lecodes-ios-sdk\.git",\s*(?:exact|from):\s*"(\d+\.\d+\.\d+)(?:-(?:core|2d|3d))?"/.exec(readFileSync24(path, "utf8"));
233634
233868
  return m && VERSION_RE.test(m[1]) ? m[1] : null;
233635
233869
  };
233636
233870
  var IOS_SDK_OPTS = { repo: SDK_REPO, minVersion: SDK_MIN_VERSION, configKey: "ios.sdk" };
@@ -233836,7 +234070,7 @@ var syncIos = async (root, config) => {
233836
234070
  rmSync8(resourcesDir, { recursive: true, force: true });
233837
234071
  mkdirSync16(resourcesDir, { recursive: true });
233838
234072
  const resources = await compileShell(join27(resourcesDir, "app.js"), { remotePatterns });
233839
- const bundleJs = readFileSync23(join27(resourcesDir, "app.js"), "utf8");
234073
+ const bundleJs = readFileSync24(join27(resourcesDir, "app.js"), "utf8");
233840
234074
  const { variant, why } = resolveVariant(requestedVariant, bundleJs);
233841
234075
  log(`${c.green("variant")} ${variant}${requestedVariant === "auto" ? ` (auto: ${why})` : " (forced by ios.variant)"}`);
233842
234076
  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 +234136,7 @@ var syncIos = async (root, config) => {
233902
234136
  if (pluginSpecs.length > 0 || existsSync25(lockPath))
233903
234137
  writeJson(lockPath, { plugins: newLock });
233904
234138
  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};`);
234139
+ 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
234140
  const mergedEntitlements = {};
233907
234141
  const appLinks = appLinksOf(config);
233908
234142
  if (appLinks.length) {
@@ -234194,8 +234428,8 @@ var doctorIosChecks = async (root, config, check) => {
234194
234428
  const validVariant = requestedVariant === "auto" || SDK_VARIANTS.includes(requestedVariant);
234195
234429
  check(validVariant, `ios.variant "${requestedVariant}"`, `use "auto" or one of ${SDK_VARIANTS.join(", ")}`);
234196
234430
  if (existsSync25(bundle) && validVariant) {
234197
- const { variant } = resolveVariant(requestedVariant, readFileSync23(bundle, "utf8"));
234198
- const pkg = readFileSync23(join27(root, "ios", "LeCodesRuntime", "Package.swift"), "utf8");
234431
+ const { variant } = resolveVariant(requestedVariant, readFileSync24(bundle, "utf8"));
234432
+ const pkg = readFileSync24(join27(root, "ios", "LeCodesRuntime", "Package.swift"), "utf8");
234199
234433
  const tag = /lecodes-ios-sdk\.git",\s*exact:\s*"([^"]+)"/.exec(pkg)?.[1] ?? "";
234200
234434
  const tagVariant = /-(core|2d|3d)$/.exec(tag)?.[1] ?? "full";
234201
234435
  check(tagVariant === variant, `SDK variant ${variant}`, "run `lecodes app sync`");
@@ -234217,25 +234451,25 @@ var doctorIosChecks = async (root, config, check) => {
234217
234451
  const inSync = wanted.every((id) => lock[id]) && Object.keys(lock).every((id) => wanted.includes(id));
234218
234452
  check(wanted.length === 0 || inSync, "Plugins synced", "run `lecodes app sync`");
234219
234453
  const pbxFile = join27(root, "ios", "App.xcodeproj", "project.pbxproj");
234220
- if (existsSync25(pbxFile) && readFileSync23(pbxFile, "utf8").includes("CODE_SIGN_ENTITLEMENTS")) {
234454
+ if (existsSync25(pbxFile) && readFileSync24(pbxFile, "utf8").includes("CODE_SIGN_ENTITLEMENTS")) {
234221
234455
  check(existsSync25(join27(root, "ios", "App", "App.entitlements")), "App/App.entitlements present", "run `lecodes app sync`");
234222
234456
  }
234223
234457
  if (wanted.includes("push")) {
234224
234458
  const runtimeSwift = join27(root, "ios", "LeCodesRuntime", "Sources", "LeCodesRuntime", "LeCodesRuntime.swift");
234225
- const baked = existsSync25(runtimeSwift) && readFileSync23(runtimeSwift, "utf8").includes("bootProjectUuid");
234459
+ const baked = existsSync25(runtimeSwift) && readFileSync24(runtimeSwift, "utf8").includes("bootProjectUuid");
234226
234460
  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
234461
  }
234228
234462
  if (config.update) {
234229
- const plist = readFileSync23(join27(root, "ios", "App", "Info.plist"), "utf8");
234463
+ const plist = readFileSync24(join27(root, "ios", "App", "Info.plist"), "utf8");
234230
234464
  check(plist.includes("<key>LeCodesUpdateURL</key>"), "OTA update URL in Info.plist", "run `lecodes app sync`", false);
234231
234465
  const appDir = join27(root, "ios", "App");
234232
- const swift = readdirSync10(appDir).filter((f) => f.endsWith(".swift")).map((f) => readFileSync23(join27(appDir, f), "utf8"));
234466
+ const swift = readdirSync10(appDir).filter((f) => f.endsWith(".swift")).map((f) => readFileSync24(join27(appDir, f), "utf8"));
234233
234467
  const launchesViaUpdater = swift.some((t) => t.includes("LeCodesAppViewController") || t.includes("LeCodesUpdater.activeBundle"));
234234
234468
  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
234469
  }
234236
234470
  {
234237
234471
  const vc = join27(root, "ios", "App", "ViewController.swift");
234238
- const is2x = !existsSync25(vc) || readFileSync23(vc, "utf8").includes("LeCodesAppViewController");
234472
+ const is2x = !existsSync25(vc) || readFileSync24(vc, "utf8").includes("LeCodesAppViewController");
234239
234473
  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
234474
  }
234241
234475
  const asc = readJson2(join27(homedir10(), ".lecodes", "asc.json"));
@@ -234465,10 +234699,11 @@ var app_default = defineCommand({
234465
234699
  init_cli();
234466
234700
 
234467
234701
  // src/hosts/cmgenTool.ts
234702
+ init_releases();
234703
+ init_cli();
234468
234704
  import { existsSync as existsSync26, readdirSync as readdirSync11 } from "node:fs";
234469
234705
  import { homedir as homedir11 } from "node:os";
234470
234706
  import { join as join28, resolve as resolve15 } from "node:path";
234471
- init_cli();
234472
234707
  var exeName2 = () => process.platform === "win32" ? "cmgen.exe" : "cmgen";
234473
234708
  var platformKey3 = () => {
234474
234709
  if (process.platform === "win32")
@@ -234652,6 +234887,7 @@ lecodes render --sim --script <file> # the WINDOWLESS native host (lecodes-
234652
234887
  # engines, no GPU, no pixels, no UI hit-test — expect.log /
234653
234888
  # expect.paint only; runs on CI and beside an open desktop window
234654
234889
  lecodes test # all tests/*.flow.json + design-derived flows; run before "done"
234890
+ bunx -p typescript tsc --noEmit # typecheck against .lecodes/types (the SDK's globals); also before "done"
234655
234891
  lecodes test --desktop # the file flows on the NATIVE host (design flows stay headless)
234656
234892
  lecodes test --sim # the windowless twin: coordinates + expect.log / expect.paint only
234657
234893
  lecodes dev --android --time 12 --png phone.png # the REAL phone over adb (USB): launches the app on
@@ -234775,9 +235011,9 @@ fetch the docs — they are served as raw markdown for AI tools:
234775
235011
  with a COOKBOOK: read the matching recipe BEFORE you design the structure.** It has the file
234776
235012
  layout, a skeleton per part, how to verify, and the traps already paid for (start with
234777
235013
  "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
235014
+ - https://le.codes/docs/api/<area>/<page>.md — one reference page as raw markdown
234779
235015
  (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)
235016
+ - https://le.codes/docs/guide/<page>.md — guide pages (project anatomy, CLI, tracks)
234781
235017
  - https://le.codes/llms-full.txt — everything in one file (large; prefer per-page)
234782
235018
 
234783
235019
  Always fetch the \`.md\` URLs — the HTML routes are a JS app and return an empty shell.
@@ -235575,7 +235811,7 @@ var desktop_default = defineCommand({
235575
235811
 
235576
235812
  // src/commands/dev.ts
235577
235813
  init_manifest();
235578
- import { existsSync as existsSync38, mkdirSync as mkdirSync22, readFileSync as readFileSync32, writeFileSync as writeFileSync25 } from "node:fs";
235814
+ import { existsSync as existsSync38, mkdirSync as mkdirSync22, readFileSync as readFileSync33, writeFileSync as writeFileSync25 } from "node:fs";
235579
235815
  import { randomBytes as randomBytes5 } from "node:crypto";
235580
235816
  import { networkInterfaces } from "node:os";
235581
235817
  import { basename as basename15, join as join43 } from "node:path";
@@ -235583,7 +235819,7 @@ import { basename as basename15, join as join43 } from "node:path";
235583
235819
  // src/dev/devServer.ts
235584
235820
  init_localFiles();
235585
235821
  import { createHash as createHash8 } from "node:crypto";
235586
- import { createReadStream as createReadStream2, readFileSync as readFileSync30, statSync as statSync11 } from "node:fs";
235822
+ import { createReadStream as createReadStream2, readFileSync as readFileSync31, statSync as statSync11 } from "node:fs";
235587
235823
  import { join as join39 } from "node:path";
235588
235824
  import { Readable as Readable3 } from "node:stream";
235589
235825
  init_projectCompile();
@@ -235718,7 +235954,7 @@ init_paths();
235718
235954
  // src/web/halves.ts
235719
235955
  init_compile();
235720
235956
  init_cli();
235721
- import { cpSync as cpSync5, existsSync as existsSync34, readFileSync as readFileSync29 } from "node:fs";
235957
+ import { cpSync as cpSync5, existsSync as existsSync34, readFileSync as readFileSync30 } from "node:fs";
235722
235958
  import { join as join38, sep as sep4 } from "node:path";
235723
235959
  var halfModule = (half) => `plugins/${half.folder}/index.js`;
235724
235960
  var asCliError = (e) => new CliError(e instanceof Error ? e.message : String(e));
@@ -235729,7 +235965,7 @@ var projectHalves = (root) => {
235729
235965
  const folder = dir.split(sep4).pop();
235730
235966
  let web;
235731
235967
  try {
235732
- web = webHalfManifestOf(readFileSync29(join38(dir, MANIFEST_FILE), "utf8"), folder, id);
235968
+ web = webHalfManifestOf(readFileSync30(join38(dir, MANIFEST_FILE), "utf8"), folder, id);
235733
235969
  } catch (e) {
235734
235970
  throw asCliError(e);
235735
235971
  }
@@ -235870,7 +236106,7 @@ var startDevServer = async (options) => {
235870
236106
  const liteTable = () => {
235871
236107
  let plugins = {};
235872
236108
  try {
235873
- plugins = JSON.parse(readFileSync30(join39(lite, "plugins.json"), "utf8")).plugins ?? {};
236109
+ plugins = JSON.parse(readFileSync31(join39(lite, "plugins.json"), "utf8")).plugins ?? {};
235874
236110
  } catch {}
235875
236111
  const table = { ...plugins };
235876
236112
  for (const { half, hash } of halves) {
@@ -236361,7 +236597,7 @@ var androidCrashLines = (session) => {
236361
236597
 
236362
236598
  // src/dev/iosDev.ts
236363
236599
  init_cli();
236364
- import { existsSync as existsSync37, readdirSync as readdirSync17, readFileSync as readFileSync31, statSync as statSync13, unlinkSync } from "node:fs";
236600
+ import { existsSync as existsSync37, readdirSync as readdirSync17, readFileSync as readFileSync32, statSync as statSync13, unlinkSync } from "node:fs";
236365
236601
  import { homedir as homedir12 } from "node:os";
236366
236602
  import { join as join42 } from "node:path";
236367
236603
  import { spawnSync as spawnSync10 } from "node:child_process";
@@ -236394,7 +236630,7 @@ var listDevices2 = () => {
236394
236630
  if (!ok || !existsSync37(tmp))
236395
236631
  return [];
236396
236632
  try {
236397
- const json = JSON.parse(readFileSync31(tmp, "utf8"));
236633
+ const json = JSON.parse(readFileSync32(tmp, "utf8"));
236398
236634
  return (json.result?.devices ?? []).map((d) => ({
236399
236635
  udid: d.identifier,
236400
236636
  name: d.deviceProperties?.name ?? d.identifier,
@@ -236441,7 +236677,7 @@ var deviceHasApp = (udid, bundleId) => {
236441
236677
  if (!ok || !existsSync37(tmp))
236442
236678
  return false;
236443
236679
  try {
236444
- const json = JSON.parse(readFileSync31(tmp, "utf8"));
236680
+ const json = JSON.parse(readFileSync32(tmp, "utf8"));
236445
236681
  return (json.result?.apps ?? []).some((a) => a.bundleIdentifier === bundleId);
236446
236682
  } catch {
236447
236683
  return false;
@@ -236481,7 +236717,7 @@ var launchOnIos = (opts) => {
236481
236717
  if (!start.ok)
236482
236718
  throw new CliError(`Could not launch ${bundleId} on ${target.name}: ${start.out}`);
236483
236719
  try {
236484
- const json = JSON.parse(readFileSync31(tmp, "utf8"));
236720
+ const json = JSON.parse(readFileSync32(tmp, "utf8"));
236485
236721
  pid = json.result?.process?.processIdentifier?.toString() ?? null;
236486
236722
  } catch {
236487
236723
  pid = null;
@@ -236553,7 +236789,7 @@ var lanCandidates = () => {
236553
236789
  var loadDevToken = (root) => {
236554
236790
  const path = join43(root, LECODES_DIR, "dev.json");
236555
236791
  try {
236556
- const token = JSON.parse(readFileSync32(path, "utf8")).token;
236792
+ const token = JSON.parse(readFileSync33(path, "utf8")).token;
236557
236793
  if (token && /^[a-z0-9]{8,}$/.test(token))
236558
236794
  return token;
236559
236795
  } catch {}
@@ -236788,7 +237024,7 @@ init_api();
236788
237024
 
236789
237025
  // src/platform/serverDiff.ts
236790
237026
  init_localFiles();
236791
- import { readFileSync as readFileSync33 } from "node:fs";
237027
+ import { readFileSync as readFileSync34 } from "node:fs";
236792
237028
  var isTextAsset = (t) => t === "text" || t === "shader" || t === "scene";
236793
237029
  var computeServerDiff = (project, scan) => {
236794
237030
  const pathById = buildPaths(project.assets);
@@ -236806,7 +237042,7 @@ var computeServerDiff = (project, scan) => {
236806
237042
  const local = localByPath.get(path);
236807
237043
  if (local && !server2) {
236808
237044
  const text = isTextKind(local.kind);
236809
- entries.push({ path, status: "added", binary: !text, serverText: "", localText: text ? readFileSync33(local.absPath, "utf8") : undefined });
237045
+ entries.push({ path, status: "added", binary: !text, serverText: "", localText: text ? readFileSync34(local.absPath, "utf8") : undefined });
236810
237046
  continue;
236811
237047
  }
236812
237048
  if (server2 && !local) {
@@ -236817,12 +237053,12 @@ var computeServerDiff = (project, scan) => {
236817
237053
  if (server2 && local) {
236818
237054
  if (isTextAsset(server2.type)) {
236819
237055
  const serverText = server2.text ?? "";
236820
- const localText = readFileSync33(local.absPath, "utf8");
237056
+ const localText = readFileSync34(local.absPath, "utf8");
236821
237057
  if (serverText !== localText)
236822
237058
  entries.push({ path, status: "modified", binary: false, serverText, localText });
236823
237059
  } else {
236824
237060
  const serverSize = server2.file?.size;
236825
- const localSize = Buffer.byteLength(readFileSync33(local.absPath));
237061
+ const localSize = Buffer.byteLength(readFileSync34(local.absPath));
236826
237062
  if (serverSize !== undefined && serverSize !== localSize) {
236827
237063
  entries.push({ path, status: "modified", binary: true });
236828
237064
  }
@@ -237154,7 +237390,7 @@ var init3 = (dir, forDesktop) => {
237154
237390
  // src/commands/install.ts
237155
237391
  init_cli();
237156
237392
  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";
237393
+ 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
237394
  import { homedir as homedir13 } from "node:os";
237159
237395
  import { basename as basename17, delimiter as delimiter2, dirname as dirname22, join as join45, resolve as resolve21 } from "node:path";
237160
237396
  var isWin = process.platform === "win32";
@@ -237208,7 +237444,7 @@ var appendPathToShellRc = (binDir) => {
237208
237444
  const { file, name } = shellRc();
237209
237445
  const line = `export PATH="${binDir}:$PATH"`;
237210
237446
  try {
237211
- const existing = existsSync40(file) ? readFileSync34(file, "utf8") : "";
237447
+ const existing = existsSync40(file) ? readFileSync35(file, "utf8") : "";
237212
237448
  if (existing.includes(line))
237213
237449
  return name;
237214
237450
  appendFileSync3(file, `${existing && !existing.endsWith(`
@@ -237330,9 +237566,9 @@ var install_default = defineCommand({
237330
237566
  });
237331
237567
 
237332
237568
  // 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";
237569
+ 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
237570
  import { tmpdir as tmpdir4 } from "node:os";
237335
- import { basename as basename18, join as join46, relative as relative7, resolve as resolve22 } from "node:path";
237571
+ import { basename as basename18, dirname as dirname23, join as join46, relative as relative7, resolve as resolve22, sep as sep5 } from "node:path";
237336
237572
  import { deflateSync } from "node:zlib";
237337
237573
  init_peerInstall();
237338
237574
  init_cli();
@@ -237345,7 +237581,7 @@ var range = (name, value, min, max) => {
237345
237581
  var readBake = (file) => {
237346
237582
  if (!existsSync41(file))
237347
237583
  throw new CliError(`No bake at ${file} — run \`lecodes lightmap bake\` first.`);
237348
- const data = JSON.parse(readFileSync35(file, "utf8"));
237584
+ const data = JSON.parse(readFileSync36(file, "utf8"));
237349
237585
  if (data.version !== 4)
237350
237586
  throw new CliError(`${file} is not a creator-bake file (version ${String(data.version)}) — rebake.`);
237351
237587
  return data;
@@ -237381,54 +237617,47 @@ var cubePatternsOf = (argv) => {
237381
237617
  }
237382
237618
  return out.filter((p) => p !== "");
237383
237619
  };
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;
237620
+ var stemOf = (path) => basename18(path).replace(/\.bake$/i, "");
237621
+ var pickTarget2 = (bakes, name, entry) => {
237622
+ if (bakes.length === 0)
237623
+ 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'))\`).`);
237624
+ const listed = bakes.map((b) => `${stemOf(b.path)} (${b.path})`).join(", ");
237625
+ if (name === undefined) {
237626
+ if (bakes.length > 1)
237627
+ throw new CliError(`${entry} reaches ${bakes.length} bake() calls: ${listed} — pass --name <stem> to pick the level to bake.`);
237628
+ return bakes[0];
237629
+ }
237630
+ const named = bakes.filter((b) => stemOf(b.path) === name);
237631
+ if (named.length === 0)
237632
+ throw new CliError(`--name ${name}: ${entry} reaches no bake() of that stem — it reaches ${listed}.`);
237633
+ if (named.length > 1)
237634
+ throw new CliError(`--name ${name}: two bake() targets share that stem (${named.map((b) => b.path).join(", ")}) — rename one.`);
237635
+ return named[0];
237413
237636
  };
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");
237637
+ var findBakes = (root) => {
237638
+ const SKIP = new Set(["node_modules", ".lightmap", ".git", ".lecodes"]);
237639
+ const out = [];
237640
+ const walk = (dir) => {
237641
+ for (const e of readdirSync18(dir, { withFileTypes: true })) {
237642
+ if (e.isDirectory()) {
237643
+ if (!SKIP.has(e.name))
237644
+ walk(join46(dir, e.name));
237645
+ } else if (/\.bake$/i.test(e.name))
237646
+ out.push(relative7(root, join46(dir, e.name)).replace(/\\/g, "/"));
237647
+ }
237648
+ };
237649
+ walk(root);
237650
+ return out.sort();
237651
+ };
237652
+ var bakeFileOf = (root, flag) => {
237653
+ if (flag)
237654
+ return resolve22(root, flag);
237655
+ const found = findBakes(root);
237656
+ if (found.length === 0)
237657
+ throw new CliError("No .bake in the project — run `lecodes lightmap bake` first.");
237658
+ if (found.length > 1)
237659
+ throw new CliError(`The project has ${found.length} bakes (${found.join(", ")}) — pass --bake <file>.`);
237660
+ return resolve22(root, found[0]);
237432
237661
  };
237433
237662
  var readGlbJson = (file) => {
237434
237663
  let fd;
@@ -237474,6 +237703,9 @@ var unwrapReferenced = async (js, resources, root, texel) => {
237474
237703
  }
237475
237704
  note(`Unwrapping ${raw.length} of the ${glbs.length} GLB(s) the app references — no lightmap uvs; written in place (--no-unwrap skips)…`);
237476
237705
  const assets = await loadPeer("lecodes-assets", "cli", { for: "lecodes lightmap bake (lightmap uvs)" });
237706
+ if (typeof assets.unwrapGlbFiles !== "function") {
237707
+ throw new CliError("The lecodes-assets this CLI loaded has no unwrapGlbFiles (it predates the bake's unwrap) — `lecodes update` moves it forward; --no-unwrap bakes the GLBs as they are.");
237708
+ }
237477
237709
  const t0 = performance.now();
237478
237710
  const rel = (f) => relative7(root, f).replace(/\\/g, "/");
237479
237711
  const reports = await assets.unwrapGlbFiles(raw, {
@@ -237491,25 +237723,27 @@ var unwrapReferenced = async (js, resources, root, texel) => {
237491
237723
  var bake = defineCommand({
237492
237724
  name: "bake",
237493
237725
  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).
237726
+ 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).
237727
+
237728
+ 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
237729
 
237496
237730
  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
237731
  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" }),
237732
+ out: str("write the files into this folder instead of next to the level's .bake (a second seed for `lightmap diff`)", { value: "<dir>" }),
237733
+ name: str("bake the level whose bake() names <stem>.bake — when the entry reaches several", { value: "<stem>" }),
237500
237734
  ...bundleFlags,
237501
- entry: str("the file that builds the scene and calls Lightmap.load", { value: "<file>", default: "main.ts" }),
237735
+ entry: str("the file that opens the level (it reaches the bake() that names the target)", { value: "<file>", default: "main.ts" }),
237502
237736
  size: num("atlas page edge (texels)", { default: 4096 }),
237503
237737
  texel: num("texel size (m); the bake coarsens only when the pages are full", { default: 0.05 }),
237504
237738
  pages: num("atlas pages to fill before the bake coarsens the texel", { default: 4 }),
237505
237739
  "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 }),
237740
+ "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 }),
237741
+ "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 }),
237742
+ "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
237743
  bounces: num("path vertices at most (Russian roulette from the third); 0 = direct light only", { default: 3 }),
237510
237744
  "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" }),
237745
+ split: bool('also write <stem>-direct[_n].ktx2 / <stem>-indirect[_n].ktx2 — the pages the "direct" / "indirect" views bind (the manifest names them)'),
237746
+ 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
237747
  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
237748
  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
237749
  "light-radius": num("lamps: the emitter's radius (m) — soft shadows, no 1/d² singularity", { default: 0.05 }),
@@ -237523,13 +237757,13 @@ Lightmap UVs: the GLBs the app references that have none (TEXCOORD_1) are unwrap
237523
237757
  "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
237758
  "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
237759
  "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>" }),
237760
+ "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 }),
237761
+ 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
237762
  "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
237763
  "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
237764
  "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>" }),
237765
+ 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 }),
237766
+ "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
237767
  "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
237768
  "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
237769
  frames: num("host frames to run before giving up — load headroom", { default: 900 }),
@@ -237540,14 +237774,16 @@ Lightmap UVs: the GLBs the app references that have none (TEXCOORD_1) are unwrap
237540
237774
  logs: bool("forward the host's output"),
237541
237775
  "no-unwrap": bool("leave the referenced GLBs as they are: no lightmap unwrap of those without TEXCOORD_1 (they bake without lightmap uvs)")
237542
237776
  },
237543
- examples: ["lecodes lightmap bake", "lecodes lightmap bake --entry school.ts --name school --texel 0.04 --pages 4 --debug-dir .lightmap/school --logs"],
237777
+ examples: [
237778
+ "lecodes lightmap bake",
237779
+ "lecodes lightmap bake --dry-run --logs",
237780
+ "lecodes lightmap bake --entry level2.ts --texel 0.04 --debug-dir .lightmap/level2 --logs"
237781
+ ],
237544
237782
  run: async ({ flags, argv }) => {
237545
237783
  const { root } = localProject();
237546
- const outDir = resolve22(root, flags.out);
237547
- const stem = flags.name;
237548
237784
  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}".`);
237785
+ if (flags.name !== undefined && !/^[A-Za-z0-9_.-]+$/.test(flags.name))
237786
+ throw new CliError(`--name wants the stem of a .bake (its file name without .bake), got "${flags.name}".`);
237551
237787
  const entryFile = flags.entry;
237552
237788
  if (!existsSync41(resolve22(root, entryFile))) {
237553
237789
  throw new CliError(`No ${entryFile} in ${root} — pass --entry <file> (the file that builds the scene and calls Lightmap.load).`);
@@ -237560,8 +237796,9 @@ Lightmap UVs: the GLBs the app references that have none (TEXCOORD_1) are unwrap
237560
237796
  if (!["auto", "optix", "oidn"].includes(flags.denoiser))
237561
237797
  throw new CliError(`--denoiser wants auto, optix or oidn, got "${flags.denoiser}".`);
237562
237798
  const cfg = {
237563
- outDir: outDir.replace(/\\/g, "/"),
237564
- stem,
237799
+ root: root.replace(/\\/g, "/"),
237800
+ ...flags.out ? { outDir: resolve22(root, flags.out).replace(/\\/g, "/") } : {},
237801
+ ...flags.name !== undefined ? { stem: flags.name } : {},
237565
237802
  size: range("size", flags.size, 256, 16384),
237566
237803
  texel: range("texel", flags.texel, 0.001, 2),
237567
237804
  pages: range("pages", flags.pages, 1, 16),
@@ -237604,23 +237841,26 @@ Lightmap UVs: the GLBs the app references that have none (TEXCOORD_1) are unwrap
237604
237841
  const entryText = `globalThis.__lecodesLightmap = ${JSON.stringify(cfg)}
237605
237842
  import ${JSON.stringify(importPath)}
237606
237843
  `;
237607
- mkdirSync25(outDir, { recursive: true });
237608
- ensurePlaceholders(outDir, stem);
237609
- if (debugDir)
237610
- mkdirSync25(debugDir, { recursive: true });
237611
237844
  const desktopRenderer = "gl";
237612
- const { js, resources } = await buildBundle(bundleOptions(flags), {
237845
+ const { js, resources, bakes } = await buildBundle(bundleOptions(flags), {
237613
237846
  localAssets: true,
237614
237847
  desktopRenderer,
237615
237848
  extraEntries: [{ path: entryPath, type: "text", text: entryText }],
237616
237849
  entryOverride: entryPath
237617
237850
  });
237851
+ const target = pickTarget2(bakes, flags.name, entryFile);
237852
+ const stem = stemOf(target.path);
237853
+ const outDir = flags.out ? resolve22(root, flags.out) : dirname23(target.absPath);
237854
+ mkdirSync25(outDir, { recursive: true });
237855
+ if (debugDir)
237856
+ mkdirSync25(debugDir, { recursive: true });
237857
+ note(`Target: ${relative7(root, join46(outDir, `${stem}.bake`)).replace(/\\/g, "/")}${target.exists ? "" : " (the first bake: nothing there yet)"}${flags.out ? " (--out)" : ""}`);
237618
237858
  if (!flags["no-unwrap"])
237619
237859
  await unwrapReferenced(js, resources, root, cfg.texel);
237620
237860
  const scratch = mkdtempSync4(join46(tmpdir4(), "lecodes-lightmap-"));
237621
237861
  const outPng = join46(scratch, "frame.png");
237622
237862
  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}.*…`);
237863
+ 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
237864
  let output = "";
237625
237865
  if (flags["dry-run"])
237626
237866
  process.env.CBK_LAYOUT_ONLY = "1";
@@ -237664,16 +237904,12 @@ import ${JSON.stringify(importPath)}
237664
237904
  }
237665
237905
  if (/\[lightmap\] done/.test(output)) {
237666
237906
  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
237907
  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
237908
  const probeFiles = data.probes ? ` + ${data.probes.file} (${data.probes.count} probe(s) of ${data.probes.size}² at ${data.probes.spacing} m)` : "";
237672
237909
  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`));
237910
+ const uv1File = data.uv1 ? ` + ${data.uv1} (the packed lightmap uv1)` : "";
237911
+ logErr(c.green(`✓ baked → ${relative7(root, outDir).replace(/\\/g, "/") || "."}/${stem}.bake + ${data.light.join(", ")} + ${data.aux.join(", ")}${splitFiles}${probeFiles}${gridFile}${uv1File}`));
237912
+ 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
237913
  if (debugDir)
237678
237914
  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
237915
  return;
@@ -237693,15 +237929,16 @@ var stats = defineCommand({
237693
237929
  summary: "The per-instance numbers a .bake carries: mean lux, sun / sky visibility, texels, rect",
237694
237930
  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
237931
  flags: {
237696
- bake: str("the .bake file", { value: "<file>", default: "assets/lightmap/lightmap.bake" }),
237932
+ bake: str("the .bake file (default: the project's one .bake)", { value: "<file>" }),
237697
237933
  key: str("only instances whose key matches this regular expression", { value: "<regex>" }),
237698
237934
  sort: str("row order", { value: "<lux|key|texels|sun|sky>", default: "lux" }),
237699
237935
  limit: num("rows to print (0 = all)", { default: 60 })
237700
237936
  },
237701
- examples: ["lecodes lightmap stats --bake assets/school/lightmap/school.bake --key Wall", "lecodes lightmap stats --sort sky --limit 0"],
237937
+ examples: ["lecodes lightmap stats --key Wall", "lecodes lightmap stats --bake assets/level2/lightmap/level2.bake --sort sky --limit 0"],
237702
237938
  run: ({ flags }) => {
237703
237939
  const { root } = localProject();
237704
- const data = readBake(resolve22(root, flags.bake));
237940
+ const bakeFile = bakeFileOf(root, flags.bake);
237941
+ const data = readBake(bakeFile);
237705
237942
  const re = flags.key ? new RegExp(flags.key) : null;
237706
237943
  let rows = data.instances.filter((i) => i.receiver && (!re || re.test(i.key)));
237707
237944
  const sorters = {
@@ -237718,7 +237955,7 @@ var stats = defineCommand({
237718
237955
  const total = rows.length;
237719
237956
  if (flags.limit > 0)
237720
237957
  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}`);
237958
+ log(`${basename18(bakeFile)}: ${data.pages} page(s) of ${data.size}, ${(data.texel * 100).toFixed(2)} cm texels, ${data.texels ?? "?"} texels, lightScale ${data.lightScale}`);
237722
237959
  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
237960
  for (const r of rows) {
237724
237961
  const rect = r.rect ? `${r.rect[0]},${r.rect[1]} ${r.rect[2]}x${r.rect[3]}` : "";
@@ -237733,7 +237970,7 @@ var diff = defineCommand({
237733
237970
  summary: "The noise of a bake, per instance: two bakes that differ only by --seed, compared texel by texel",
237734
237971
  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
237972
  flags: {
237736
- bake: str("the first .bake", { value: "<file>", default: "assets/lightmap/lightmap.bake" }),
237973
+ bake: str("the first .bake (default: the project's one .bake)", { value: "<file>" }),
237737
237974
  "debug-dir": str("where the first bake kept its PFM layers", { value: "<dir>", default: ".lightmap" }),
237738
237975
  bake2: str("the second .bake (another --seed)", { value: "<file>" }),
237739
237976
  "debug-dir2": str("where the second bake kept its PFM layers", { value: "<dir>" }),
@@ -237744,21 +237981,23 @@ var diff = defineCommand({
237744
237981
  limit: num("rows to print (0 = all)", { default: 40 })
237745
237982
  },
237746
237983
  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"
237984
+ "lecodes lightmap bake --debug-dir .lightmap/seed1 --logs",
237985
+ "lecodes lightmap bake --seed 2 --out .lightmap/seed2/bake --debug-dir .lightmap/seed2 --logs",
237986
+ "lecodes lightmap diff --debug-dir .lightmap/seed1 --bake2 .lightmap/seed2/bake/level.bake --debug-dir2 .lightmap/seed2 --layer bounce"
237749
237987
  ],
237750
237988
  run: ({ flags }) => {
237751
237989
  const { root } = localProject();
237752
237990
  if (!flags.bake2 || !flags["debug-dir2"])
237753
237991
  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));
237992
+ const bakeFile = bakeFileOf(root, flags.bake);
237993
+ const a = readBake(bakeFile), b = readBake(resolve22(root, flags.bake2));
237755
237994
  if (a.size !== b.size || a.pages !== b.pages)
237756
237995
  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
237996
  const ra = a.instances.filter((i) => i.receiver), rb = b.instances.filter((i) => i.receiver);
237758
237997
  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
237998
  throw new CliError("The bakes list different instances or rects — a diff needs the same scene baked twice with another --seed.");
237760
237999
  }
237761
- const stemA = basename18(flags.bake, ".bake"), stemB = basename18(flags.bake2, ".bake");
238000
+ const stemA = basename18(bakeFile, ".bake"), stemB = basename18(flags.bake2, ".bake");
237762
238001
  const dirA = resolve22(root, flags["debug-dir"]), dirB = resolve22(root, flags["debug-dir2"]);
237763
238002
  const layer = flags.layer;
237764
238003
  if (!["irradiance", "bounce", "sun", "sky", "lamps"].includes(layer))
@@ -237831,7 +238070,7 @@ var diff = defineCommand({
237831
238070
  }
237832
238071
  });
237833
238072
  var readPfm = (file) => {
237834
- const buf = readFileSync35(file);
238073
+ const buf = readFileSync36(file);
237835
238074
  let o = 0;
237836
238075
  const token = () => {
237837
238076
  while (o < buf.length && /\s/.test(String.fromCharCode(buf[o])))
@@ -237911,7 +238150,7 @@ var inspect = defineCommand({
237911
238150
  summary: "PNGs from a bake's debug layers: whole pages, or one instance's rect enlarged",
237912
238151
  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
238152
  flags: {
237914
- bake: str("the .bake file", { value: "<file>", default: "assets/lightmap/lightmap.bake" }),
238153
+ bake: str("the .bake file (default: the project's one .bake)", { value: "<file>" }),
237915
238154
  "debug-dir": str("where the bake kept its PFM layers", { value: "<dir>", default: ".lightmap" }),
237916
238155
  out: str("where the PNGs go", { value: "<dir>", default: ".lightmap/png" }),
237917
238156
  key: str("instances whose key matches this regular expression (default: whole pages)", { value: "<regex>" }),
@@ -237922,10 +238161,10 @@ var inspect = defineCommand({
237922
238161
  white: num("linear mode: the lux shown as white (0 = log mode)", { default: 0 }),
237923
238162
  limit: num("instances to render with --key (0 = all)", { default: 24 })
237924
238163
  },
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"],
238164
+ examples: ["lecodes lightmap inspect --debug-dir .lightmap/level", "lecodes lightmap inspect --key 'Wall_05' --layer irradiance,sun,sky,lamps --scale 6"],
237926
238165
  run: ({ flags }) => {
237927
238166
  const { root } = localProject();
237928
- const bakeFile = resolve22(root, flags.bake);
238167
+ const bakeFile = bakeFileOf(root, flags.bake);
237929
238168
  const data = readBake(bakeFile);
237930
238169
  const stem = basename18(bakeFile).replace(/\.bake$/, "");
237931
238170
  const dbg = resolve22(root, flags["debug-dir"]);
@@ -238097,7 +238336,7 @@ init_types4();
238097
238336
  // src/commands/push.ts
238098
238337
  init_cli();
238099
238338
  init_api();
238100
- import { readFileSync as readFileSync37 } from "node:fs";
238339
+ import { readFileSync as readFileSync38 } from "node:fs";
238101
238340
  import { basename as basename19 } from "node:path";
238102
238341
  init_localFiles();
238103
238342
  init_manifest();
@@ -238106,7 +238345,7 @@ init_manifest();
238106
238345
  init_cli();
238107
238346
  init_api();
238108
238347
  init_localFiles();
238109
- import { existsSync as existsSync42, readFileSync as readFileSync36 } from "node:fs";
238348
+ import { existsSync as existsSync42, readFileSync as readFileSync37 } from "node:fs";
238110
238349
  import { resolve as resolve23 } from "node:path";
238111
238350
  init_shared();
238112
238351
  var compareVersions = (a, b) => {
@@ -238193,11 +238432,11 @@ var upload2 = defineCommand({
238193
238432
  const path = resolve23(file);
238194
238433
  if (!existsSync42(path))
238195
238434
  throw new CliError(`No such file: ${file}`);
238196
- const code = readFileSync36(path, "utf8");
238435
+ const code = readFileSync37(path, "utf8");
238197
238436
  const sdk = /^\/\/ sdk: (\S+)/m.exec(code)?.[1];
238198
238437
  if (!sdk)
238199
238438
  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;
238439
+ const map = existsSync42(`${path}.map`) ? readFileSync37(`${path}.map`, "utf8") : null;
238201
238440
  const { root, manifest, apiUrl, token } = linkedProject();
238202
238441
  if (hasChanges(diffLocal(manifest, scanProject(root, manifest))))
238203
238442
  await runPush({ message: flags.message || "Release" });
@@ -238302,9 +238541,9 @@ var runPush = async (opts) => {
238302
238541
  if (source)
238303
238542
  entry.assetId = source.id;
238304
238543
  if (isTextKind(kind))
238305
- entry.text = readFileSync37(file.absPath, "utf8");
238544
+ entry.text = readFileSync38(file.absPath, "utf8");
238306
238545
  else if (!source || source.sha !== file.sha) {
238307
- const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename19(file.path), readFileSync37(file.absPath));
238546
+ const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename19(file.path), readFileSync38(file.absPath));
238308
238547
  entry.blobFileId = blob.id;
238309
238548
  uploaded++;
238310
238549
  }
@@ -238504,7 +238743,7 @@ var login_default = defineCommand({
238504
238743
  });
238505
238744
 
238506
238745
  // 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";
238746
+ 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
238747
  import { tmpdir as tmpdir5 } from "node:os";
238509
238748
  import { basename as basename21, join as join48, relative as relative8, resolve as resolve25 } from "node:path";
238510
238749
  init_cli();
@@ -239307,7 +239546,7 @@ var guessName = (root) => {
239307
239546
  continue;
239308
239547
  let entries;
239309
239548
  try {
239310
- entries = readdirSync18(dir);
239549
+ entries = readdirSync19(dir);
239311
239550
  } catch {
239312
239551
  continue;
239313
239552
  }
@@ -239315,7 +239554,7 @@ var guessName = (root) => {
239315
239554
  if (!f.endsWith(".scene.ts"))
239316
239555
  continue;
239317
239556
  try {
239318
- if (/\bnavmesh\s*:/.test(readFileSync38(join48(dir, f), "utf8")))
239557
+ if (/\bnavmesh\s*:/.test(readFileSync39(join48(dir, f), "utf8")))
239319
239558
  return f.slice(0, -".scene.ts".length);
239320
239559
  } catch {}
239321
239560
  }
@@ -239465,7 +239704,7 @@ import ${JSON.stringify(importPath)}
239465
239704
  const geoPath = join48(outDir, `${name}.navgeo`);
239466
239705
  if (!existsSync44(geoPath))
239467
239706
  throw new CliError(`The host reported a dump but ${geoPath} is missing.`);
239468
- const geo = readNgeo(new Uint8Array(readFileSync38(geoPath)));
239707
+ const geo = readNgeo(new Uint8Array(readFileSync39(geoPath)));
239469
239708
  const params = {
239470
239709
  agentRadius: range2("radius", flags.radius, config.agent.radius, 0.05, 10),
239471
239710
  agentHeight: range2("height", flags.height, config.agent.height, 0.1, 20),
@@ -239519,7 +239758,7 @@ import ${JSON.stringify(importPath)}
239519
239758
  }
239520
239759
  if (!flags["keep-geometry"])
239521
239760
  unlinkSync2(geoPath);
239522
- const kb = Math.round(statSync15(navmeshPath).size / 1024);
239761
+ const kb = Math.round(statSync14(navmeshPath).size / 1024);
239523
239762
  const b = result.bounds;
239524
239763
  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
239764
  logErr(c.green(`✓ baked → ${relative8(root, navmeshPath).replace(/\\/g, "/")} (${kb} KB)`));
@@ -239539,9 +239778,9 @@ init_cli();
239539
239778
 
239540
239779
  // src/plugin/generate.ts
239541
239780
  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";
239781
+ import { existsSync as existsSync45, mkdirSync as mkdirSync27, readFileSync as readFileSync40, readdirSync as readdirSync20, statSync as statSync15, writeFileSync as writeFileSync29 } from "node:fs";
239543
239782
  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";
239783
+ import { basename as basename22, join as join49, relative as relative9, resolve as resolve26, sep as sep6 } from "node:path";
239545
239784
 
239546
239785
  // src/plugin/permissions.ts
239547
239786
  var PERMISSION_KINDS = ["camera", "microphone", "location", "speech"];
@@ -242410,10 +242649,10 @@ const wire = {
242410
242649
  `;
242411
242650
 
242412
242651
  // src/plugin/generate.ts
242413
- var shown = (from, to) => relative9(from, to).split(sep5).join("/");
242652
+ var shown = (from, to) => relative9(from, to).split(sep6).join("/");
242414
242653
  var implementsOf = (dir) => {
242415
242654
  try {
242416
- const raw = JSON.parse(readFileSync39(join49(dir, MANIFEST_FILE), "utf8").replace(/^/, ""));
242655
+ const raw = JSON.parse(readFileSync40(join49(dir, MANIFEST_FILE), "utf8").replace(/^/, ""));
242417
242656
  return typeof raw.implements === "string" ? raw.implements : null;
242418
242657
  } catch {
242419
242658
  return null;
@@ -242427,7 +242666,7 @@ var findPlugins = (path) => {
242427
242666
  const holder = join49(root, "plugins");
242428
242667
  if (!existsSync45(holder))
242429
242668
  return [];
242430
- return readdirSync19(holder).sort().map((id) => ({ id, dir: join49(holder, id) })).filter((p) => statSync16(p.dir).isDirectory() && isPluginDir(p.dir));
242669
+ return readdirSync20(holder).sort().map((id) => ({ id, dir: join49(holder, id) })).filter((p) => statSync15(p.dir).isDirectory() && isPluginDir(p.dir));
242431
242670
  };
242432
242671
  var loadTypescript = (dir) => {
242433
242672
  for (const from of [join49(dir, "package.json"), import.meta.url]) {
@@ -242444,7 +242683,7 @@ var generatePlugin = (ts, plugin) => {
242444
242683
  const manifest = readManifestIfAny(plugin.dir, plugin.id);
242445
242684
  const implemented = manifest?.implements;
242446
242685
  const file = implemented ? firstPartyContractFile(implemented) : join49(plugin.dir, CONTRACT_FILE);
242447
- const contract = readContract(ts, file, implemented ? firstPartyContractText(file) : readFileSync39(file, "utf8"));
242686
+ const contract = readContract(ts, file, implemented ? firstPartyContractText(file) : readFileSync40(file, "utf8"));
242448
242687
  checkPermissions(plugin, contract, manifest);
242449
242688
  const id = implemented ?? plugin.id;
242450
242689
  const handWritten = existsSync45(join49(plugin.dir, "sdk", `${id}.ts`));
@@ -242484,7 +242723,7 @@ var checkPermissions = (plugin, contract, manifest) => {
242484
242723
  if (kinds.length === 0)
242485
242724
  return;
242486
242725
  const androidManifest = join49(plugin.dir, "android", "src", "main", "AndroidManifest.xml");
242487
- const androidXml = manifest.android ? existsSync45(androidManifest) ? readFileSync39(androidManifest, "utf8") : "" : null;
242726
+ const androidXml = manifest.android ? existsSync45(androidManifest) ? readFileSync40(androidManifest, "utf8") : "" : null;
242488
242727
  const missing = missingDeclarations(kinds, manifest.ios ? manifest.ios.infoPlist ?? {} : null, androidXml === null ? null : usesPermissions(androidXml), androidXml === null ? null : usesFeatures(androidXml));
242489
242728
  const problems = [];
242490
242729
  if (missing.infoPlist.length)
@@ -242499,16 +242738,16 @@ var checkPermissions = (plugin, contract, manifest) => {
242499
242738
  var hostImports = (web) => {
242500
242739
  const found = [];
242501
242740
  const walk = (dir) => {
242502
- for (const entry of readdirSync19(dir).sort()) {
242741
+ for (const entry of readdirSync20(dir).sort()) {
242503
242742
  const path = join49(dir, entry);
242504
- if (statSync16(path).isDirectory()) {
242743
+ if (statSync15(path).isDirectory()) {
242505
242744
  if (entry !== "node_modules" && entry !== "dist")
242506
242745
  walk(path);
242507
242746
  continue;
242508
242747
  }
242509
242748
  if (!/\.(ts|tsx|js|mjs)$/.test(entry))
242510
242749
  continue;
242511
- readFileSync39(path, "utf8").split(`
242750
+ readFileSync40(path, "utf8").split(`
242512
242751
  `).forEach((line, i) => {
242513
242752
  const m = /(?:from\s+|import\s*\(\s*|import\s+)["'](lecodes-[^"']*)["']/.exec(line);
242514
242753
  if (m && !/^\s*\/\//.test(line))
@@ -242529,8 +242768,8 @@ var kotlinPackage = (android, register) => {
242529
242768
  }
242530
242769
  let dir = root;
242531
242770
  while (existsSync45(dir)) {
242532
- const entries = readdirSync19(dir).filter((e) => !e.startsWith("."));
242533
- const dirs = entries.filter((e) => statSync16(join49(dir, e)).isDirectory());
242771
+ const entries = readdirSync20(dir).filter((e) => !e.startsWith("."));
242772
+ const dirs = entries.filter((e) => statSync15(join49(dir, e)).isDirectory());
242534
242773
  if (dirs.length !== 1 || entries.length !== 1)
242535
242774
  break;
242536
242775
  dir = join49(dir, dirs[0]);
@@ -242545,7 +242784,7 @@ var repoSdkPath = (root) => {
242545
242784
  if (!existsSync45(file))
242546
242785
  return DEFAULT_SDK_PATH;
242547
242786
  try {
242548
- const pkg = JSON.parse(readFileSync39(file, "utf8"));
242787
+ const pkg = JSON.parse(readFileSync40(file, "utf8"));
242549
242788
  const path = pkg.lecodes?.iosSdkPath;
242550
242789
  if (path !== undefined && typeof path !== "string")
242551
242790
  throw new PackageError(`package.json lecodes.iosSdkPath: a path, as a string`);
@@ -242565,7 +242804,7 @@ var generate = (path, opts = {}) => {
242565
242804
  const report = { plugins: [], repo: { written: [], stale: [], fresh: [] }, failures: [] };
242566
242805
  const place = (f, entry) => {
242567
242806
  const name = shown(root, f.file) || basename22(f.file);
242568
- const current = existsSync45(f.file) ? readFileSync39(f.file, "utf8") : null;
242807
+ const current = existsSync45(f.file) ? readFileSync40(f.file, "utf8") : null;
242569
242808
  if (current === f.text) {
242570
242809
  entry.fresh.push(name);
242571
242810
  return;
@@ -243002,7 +243241,7 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
243002
243241
  };
243003
243242
 
243004
243243
  // src/commands/render.ts
243005
- import { mkdirSync as mkdirSync30, readFileSync as readFileSync41, writeFileSync as writeFileSync32 } from "node:fs";
243244
+ import { mkdirSync as mkdirSync30, readFileSync as readFileSync42, writeFileSync as writeFileSync32 } from "node:fs";
243006
243245
  import { dirname as dirname25, resolve as resolve30 } from "node:path";
243007
243246
 
243008
243247
  // src/compile/headlessBundle.ts
@@ -243090,7 +243329,7 @@ var compileHeadlessBundle = async (opts = {}) => {
243090
243329
  init_designMeta();
243091
243330
  init_cli();
243092
243331
  init_paths();
243093
- import { existsSync as existsSync47, readFileSync as readFileSync40, statSync as statSync17 } from "node:fs";
243332
+ import { existsSync as existsSync47, readFileSync as readFileSync41, statSync as statSync16 } from "node:fs";
243094
243333
  import { isAbsolute as isAbsolute3, join as join51, relative as relative10, resolve as resolve27 } from "node:path";
243095
243334
  var SCREEN_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs"];
243096
243335
  var SCREEN_ENTRY_PATH = "/.screen-entry.ts";
@@ -243102,7 +243341,7 @@ var findScreenFile = (root, file, dirFlag) => {
243102
243341
  candidates.push(join51(root, (dirFlag ?? DEFAULT_DESIGN_DIR).replace(/[/\\]+$/, ""), "screens", file));
243103
243342
  }
243104
243343
  for (const base of candidates) {
243105
- if (existsSync47(base) && statSync17(base).isFile())
243344
+ if (existsSync47(base) && statSync16(base).isFile())
243106
243345
  return base;
243107
243346
  for (const ext of SCREEN_EXTS)
243108
243347
  if (existsSync47(base + ext))
@@ -243124,7 +243363,7 @@ var resolveScreenTarget = (root, file, opts = {}) => {
243124
243363
  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
243364
  }
243126
243365
  const key = toKey(rel);
243127
- if (!/^\s*export\s+default\b/m.test(readFileSync40(abs, "utf8"))) {
243366
+ if (!/^\s*export\s+default\b/m.test(readFileSync41(abs, "utf8"))) {
243128
243367
  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
243368
  }
243130
243369
  const invoke = opts.state !== undefined ? `screen(${JSON.stringify(opts.state)})` : "screen()";
@@ -243921,7 +244160,7 @@ var resolveFixtures = (value, baseDir) => {
243921
244160
  if (typeof value === "string") {
243922
244161
  const abs = resolve30(baseDir, value);
243923
244162
  try {
243924
- map = JSON.parse(readFileSync41(abs, "utf8"));
244163
+ map = JSON.parse(readFileSync42(abs, "utf8"));
243925
244164
  } catch (e) {
243926
244165
  throw new CliError(`Can't read fixtures "${value}": ${e instanceof Error ? e.message : String(e)}`);
243927
244166
  }
@@ -243934,7 +244173,7 @@ var resolveFixtures = (value, baseDir) => {
243934
244173
  var readScenarioFile = (path) => {
243935
244174
  let raw;
243936
244175
  try {
243937
- raw = JSON.parse(readFileSync41(resolve30(path), "utf8"));
244176
+ raw = JSON.parse(readFileSync42(resolve30(path), "utf8"));
243938
244177
  } catch (e) {
243939
244178
  throw new CliError(`Can't read scenario "${path}": ${e instanceof Error ? e.message : String(e)}`);
243940
244179
  }
@@ -244279,7 +244518,7 @@ var renderHeadless = async (flags, file) => {
244279
244518
 
244280
244519
  // src/commands/scene.ts
244281
244520
  init_cli();
244282
- import { existsSync as existsSync50, readFileSync as readFileSync43, readdirSync as readdirSync20, statSync as statSync18, writeFileSync as writeFileSync34 } from "node:fs";
244521
+ import { existsSync as existsSync50, readFileSync as readFileSync44, readdirSync as readdirSync21, statSync as statSync17, writeFileSync as writeFileSync34 } from "node:fs";
244283
244522
  import { basename as basename23, dirname as dirname26, join as join54, relative as relative11, resolve as resolve31 } from "node:path";
244284
244523
 
244285
244524
  // src/compile/sceneCompile.ts
@@ -244288,7 +244527,7 @@ init_ignore();
244288
244527
  init_cli();
244289
244528
  init_projectCompile();
244290
244529
  import { createHash as createHash9 } from "node:crypto";
244291
- import { existsSync as existsSync49, mkdirSync as mkdirSync31, readFileSync as readFileSync42, writeFileSync as writeFileSync33 } from "node:fs";
244530
+ import { existsSync as existsSync49, mkdirSync as mkdirSync31, readFileSync as readFileSync43, writeFileSync as writeFileSync33 } from "node:fs";
244292
244531
  import { join as join53 } from "node:path";
244293
244532
  var TEXT_EXT = /\.(ts|js|json|svg|txt|md)$/i;
244294
244533
  var rel = (key) => key.replace(/^\//, "");
@@ -244300,11 +244539,11 @@ var compileSceneEditor = async (opts) => {
244300
244539
  for (const file of scan.files) {
244301
244540
  const path = "./" + rel(file.path);
244302
244541
  if (TEXT_EXT.test(path)) {
244303
- entries.push({ path, type: "text", text: readFileSync42(file.absPath, "utf8") });
244542
+ entries.push({ path, type: "text", text: readFileSync43(file.absPath, "utf8") });
244304
244543
  } else if (/\.mat$/i.test(path)) {
244305
- entries.push({ path, type: "shader", text: readFileSync42(file.absPath, "utf8"), absPath: file.absPath });
244544
+ entries.push({ path, type: "shader", text: readFileSync43(file.absPath, "utf8"), absPath: file.absPath });
244306
244545
  } else {
244307
- entries.push({ path, type: "resource", fileSrc: "/project/" + rel(file.path) });
244546
+ entries.push({ path, type: "resource", fileSrc: "/project/" + rel(file.path), absPath: file.absPath });
244308
244547
  }
244309
244548
  }
244310
244549
  const shaderEntries = entries.filter((e) => e.type === "shader");
@@ -244384,7 +244623,7 @@ var getContext2 = (pathArg) => {
244384
244623
  manifest = null;
244385
244624
  if (pathArg) {
244386
244625
  const abs = resolve31(process.cwd(), pathArg);
244387
- if (existsSync50(abs) && statSync18(abs).isDirectory())
244626
+ if (existsSync50(abs) && statSync17(abs).isDirectory())
244388
244627
  root = abs;
244389
244628
  else if (abs.endsWith(".scene.ts") && existsSync50(abs))
244390
244629
  root = findAppRoot(dirname26(abs)) ?? dirname26(abs);
@@ -244409,7 +244648,7 @@ var writeMcpConfig2 = (root, port) => {
244409
244648
  let config = {};
244410
244649
  if (existsSync50(path)) {
244411
244650
  try {
244412
- config = JSON.parse(readFileSync43(path, "utf8"));
244651
+ config = JSON.parse(readFileSync44(path, "utf8"));
244413
244652
  } catch {
244414
244653
  warnErr(".mcp.json exists but isn't valid JSON — left it untouched. Add the lecodes-scene server by hand.");
244415
244654
  return;
@@ -244428,11 +244667,11 @@ var writeMcpConfig2 = (root, port) => {
244428
244667
  };
244429
244668
  var countScenes = (dir) => {
244430
244669
  let n = 0;
244431
- for (const name of readdirSync20(dir)) {
244670
+ for (const name of readdirSync21(dir)) {
244432
244671
  if (name === "node_modules" || name.startsWith("."))
244433
244672
  continue;
244434
244673
  const full = join54(dir, name);
244435
- if (statSync18(full).isDirectory())
244674
+ if (statSync17(full).isDirectory())
244436
244675
  n += countScenes(full);
244437
244676
  else if (name.endsWith(".scene.ts"))
244438
244677
  n++;
@@ -244524,8 +244763,9 @@ var serve_default = defineCommand({
244524
244763
  });
244525
244764
 
244526
244765
  // src/commands/shaders/index.ts
244527
- import { readFileSync as readFileSync45 } from "node:fs";
244766
+ import { readFileSync as readFileSync46 } from "node:fs";
244528
244767
  import { relative as relative13 } from "node:path";
244768
+ init_releases();
244529
244769
  init_cli();
244530
244770
  init_shared();
244531
244771
 
@@ -244533,7 +244773,7 @@ init_shared();
244533
244773
  init_compile();
244534
244774
  init_distRoot();
244535
244775
  init_cli();
244536
- import { existsSync as existsSync51, mkdirSync as mkdirSync32, readdirSync as readdirSync21, readFileSync as readFileSync44, writeFileSync as writeFileSync35 } from "node:fs";
244776
+ import { existsSync as existsSync51, mkdirSync as mkdirSync32, readdirSync as readdirSync22, readFileSync as readFileSync45, writeFileSync as writeFileSync35 } from "node:fs";
244537
244777
  import { basename as basename25, join as join55, relative as relative12, resolve as resolve32 } from "node:path";
244538
244778
  var engineMaterialsDir = () => {
244539
244779
  const candidates = [
@@ -244549,7 +244789,7 @@ var engineMaterialNames = () => {
244549
244789
  const dir = engineMaterialsDir();
244550
244790
  if (!dir)
244551
244791
  return [];
244552
- return readdirSync21(dir).filter((f) => f.endsWith(".mat") && !f.startsWith("camera") && f !== "shader360.mat").map((f) => f.slice(0, -4)).sort();
244792
+ return readdirSync22(dir).filter((f) => f.endsWith(".mat") && !f.startsWith("camera") && f !== "shader360.mat").map((f) => f.slice(0, -4)).sort();
244553
244793
  };
244554
244794
  var NAME_RE = /^[a-z][a-z0-9_-]*$/i;
244555
244795
  var matName = (name) => name.replace(/-/g, "_");
@@ -244640,7 +244880,7 @@ var writeNewShader = (o) => {
244640
244880
  const known = engineMaterialNames();
244641
244881
  if (!known.includes(ref))
244642
244882
  throw new CliError(`no engine material "${ref}". Known: ${known.join(", ")}`);
244643
- mat = refMat(name, ref, readFileSync44(join55(srcDir, `${ref}.mat`), "utf8"));
244883
+ mat = refMat(name, ref, readFileSync45(join55(srcDir, `${ref}.mat`), "utf8"));
244644
244884
  } else
244645
244885
  mat = starterMat(name);
244646
244886
  mkdirSync32(dir, { recursive: true });
@@ -244672,7 +244912,7 @@ var status2 = async () => {
244672
244912
  if (matcId === null)
244673
244913
  state = c.dim("unknown (matc not downloaded)");
244674
244914
  else {
244675
- const text = readFileSync45(shader.absPath, "utf8");
244915
+ const text = readFileSync46(shader.absPath, "utf8");
244676
244916
  if (shaderIsCompiled(root, text, matcId))
244677
244917
  state = "compiled";
244678
244918
  else {
@@ -244681,7 +244921,7 @@ var status2 = async () => {
244681
244921
  }
244682
244922
  }
244683
244923
  log(` ${shader.path.padEnd(width)} ${state}`);
244684
- const schema = parseShaderSchema(readFileSync45(shader.absPath, "utf8"));
244924
+ const schema = parseShaderSchema(readFileSync46(shader.absPath, "utf8"));
244685
244925
  for (const p of schema.params) {
244686
244926
  const range = p.min !== undefined ? ` [${p.min}..${p.max}]` : "";
244687
244927
  const def = p.default !== null && p.default !== undefined ? ` = ${JSON.stringify(p.default)}` : "";
@@ -244722,7 +244962,7 @@ var compileShaders = async (platforms) => {
244722
244962
  }
244723
244963
  const matcExe = await resolveMatcExe();
244724
244964
  const matcId = matcIdentity(matcExe);
244725
- const wasCompiled = new Map(shaders.map((s) => [s.path, shaderIsCompiled(root, readFileSync45(s.absPath, "utf8"), matcId)]));
244965
+ const wasCompiled = new Map(shaders.map((s) => [s.path, shaderIsCompiled(root, readFileSync46(s.absPath, "utf8"), matcId)]));
244726
244966
  const errors = [];
244727
244967
  for (const shader of shaders) {
244728
244968
  try {
@@ -244824,7 +245064,7 @@ var status_default = defineCommand({
244824
245064
  });
244825
245065
 
244826
245066
  // src/commands/test.ts
244827
- import { existsSync as existsSync53, mkdirSync as mkdirSync34, readFileSync as readFileSync46, readdirSync as readdirSync22 } from "node:fs";
245067
+ import { existsSync as existsSync53, mkdirSync as mkdirSync34, readFileSync as readFileSync47, readdirSync as readdirSync23 } from "node:fs";
244828
245068
  import { dirname as dirname28, resolve as resolve33 } from "node:path";
244829
245069
  init_manifest();
244830
245070
 
@@ -245341,7 +245581,7 @@ var slug = (s) => s.replace(/[^a-zA-Z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").sli
245341
245581
  var readFlowFile = (abs, displayName) => {
245342
245582
  let raw;
245343
245583
  try {
245344
- raw = JSON.parse(readFileSync46(abs, "utf8"));
245584
+ raw = JSON.parse(readFileSync47(abs, "utf8"));
245345
245585
  } catch (e) {
245346
245586
  throw new CliError(`Can't read "${displayName}": ${e instanceof Error ? e.message : String(e)}`);
245347
245587
  }
@@ -245449,7 +245689,7 @@ var runTests = async (args, flags) => {
245449
245689
  cases.push(readFlowFile(abs, given));
245450
245690
  }
245451
245691
  } else if (existsSync53(testsDir)) {
245452
- for (const f of readdirSync22(testsDir).filter((f) => f.endsWith(".flow.json")).sort()) {
245692
+ for (const f of readdirSync23(testsDir).filter((f) => f.endsWith(".flow.json")).sort()) {
245453
245693
  cases.push(readFlowFile(resolve33(testsDir, f), f));
245454
245694
  }
245455
245695
  }
@@ -245467,7 +245707,7 @@ var runTests = async (args, flags) => {
245467
245707
  if (!desktop && !lite && (wantFlows || flowsOnly) && existsSync53(designMetaPath)) {
245468
245708
  let meta;
245469
245709
  try {
245470
- meta = JSON.parse(readFileSync46(designMetaPath, "utf8"));
245710
+ meta = JSON.parse(readFileSync47(designMetaPath, "utf8"));
245471
245711
  } catch (e) {
245472
245712
  throw new CliError(`Can't read ${designMetaPath}: ${e instanceof Error ? e.message : String(e)}`);
245473
245713
  }
@@ -245540,7 +245780,7 @@ var runTests = async (args, flags) => {
245540
245780
  script: plan.text,
245541
245781
  fixedDtMs: nativeFixedDt
245542
245782
  });
245543
- const r = evaluateDesktopRun(plan, output, (path) => existsSync53(path) ? readFileSync46(path, "utf8") : null);
245783
+ const r = evaluateDesktopRun(plan, output, (path) => existsSync53(path) ? readFileSync47(path, "utf8") : null);
245544
245784
  if (!r.loaded)
245545
245785
  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
245786
  return {
@@ -245579,7 +245819,7 @@ var runTests = async (args, flags) => {
245579
245819
  height: flags.height || kase.height || projectViewport?.height || 844,
245580
245820
  onConsole
245581
245821
  });
245582
- const r = evaluateDesktopRun(plan, output, (path) => existsSync53(path) ? readFileSync46(path, "utf8") : null);
245822
+ const r = evaluateDesktopRun(plan, output, (path) => existsSync53(path) ? readFileSync47(path, "utf8") : null);
245583
245823
  return {
245584
245824
  name: kase.name,
245585
245825
  kind: kase.kind,
@@ -245688,7 +245928,7 @@ var runTests = async (args, flags) => {
245688
245928
  init_peerInstall();
245689
245929
  import { createRequire as createRequire8 } from "node:module";
245690
245930
  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";
245931
+ import { existsSync as existsSync54, mkdtempSync as mkdtempSync7, readdirSync as readdirSync24, rmSync as rmSync17, statSync as statSync18 } from "node:fs";
245692
245932
  import { tmpdir as tmpdir7 } from "node:os";
245693
245933
  import { extname, join as join57, relative as relative14, resolve as resolve34 } from "node:path";
245694
245934
  init_cli();
@@ -245746,11 +245986,11 @@ var ktxToPng = (ktx, file, out) => {
245746
245986
  };
245747
245987
  var collect = (dir) => {
245748
245988
  const out = [];
245749
- for (const name of readdirSync23(dir)) {
245989
+ for (const name of readdirSync24(dir)) {
245750
245990
  if (name === "node_modules" || name.startsWith("."))
245751
245991
  continue;
245752
245992
  const full = join57(dir, name);
245753
- if (statSync19(full).isDirectory())
245993
+ if (statSync18(full).isDirectory())
245754
245994
  out.push(...collect(full));
245755
245995
  else if (IMAGE_EXT.test(name))
245756
245996
  out.push(full);
@@ -245778,7 +246018,7 @@ var thumbs = async (targets, size, force) => {
245778
246018
  const abs = resolve34(process.cwd(), arg);
245779
246019
  if (!existsSync54(abs))
245780
246020
  throw new CliError(`No such file or folder: ${arg}`);
245781
- if (statSync19(abs).isDirectory())
246021
+ if (statSync18(abs).isDirectory())
245782
246022
  files.push(...collect(abs));
245783
246023
  else if (IMAGE_EXT.test(abs))
245784
246024
  files.push(abs);
@@ -245855,7 +246095,7 @@ init_peerInstall();
245855
246095
  init_archives();
245856
246096
  init_packageManager();
245857
246097
  import { spawnSync as spawnSync11 } from "node:child_process";
245858
- import { existsSync as existsSync55, readdirSync as readdirSync24 } from "node:fs";
246098
+ import { existsSync as existsSync55, readdirSync as readdirSync25 } from "node:fs";
245859
246099
  import { homedir as homedir14 } from "node:os";
245860
246100
  import { dirname as dirname29, join as join58, resolve as resolve36 } from "node:path";
245861
246101
 
@@ -246026,7 +246266,7 @@ ${c.bold(name)} ${c.dim(what)}`);
246026
246266
  return ["skipped", "not inside a project"];
246027
246267
  if (flags["no-shells"])
246028
246268
  return ["skipped", "--no-shells"];
246029
- const present = ["ios", "android"].filter((d) => existsSync55(join58(root, d)) && readdirSync24(join58(root, d)).length > 0);
246269
+ const present = ["ios", "android"].filter((d) => existsSync55(join58(root, d)) && readdirSync25(join58(root, d)).length > 0);
246030
246270
  if (present.length === 0)
246031
246271
  return ["skipped", "no ios/ or android/ shell in this project (`lecodes app init ios|android`)"];
246032
246272
  await runSubcommand(app_default, "update");
@@ -246105,7 +246345,7 @@ init_cli();
246105
246345
  // src/web/build.ts
246106
246346
  init_compile();
246107
246347
  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";
246348
+ 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
246349
  import { dirname as dirname30, extname as extname2, join as join59, resolve as resolve37 } from "node:path";
246110
246350
  init_fonts();
246111
246351
  init_assetIcons();
@@ -246118,9 +246358,9 @@ var normalBase = (base) => {
246118
246358
  };
246119
246359
  var walk = (dir, prefix = "") => {
246120
246360
  const out = [];
246121
- for (const name of readdirSync25(dir)) {
246361
+ for (const name of readdirSync26(dir)) {
246122
246362
  const abs = join59(dir, name);
246123
- const stat = statSync20(abs);
246363
+ const stat = statSync19(abs);
246124
246364
  if (stat.isDirectory())
246125
246365
  out.push(...walk(abs, `${prefix}${name}/`));
246126
246366
  else
@@ -246149,7 +246389,7 @@ var buildWeb = async (options) => {
246149
246389
  for (const entry of entries) {
246150
246390
  if (entry.type !== "shader" || !entry.absPath)
246151
246391
  continue;
246152
- const text = readFileSync47(entry.absPath, "utf8");
246392
+ const text = readFileSync48(entry.absPath, "utf8");
246153
246393
  const hash = createHash10("sha256").update(text).digest("hex").slice(0, 16);
246154
246394
  shaders.set(hash, JSON.stringify(buildLiteMaterialArtifact(text)));
246155
246395
  entry.shaders = [{ platform: "opengl", src: `shaders/${hash}_opengl.filamat` }];
@@ -246196,7 +246436,7 @@ var buildWeb = async (options) => {
246196
246436
  cpSync7(lite, out, { recursive: true });
246197
246437
  const table = join59(out, "features.json");
246198
246438
  if (existsSync56(table)) {
246199
- const { features, fonts } = JSON.parse(readFileSync47(table, "utf8"));
246439
+ const { features, fonts } = JSON.parse(readFileSync48(table, "utf8"));
246200
246440
  if (config.font === "system")
246201
246441
  for (const file of fonts ?? [])
246202
246442
  rmSync18(join59(out, file), { force: true });
@@ -246210,7 +246450,7 @@ var buildWeb = async (options) => {
246210
246450
  }
246211
246451
  const pluginsTable = join59(out, "plugins.json");
246212
246452
  if (existsSync56(pluginsTable) || halves.length > 0) {
246213
- const { plugins } = existsSync56(pluginsTable) ? JSON.parse(readFileSync47(pluginsTable, "utf8")) : { plugins: {} };
246453
+ const { plugins } = existsSync56(pluginsTable) ? JSON.parse(readFileSync48(pluginsTable, "utf8")) : { plugins: {} };
246214
246454
  const kept = {};
246215
246455
  for (const id of Object.keys(plugins)) {
246216
246456
  if (listed.has(id))
@@ -246260,7 +246500,7 @@ var buildWeb = async (options) => {
246260
246500
  writeFileSync37(join59(out, "shaders", `${hash}_webgl-lite.filamat`), artifact);
246261
246501
  }
246262
246502
  const app = readAppConfigAt(root);
246263
- let html = readFileSync47(join59(lite, "index.html"), "utf8");
246503
+ let html = readFileSync48(join59(lite, "index.html"), "utf8");
246264
246504
  const head = [];
246265
246505
  head.push(`<meta name="theme-color" content="${escapeHtml(config.themeColor ?? "#000000")}" />`);
246266
246506
  if (app?.icon) {
@@ -246300,7 +246540,7 @@ var buildWeb = async (options) => {
246300
246540
  };
246301
246541
 
246302
246542
  // src/web/preview.ts
246303
- import { createReadStream as createReadStream3, existsSync as existsSync57, statSync as statSync21 } from "node:fs";
246543
+ import { createReadStream as createReadStream3, existsSync as existsSync57, statSync as statSync20 } from "node:fs";
246304
246544
  import { createServer as createServer4 } from "node:http";
246305
246545
  import { join as join60, resolve as resolve38 } from "node:path";
246306
246546
  var startPreview = async (dir, port) => {
@@ -246322,10 +246562,10 @@ var startPreview = async (dir, port) => {
246322
246562
  return;
246323
246563
  }
246324
246564
  let file = join60(root, ...parts);
246325
- if (existsSync57(file) && statSync21(file).isDirectory())
246565
+ if (existsSync57(file) && statSync20(file).isDirectory())
246326
246566
  file = join60(file, "index.html");
246327
246567
  let status = 200;
246328
- if (!existsSync57(file) || !statSync21(file).isFile()) {
246568
+ if (!existsSync57(file) || !statSync20(file).isFile()) {
246329
246569
  if (!fallback || /\.[a-z0-9]+$/i.test(parts[parts.length - 1] ?? "")) {
246330
246570
  res.writeHead(404, { "content-type": "text/plain" });
246331
246571
  res.end("Not found");
@@ -246355,7 +246595,7 @@ var startPreview = async (dir, port) => {
246355
246595
  init_shared();
246356
246596
 
246357
246597
  // src/dev/https.ts
246358
- import { existsSync as existsSync58, mkdirSync as mkdirSync36, readFileSync as readFileSync48, writeFileSync as writeFileSync38 } from "node:fs";
246598
+ import { existsSync as existsSync58, mkdirSync as mkdirSync36, readFileSync as readFileSync49, writeFileSync as writeFileSync38 } from "node:fs";
246359
246599
  import { homedir as homedir15 } from "node:os";
246360
246600
  import { join as join61 } from "node:path";
246361
246601
  var DIR = () => join61(homedir15(), ".lecodes", "dev-https");
@@ -246365,12 +246605,12 @@ var devCertificate = async (names, dir = DIR()) => {
246365
246605
  const files = { cert: join61(dir, "cert.pem"), key: join61(dir, "key.pem"), meta: join61(dir, "names.json") };
246366
246606
  let kept = null;
246367
246607
  try {
246368
- kept = JSON.parse(readFileSync48(files.meta, "utf8"));
246608
+ kept = JSON.parse(readFileSync49(files.meta, "utf8"));
246369
246609
  } catch {}
246370
246610
  const wanted = [...new Set(["localhost", "127.0.0.1", ...names])];
246371
246611
  const fresh = kept && Date.now() - kept.made < RENEW_DAYS * 86400000 && wanted.every((n) => kept.names.includes(n));
246372
246612
  if (fresh && existsSync58(files.cert) && existsSync58(files.key)) {
246373
- return { cert: readFileSync48(files.cert, "utf8"), key: readFileSync48(files.key, "utf8") };
246613
+ return { cert: readFileSync49(files.cert, "utf8"), key: readFileSync49(files.key, "utf8") };
246374
246614
  }
246375
246615
  const { generate } = await Promise.resolve().then(() => __toESM(require_selfsigned(), 1));
246376
246616
  const all = [...new Set([...kept?.names ?? [], ...wanted])];