lecodes-cli 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/index.js +300 -149
  2. package/package.json +5 -3
  3. package/runtime/sdk/animate/animate.ts +238 -0
  4. package/runtime/sdk/animate/bezier.ts +138 -0
  5. package/runtime/sdk/animate/easings.ts +126 -0
  6. package/runtime/sdk/core/color.ts +66 -0
  7. package/runtime/sdk/core/events.ts +38 -0
  8. package/runtime/sdk/core/registry.ts +23 -0
  9. package/runtime/sdk/g2/Camera2D.ts +73 -0
  10. package/runtime/sdk/g2/Node2D.ts +104 -0
  11. package/runtime/sdk/g2/Scene2D.ts +73 -0
  12. package/runtime/sdk/g2/Sprite.ts +164 -0
  13. package/runtime/sdk/g2/Texture2D.ts +37 -0
  14. package/runtime/sdk/g2/Tilemap.ts +41 -0
  15. package/runtime/sdk/g2/loop.ts +22 -0
  16. package/runtime/sdk/gl/Body.ts +101 -0
  17. package/runtime/sdk/gl/Camera.ts +40 -0
  18. package/runtime/sdk/gl/Geometry.ts +218 -0
  19. package/runtime/sdk/gl/Light.ts +37 -0
  20. package/runtime/sdk/gl/Material.ts +124 -0
  21. package/runtime/sdk/gl/Mesh.ts +123 -0
  22. package/runtime/sdk/gl/Node.ts +307 -0
  23. package/runtime/sdk/gl/Noise.ts +30 -0
  24. package/runtime/sdk/gl/Particles.ts +185 -0
  25. package/runtime/sdk/gl/Plane.ts +53 -0
  26. package/runtime/sdk/gl/Ray.ts +16 -0
  27. package/runtime/sdk/gl/Scene.ts +179 -0
  28. package/runtime/sdk/gl/Texture.ts +35 -0
  29. package/runtime/sdk/gl/controls.ts +95 -0
  30. package/runtime/sdk/gl/state.ts +6 -0
  31. package/runtime/sdk/gl/touch.ts +56 -0
  32. package/runtime/sdk/inject.ts +72 -0
  33. package/runtime/sdk/math/Mathf.ts +118 -0
  34. package/runtime/sdk/math/mat4.ts +278 -0
  35. package/runtime/sdk/math/quat.ts +232 -0
  36. package/runtime/sdk/math/vec.ts +255 -0
  37. package/runtime/sdk/plugins/qr.ts +36 -0
  38. package/runtime/sdk/runtime/device.ts +33 -0
  39. package/runtime/sdk/runtime/fetch.ts +71 -0
  40. package/runtime/sdk/runtime/files.ts +23 -0
  41. package/runtime/sdk/runtime/input.ts +12 -0
  42. package/runtime/sdk/runtime/media.ts +111 -0
  43. package/runtime/sdk/runtime/misc.ts +12 -0
  44. package/runtime/sdk/runtime/net.ts +36 -0
  45. package/runtime/sdk/runtime/storage.ts +13 -0
  46. package/runtime/sdk/runtime/touch.ts +44 -0
  47. package/runtime/sdk/ui/UI.ts +19 -0
  48. package/runtime/sdk/ui/UIButton.ts +84 -0
  49. package/runtime/sdk/ui/UIContainer.ts +100 -0
  50. package/runtime/sdk/ui/UIImage.ts +68 -0
  51. package/runtime/sdk/ui/UIInput.ts +78 -0
  52. package/runtime/sdk/ui/UINode.ts +342 -0
  53. package/runtime/sdk/ui/UIScreen.ts +122 -0
  54. package/runtime/sdk/ui/UIScrollable.ts +70 -0
  55. package/runtime/sdk/ui/UISpacer.ts +14 -0
  56. package/runtime/sdk/ui/UIText.ts +40 -0
  57. package/runtime/sdk/ui/UIVideo.ts +38 -0
  58. package/runtime/sdk/ui/UIVirtualizedList.ts +222 -0
  59. package/runtime/sdk/ui/UIWidget.ts +97 -0
  60. package/runtime/sdk/ui/fonts.ts +10 -0
  61. package/runtime/sdk/ui/router.ts +73 -0
  62. package/runtime/gl-matrix/common.js +0 -54
  63. package/runtime/gl-matrix/header.js +0 -12
  64. package/runtime/gl-matrix/index.js +0 -11
  65. package/runtime/gl-matrix/mat2.js +0 -425
  66. package/runtime/gl-matrix/mat2d.js +0 -476
  67. package/runtime/gl-matrix/mat3.js +0 -765
  68. package/runtime/gl-matrix/mat4.js +0 -1947
  69. package/runtime/gl-matrix/quat.js +0 -737
  70. package/runtime/gl-matrix/quat2.js +0 -822
  71. package/runtime/gl-matrix/vec2.js +0 -620
  72. package/runtime/gl-matrix/vec3.js +0 -791
  73. package/runtime/gl-matrix/vec4.js +0 -652
  74. package/runtime/worker/animate.js +0 -304
  75. package/runtime/worker/chunk-4ZN32GYS.js +0 -34
  76. package/runtime/worker/chunk-G5ZA3LKB.js +0 -52
  77. package/runtime/worker/chunk-MI3UXE5H.js +0 -1395
  78. package/runtime/worker/creator.js +0 -930
  79. package/runtime/worker/globals.js +0 -208
  80. package/runtime/worker/ui.js +0 -763
  81. package/runtime/worker/utils.js +0 -16
