lecodes-cli 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/index.js +202 -84
- package/package.json +4 -4
- package/runtime/sdk/animate/animate.ts +238 -0
- package/runtime/sdk/animate/bezier.ts +138 -0
- package/runtime/sdk/animate/easings.ts +126 -0
- package/runtime/sdk/core/Aspect.ts +151 -0
- package/runtime/sdk/core/color.ts +66 -0
- package/runtime/sdk/core/events.ts +38 -0
- package/runtime/sdk/core/registry.ts +23 -0
- package/runtime/sdk/g2/Camera2D.ts +33 -0
- package/runtime/sdk/g2/CharacterController2D.ts +75 -0
- package/runtime/sdk/g2/Node2D.ts +194 -0
- package/runtime/sdk/g2/Physics2D.ts +126 -0
- package/runtime/sdk/g2/Scene2D.ts +101 -0
- package/runtime/sdk/g2/Shape.ts +63 -0
- package/runtime/sdk/g2/Sprite.ts +79 -0
- package/runtime/sdk/g2/SpriteAnimation.ts +90 -0
- package/runtime/sdk/g2/Texture2D.ts +37 -0
- package/runtime/sdk/g2/Tilemap.ts +41 -0
- package/runtime/sdk/g2/Trigger.ts +47 -0
- package/runtime/sdk/g2/loop.ts +39 -0
- package/runtime/sdk/g2/touch.ts +83 -0
- package/runtime/sdk/gl/Camera.ts +40 -0
- package/runtime/sdk/gl/Collider.ts +29 -0
- package/runtime/sdk/gl/Geometry.ts +218 -0
- package/runtime/sdk/gl/Light.ts +37 -0
- package/runtime/sdk/gl/Material.ts +124 -0
- package/runtime/sdk/gl/Mesh.ts +100 -0
- package/runtime/sdk/gl/Model.ts +40 -0
- package/runtime/sdk/gl/ModelAnimation.ts +64 -0
- package/runtime/sdk/gl/Node.ts +234 -0
- package/runtime/sdk/gl/Noise.ts +30 -0
- package/runtime/sdk/gl/Particles.ts +185 -0
- package/runtime/sdk/gl/Physics.ts +95 -0
- package/runtime/sdk/gl/Plane.ts +53 -0
- package/runtime/sdk/gl/Ray.ts +16 -0
- package/runtime/sdk/gl/Scene.ts +179 -0
- package/runtime/sdk/gl/Texture.ts +35 -0
- package/runtime/sdk/gl/controls.ts +95 -0
- package/runtime/sdk/gl/state.ts +6 -0
- package/runtime/sdk/gl/touch.ts +56 -0
- package/runtime/sdk/inject.ts +85 -0
- package/runtime/sdk/math/Mathf.ts +118 -0
- package/runtime/sdk/math/mat4.ts +278 -0
- package/runtime/sdk/math/quat.ts +232 -0
- package/runtime/sdk/math/vec.ts +255 -0
- package/runtime/sdk/plugins/qr.ts +36 -0
- package/runtime/sdk/runtime/device.ts +33 -0
- package/runtime/sdk/runtime/fetch.ts +71 -0
- package/runtime/sdk/runtime/files.ts +23 -0
- package/runtime/sdk/runtime/input.ts +12 -0
- package/runtime/sdk/runtime/media.ts +111 -0
- package/runtime/sdk/runtime/misc.ts +12 -0
- package/runtime/sdk/runtime/net.ts +36 -0
- package/runtime/sdk/runtime/storage.ts +13 -0
- package/runtime/sdk/runtime/touch.ts +50 -0
- package/runtime/sdk/ui/UI.ts +19 -0
- package/runtime/sdk/ui/UIButton.ts +84 -0
- package/runtime/sdk/ui/UIContainer.ts +100 -0
- package/runtime/sdk/ui/UIImage.ts +68 -0
- package/runtime/sdk/ui/UIInput.ts +78 -0
- package/runtime/sdk/ui/UINode.ts +342 -0
- package/runtime/sdk/ui/UIScreen.ts +122 -0
- package/runtime/sdk/ui/UIScrollable.ts +70 -0
- package/runtime/sdk/ui/UISpacer.ts +14 -0
- package/runtime/sdk/ui/UIText.ts +40 -0
- package/runtime/sdk/ui/UIVideo.ts +38 -0
- package/runtime/sdk/ui/UIVirtualizedList.ts +222 -0
- package/runtime/sdk/ui/UIWidget.ts +97 -0
- package/runtime/sdk/ui/fonts.ts +10 -0
- package/runtime/sdk/ui/router.ts +73 -0
- package/runtime/inject.js +0 -4629
package/README.md
CHANGED
|
@@ -13,6 +13,12 @@ npm i -g lecodes-cli # then run: lecodes <command>
|
|
|
13
13
|
npx lecodes-cli <command>
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
The `render` command additionally needs the optional peer **[`lecodes-renderer`](https://www.npmjs.com/package/lecodes-renderer)** (`>= 0.2.0`, for UI + 2D rendering), plus `@napi-rs/canvas` for PNG output:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm i -D lecodes-renderer @napi-rs/canvas
|
|
20
|
+
```
|
|
21
|
+
|
|
16
22
|
## Quick start
|
|
17
23
|
|
|
18
24
|
```sh
|
|
@@ -35,7 +41,7 @@ lecodes preview > app.js # compile the local project to JS
|
|
|
35
41
|
| `pull` | Overwrite local files with the project's current remote state. `--force` to discard local edits. |
|
|
36
42
|
| `push` | Push all local changes as **one** new checkpoint. `-m`/`--message` for the message, `--force` to override the "server moved on" guard. |
|
|
37
43
|
| `preview` | Compile the local project to a single JS bundle, printed to **stdout** (pipeable). `-o`/`--out <file>` to write a file, `--entry <path>` to override the entrypoint, `--no-header` to omit the metadata header, `--public-url <url>` for the asset URL prefix. Diagnostics go to stderr. |
|
|
38
|
-
| `render` | Render the local **UI**
|
|
44
|
+
| `render` | Render the local project (**UI + 2D**). By default emits a semantic JSON description (great for AI/automated checks) to stdout or `-o`/`--out <file>`; `--png [file]` rasterizes a PNG (default `screenshot.png`, `--scale <n>` for DPR). A creator-2d scene is composited **under** the UI; `--time <ms>` advances its physics/animation. `asset('./x.png')` resources (images, sprite textures) load from the local working tree by default — use `--remote-assets` (or an explicit `--public-url`) to fetch them from the server instead (which then needs `--wait-network`). `--width`/`--height` set the viewport (default 390×844). Requires the optional `lecodes-renderer` package (`npm i -D lecodes-renderer@^0.2.0`); PNG also needs `@napi-rs/canvas`. 3D scenes aren't rendered. |
|
|
39
45
|
|
|
40
46
|
## How it works
|
|
41
47
|
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ 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
|
|
6
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
7
7
|
import { dirname as dirname7, join as join11 } from "node:path";
|
|
8
8
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
9
9
|
|
|
@@ -322,10 +322,13 @@ var DEFAULT_IGNORE_FILE = `# Files matched here are NOT pushed to LeCodes.
|
|
|
322
322
|
# Only untracked files are affected — assets already in the project stay tracked
|
|
323
323
|
# even if they match a pattern. Built-in defaults (node_modules, _*, *.log) always apply.
|
|
324
324
|
|
|
325
|
-
# Render / preview outputs
|
|
325
|
+
# Render / preview outputs (the default \`lecodes render --png\` target)
|
|
326
326
|
screenshot.png
|
|
327
327
|
shots/
|
|
328
|
-
|
|
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
|
|
329
332
|
|
|
330
333
|
# Local notes & dumps (uncomment to ignore)
|
|
331
334
|
# *.json
|
|
@@ -889,7 +892,7 @@ var collectEntries = (root, manifest, project) => {
|
|
|
889
892
|
const src = serverByPath.get(file.path)?.fileSrc;
|
|
890
893
|
if (!src)
|
|
891
894
|
warnings.push(`No server URL for ${file.path} — push it so its URL resolves.`);
|
|
892
|
-
entries.push({ path: file.path, type, fileSrc: src });
|
|
895
|
+
entries.push({ path: file.path, type, fileSrc: src, absPath: file.absPath });
|
|
893
896
|
} else if (type === "shader") {
|
|
894
897
|
const shaders = serverByPath.get(file.path)?.shaders;
|
|
895
898
|
if (!shaders?.length)
|
|
@@ -904,12 +907,14 @@ var collectEntries = (root, manifest, project) => {
|
|
|
904
907
|
|
|
905
908
|
// src/compile/projectCompile.ts
|
|
906
909
|
import { existsSync as existsSync6 } from "node:fs";
|
|
910
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
907
911
|
import { dirname as dirname5, join as join9 } from "node:path";
|
|
908
912
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
909
913
|
// ../sdk/src/compile/bundler.ts
|
|
910
|
-
import
|
|
911
|
-
import {
|
|
912
|
-
import {
|
|
914
|
+
import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync7, statSync } from "node:fs";
|
|
915
|
+
import { spawnSync } from "node:child_process";
|
|
916
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
917
|
+
import { dirname as dirname4, join as join8, relative as relative2 } from "node:path";
|
|
913
918
|
import { fileURLToPath } from "node:url";
|
|
914
919
|
|
|
915
920
|
// ../sdk/src/compile/assetMacro.ts
|
|
@@ -937,6 +942,36 @@ var desugarAssetMacros = (src, filePath) => {
|
|
|
937
942
|
` + out;
|
|
938
943
|
};
|
|
939
944
|
|
|
945
|
+
// ../sdk/src/compile/aspectMacro.ts
|
|
946
|
+
var ASPECT_RE = /\bclass\s+\w+\s+extends\s+Aspect\s*<\s*(["'])((?:[^"'\\]|\\.)*)\1[^{};]*>\s*\{/g;
|
|
947
|
+
var hasAspectExtends = (src) => /\bextends\s+Aspect\s*</.test(src);
|
|
948
|
+
var injectAspectNames = (src) => {
|
|
949
|
+
let out = "";
|
|
950
|
+
let last = 0;
|
|
951
|
+
ASPECT_RE.lastIndex = 0;
|
|
952
|
+
let m;
|
|
953
|
+
while ((m = ASPECT_RE.exec(src)) !== null) {
|
|
954
|
+
const braceEnd = m.index + m[0].length;
|
|
955
|
+
if (!classBodyDeclaresAspect(src, braceEnd)) {
|
|
956
|
+
out += src.slice(last, braceEnd) + ` static aspect = ${JSON.stringify(m[2])};`;
|
|
957
|
+
last = braceEnd;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return last === 0 ? src : out + src.slice(last);
|
|
961
|
+
};
|
|
962
|
+
var classBodyDeclaresAspect = (src, from) => {
|
|
963
|
+
let depth = 1;
|
|
964
|
+
let i = from;
|
|
965
|
+
for (;i < src.length && depth > 0; i++) {
|
|
966
|
+
const c2 = src[i];
|
|
967
|
+
if (c2 === "{")
|
|
968
|
+
depth++;
|
|
969
|
+
else if (c2 === "}")
|
|
970
|
+
depth--;
|
|
971
|
+
}
|
|
972
|
+
return /\bstatic\b[^;{}()=]*\baspect\b/.test(src.slice(from, i));
|
|
973
|
+
};
|
|
974
|
+
|
|
940
975
|
// ../sdk/src/compile/bundler.ts
|
|
941
976
|
var resolveInject = () => {
|
|
942
977
|
const override = process.env.LECODES_SDK_INJECT;
|
|
@@ -946,10 +981,7 @@ var resolveInject = () => {
|
|
|
946
981
|
const src = join8(here, "..", "inject.ts");
|
|
947
982
|
if (existsSync5(src))
|
|
948
983
|
return src;
|
|
949
|
-
|
|
950
|
-
if (existsSync5(dist))
|
|
951
|
-
return dist;
|
|
952
|
-
throw new Error("sdk: could not locate the inject entry (LECODES_SDK_INJECT / src/inject.ts / dist/inject.js)");
|
|
984
|
+
throw new Error("sdk: could not locate the inject source (LECODES_SDK_INJECT / src/inject.ts)");
|
|
953
985
|
};
|
|
954
986
|
var normalize = (path) => {
|
|
955
987
|
const parts = [];
|
|
@@ -963,73 +995,96 @@ var normalize = (path) => {
|
|
|
963
995
|
}
|
|
964
996
|
return "/" + parts.join("/");
|
|
965
997
|
};
|
|
966
|
-
var
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
throw new Error(`Module not found: ${specifier} from ${importer}`);
|
|
990
|
-
};
|
|
991
|
-
var getLoader = (path) => path.endsWith(".ts") ? "ts" : "js";
|
|
992
|
-
var bundleProject = async (entrypoint, files, format = "esm") => {
|
|
993
|
-
const fileMap = new Map(Object.entries(files).map(([k, v]) => [normalize(k), v]));
|
|
994
|
-
const entry = normalize(entrypoint);
|
|
995
|
-
if (!fileMap.has(entry))
|
|
996
|
-
throw new Error(`Entrypoint not found: ${entry}`);
|
|
997
|
-
const plugin = {
|
|
998
|
-
name: "memfs",
|
|
999
|
-
setup(build2) {
|
|
1000
|
-
build2.onResolve({ filter: /^entry$/ }, () => ({ path: entry, namespace: "mem" }));
|
|
1001
|
-
build2.onResolve({ filter: /.*/, namespace: "mem" }, (args) => {
|
|
1002
|
-
try {
|
|
1003
|
-
return { path: resolveWithExtensions(fileMap, args.importer || entry, args.path), namespace: "mem" };
|
|
1004
|
-
} catch (error) {
|
|
1005
|
-
return { errors: [{ text: error instanceof Error ? error.message : String(error) }] };
|
|
1006
|
-
}
|
|
1007
|
-
});
|
|
1008
|
-
build2.onLoad({ filter: /.*/, namespace: "mem" }, (args) => {
|
|
1009
|
-
const contents = fileMap.get(args.path);
|
|
1010
|
-
if (contents == null)
|
|
1011
|
-
return { errors: [{ text: `Module not found: ${args.path}` }] };
|
|
1012
|
-
const finalContents = /\.[tj]sx?$/.test(args.path) && hasAssetMacro(contents) ? desugarAssetMacros(contents, args.path) : contents;
|
|
1013
|
-
return { contents: finalContents, loader: getLoader(args.path), resolveDir: dirOf(args.path) };
|
|
1014
|
-
});
|
|
998
|
+
var CHISEL_DEFINE = { RAD2DEG: "57.29577951308232", DEG2RAD: "0.017453292519943295" };
|
|
999
|
+
var CHISEL_KEEP = ["_*"];
|
|
1000
|
+
var resolveSdkSrc = () => {
|
|
1001
|
+
const inject = resolveInject();
|
|
1002
|
+
if (!inject.endsWith("inject.ts")) {
|
|
1003
|
+
throw new Error(`chisel needs the SDK source tree (src/inject.ts); got ${inject}. Set LECODES_SDK_INJECT to the source.`);
|
|
1004
|
+
}
|
|
1005
|
+
return dirname4(inject);
|
|
1006
|
+
};
|
|
1007
|
+
var isSdkSource = (path) => /\.[tj]s$/.test(path) && !path.endsWith(".d.ts") && !path.includes("/compile/") && !path.includes("/tests/") && !path.includes("/__tests__/");
|
|
1008
|
+
var sdkFilesCache = new Map;
|
|
1009
|
+
var collectSdkFiles = (sdkSrc) => {
|
|
1010
|
+
const cached = sdkFilesCache.get(sdkSrc);
|
|
1011
|
+
if (cached)
|
|
1012
|
+
return cached;
|
|
1013
|
+
const out = {};
|
|
1014
|
+
const walk = (dir) => {
|
|
1015
|
+
for (const name of readdirSync4(dir)) {
|
|
1016
|
+
const p = join8(dir, name);
|
|
1017
|
+
if (statSync(p).isDirectory())
|
|
1018
|
+
walk(p);
|
|
1019
|
+
else if (isSdkSource(p))
|
|
1020
|
+
out["/__sdk/" + relative2(sdkSrc, p).split("\\").join("/")] = readFileSync7(p, "utf8");
|
|
1015
1021
|
}
|
|
1016
1022
|
};
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
+
walk(sdkSrc);
|
|
1024
|
+
sdkFilesCache.set(sdkSrc, out);
|
|
1025
|
+
return out;
|
|
1026
|
+
};
|
|
1027
|
+
var resolveChiselBin = () => {
|
|
1028
|
+
const override = process.env.LECODES_CHISEL_BIN;
|
|
1029
|
+
if (override) {
|
|
1030
|
+
if (existsSync5(override))
|
|
1031
|
+
return override;
|
|
1032
|
+
throw new Error(`LECODES_CHISEL_BIN points at a missing file: ${override}`);
|
|
1033
|
+
}
|
|
1034
|
+
try {
|
|
1035
|
+
const bin = createRequire2(import.meta.url)("@letary/chisel").binaryPath();
|
|
1036
|
+
if (bin && existsSync5(bin))
|
|
1037
|
+
return bin;
|
|
1038
|
+
} catch {}
|
|
1039
|
+
throw new Error("chisel binary not found. Install @letary/chisel, or set LECODES_CHISEL_BIN.");
|
|
1040
|
+
};
|
|
1041
|
+
var bundleProjectChisel = async (entrypoint, files, format, sourcemap) => {
|
|
1042
|
+
const entry = normalize(entrypoint);
|
|
1043
|
+
const projFiles = {};
|
|
1044
|
+
for (const [k, v] of Object.entries(files)) {
|
|
1045
|
+
const path = normalize(k);
|
|
1046
|
+
let src = v;
|
|
1047
|
+
if (/\.[tj]sx?$/.test(path)) {
|
|
1048
|
+
if (hasAssetMacro(src))
|
|
1049
|
+
src = desugarAssetMacros(src, path);
|
|
1050
|
+
if (hasAspectExtends(src))
|
|
1051
|
+
src = injectAspectNames(src);
|
|
1052
|
+
}
|
|
1053
|
+
projFiles[path] = src;
|
|
1054
|
+
}
|
|
1055
|
+
if (!projFiles[entry])
|
|
1056
|
+
throw new Error(`Entrypoint not found: ${entry}`);
|
|
1057
|
+
const sdkFiles = collectSdkFiles(resolveSdkSrc());
|
|
1058
|
+
const minify = process.env.LECODES_CHISEL_MINIFY !== "0";
|
|
1059
|
+
const input = JSON.stringify({
|
|
1060
|
+
files: { ...sdkFiles, ...projFiles },
|
|
1061
|
+
entry,
|
|
1062
|
+
inject: ["/__sdk/inject.ts"],
|
|
1023
1063
|
format,
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1064
|
+
minify,
|
|
1065
|
+
fuse: process.env.LECODES_CHISEL_FUSE === "1",
|
|
1066
|
+
define: CHISEL_DEFINE,
|
|
1067
|
+
keep: CHISEL_KEEP,
|
|
1068
|
+
sourcemap
|
|
1027
1069
|
});
|
|
1028
|
-
const
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1070
|
+
const bin = resolveChiselBin();
|
|
1071
|
+
const r = spawnSync(bin, [], { input, encoding: "utf8", maxBuffer: 256 * 1024 * 1024 });
|
|
1072
|
+
if (r.status === 2 || r.error) {
|
|
1073
|
+
throw new Error(`chisel failed: ${r.error?.message ?? r.stderr ?? "unknown error"}`);
|
|
1074
|
+
}
|
|
1075
|
+
let parsed;
|
|
1076
|
+
try {
|
|
1077
|
+
parsed = JSON.parse(r.stdout || "{}");
|
|
1078
|
+
} catch {
|
|
1079
|
+
throw new Error(`chisel produced invalid output: ${(r.stdout || r.stderr || "").slice(0, 200)}`);
|
|
1080
|
+
}
|
|
1081
|
+
if (parsed.error)
|
|
1082
|
+
throw new Error(`chisel: ${parsed.error}`);
|
|
1083
|
+
if (parsed.code == null)
|
|
1084
|
+
throw new Error("chisel produced no code");
|
|
1085
|
+
return { code: parsed.code, map: parsed.map ?? null };
|
|
1032
1086
|
};
|
|
1087
|
+
var bundleProjectWithMap = async (entrypoint, files, format = "esm") => bundleProjectChisel(entrypoint, files, format, true);
|
|
1033
1088
|
|
|
1034
1089
|
// ../sdk/src/compile/header.ts
|
|
1035
1090
|
var buildHeader = (name, code, opts = {}) => {
|
|
@@ -1067,6 +1122,19 @@ var buildHeader = (name, code, opts = {}) => {
|
|
|
1067
1122
|
return header;
|
|
1068
1123
|
};
|
|
1069
1124
|
|
|
1125
|
+
// ../sdk/src/compile/sourcemap.ts
|
|
1126
|
+
var offsetSourceMap = (mapJson, lines) => {
|
|
1127
|
+
if (lines <= 0)
|
|
1128
|
+
return mapJson;
|
|
1129
|
+
const map = JSON.parse(mapJson);
|
|
1130
|
+
map.mappings = ";".repeat(lines) + map.mappings;
|
|
1131
|
+
return JSON.stringify(map);
|
|
1132
|
+
};
|
|
1133
|
+
var inlineSourceMapComment = (mapJson) => {
|
|
1134
|
+
const b64 = Buffer.from(mapJson, "utf8").toString("base64");
|
|
1135
|
+
return `//# lecodesMap=data:application/json;charset=utf-8;base64,${b64}`;
|
|
1136
|
+
};
|
|
1137
|
+
|
|
1070
1138
|
// ../sdk/src/compile/compileProject.ts
|
|
1071
1139
|
var detectEntry = (entries) => {
|
|
1072
1140
|
let entry = "";
|
|
@@ -1106,13 +1174,35 @@ var compileProject = async (opts) => {
|
|
|
1106
1174
|
files[e.path] = e.text ?? "";
|
|
1107
1175
|
}
|
|
1108
1176
|
}
|
|
1109
|
-
const code = await
|
|
1110
|
-
|
|
1177
|
+
const { code, map } = await bundleProjectWithMap(entry, files, opts.format ?? "esm");
|
|
1178
|
+
if (opts.header === false) {
|
|
1179
|
+
return map ? `${code}
|
|
1180
|
+
${inlineSourceMapComment(map)}` : code;
|
|
1181
|
+
}
|
|
1182
|
+
const header = buildHeader(opts.name, code, { icon: opts.icon });
|
|
1183
|
+
if (!map)
|
|
1184
|
+
return header + code;
|
|
1185
|
+
const headerLines = (header.match(/\n/g) ?? []).length;
|
|
1186
|
+
return `${header}${code}
|
|
1187
|
+
${inlineSourceMapComment(offsetSourceMap(map, headerLines))}`;
|
|
1111
1188
|
};
|
|
1112
1189
|
// src/compile/projectCompile.ts
|
|
1113
|
-
var
|
|
1190
|
+
var here = dirname5(fileURLToPath2(import.meta.url));
|
|
1191
|
+
var vendoredInject = join9(here, "..", "runtime", "sdk", "inject.ts");
|
|
1114
1192
|
if (existsSync6(vendoredInject))
|
|
1115
1193
|
process.env.LECODES_SDK_INJECT = vendoredInject;
|
|
1194
|
+
var resolveChiselBin2 = () => {
|
|
1195
|
+
if (process.env.LECODES_CHISEL_BIN)
|
|
1196
|
+
return process.env.LECODES_CHISEL_BIN;
|
|
1197
|
+
try {
|
|
1198
|
+
return createRequire3(import.meta.url)("@letary/chisel").binaryPath() ?? null;
|
|
1199
|
+
} catch {
|
|
1200
|
+
return null;
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
var chiselBin = resolveChiselBin2();
|
|
1204
|
+
if (chiselBin)
|
|
1205
|
+
process.env.LECODES_CHISEL_BIN = chiselBin;
|
|
1116
1206
|
var compileProject2 = (opts) => compileProject({
|
|
1117
1207
|
entries: opts.entries.map((e) => ({ path: e.path, type: e.type, text: e.text, fileSrc: e.fileSrc, shaders: e.shaders })),
|
|
1118
1208
|
name: opts.name,
|
|
@@ -1160,6 +1250,7 @@ var preview = async (args) => {
|
|
|
1160
1250
|
// src/commands/render.ts
|
|
1161
1251
|
import { writeFileSync as writeFileSync8 } from "node:fs";
|
|
1162
1252
|
import { resolve as resolve4 } from "node:path";
|
|
1253
|
+
var LOCAL_ASSET_SCHEME = "lecodesfile://";
|
|
1163
1254
|
var DEVICE_SAFE_AREA = {
|
|
1164
1255
|
none: [0, 0, 0, 0],
|
|
1165
1256
|
iphone: [47, 0, 34, 0],
|
|
@@ -1196,12 +1287,16 @@ var render = async (args) => {
|
|
|
1196
1287
|
const token = requireToken(config);
|
|
1197
1288
|
note("Resolving project assets…");
|
|
1198
1289
|
const project = await getProject(apiUrl, token, manifest.uuid);
|
|
1199
|
-
const { entries, warnings } = collectEntries(root, manifest, project);
|
|
1290
|
+
const { entries, warnings: collectWarnings } = collectEntries(root, manifest, project);
|
|
1291
|
+
const publicUrlFlag = flagStr(args, "public-url");
|
|
1292
|
+
const useLocalAssets = !publicUrlFlag && !flagBool(args, "remote-assets");
|
|
1293
|
+
const compileEntries = useLocalAssets ? entries.map((e) => e.type === "resource" && e.absPath ? { ...e, fileSrc: encodeURIComponent(e.absPath) } : e) : entries;
|
|
1294
|
+
const warnings = useLocalAssets ? collectWarnings.filter((w) => !w.includes("No server URL")) : collectWarnings;
|
|
1200
1295
|
note("Compiling…");
|
|
1201
1296
|
const js = await compileProject2({
|
|
1202
|
-
entries,
|
|
1297
|
+
entries: compileEntries,
|
|
1203
1298
|
name: manifest.name,
|
|
1204
|
-
publicUrl:
|
|
1299
|
+
publicUrl: useLocalAssets ? LOCAL_ASSET_SCHEME : publicUrlFlag ?? apiUrl,
|
|
1205
1300
|
entryOverride: flagStr(args, "entry"),
|
|
1206
1301
|
header: false,
|
|
1207
1302
|
format: "iife"
|
|
@@ -1209,6 +1304,9 @@ var render = async (args) => {
|
|
|
1209
1304
|
if (js.includes("_creator.createScene") || js.includes("_creator.createEntity")) {
|
|
1210
1305
|
warnings.push("This looks like a 3D/GL project — only the UI layer is rendered.");
|
|
1211
1306
|
}
|
|
1307
|
+
if (js.includes("_creator2d") && !useLocalAssets && !flagBool(args, "wait-network")) {
|
|
1308
|
+
warnings.push("2D scene detected: sprite textures load over the network — add --wait-network to include them (else sprites render as flat placeholders).");
|
|
1309
|
+
}
|
|
1212
1310
|
let renderer;
|
|
1213
1311
|
try {
|
|
1214
1312
|
renderer = await import("lecodes-renderer/headless");
|
|
@@ -1223,6 +1321,7 @@ var render = async (args) => {
|
|
|
1223
1321
|
const timing = {
|
|
1224
1322
|
settleMs: settleStr !== undefined ? Number(settleStr) : undefined,
|
|
1225
1323
|
waitNetwork: flagBool(args, "wait-network"),
|
|
1324
|
+
localAssets: useLocalAssets,
|
|
1226
1325
|
timeMs: timeStr !== undefined ? Number(timeStr) : undefined,
|
|
1227
1326
|
afterAnimations: !flagBool(args, "no-after-animations"),
|
|
1228
1327
|
safeArea: resolveSafeArea(args),
|
|
@@ -1356,7 +1455,7 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
|
|
|
1356
1455
|
|
|
1357
1456
|
// src/commands/push.ts
|
|
1358
1457
|
import { basename as basename3 } from "node:path";
|
|
1359
|
-
import { readFileSync as
|
|
1458
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
1360
1459
|
var push = async (args) => {
|
|
1361
1460
|
const root = findProjectRoot(process.cwd());
|
|
1362
1461
|
const manifest = readManifest(root);
|
|
@@ -1381,6 +1480,12 @@ var push = async (args) => {
|
|
|
1381
1480
|
if (commits.headId !== manifest.baseHeadId) {
|
|
1382
1481
|
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.");
|
|
1383
1482
|
}
|
|
1483
|
+
if (commits.dirty) {
|
|
1484
|
+
const drift = serverPathDrift(await getProject(apiUrl, token, manifest.uuid), manifest);
|
|
1485
|
+
if (drift) {
|
|
1486
|
+
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.`);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1384
1489
|
}
|
|
1385
1490
|
if (force) {
|
|
1386
1491
|
const project = await getProject(apiUrl, token, manifest.uuid);
|
|
@@ -1409,9 +1514,9 @@ var push = async (args) => {
|
|
|
1409
1514
|
if (source)
|
|
1410
1515
|
entry.assetId = source.id;
|
|
1411
1516
|
if (isTextKind(kind)) {
|
|
1412
|
-
entry.text =
|
|
1517
|
+
entry.text = readFileSync8(file.absPath, "utf8");
|
|
1413
1518
|
} else if (!source || source.sha !== file.sha) {
|
|
1414
|
-
const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename3(file.path),
|
|
1519
|
+
const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename3(file.path), readFileSync8(file.absPath));
|
|
1415
1520
|
entry.blobFileId = blob.id;
|
|
1416
1521
|
uploaded++;
|
|
1417
1522
|
}
|
|
@@ -1443,6 +1548,19 @@ var push = async (args) => {
|
|
|
1443
1548
|
if (publish)
|
|
1444
1549
|
reportPublish(res);
|
|
1445
1550
|
};
|
|
1551
|
+
var serverPathDrift = (project, manifest) => {
|
|
1552
|
+
const pathById = buildPaths(project.assets);
|
|
1553
|
+
const serverPaths = new Set(project.assets.filter((a) => a.type !== "folder").map((a) => pathById.get(a.id)));
|
|
1554
|
+
const manifestPaths = new Set(Object.keys(manifest.files));
|
|
1555
|
+
const added = [...serverPaths].filter((p) => !manifestPaths.has(p));
|
|
1556
|
+
const removed = [...manifestPaths].filter((p) => !serverPaths.has(p));
|
|
1557
|
+
const describe = (label, paths) => `${paths.length} ${label} on the server: ${paths.slice(0, 3).join(", ")}${paths.length > 3 ? ", …" : ""}`;
|
|
1558
|
+
const parts = [
|
|
1559
|
+
added.length ? describe("added", added) : "",
|
|
1560
|
+
removed.length ? describe("removed", removed) : ""
|
|
1561
|
+
].filter(Boolean);
|
|
1562
|
+
return parts.length ? parts.join("; ") : null;
|
|
1563
|
+
};
|
|
1446
1564
|
var reportPublish = (res) => {
|
|
1447
1565
|
if (res.published)
|
|
1448
1566
|
success("Published — the project is now live.");
|
|
@@ -1527,8 +1645,8 @@ Config lives in ~/.lecodes/config.json (override with LECODES_API / LECODES_TOKE
|
|
|
1527
1645
|
var commands = { login, create, clone, status, diff, pull, push, preview, render };
|
|
1528
1646
|
var readVersion = () => {
|
|
1529
1647
|
try {
|
|
1530
|
-
const
|
|
1531
|
-
return JSON.parse(
|
|
1648
|
+
const here2 = dirname7(fileURLToPath3(import.meta.url));
|
|
1649
|
+
return JSON.parse(readFileSync9(join11(here2, "..", "package.json"), "utf8")).version ?? "unknown";
|
|
1532
1650
|
} catch {
|
|
1533
1651
|
return "unknown";
|
|
1534
1652
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lecodes-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Clone, edit and push LeCodes projects from your machine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,16 +21,16 @@
|
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
23
|
"vendor-runtime": "bun scripts/vendor-runtime.ts",
|
|
24
|
-
"build": "bun scripts/vendor-runtime.ts && bun build src/index.ts --target=node --outfile dist/index.js --external
|
|
24
|
+
"build": "bun scripts/vendor-runtime.ts && bun build src/index.ts --target=node --outfile dist/index.js --external @letary/chisel --external lecodes-renderer --banner=\"#!/usr/bin/env node\"",
|
|
25
25
|
"typecheck": "tsc --noEmit",
|
|
26
26
|
"dev": "bun run src/index.ts",
|
|
27
27
|
"prepublishOnly": "bun run build"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"
|
|
30
|
+
"@letary/chisel": "^0.1.2"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"lecodes-renderer": "^0.
|
|
33
|
+
"lecodes-renderer": "^0.2.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
36
36
|
"lecodes-renderer": {
|