package/dist/index.js CHANGED
@@ -3,9 +3,9 @@ import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // src/index.ts
6
- import { readFileSync as readFileSync8 } from "node:fs";
7
- import { dirname as dirname5, join as join9 } from "node:path";
8
- import { fileURLToPath as fileURLToPath2 } from "node:url";
6
+ import { readFileSync as readFileSync9 } from "node:fs";
7
+ import { dirname as dirname7, join as join11 } from "node:path";
8
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
9
9
 
10
10
  // src/commands/clone.ts
11
11
  import { existsSync as existsSync3, mkdirSync as mkdirSync5, readdirSync } from "node:fs";
@@ -160,6 +160,7 @@ var apiRequest = async (apiUrl, path, opts = {}) => {
160
160
  };
161
161
  var getAccount = (apiUrl, token) => apiRequest(apiUrl, "/account", { token });
162
162
  var getProject = (apiUrl, token, uuid) => apiRequest(apiUrl, `/projects/${uuid}`, { token });
163
+ var createProject = (apiUrl, token, name, teamUuid) => apiRequest(apiUrl, "/projects", { token, body: { name, teamUuid } });
163
164
  var getCommits = (apiUrl, token, uuid) => apiRequest(apiUrl, `/projects/${uuid}/commits`, { token });
164
165
  var getTypes = (apiUrl) => apiRequest(apiUrl, "/types");
165
166
  var uploadBlob = (apiUrl, token, uuid, name, bytes) => {
@@ -282,38 +283,8 @@ var materializeAssets = async (root, project, apiUrl, token, onProgress) => {
282
283
  };
283
284
 
284
285
  // src/types.ts
285
- import { join as join4 } from "node:path";
286
+ import { dirname as dirname3, join as join4 } from "node:path";
286
287
  import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "node:fs";
287
- var wrapGlobal = (text) => {
288
- const marker = text.indexOf("declare global");
289
- if (marker < 0)
290
- return text;
291
- const importLines = [];
292
- const bodyLines = [];
293
- for (const line of text.split(`
294
- `)) {
295
- if (/^\s*import\s.*\sfrom\s/.test(line))
296
- importLines.push(line);
297
- else
298
- bodyLines.push(line);
299
- }
300
- const rest = bodyLines.join(`
301
- `);
302
- const open = rest.indexOf("{", rest.indexOf("declare global"));
303
- const close = rest.lastIndexOf("}");
304
- const preamble = rest.slice(0, rest.indexOf("declare global"));
305
- const inner = rest.slice(open + 1, close);
306
- const stripDeclare = (s) => s.replace(/(^|\n)([ \t]*)declare\s+/g, "$1$2");
307
- const merged = stripDeclare(preamble) + `
308
- ` + stripDeclare(inner);
309
- return `${importLines.join(`
310
- `)}
311
-
312
- declare global {
313
- ${merged}
314
- }
315
- `;
316
- };
317
288
  var TSCONFIG = {
318
289
  compilerOptions: {
319
290
  target: "ES2020",
@@ -333,10 +304,10 @@ var TSCONFIG = {
333
304
  var materializeTypes = async (root, apiUrl) => {
334
305
  const bundle = await getTypes(apiUrl);
335
306
  const typesDir = join4(root, LECODES_DIR, "types");
336
- mkdirSync4(typesDir, { recursive: true });
337
- for (const [name, text] of Object.entries(bundle)) {
338
- const content = name === "global" ? wrapGlobal(text) : text;
339
- writeFileSync4(join4(typesDir, `${name}.d.ts`), content);
307
+ for (const [path, text] of Object.entries(bundle)) {
308
+ const dest = join4(typesDir, path);
309
+ mkdirSync4(dirname3(dest), { recursive: true });
310
+ writeFileSync4(dest, text);
340
311
  }
341
312
  };
342
313
  var writeTsconfig = (root) => {
@@ -351,10 +322,13 @@ var DEFAULT_IGNORE_FILE = `# Files matched here are NOT pushed to LeCodes.
351
322
  # Only untracked files are affected — assets already in the project stay tracked
352
323
  # even if they match a pattern. Built-in defaults (node_modules, _*, *.log) always apply.
353
324
 
354
- # Render / preview outputs
325
+ # Render / preview outputs (the default \`lecodes render --png\` target)
355
326
  screenshot.png
356
327
  shots/
357
- *.png
328
+
329
+ # Image & other binary assets ARE pushed by default — they're real project resources.
330
+ # Uncomment a pattern below only if you want to keep some untracked ones out of pushes.
331
+ # *.png
358
332
 
359
333
  # Local notes & dumps (uncomment to ignore)
360
334
  # *.json
@@ -471,9 +445,47 @@ var clone = async (args) => {
471
445
  info(c.dim(` cd ${dir} — edit, then \`lecodes push -m "your message"\``));
472
446
  };
473
447
 
448
+ // src/commands/create.ts
449
+ import { existsSync as existsSync4, mkdirSync as mkdirSync6, readdirSync as readdirSync2, writeFileSync as writeFileSync6 } from "node:fs";
450
+ import { join as join6, resolve as resolve2 } from "node:path";
451
+ var slug2 = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
452
+ var STARTER = `// Welcome to le.codes — no imports needed, the whole SDK is available as globals.
453
+ const scene = new Scene2D({ background: '#10131a' })
454
+ scene.open()
455
+
456
+ setLoop((dt) => {
457
+ // your per-frame logic — dt is seconds since the previous frame
458
+ })
459
+ `;
460
+ var create = async (args) => {
461
+ const config = loadConfig();
462
+ const apiUrl = requireApiUrl(config);
463
+ const token = requireToken(config);
464
+ const name = args._[0];
465
+ if (!name)
466
+ throw new CliError("Usage: lecodes create <name> [directory]");
467
+ const dir = args._[1] ?? slug2(name);
468
+ const root = resolve2(dir);
469
+ if (existsSync4(root) && readdirSync2(root).length > 0) {
470
+ throw new CliError(`Directory "${dir}" already exists and is not empty.`);
471
+ }
472
+ mkdirSync6(root, { recursive: true });
473
+ info(c.dim(`Creating project ${c.bold(name)}…`));
474
+ const { uuid } = await createProject(apiUrl, token, name, flagStr(args, "team"));
475
+ info(c.dim("Fetching type definitions…"));
476
+ await materializeTypes(root, apiUrl);
477
+ writeTsconfig(root);
478
+ writeDefaultIgnore(root);
479
+ writeFileSync6(join6(root, "main.ts"), STARTER);
480
+ const commits = await getCommits(apiUrl, token, uuid);
481
+ writeManifest(root, { uuid, apiUrl, name, baseHeadId: commits.headId, files: {} });
482
+ success(`Created ${c.bold(name)} in ${c.bold(dir)}/`);
483
+ info(c.dim(` cd ${dir} — edit main.ts, then \`lecodes push -m "first commit"\` to save it`));
484
+ };
485
+
474
486
  // src/localFiles.ts
475
- import { readdirSync as readdirSync2, readFileSync as readFileSync4 } from "node:fs";
476
- import { join as join6, relative } from "node:path";
487
+ import { readdirSync as readdirSync3, readFileSync as readFileSync4 } from "node:fs";
488
+ import { join as join7, relative } from "node:path";
477
489
  var SKIP_DIRS = new Set([".lecodes", ".git", "node_modules"]);
478
490
  var SKIP_FILES = new Set(["tsconfig.json", "jsconfig.json", ".gitignore", ".lecodesignore", ".DS_Store"]);
479
491
  var TEXT_EXTS = new Set([
@@ -526,8 +538,8 @@ var scanLocal = (root, opts = {}) => {
526
538
  return false;
527
539
  };
528
540
  const walk = (dir, parentIgnored) => {
529
- for (const entry of readdirSync2(dir, { withFileTypes: true })) {
530
- const abs = join6(dir, entry.name);
541
+ for (const entry of readdirSync3(dir, { withFileTypes: true })) {
542
+ const abs = join7(dir, entry.name);
531
543
  const key = toKey(relative(root, abs));
532
544
  if (entry.isDirectory()) {
533
545
  if (SKIP_DIRS.has(entry.name))
@@ -804,10 +816,10 @@ var openBrowser = (url) => {
804
816
  };
805
817
  var browserLogin = async (webBase, timeoutMs = 5 * 60 * 1000, open = openBrowser) => {
806
818
  const state = randomBytes(16).toString("hex");
807
- let resolve2;
819
+ let resolve3;
808
820
  let reject;
809
821
  const tokenPromise = new Promise((res, rej) => {
810
- resolve2 = res;
822
+ resolve3 = res;
811
823
  reject = rej;
812
824
  });
813
825
  const server = createServer((req, res) => {
@@ -825,7 +837,7 @@ var browserLogin = async (webBase, timeoutMs = 5 * 60 * 1000, open = openBrowser
825
837
  }
826
838
  res.writeHead(200, { "content-type": "text/html" });
827
839
  res.end(SUCCESS_HTML);
828
- resolve2(token);
840
+ resolve3(token);
829
841
  });
830
842
  await new Promise((res, rej) => {
831
843
  server.once("error", rej);
@@ -858,8 +870,8 @@ var login = async (args) => {
858
870
  };
859
871
 
860
872
  // src/commands/preview.ts
861
- import { writeFileSync as writeFileSync6 } from "node:fs";
862
- import { resolve as resolve2 } from "node:path";
873
+ import { writeFileSync as writeFileSync7 } from "node:fs";
874
+ import { resolve as resolve3 } from "node:path";
863
875
 
864
876
  // src/compile/collect.ts
865
877
  import { readFileSync as readFileSync6 } from "node:fs";
@@ -893,13 +905,19 @@ var collectEntries = (root, manifest, project) => {
893
905
  return { entries, warnings };
894
906
  };
895
907
 
896
- // src/compile/bundler.ts
908
+ // src/compile/projectCompile.ts
909
+ import { existsSync as existsSync6 } from "node:fs";
910
+ import { createRequire as createRequire2 } from "node:module";
911
+ import { dirname as dirname5, join as join9 } from "node:path";
912
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
913
+ // ../sdk/src/compile/bundler.ts
897
914
  import * as esbuild from "esbuild";
898
- import { existsSync as existsSync4 } from "node:fs";
899
- import { dirname as dirname3, join as join7 } from "node:path";
915
+ import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync7, statSync } from "node:fs";
916
+ import { spawnSync } from "node:child_process";
917
+ import { dirname as dirname4, join as join8, relative as relative2 } from "node:path";
900
918
  import { fileURLToPath } from "node:url";
901
919
 
902
- // src/compile/assetMacro.ts
920
+ // ../sdk/src/compile/assetMacro.ts
903
921
  var ASSET_RE = /\basset\(\s*(["'])((?:[^"'\\]|\\.)*)\1\s*\)/g;
904
922
  var hasAssetMacro = (src) => src.includes("asset(");
905
923
  var desugarAssetMacros = (src, filePath) => {
@@ -924,52 +942,21 @@ var desugarAssetMacros = (src, filePath) => {
924
942
  ` + out;
925
943
  };
926
944
 
927
- // src/compile/bundler.ts
928
- var GL_MATRIX_HEADER = `
929
- import * as glMatrix from "gl-matrix/common";
930
- import * as mat2 from "gl-matrix/mat2";
931
- import * as mat2d from "gl-matrix/mat2d";
932
- import * as mat3 from "gl-matrix/mat3";
933
- import * as mat4 from "gl-matrix/mat4";
934
- import * as quat from "gl-matrix/quat";
935
- import * as quat2 from "gl-matrix/quat2";
936
- import * as vec2 from "gl-matrix/vec2";
937
- import * as vec3 from "gl-matrix/vec3";
938
- import * as vec4 from "gl-matrix/vec4";
939
- `;
940
- var resolveRuntime = () => {
941
- const here = dirname3(fileURLToPath(import.meta.url));
942
- const vendored = join7(here, "..", "runtime");
943
- if (existsSync4(join7(vendored, "worker", "creator.js"))) {
944
- const w = join7(vendored, "worker");
945
- return {
946
- alias: {
947
- creator: join7(w, "creator.js"),
948
- ui: join7(w, "ui.js"),
949
- animate: join7(w, "animate.js"),
950
- utils: join7(w, "utils.js"),
951
- "gl-matrix": join7(vendored, "gl-matrix")
952
- },
953
- inject: [join7(w, "globals.js")]
954
- };
955
- }
956
- const repoRoot = join7(here, "..", "..", "..", "..");
957
- const ws = join7(repoRoot, "packages", "worker", "src");
958
- if (existsSync4(join7(ws, "index.ts"))) {
959
- return {
960
- alias: {
961
- creator: join7(ws, "index.ts"),
962
- ui: join7(ws, "ui.ts"),
963
- animate: join7(ws, "animate.ts"),
964
- utils: join7(ws, "utils.ts"),
965
- "gl-matrix": join7(repoRoot, "packages", "backend", "src", "assets", "dependencies", "gl-matrix")
966
- },
967
- inject: [join7(ws, "globals.ts")]
968
- };
969
- }
970
- throw new CliError("Could not locate the compile runtime — try reinstalling lecodes-cli.");
971
- };
972
- function normalize(path) {
945
+ // ../sdk/src/compile/bundler.ts
946
+ var resolveInject = () => {
947
+ const override = process.env.LECODES_SDK_INJECT;
948
+ if (override && existsSync5(override))
949
+ return override;
950
+ const here = dirname4(fileURLToPath(import.meta.url));
951
+ const src = join8(here, "..", "inject.ts");
952
+ if (existsSync5(src))
953
+ return src;
954
+ const dist = join8(here, "..", "..", "dist", "inject.js");
955
+ if (existsSync5(dist))
956
+ return dist;
957
+ throw new Error("sdk: could not locate the inject entry (LECODES_SDK_INJECT / src/inject.ts / dist/inject.js)");
958
+ };
959
+ var normalize = (path) => {
973
960
  const parts = [];
974
961
  for (const part of path.split("/")) {
975
962
  if (!part || part === ".")
@@ -980,20 +967,20 @@ function normalize(path) {
980
967
  parts.push(part);
981
968
  }
982
969
  return "/" + parts.join("/");
983
- }
984
- function dirOf(path) {
970
+ };
971
+ var dirOf = (path) => {
985
972
  const i = path.lastIndexOf("/");
986
973
  return i <= 0 ? "/" : path.slice(0, i);
987
- }
974
+ };
988
975
  var hasExtension = (path) => /\.[a-zA-Z0-9]+$/.test(path);
989
- function resolveBase(importer, specifier) {
976
+ var resolveBase = (importer, specifier) => {
990
977
  if (specifier.startsWith("/"))
991
978
  return normalize(specifier);
992
979
  if (specifier.startsWith("./") || specifier.startsWith("../"))
993
980
  return normalize(dirOf(importer) + "/" + specifier);
994
- throw new Error(`Bare import is not allowed here: ${specifier}`);
995
- }
996
- function resolveWithExtensions(files, importer, specifier) {
981
+ throw new Error(`Bare imports are not allowed — the whole SDK is available as globals (no imports): ${specifier}`);
982
+ };
983
+ var resolveWithExtensions = (files, importer, specifier) => {
997
984
  const base = resolveBase(importer, specifier);
998
985
  if (hasExtension(base)) {
999
986
  if (files.has(base))
@@ -1005,31 +992,26 @@ function resolveWithExtensions(files, importer, specifier) {
1005
992
  return candidate;
1006
993
  }
1007
994
  throw new Error(`Module not found: ${specifier} from ${importer}`);
1008
- }
995
+ };
1009
996
  var getLoader = (path) => path.endsWith(".ts") ? "ts" : "js";
1010
- async function buildInMemory(entrypoint, inputFiles, format = "esm") {
1011
- const runtime = resolveRuntime();
1012
- const files = new Map(Object.entries(inputFiles).map(([k, v]) => [normalize(k), v]));
997
+ var bundleProjectEsbuild = async (entrypoint, files, format = "esm") => {
998
+ const fileMap = new Map(Object.entries(files).map(([k, v]) => [normalize(k), v]));
1013
999
  const entry = normalize(entrypoint);
1014
- if (!files.has(entry))
1015
- throw new CliError(`Entrypoint not found: ${entry}`);
1000
+ if (!fileMap.has(entry))
1001
+ throw new Error(`Entrypoint not found: ${entry}`);
1016
1002
  const plugin = {
1017
1003
  name: "memfs",
1018
1004
  setup(build2) {
1019
1005
  build2.onResolve({ filter: /^entry$/ }, () => ({ path: entry, namespace: "mem" }));
1020
1006
  build2.onResolve({ filter: /.*/, namespace: "mem" }, (args) => {
1021
- const isRelative = args.path.startsWith("./") || args.path.startsWith("../");
1022
- const isAbsolute = args.path.startsWith("/");
1023
- if (!isRelative && !isAbsolute)
1024
- return null;
1025
1007
  try {
1026
- return { path: resolveWithExtensions(files, args.importer || entry, args.path), namespace: "mem" };
1008
+ return { path: resolveWithExtensions(fileMap, args.importer || entry, args.path), namespace: "mem" };
1027
1009
  } catch (error) {
1028
1010
  return { errors: [{ text: error instanceof Error ? error.message : String(error) }] };
1029
1011
  }
1030
1012
  });
1031
1013
  build2.onLoad({ filter: /.*/, namespace: "mem" }, (args) => {
1032
- const contents = files.get(args.path);
1014
+ const contents = fileMap.get(args.path);
1033
1015
  if (contents == null)
1034
1016
  return { errors: [{ text: `Module not found: ${args.path}` }] };
1035
1017
  const finalContents = /\.[tj]sx?$/.test(args.path) && hasAssetMacro(contents) ? desugarAssetMacros(contents, args.path) : contents;
@@ -1039,9 +1021,8 @@ async function buildInMemory(entrypoint, inputFiles, format = "esm") {
1039
1021
  };
1040
1022
  const result = await esbuild.build({
1041
1023
  stdin: { contents: `import "entry";`, loader: "js", sourcefile: "bootstrap.js" },
1042
- alias: runtime.alias,
1024
+ inject: [resolveInject()],
1043
1025
  define: { RAD2DEG: "57.29577951308232", DEG2RAD: "0.017453292519943295" },
1044
- inject: runtime.inject,
1045
1026
  bundle: true,
1046
1027
  write: false,
1047
1028
  format,
@@ -1051,13 +1032,105 @@ async function buildInMemory(entrypoint, inputFiles, format = "esm") {
1051
1032
  });
1052
1033
  const output = result.outputFiles?.[0];
1053
1034
  if (!output)
1054
- throw new CliError("Compilation produced no output");
1035
+ throw new Error("Compilation produced no output");
1055
1036
  return output.text;
1056
- }
1037
+ };
1038
+ var CHISEL_DEFINE = { RAD2DEG: "57.29577951308232", DEG2RAD: "0.017453292519943295" };
1039
+ var CHISEL_KEEP = ["_*"];
1040
+ var resolveSdkSrc = () => {
1041
+ const inject = resolveInject();
1042
+ if (!inject.endsWith("inject.ts")) {
1043
+ throw new Error(`chisel needs the SDK source tree (src/inject.ts); got a prebuilt inject at ${inject}. ` + `Set LECODES_SDK_INJECT to the source, or use esbuild (unset LECODES_BUNDLER).`);
1044
+ }
1045
+ return dirname4(inject);
1046
+ };
1047
+ var isSdkSource = (path) => /\.[tj]s$/.test(path) && !path.endsWith(".d.ts") && !path.includes("/compile/") && !path.includes("/tests/") && !path.includes("/__tests__/");
1048
+ var sdkFilesCache = new Map;
1049
+ var collectSdkFiles = (sdkSrc) => {
1050
+ const cached = sdkFilesCache.get(sdkSrc);
1051
+ if (cached)
1052
+ return cached;
1053
+ const out = {};
1054
+ const walk = (dir) => {
1055
+ for (const name of readdirSync4(dir)) {
1056
+ const p = join8(dir, name);
1057
+ if (statSync(p).isDirectory())
1058
+ walk(p);
1059
+ else if (isSdkSource(p))
1060
+ out["/__sdk/" + relative2(sdkSrc, p)] = readFileSync7(p, "utf8");
1061
+ }
1062
+ };
1063
+ walk(sdkSrc);
1064
+ sdkFilesCache.set(sdkSrc, out);
1065
+ return out;
1066
+ };
1067
+ var resolveChiselBin = () => {
1068
+ const override = process.env.LECODES_CHISEL_BIN;
1069
+ if (override) {
1070
+ if (existsSync5(override))
1071
+ return override;
1072
+ throw new Error(`LECODES_CHISEL_BIN points at a missing file: ${override}`);
1073
+ }
1074
+ const here = dirname4(fileURLToPath(import.meta.url));
1075
+ for (const rel of ["release/chisel", "debug/chisel"]) {
1076
+ const p = join8(here, "..", "..", "..", "..", "..", "chisel", "target", rel);
1077
+ if (existsSync5(p))
1078
+ return p;
1079
+ }
1080
+ throw new Error("chisel binary not found. Build it (`cargo build --release` in the chisel repo) or set LECODES_CHISEL_BIN.");
1081
+ };
1082
+ var bundleProjectChisel = async (entrypoint, files, format, sourcemap) => {
1083
+ const entry = normalize(entrypoint);
1084
+ const projFiles = {};
1085
+ for (const [k, v] of Object.entries(files)) {
1086
+ const path = normalize(k);
1087
+ projFiles[path] = /\.[tj]sx?$/.test(path) && hasAssetMacro(v) ? desugarAssetMacros(v, path) : v;
1088
+ }
1089
+ if (!projFiles[entry])
1090
+ throw new Error(`Entrypoint not found: ${entry}`);
1091
+ const sdkFiles = collectSdkFiles(resolveSdkSrc());
1092
+ const minify = process.env.LECODES_CHISEL_MINIFY !== "0";
1093
+ const input = JSON.stringify({
1094
+ files: { ...sdkFiles, ...projFiles },
1095
+ entry,
1096
+ inject: ["/__sdk/inject.ts"],
1097
+ format,
1098
+ minify,
1099
+ fuse: process.env.LECODES_CHISEL_FUSE === "1",
1100
+ define: CHISEL_DEFINE,
1101
+ keep: CHISEL_KEEP,
1102
+ sourcemap
1103
+ });
1104
+ const bin = resolveChiselBin();
1105
+ const r = spawnSync(bin, [], { input, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
1106
+ if (r.status === 2 || r.error) {
1107
+ throw new Error(`chisel failed: ${r.error?.message ?? r.stderr ?? "unknown error"}`);
1108
+ }
1109
+ let parsed;
1110
+ try {
1111
+ parsed = JSON.parse(r.stdout || "{}");
1112
+ } catch {
1113
+ throw new Error(`chisel produced invalid output: ${(r.stdout || r.stderr || "").slice(0, 200)}`);
1114
+ }
1115
+ if (parsed.error)
1116
+ throw new Error(`chisel: ${parsed.error}`);
1117
+ if (parsed.code == null)
1118
+ throw new Error("chisel produced no code");
1119
+ return { code: parsed.code, map: parsed.map ?? null };
1120
+ };
1121
+ var bundleProjectWithMap = async (entrypoint, files, format = "esm") => {
1122
+ if (process.env.LECODES_BUNDLER === "chisel") {
1123
+ return bundleProjectChisel(entrypoint, files, format, true);
1124
+ }
1125
+ return { code: await bundleProjectEsbuild(entrypoint, files, format), map: null };
1126
+ };
1057
1127
 
1058
- // src/compile/projectCompile.ts
1059
- var buildHeader = (name, code) => {
1128
+ // ../sdk/src/compile/header.ts
1129
+ var buildHeader = (name, code, opts = {}) => {
1060
1130
  let header = `// name: ${name.slice(0, 20)}
1131
+ `;
1132
+ if (opts.icon)
1133
+ header += `// icon: ${opts.icon}
1061
1134
  `;
1062
1135
  header += `// last-updated: ${new Date().toISOString()}
1063
1136
  `;
@@ -1077,6 +1150,9 @@ var buildHeader = (name, code) => {
1077
1150
  `;
1078
1151
  else if (code.includes("_creator.createEngine"))
1079
1152
  header += `// gl: defer
1153
+ `;
1154
+ if (code.includes("_creator2d."))
1155
+ header += `// 2d: enable
1080
1156
  `;
1081
1157
  if (code.includes("_creator.createARController"))
1082
1158
  header += "// ar: enable";
@@ -1084,6 +1160,21 @@ var buildHeader = (name, code) => {
1084
1160
  `;
1085
1161
  return header;
1086
1162
  };
1163
+
1164
+ // ../sdk/src/compile/sourcemap.ts
1165
+ var offsetSourceMap = (mapJson, lines) => {
1166
+ if (lines <= 0)
1167
+ return mapJson;
1168
+ const map = JSON.parse(mapJson);
1169
+ map.mappings = ";".repeat(lines) + map.mappings;
1170
+ return JSON.stringify(map);
1171
+ };
1172
+ var inlineSourceMapComment = (mapJson) => {
1173
+ const b64 = Buffer.from(mapJson, "utf8").toString("base64");
1174
+ return `//# lecodesMap=data:application/json;charset=utf-8;base64,${b64}`;
1175
+ };
1176
+
1177
+ // ../sdk/src/compile/compileProject.ts
1087
1178
  var detectEntry = (entries) => {
1088
1179
  let entry = "";
1089
1180
  let max = -1;
@@ -1101,7 +1192,7 @@ var detectEntry = (entries) => {
1101
1192
  var compileProject = async (opts) => {
1102
1193
  const entry = opts.entryOverride ?? detectEntry(opts.entries);
1103
1194
  if (!entry) {
1104
- throw new CliError("Couldn't find an entrypoint (a top-level .ts/.js file with no `export`). Pass --entry <path>.");
1195
+ throw new Error("Couldn't find an entrypoint (a top-level .ts/.js file with no `export`). Pass entryOverride.");
1105
1196
  }
1106
1197
  const files = {};
1107
1198
  for (const e of opts.entries) {
@@ -1119,12 +1210,50 @@ var compileProject = async (opts) => {
1119
1210
  } else if (e.path.toLowerCase().endsWith(".svg")) {
1120
1211
  files[e.path] = `export default SvgSource(\`${e.text ?? ""}\`)`;
1121
1212
  } else {
1122
- files[e.path] = GL_MATRIX_HEADER + (e.text ?? "");
1213
+ files[e.path] = e.text ?? "";
1123
1214
  }
1124
1215
  }
1125
- const code = await buildInMemory(entry, files, opts.format ?? "esm");
1126
- return opts.header === false ? code : buildHeader(opts.name, code) + code;
1216
+ const { code, map } = await bundleProjectWithMap(entry, files, opts.format ?? "esm");
1217
+ if (opts.header === false) {
1218
+ return map ? `${code}
1219
+ ${inlineSourceMapComment(map)}` : code;
1220
+ }
1221
+ const header = buildHeader(opts.name, code, { icon: opts.icon });
1222
+ if (!map)
1223
+ return header + code;
1224
+ const headerLines = (header.match(/\n/g) ?? []).length;
1225
+ return `${header}${code}
1226
+ ${inlineSourceMapComment(offsetSourceMap(map, headerLines))}`;
1227
+ };
1228
+ // src/compile/projectCompile.ts
1229
+ var here = dirname5(fileURLToPath2(import.meta.url));
1230
+ var vendoredInject = join9(here, "..", "runtime", "sdk", "inject.ts");
1231
+ if (existsSync6(vendoredInject))
1232
+ process.env.LECODES_SDK_INJECT = vendoredInject;
1233
+ var resolveChiselBin2 = () => {
1234
+ if (process.env.LECODES_CHISEL_BIN)
1235
+ return process.env.LECODES_CHISEL_BIN;
1236
+ try {
1237
+ return createRequire2(import.meta.url)("@letary/chisel").binaryPath() ?? null;
1238
+ } catch {
1239
+ return null;
1240
+ }
1127
1241
  };
1242
+ if (!process.env.LECODES_BUNDLER) {
1243
+ const chiselBin = resolveChiselBin2();
1244
+ if (chiselBin) {
1245
+ process.env.LECODES_CHISEL_BIN = chiselBin;
1246
+ process.env.LECODES_BUNDLER = "chisel";
1247
+ }
1248
+ }
1249
+ var compileProject2 = (opts) => compileProject({
1250
+ entries: opts.entries.map((e) => ({ path: e.path, type: e.type, text: e.text, fileSrc: e.fileSrc, shaders: e.shaders })),
1251
+ name: opts.name,
1252
+ publicUrl: opts.publicUrl,
1253
+ entryOverride: opts.entryOverride,
1254
+ header: opts.header,
1255
+ format: opts.format
1256
+ });
1128
1257
 
1129
1258
  // src/commands/preview.ts
1130
1259
  var preview = async (args) => {
@@ -1137,7 +1266,7 @@ var preview = async (args) => {
1137
1266
  const project = await getProject(apiUrl, token, manifest.uuid);
1138
1267
  const { entries, warnings } = collectEntries(root, manifest, project);
1139
1268
  note("Compiling…");
1140
- const js = await compileProject({
1269
+ const js = await compileProject2({
1141
1270
  entries,
1142
1271
  name: manifest.name,
1143
1272
  publicUrl: flagStr(args, "public-url") ?? apiUrl,
@@ -1150,7 +1279,7 @@ var preview = async (args) => {
1150
1279
  if (out && isNullSink(out)) {
1151
1280
  note(`Discarded output (${out})`);
1152
1281
  } else if (out) {
1153
- writeFileSync6(resolve2(out), js);
1282
+ writeFileSync7(resolve3(out), js);
1154
1283
  note(`Wrote ${out} (${js.length} bytes)`);
1155
1284
  } else {
1156
1285
  process.stdout.write(js);
@@ -1162,8 +1291,8 @@ var preview = async (args) => {
1162
1291
  };
1163
1292
 
1164
1293
  // src/commands/render.ts
1165
- import { writeFileSync as writeFileSync7 } from "node:fs";
1166
- import { resolve as resolve3 } from "node:path";
1294
+ import { writeFileSync as writeFileSync8 } from "node:fs";
1295
+ import { resolve as resolve4 } from "node:path";
1167
1296
  var DEVICE_SAFE_AREA = {
1168
1297
  none: [0, 0, 0, 0],
1169
1298
  iphone: [47, 0, 34, 0],
@@ -1202,7 +1331,7 @@ var render = async (args) => {
1202
1331
  const project = await getProject(apiUrl, token, manifest.uuid);
1203
1332
  const { entries, warnings } = collectEntries(root, manifest, project);
1204
1333
  note("Compiling…");
1205
- const js = await compileProject({
1334
+ const js = await compileProject2({
1206
1335
  entries,
1207
1336
  name: manifest.name,
1208
1337
  publicUrl: flagStr(args, "public-url") ?? apiUrl,
@@ -1250,7 +1379,7 @@ var render = async (args) => {
1250
1379
  if (isNullSink(pngFlag)) {
1251
1380
  note(`Discarded PNG (${pngFlag})`);
1252
1381
  } else {
1253
- writeFileSync7(resolve3(pngFlag), png);
1382
+ writeFileSync8(resolve4(pngFlag), png);
1254
1383
  note(`Wrote ${pngFlag} (${png.length} bytes, ${width}x${height})`);
1255
1384
  }
1256
1385
  return done((cb) => cb());
@@ -1265,7 +1394,7 @@ var render = async (args) => {
1265
1394
  return done((cb) => cb());
1266
1395
  }
1267
1396
  if (out) {
1268
- writeFileSync7(resolve3(out), result.text);
1397
+ writeFileSync8(resolve4(out), result.text);
1269
1398
  note(`Wrote ${out} (${result.text.length} bytes)`);
1270
1399
  return done((cb) => cb());
1271
1400
  }
@@ -1276,8 +1405,8 @@ var render = async (args) => {
1276
1405
  };
1277
1406
 
1278
1407
  // src/commands/pull.ts
1279
- import { existsSync as existsSync5, mkdirSync as mkdirSync6, rmSync, writeFileSync as writeFileSync8 } from "node:fs";
1280
- import { dirname as dirname4, join as join8 } from "node:path";
1408
+ import { existsSync as existsSync7, mkdirSync as mkdirSync7, rmSync, writeFileSync as writeFileSync9 } from "node:fs";
1409
+ import { dirname as dirname6, join as join10 } from "node:path";
1281
1410
  var pull = async (args) => {
1282
1411
  const root = findProjectRoot(process.cwd());
1283
1412
  const manifest = readManifest(root);
@@ -1297,8 +1426,8 @@ var pull = async (args) => {
1297
1426
  const remotePaths = new Set([...buildPaths(project.assets).entries()].filter(([id]) => project.assets.find((a) => a.id === id)?.type !== "folder").map(([, path]) => path));
1298
1427
  for (const path of Object.keys(manifest.files)) {
1299
1428
  if (!remotePaths.has(path)) {
1300
- const abs = join8(root, ...path.replace(/^\//, "").split("/"));
1301
- if (existsSync5(abs))
1429
+ const abs = join10(root, ...path.replace(/^\//, "").split("/"));
1430
+ if (existsSync7(abs))
1302
1431
  rmSync(abs);
1303
1432
  }
1304
1433
  }
@@ -1336,18 +1465,18 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
1336
1465
  const files = { ...manifest.files };
1337
1466
  for (const asset of targets) {
1338
1467
  const key = pathById.get(asset.id);
1339
- const abs = join8(root, ...key.replace(/^\//, "").split("/"));
1340
- mkdirSync6(dirname4(abs), { recursive: true });
1468
+ const abs = join10(root, ...key.replace(/^\//, "").split("/"));
1469
+ mkdirSync7(dirname6(abs), { recursive: true });
1341
1470
  let sha;
1342
1471
  if (asset.type === "file") {
1343
1472
  if (!asset.file?.src)
1344
1473
  continue;
1345
1474
  const bytes = await downloadBlob(apiUrl, asset.file.src);
1346
- writeFileSync8(abs, bytes);
1475
+ writeFileSync9(abs, bytes);
1347
1476
  sha = sha256(bytes);
1348
1477
  } else {
1349
1478
  const text = asset.text ?? "";
1350
- writeFileSync8(abs, text);
1479
+ writeFileSync9(abs, text);
1351
1480
  sha = sha256(text);
1352
1481
  }
1353
1482
  files[key] = { id: asset.id, type: kindOf2(asset.type), sha };
@@ -1360,7 +1489,7 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
1360
1489
 
1361
1490
  // src/commands/push.ts
1362
1491
  import { basename as basename3 } from "node:path";
1363
- import { readFileSync as readFileSync7 } from "node:fs";
1492
+ import { readFileSync as readFileSync8 } from "node:fs";
1364
1493
  var push = async (args) => {
1365
1494
  const root = findProjectRoot(process.cwd());
1366
1495
  const manifest = readManifest(root);
@@ -1385,6 +1514,12 @@ var push = async (args) => {
1385
1514
  if (commits.headId !== manifest.baseHeadId) {
1386
1515
  throw new CliError("The project changed on the server since your last sync. Run `lecodes diff` to compare, `lecodes pull` to take the server's version, or `lecodes push --force` to overwrite.");
1387
1516
  }
1517
+ if (commits.dirty) {
1518
+ const drift = serverPathDrift(await getProject(apiUrl, token, manifest.uuid), manifest);
1519
+ if (drift) {
1520
+ throw new CliError(`The project has uncommitted changes in the web editor your checkout doesn't have (${drift}). Run \`lecodes pull\` to take them, or \`lecodes push --force\` to overwrite the server.`);
1521
+ }
1522
+ }
1388
1523
  }
1389
1524
  if (force) {
1390
1525
  const project = await getProject(apiUrl, token, manifest.uuid);
@@ -1413,9 +1548,9 @@ var push = async (args) => {
1413
1548
  if (source)
1414
1549
  entry.assetId = source.id;
1415
1550
  if (isTextKind(kind)) {
1416
- entry.text = readFileSync7(file.absPath, "utf8");
1551
+ entry.text = readFileSync8(file.absPath, "utf8");
1417
1552
  } else if (!source || source.sha !== file.sha) {
1418
- const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename3(file.path), readFileSync7(file.absPath));
1553
+ const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename3(file.path), readFileSync8(file.absPath));
1419
1554
  entry.blobFileId = blob.id;
1420
1555
  uploaded++;
1421
1556
  }
@@ -1447,6 +1582,19 @@ var push = async (args) => {
1447
1582
  if (publish)
1448
1583
  reportPublish(res);
1449
1584
  };
1585
+ var serverPathDrift = (project, manifest) => {
1586
+ const pathById = buildPaths(project.assets);
1587
+ const serverPaths = new Set(project.assets.filter((a) => a.type !== "folder").map((a) => pathById.get(a.id)));
1588
+ const manifestPaths = new Set(Object.keys(manifest.files));
1589
+ const added = [...serverPaths].filter((p) => !manifestPaths.has(p));
1590
+ const removed = [...manifestPaths].filter((p) => !serverPaths.has(p));
1591
+ const describe = (label, paths) => `${paths.length} ${label} on the server: ${paths.slice(0, 3).join(", ")}${paths.length > 3 ? ", …" : ""}`;
1592
+ const parts = [
1593
+ added.length ? describe("added", added) : "",
1594
+ removed.length ? describe("removed", removed) : ""
1595
+ ].filter(Boolean);
1596
+ return parts.length ? parts.join("; ") : null;
1597
+ };
1450
1598
  var reportPublish = (res) => {
1451
1599
  if (res.published)
1452
1600
  success("Published — the project is now live.");
@@ -1493,6 +1641,9 @@ ${c.bold("Commands:")}
1493
1641
  --token <pat> store a token from the web UI instead
1494
1642
  --api <url> API origin (default https://le.codes)
1495
1643
  --web <url> app origin for the browser flow
1644
+ create <name> [dir] Create a new project on the server and scaffold it locally
1645
+ (types from the server + tsconfig + a starter main.ts)
1646
+ --team <uuid> create it inside a team
1496
1647
  clone <uuid|url> [dir] Download a project's files + types into a new folder
1497
1648
  --no-types skip the .d.ts / tsconfig generation
1498
1649
  status Show local changes vs the last sync
@@ -1525,11 +1676,11 @@ ${c.bold("Commands:")}
1525
1676
  --logs forward the app's console output (to stderr)
1526
1677
 
1527
1678
  Config lives in ~/.lecodes/config.json (override with LECODES_API / LECODES_TOKEN).`;
1528
- var commands = { login, clone, status, diff, pull, push, preview, render };
1679
+ var commands = { login, create, clone, status, diff, pull, push, preview, render };
1529
1680
  var readVersion = () => {
1530
1681
  try {
1531
- const here = dirname5(fileURLToPath2(import.meta.url));
1532
- return JSON.parse(readFileSync8(join9(here, "..", "package.json"), "utf8")).version ?? "unknown";
1682
+ const here2 = dirname7(fileURLToPath3(import.meta.url));
1683
+ return JSON.parse(readFileSync9(join11(here2, "..", "package.json"), "utf8")).version ?? "unknown";
1533
1684
  } catch {
1534
1685
  return "unknown";
1535
1686
  }