lecodes-cli 0.18.0 → 0.18.1
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 +1 -1
- package/dist/index.js +1222 -293
- package/package.json +4 -4
- package/runtime/scene-harness.json +1 -1
- package/runtime/sdk/compile/aspectMacro.ts +52 -8
- package/runtime/sdk/compile/assetMacro.ts +116 -15
- package/runtime/sdk/compile/bundler.ts +38 -3
- package/runtime/sdk/compile/index.ts +15 -0
- package/runtime/sdk/compile/sceneEditor.ts +11 -1
- package/runtime/sdk/compile/shaderSchema.ts +202 -0
- package/runtime/sdk/compile/shaderTargets.ts +42 -3
- package/runtime/sdk/core/Aspect.ts +512 -255
- package/runtime/sdk/core/compWrite.ts +42 -0
- package/runtime/sdk/core/fields.ts +1 -1
- package/runtime/sdk/core/time.ts +81 -0
- package/runtime/sdk/g2/Camera2D.ts +8 -1
- package/runtime/sdk/g2/CharacterController2D.ts +253 -53
- package/runtime/sdk/g2/Node2D.ts +80 -10
- package/runtime/sdk/g2/OneWay2D.ts +66 -0
- package/runtime/sdk/g2/Physics2D.ts +240 -30
- package/runtime/sdk/g2/Scene2D.ts +33 -1
- package/runtime/sdk/g2/Shape2D.ts +218 -22
- package/runtime/sdk/g2/Trigger2D.ts +42 -12
- package/runtime/sdk/g2/groups2d.ts +106 -0
- package/runtime/sdk/g2/loop.ts +15 -4
- package/runtime/sdk/gl/Camera.ts +1 -1
- package/runtime/sdk/gl/CameraPlace.ts +4 -3
- package/runtime/sdk/gl/CharacterController.ts +184 -56
- package/runtime/sdk/gl/Gearbox.ts +212 -0
- package/runtime/sdk/gl/Geometry.ts +70 -9
- package/runtime/sdk/gl/Light.ts +64 -2
- package/runtime/sdk/gl/Lightmap.ts +179 -0
- package/runtime/sdk/gl/Material.ts +25 -0
- package/runtime/sdk/gl/Mesh.ts +6 -23
- package/runtime/sdk/gl/Model.ts +16 -2
- package/runtime/sdk/gl/Node.ts +119 -39
- package/runtime/sdk/gl/Physics.ts +75 -24
- package/runtime/sdk/gl/Scene.ts +148 -5
- package/runtime/sdk/gl/Shape.ts +42 -3
- package/runtime/sdk/gl/Trigger.ts +1 -6
- package/runtime/sdk/gl/Vehicle.ts +276 -322
- package/runtime/sdk/gl/Wheel.ts +240 -0
- package/runtime/sdk/gl/scenarios.ts +291 -317
- package/runtime/sdk/inject.ts +186 -171
- package/runtime/sdk/scene/defineScene.ts +1227 -1172
- package/runtime/sdk/scene/gizmos.ts +148 -128
- package/runtime/sdk/scene/material.ts +188 -0
- package/runtime/sdk-types.json +1 -1
package/dist/index.js
CHANGED
|
@@ -418,7 +418,7 @@ node_modules/
|
|
|
418
418
|
return "updated";
|
|
419
419
|
};
|
|
420
420
|
var init_ignore = __esm(() => {
|
|
421
|
-
BUILTIN_PATTERNS = ["node_modules/", ".git/", "_*", "*.log", ".DS_Store", ".artifacts/"];
|
|
421
|
+
BUILTIN_PATTERNS = ["node_modules/", ".git/", "_*", "*.log", ".DS_Store", ".artifacts/", "dist/"];
|
|
422
422
|
});
|
|
423
423
|
|
|
424
424
|
// src/localFiles.ts
|
|
@@ -564,45 +564,212 @@ var init_distRoot = __esm(() => {
|
|
|
564
564
|
});
|
|
565
565
|
|
|
566
566
|
// ../sdk/src/compile/assetMacro.ts
|
|
567
|
-
var ASSET_RE, hasAssetMacro = (src) => src.includes("asset("),
|
|
568
|
-
const
|
|
567
|
+
var ASSET_RE, ASSET_CALL_RE, hasAssetMacro = (src) => src.includes("asset("), maskNonCode = (src) => {
|
|
568
|
+
const out = src.split("");
|
|
569
|
+
const blank = (from, to) => {
|
|
570
|
+
for (let k = from;k < to && k < out.length; k++)
|
|
571
|
+
if (out[k] !== `
|
|
572
|
+
`)
|
|
573
|
+
out[k] = " ";
|
|
574
|
+
};
|
|
575
|
+
const tpl = [];
|
|
576
|
+
let braces = 0;
|
|
569
577
|
let i = 0;
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
578
|
+
const scanTemplate = () => {
|
|
579
|
+
const start = i;
|
|
580
|
+
while (i < src.length) {
|
|
581
|
+
const c2 = src[i];
|
|
582
|
+
if (c2 === "\\") {
|
|
583
|
+
i += 2;
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
if (c2 === "`") {
|
|
587
|
+
blank(start, i);
|
|
588
|
+
i++;
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (c2 === "$" && src[i + 1] === "{") {
|
|
592
|
+
blank(start, i);
|
|
593
|
+
tpl.push(braces);
|
|
594
|
+
i += 2;
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
i++;
|
|
575
598
|
}
|
|
576
|
-
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
|
|
599
|
+
blank(start, i);
|
|
600
|
+
};
|
|
601
|
+
while (i < src.length) {
|
|
602
|
+
const c2 = src[i], d = src[i + 1];
|
|
603
|
+
if (c2 === "/" && d === "/") {
|
|
604
|
+
const e = src.indexOf(`
|
|
605
|
+
`, i);
|
|
606
|
+
const stop = e === -1 ? src.length : e;
|
|
607
|
+
blank(i, stop);
|
|
608
|
+
i = stop;
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
if (c2 === "/" && d === "*") {
|
|
612
|
+
const e = src.indexOf("*/", i + 2);
|
|
613
|
+
const stop = e === -1 ? src.length : e + 2;
|
|
614
|
+
blank(i, stop);
|
|
615
|
+
i = stop;
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
if (c2 === "'" || c2 === '"') {
|
|
619
|
+
const start = i;
|
|
620
|
+
i++;
|
|
621
|
+
while (i < src.length) {
|
|
622
|
+
const q = src[i];
|
|
623
|
+
if (q === "\\") {
|
|
624
|
+
i += 2;
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
i++;
|
|
628
|
+
if (q === c2 || q === `
|
|
629
|
+
`)
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
blank(start, i);
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
if (c2 === "`") {
|
|
636
|
+
i++;
|
|
637
|
+
scanTemplate();
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
if (c2 === "{") {
|
|
641
|
+
braces++;
|
|
642
|
+
i++;
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
if (c2 === "}") {
|
|
646
|
+
if (tpl.length > 0 && braces === tpl[tpl.length - 1]) {
|
|
647
|
+
tpl.pop();
|
|
648
|
+
i++;
|
|
649
|
+
scanTemplate();
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
if (braces > 0)
|
|
653
|
+
braces--;
|
|
654
|
+
i++;
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
i++;
|
|
580
658
|
}
|
|
581
|
-
|
|
659
|
+
return out.join("");
|
|
660
|
+
}, collectAssetMacros = (src, filePath) => {
|
|
661
|
+
const mask = maskNonCode(src);
|
|
662
|
+
const refs = [];
|
|
663
|
+
const literal = new Set;
|
|
664
|
+
let scanned = 0;
|
|
665
|
+
let line = 1;
|
|
666
|
+
for (const m of src.matchAll(ASSET_RE)) {
|
|
667
|
+
const index = m.index ?? 0;
|
|
668
|
+
if (mask[index] !== "a")
|
|
669
|
+
continue;
|
|
670
|
+
for (;scanned < index; scanned++)
|
|
671
|
+
if (src[scanned] === `
|
|
672
|
+
`)
|
|
673
|
+
line++;
|
|
674
|
+
refs.push({ path: m[2], index, length: m[0].length, line });
|
|
675
|
+
literal.add(index);
|
|
676
|
+
}
|
|
677
|
+
for (const m of src.matchAll(ASSET_CALL_RE)) {
|
|
678
|
+
const index = m.index ?? 0;
|
|
679
|
+
if (mask[index] === "a" && !literal.has(index)) {
|
|
680
|
+
throw new Error(`asset() expects a string literal, found a dynamic argument in: ${filePath}`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return refs;
|
|
684
|
+
}, desugarAssetMacros = (src, filePath, found) => {
|
|
685
|
+
const refs = found ?? collectAssetMacros(src, filePath);
|
|
686
|
+
if (refs.length === 0)
|
|
582
687
|
return src;
|
|
688
|
+
const byPath = new Map;
|
|
689
|
+
const parts = [];
|
|
690
|
+
let at = 0;
|
|
691
|
+
for (const ref of refs) {
|
|
692
|
+
let name = byPath.get(ref.path);
|
|
693
|
+
if (!name) {
|
|
694
|
+
name = `__asset_${byPath.size}`;
|
|
695
|
+
byPath.set(ref.path, name);
|
|
696
|
+
}
|
|
697
|
+
parts.push(src.slice(at, ref.index), name);
|
|
698
|
+
at = ref.index + ref.length;
|
|
699
|
+
}
|
|
700
|
+
parts.push(src.slice(at));
|
|
583
701
|
const imports = [...byPath].map(([path, name]) => `import ${name} from ${JSON.stringify(path)};`).join(`
|
|
584
702
|
`);
|
|
585
|
-
return
|
|
703
|
+
return parts.join("") + `
|
|
586
704
|
` + imports;
|
|
587
705
|
};
|
|
588
706
|
var init_assetMacro = __esm(() => {
|
|
589
707
|
ASSET_RE = /(?<![.\w$])asset\(\s*(["'])((?:[^"'\\]|\\.)*)\1\s*\)/g;
|
|
708
|
+
ASSET_CALL_RE = /(?<![.\w$])asset\(/g;
|
|
590
709
|
});
|
|
591
710
|
|
|
592
711
|
// ../sdk/src/compile/aspectMacro.ts
|
|
593
|
-
var
|
|
712
|
+
var HEAD_RE, hasAspectExtends = (src) => /\bextends\s+(?:Aspect|System)\s*</.test(src), injectAspectNames = (src) => {
|
|
594
713
|
let out = "";
|
|
595
714
|
let last = 0;
|
|
596
|
-
|
|
715
|
+
HEAD_RE.lastIndex = 0;
|
|
597
716
|
let m;
|
|
598
|
-
while ((m =
|
|
599
|
-
const braceEnd = m.index + m[0].length;
|
|
717
|
+
while ((m = HEAD_RE.exec(src)) !== null) {
|
|
718
|
+
const braceEnd = classBodyStart(src, m.index + m[0].length);
|
|
719
|
+
if (braceEnd < 0)
|
|
720
|
+
continue;
|
|
721
|
+
HEAD_RE.lastIndex = braceEnd;
|
|
600
722
|
if (!classBodyDeclaresAspect(src, braceEnd)) {
|
|
601
723
|
out += src.slice(last, braceEnd) + ` static aspect = ${JSON.stringify(m[2])};`;
|
|
602
724
|
last = braceEnd;
|
|
603
725
|
}
|
|
604
726
|
}
|
|
605
727
|
return last === 0 ? src : out + src.slice(last);
|
|
728
|
+
}, classBodyStart = (src, from) => {
|
|
729
|
+
let angle = 1;
|
|
730
|
+
let i = from;
|
|
731
|
+
for (;i < src.length && angle > 0; i++) {
|
|
732
|
+
const c2 = src[i];
|
|
733
|
+
if (c2 === '"' || c2 === "'" || c2 === "`") {
|
|
734
|
+
i = skipString(src, i) - 1;
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
if (c2 === "=" && src[i + 1] === ">") {
|
|
738
|
+
i++;
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
if (c2 === "<")
|
|
742
|
+
angle++;
|
|
743
|
+
else if (c2 === ">")
|
|
744
|
+
angle--;
|
|
745
|
+
}
|
|
746
|
+
if (angle !== 0)
|
|
747
|
+
return -1;
|
|
748
|
+
angle = 0;
|
|
749
|
+
for (;i < src.length; i++) {
|
|
750
|
+
const c2 = src[i];
|
|
751
|
+
if (c2 === "<")
|
|
752
|
+
angle++;
|
|
753
|
+
else if (c2 === ">")
|
|
754
|
+
angle--;
|
|
755
|
+
else if (c2 === "{" && angle === 0)
|
|
756
|
+
return i + 1;
|
|
757
|
+
else if (c2 === ";" || c2 === "}")
|
|
758
|
+
return -1;
|
|
759
|
+
}
|
|
760
|
+
return -1;
|
|
761
|
+
}, skipString = (src, at) => {
|
|
762
|
+
const q = src[at];
|
|
763
|
+
let i = at + 1;
|
|
764
|
+
for (;i < src.length; i++) {
|
|
765
|
+
if (src[i] === "\\") {
|
|
766
|
+
i++;
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
if (src[i] === q)
|
|
770
|
+
return i + 1;
|
|
771
|
+
}
|
|
772
|
+
return i;
|
|
606
773
|
}, classBodyDeclaresAspect = (src, from) => {
|
|
607
774
|
let depth = 1;
|
|
608
775
|
let i = from;
|
|
@@ -616,7 +783,7 @@ var ASPECT_RE, hasAspectExtends = (src) => /\bextends\s+Aspect\s*</.test(src), i
|
|
|
616
783
|
return /\bstatic\b[^;{}()=]*\baspect\b/.test(src.slice(from, i));
|
|
617
784
|
};
|
|
618
785
|
var init_aspectMacro = __esm(() => {
|
|
619
|
-
|
|
786
|
+
HEAD_RE = /\bclass\s+\w+\s+extends\s+(?:Aspect|System)\s*<\s*(["'])((?:[^"'\\]|\\.)*)\1/g;
|
|
620
787
|
});
|
|
621
788
|
|
|
622
789
|
// ../sdk/src/compile/libraryImports.ts
|
|
@@ -723,18 +890,37 @@ var resolveInject = () => {
|
|
|
723
890
|
} catch {
|
|
724
891
|
return null;
|
|
725
892
|
}
|
|
893
|
+
}, assertAssetsExist = (refs, files) => {
|
|
894
|
+
const missing = [];
|
|
895
|
+
for (const { file, ref } of refs) {
|
|
896
|
+
if (!ref.path.startsWith("./") && !ref.path.startsWith("../") && !ref.path.startsWith("/"))
|
|
897
|
+
continue;
|
|
898
|
+
const dir = file.slice(0, file.lastIndexOf("/"));
|
|
899
|
+
const base = ref.path.startsWith("/") ? normalize(ref.path) : normalize(`${dir}/${ref.path}`);
|
|
900
|
+
const found = [base, `${base}.ts`, `${base}.js`, `${base}/index.ts`].some((c2) => files[c2] !== undefined);
|
|
901
|
+
if (!found)
|
|
902
|
+
missing.push(`asset not found: ${ref.path} (${file.replace(/^\//, "")}:${ref.line})`);
|
|
903
|
+
}
|
|
904
|
+
if (missing.length > 0)
|
|
905
|
+
throw new Error(missing.join(`
|
|
906
|
+
`));
|
|
726
907
|
}, bundleProjectChisel = async (entrypoint, files, format, sourcemap, minifyOverride, editor, libraries, inject = CLIENT_INJECT) => {
|
|
727
908
|
const entry = normalize(entrypoint);
|
|
728
909
|
const slugs = new Set(Object.keys(libraries ?? {}));
|
|
729
910
|
const projFiles = {};
|
|
911
|
+
const assetRefs = [];
|
|
730
912
|
for (const [k, v] of Object.entries(files)) {
|
|
731
913
|
const path = normalize(k);
|
|
732
914
|
let src = v;
|
|
733
915
|
if (/\.[tj]sx?$/.test(path)) {
|
|
734
916
|
if (hasImportStatement(src))
|
|
735
917
|
src = rewriteLibraryImports(src, slugs);
|
|
736
|
-
if (hasAssetMacro(src))
|
|
737
|
-
|
|
918
|
+
if (hasAssetMacro(src)) {
|
|
919
|
+
const found = collectAssetMacros(src, path);
|
|
920
|
+
for (const ref of found)
|
|
921
|
+
assetRefs.push({ file: path, ref });
|
|
922
|
+
src = desugarAssetMacros(src, path, found);
|
|
923
|
+
}
|
|
738
924
|
if (hasAspectExtends(src))
|
|
739
925
|
src = injectAspectNames(src);
|
|
740
926
|
}
|
|
@@ -747,14 +933,19 @@ var resolveInject = () => {
|
|
|
747
933
|
const path = normalize(`/__lib/${slug}/${k}`);
|
|
748
934
|
let src = v;
|
|
749
935
|
if (/\.[tj]sx?$/.test(path)) {
|
|
750
|
-
if (hasAssetMacro(src))
|
|
751
|
-
|
|
936
|
+
if (hasAssetMacro(src)) {
|
|
937
|
+
const found = collectAssetMacros(src, path);
|
|
938
|
+
for (const ref of found)
|
|
939
|
+
assetRefs.push({ file: path, ref });
|
|
940
|
+
src = desugarAssetMacros(src, path, found);
|
|
941
|
+
}
|
|
752
942
|
if (hasAspectExtends(src))
|
|
753
943
|
src = injectAspectNames(src);
|
|
754
944
|
}
|
|
755
945
|
projFiles[path] = src;
|
|
756
946
|
}
|
|
757
947
|
}
|
|
948
|
+
assertAssetsExist(assetRefs, projFiles);
|
|
758
949
|
const sdkFiles = collectSdkFiles(resolveSdkSrc());
|
|
759
950
|
const minify = minifyOverride ?? process.env.LECODES_CHISEL_MINIFY !== "0";
|
|
760
951
|
const input = JSON.stringify({
|
|
@@ -98214,23 +98405,23 @@ ${lanes.join(`
|
|
|
98214
98405
|
const call = cast(aliasAssignment ? skipOuterExpressions(aliasAssignment.right) : initializer, isCallExpression);
|
|
98215
98406
|
const func = cast(skipOuterExpressions(call.expression), isFunctionExpression);
|
|
98216
98407
|
const funcStatements = func.body.statements;
|
|
98217
|
-
let
|
|
98408
|
+
let classBodyStart2 = 0;
|
|
98218
98409
|
let classBodyEnd = -1;
|
|
98219
98410
|
const statements = [];
|
|
98220
98411
|
if (aliasAssignment) {
|
|
98221
|
-
const extendsCall = tryCast(funcStatements[
|
|
98412
|
+
const extendsCall = tryCast(funcStatements[classBodyStart2], isExpressionStatement);
|
|
98222
98413
|
if (extendsCall) {
|
|
98223
98414
|
statements.push(extendsCall);
|
|
98224
|
-
|
|
98415
|
+
classBodyStart2++;
|
|
98225
98416
|
}
|
|
98226
|
-
statements.push(funcStatements[
|
|
98227
|
-
|
|
98417
|
+
statements.push(funcStatements[classBodyStart2]);
|
|
98418
|
+
classBodyStart2++;
|
|
98228
98419
|
statements.push(factory2.createExpressionStatement(factory2.createAssignment(aliasAssignment.left, cast(variable.name, isIdentifier))));
|
|
98229
98420
|
}
|
|
98230
98421
|
while (!isReturnStatement(elementAt(funcStatements, classBodyEnd))) {
|
|
98231
98422
|
classBodyEnd--;
|
|
98232
98423
|
}
|
|
98233
|
-
addRange(statements, funcStatements,
|
|
98424
|
+
addRange(statements, funcStatements, classBodyStart2, classBodyEnd);
|
|
98234
98425
|
if (classBodyEnd < -1) {
|
|
98235
98426
|
addRange(statements, funcStatements, classBodyEnd + 1);
|
|
98236
98427
|
}
|
|
@@ -171460,11 +171651,11 @@ var init_detectEntry = __esm(() => {
|
|
|
171460
171651
|
});
|
|
171461
171652
|
|
|
171462
171653
|
// ../sdk/src/compile/shaderTargets.ts
|
|
171463
|
-
var externalSamplerReg, PLATFORM_BUILD, matcPlatformsFor = (text) => {
|
|
171654
|
+
var externalSamplerReg, PLATFORM_BUILD, LOCAL_PLATFORM_BUILD, ALL_PLATFORM_BUILD, LOCAL_ONLY_PLATFORMS, TARGET_MATRIX_GENERATION = 1, matcPlatformsFor = (text) => {
|
|
171464
171655
|
externalSamplerReg.lastIndex = 0;
|
|
171465
171656
|
return externalSamplerReg.test(text) ? ["opengl", "metal", "webgl", "desktop"] : ["opengl", "metal", "desktop"];
|
|
171466
171657
|
}, matcArgs = (platform, outPath, inPath) => {
|
|
171467
|
-
const build =
|
|
171658
|
+
const build = ALL_PLATFORM_BUILD[platform];
|
|
171468
171659
|
return ["-p", build.profile, "--no-essl1", "-a", build.api, "-o", outPath, inPath];
|
|
171469
171660
|
};
|
|
171470
171661
|
var init_shaderTargets = __esm(() => {
|
|
@@ -171475,6 +171666,11 @@ var init_shaderTargets = __esm(() => {
|
|
|
171475
171666
|
webgl: { profile: "mobile", api: "opengl", replaceExternal: true },
|
|
171476
171667
|
desktop: { profile: "desktop", api: "opengl", replaceExternal: true }
|
|
171477
171668
|
};
|
|
171669
|
+
LOCAL_PLATFORM_BUILD = {
|
|
171670
|
+
"desktop-vulkan": { profile: "desktop", api: "vulkan", replaceExternal: true }
|
|
171671
|
+
};
|
|
171672
|
+
ALL_PLATFORM_BUILD = { ...PLATFORM_BUILD, ...LOCAL_PLATFORM_BUILD };
|
|
171673
|
+
LOCAL_ONLY_PLATFORMS = ["desktop-vulkan"];
|
|
171478
171674
|
});
|
|
171479
171675
|
|
|
171480
171676
|
// ../sdk/src/compile/liteMaterial.ts
|
|
@@ -171717,6 +171913,191 @@ var init_liteMaterial = __esm(() => {
|
|
|
171717
171913
|
SAMPLER_TYPES = new Set(["sampler2d", "samplerExternal"]);
|
|
171718
171914
|
});
|
|
171719
171915
|
|
|
171916
|
+
// ../sdk/src/compile/shaderSchema.ts
|
|
171917
|
+
var SAMPLER_TYPES2, COLOR_NAME, zeroFor = (type, editor) => {
|
|
171918
|
+
switch (editor) {
|
|
171919
|
+
case "number":
|
|
171920
|
+
return 0;
|
|
171921
|
+
case "int":
|
|
171922
|
+
return 0;
|
|
171923
|
+
case "switch":
|
|
171924
|
+
return false;
|
|
171925
|
+
case "vec2":
|
|
171926
|
+
return [0, 0];
|
|
171927
|
+
case "vec3":
|
|
171928
|
+
return [0, 0, 0];
|
|
171929
|
+
case "vec4":
|
|
171930
|
+
return [0, 0, 0, 0];
|
|
171931
|
+
case "color":
|
|
171932
|
+
return type === "float4" ? "#ffffffff" : "#ffffff";
|
|
171933
|
+
case "texture":
|
|
171934
|
+
return null;
|
|
171935
|
+
default:
|
|
171936
|
+
return null;
|
|
171937
|
+
}
|
|
171938
|
+
}, parseDefault = (raw, editor) => {
|
|
171939
|
+
const s = raw.trim();
|
|
171940
|
+
if (s === "true")
|
|
171941
|
+
return true;
|
|
171942
|
+
if (s === "false")
|
|
171943
|
+
return false;
|
|
171944
|
+
if (/^#[0-9a-f]{6}([0-9a-f]{2})?$/i.test(s))
|
|
171945
|
+
return s.toLowerCase();
|
|
171946
|
+
if (/^-?\d+(\.\d+)?$/.test(s))
|
|
171947
|
+
return editor === "switch" ? Number(s) !== 0 : Number(s);
|
|
171948
|
+
const arr = /^\[(.*)\]$/.exec(s);
|
|
171949
|
+
if (arr) {
|
|
171950
|
+
const nums = arr[1].split(",").map((p) => Number(p.trim()));
|
|
171951
|
+
if (nums.length > 0 && nums.every((n) => Number.isFinite(n)))
|
|
171952
|
+
return nums;
|
|
171953
|
+
}
|
|
171954
|
+
return;
|
|
171955
|
+
}, parseAnnotation = (comment) => {
|
|
171956
|
+
const out = { tooltip: "", errors: [] };
|
|
171957
|
+
let rest = comment;
|
|
171958
|
+
rest = rest.replace(/@(color|vec)\b/g, (_m, tag) => {
|
|
171959
|
+
out[tag] = true;
|
|
171960
|
+
return " ";
|
|
171961
|
+
});
|
|
171962
|
+
rest = rest.replace(/@(range|step|default)\s*\(([^)]*)\)/g, (_m, tag, body) => {
|
|
171963
|
+
if (tag === "range") {
|
|
171964
|
+
const parts = body.split(",").map((p) => Number(p.trim()));
|
|
171965
|
+
if (parts.length === 2 && parts.every((n) => Number.isFinite(n)))
|
|
171966
|
+
out.range = [parts[0], parts[1]];
|
|
171967
|
+
else
|
|
171968
|
+
out.errors.push(`@range expects two numbers, got "${body.trim()}"`);
|
|
171969
|
+
} else if (tag === "step") {
|
|
171970
|
+
const n = Number(body.trim());
|
|
171971
|
+
if (Number.isFinite(n) && n > 0)
|
|
171972
|
+
out.step = n;
|
|
171973
|
+
else
|
|
171974
|
+
out.errors.push(`@step expects a positive number, got "${body.trim()}"`);
|
|
171975
|
+
} else {
|
|
171976
|
+
out.default = body;
|
|
171977
|
+
}
|
|
171978
|
+
return " ";
|
|
171979
|
+
});
|
|
171980
|
+
rest = rest.replace(/@\w+(\([^)]*\))?/g, " ");
|
|
171981
|
+
out.tooltip = rest.replace(/\s+/g, " ").trim();
|
|
171982
|
+
return out;
|
|
171983
|
+
}, collectComments = (materialBlock) => {
|
|
171984
|
+
const out = new Map;
|
|
171985
|
+
const start = materialBlock.search(/\bparameters\s*[:=]/);
|
|
171986
|
+
if (start < 0)
|
|
171987
|
+
return out;
|
|
171988
|
+
const lines = materialBlock.slice(start).split(`
|
|
171989
|
+
`);
|
|
171990
|
+
let above = [];
|
|
171991
|
+
for (const line of lines) {
|
|
171992
|
+
const trimmed = line.trim();
|
|
171993
|
+
const name = /\bname\s*[:=]\s*"?([A-Za-z_]\w*)"?/.exec(line)?.[1];
|
|
171994
|
+
if (name) {
|
|
171995
|
+
const trailing = /\}[^/\n]*\/\/(.*)$/.exec(line)?.[1] ?? /\/\/(.*)$/.exec(line.slice(line.indexOf("}") + 1))?.[1];
|
|
171996
|
+
const text = trailing !== undefined ? trailing : above.join(" ");
|
|
171997
|
+
if (text.trim() !== "")
|
|
171998
|
+
out.set(name, text.trim());
|
|
171999
|
+
above = [];
|
|
172000
|
+
} else if (trimmed.startsWith("//")) {
|
|
172001
|
+
above.push(trimmed.slice(2).trim());
|
|
172002
|
+
} else if (trimmed !== "" && trimmed !== "[" && trimmed !== "],") {
|
|
172003
|
+
above = [];
|
|
172004
|
+
}
|
|
172005
|
+
}
|
|
172006
|
+
return out;
|
|
172007
|
+
}, parseShaderSchema = (text) => {
|
|
172008
|
+
const schema = { name: "", shadingModel: "lit", blending: "opaque", params: [], errors: [] };
|
|
172009
|
+
let block;
|
|
172010
|
+
let def;
|
|
172011
|
+
try {
|
|
172012
|
+
block = extractMatBlocks(text).material;
|
|
172013
|
+
if (block === undefined) {
|
|
172014
|
+
schema.errors.push("no material block");
|
|
172015
|
+
return schema;
|
|
172016
|
+
}
|
|
172017
|
+
def = parseJsonish("{" + block + "}");
|
|
172018
|
+
} catch (e) {
|
|
172019
|
+
schema.errors.push(e instanceof Error ? e.message : String(e));
|
|
172020
|
+
return schema;
|
|
172021
|
+
}
|
|
172022
|
+
schema.name = typeof def.name === "string" ? def.name : "";
|
|
172023
|
+
schema.shadingModel = typeof def.shadingModel === "string" ? def.shadingModel : "lit";
|
|
172024
|
+
schema.blending = typeof def.blending === "string" ? def.blending : "opaque";
|
|
172025
|
+
const comments = collectComments(block);
|
|
172026
|
+
const rawParams = def.parameters === undefined ? [] : Array.isArray(def.parameters) ? def.parameters : [def.parameters];
|
|
172027
|
+
for (const raw of rawParams) {
|
|
172028
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
172029
|
+
schema.errors.push("malformed parameter entry");
|
|
172030
|
+
continue;
|
|
172031
|
+
}
|
|
172032
|
+
const entry = raw;
|
|
172033
|
+
const name = String(entry.name ?? "");
|
|
172034
|
+
const typeRaw = String(entry.type ?? "");
|
|
172035
|
+
const typeMatch = /^(\w+)(?:\[(\d+)\])?$/.exec(typeRaw);
|
|
172036
|
+
if (!name || !typeMatch) {
|
|
172037
|
+
schema.errors.push(`malformed parameter "${name || typeRaw}"`);
|
|
172038
|
+
continue;
|
|
172039
|
+
}
|
|
172040
|
+
const type = typeMatch[1];
|
|
172041
|
+
const size = typeMatch[2] ? Number(typeMatch[2]) : undefined;
|
|
172042
|
+
const ann = parseAnnotation(comments.get(name) ?? "");
|
|
172043
|
+
for (const e of ann.errors)
|
|
172044
|
+
schema.errors.push(`${name}: ${e}`);
|
|
172045
|
+
let editor;
|
|
172046
|
+
if (size !== undefined)
|
|
172047
|
+
editor = "array";
|
|
172048
|
+
else if (SAMPLER_TYPES2.has(type))
|
|
172049
|
+
editor = "texture";
|
|
172050
|
+
else if (type === "float")
|
|
172051
|
+
editor = "number";
|
|
172052
|
+
else if (type === "int" || type === "uint")
|
|
172053
|
+
editor = "int";
|
|
172054
|
+
else if (type === "bool")
|
|
172055
|
+
editor = "switch";
|
|
172056
|
+
else if (type === "float2")
|
|
172057
|
+
editor = "vec2";
|
|
172058
|
+
else if (type === "float3" || type === "float4") {
|
|
172059
|
+
const isColor = ann.color === true || ann.vec !== true && COLOR_NAME.test(name);
|
|
172060
|
+
editor = isColor ? "color" : type === "float3" ? "vec3" : "vec4";
|
|
172061
|
+
} else if (/^(int|uint|bool)[234]$/.test(type))
|
|
172062
|
+
editor = `vec${type.slice(-1)}`;
|
|
172063
|
+
else if (/^float[234]x[234]$/.test(type))
|
|
172064
|
+
editor = "matrix";
|
|
172065
|
+
else
|
|
172066
|
+
editor = "array";
|
|
172067
|
+
const param = { name, type, editor, default: zeroFor(type, editor) };
|
|
172068
|
+
if (size !== undefined)
|
|
172069
|
+
param.size = size;
|
|
172070
|
+
if (ann.range) {
|
|
172071
|
+
param.min = ann.range[0];
|
|
172072
|
+
param.max = ann.range[1];
|
|
172073
|
+
}
|
|
172074
|
+
if (ann.step !== undefined)
|
|
172075
|
+
param.step = ann.step;
|
|
172076
|
+
if (ann.default !== undefined) {
|
|
172077
|
+
const v = parseDefault(ann.default, editor);
|
|
172078
|
+
if (v === undefined)
|
|
172079
|
+
schema.errors.push(`${name}: unreadable @default(${ann.default.trim()})`);
|
|
172080
|
+
else
|
|
172081
|
+
param.default = v;
|
|
172082
|
+
}
|
|
172083
|
+
if (ann.tooltip)
|
|
172084
|
+
param.tooltip = ann.tooltip;
|
|
172085
|
+
schema.params.push(param);
|
|
172086
|
+
}
|
|
172087
|
+
return schema;
|
|
172088
|
+
}, shaderDefaults = (schema) => {
|
|
172089
|
+
const out = {};
|
|
172090
|
+
for (const p of schema.params)
|
|
172091
|
+
if (p.editor !== "texture" && p.default !== null)
|
|
172092
|
+
out[p.name] = p.default;
|
|
172093
|
+
return out;
|
|
172094
|
+
};
|
|
172095
|
+
var init_shaderSchema = __esm(() => {
|
|
172096
|
+
init_liteMaterial();
|
|
172097
|
+
SAMPLER_TYPES2 = new Set(["sampler2d", "samplerExternal", "sampler2dArray", "samplerCubemap", "sampler3d"]);
|
|
172098
|
+
COLOR_NAME = /colou?r|tint|albedo|emissive|glare|glow/i;
|
|
172099
|
+
});
|
|
172100
|
+
|
|
171720
172101
|
// ../sdk/src/compile/sourcemap.ts
|
|
171721
172102
|
var offsetSourceMap = (mapJson, lines) => {
|
|
171722
172103
|
if (lines <= 0)
|
|
@@ -171865,13 +172246,18 @@ export {}
|
|
|
171865
172246
|
});
|
|
171866
172247
|
const editorImports = projectPaths.filter((p) => p.endsWith(".editor.ts")).map((p) => `import "${p}"
|
|
171867
172248
|
`).join("");
|
|
172249
|
+
const materialPaths = projectPaths.filter((p) => p.endsWith(".material.ts"));
|
|
172250
|
+
const materialImports = materialPaths.map((p, i) => `import __mat${i} from "${p}"
|
|
172251
|
+
`).join("");
|
|
172252
|
+
const materialRegister = materialPaths.length === 0 ? "" : `;(globalThis as any).__lecodesMaterials = { ${materialPaths.map((p, i) => `${JSON.stringify(p)}: __mat${i}`).join(", ")} }
|
|
172253
|
+
`;
|
|
171868
172254
|
entries.push({
|
|
171869
172255
|
path: SCENE_ENTRY_PATH,
|
|
171870
172256
|
type: "text",
|
|
171871
172257
|
text: `import "${SCENE_HARNESS_DIR}/.edit-mode.ts"
|
|
171872
|
-
` + `import "${scenePath}"
|
|
172258
|
+
` + materialImports + `import "${scenePath}"
|
|
171873
172259
|
` + editorImports + `import "${SCENE_HARNESS_DIR}/main.ts"
|
|
171874
|
-
`
|
|
172260
|
+
` + materialRegister
|
|
171875
172261
|
});
|
|
171876
172262
|
return entries;
|
|
171877
172263
|
};
|
|
@@ -172002,7 +172388,7 @@ var TAG_TTF = 65536, TAG_OTTO = 1330926671, TAG_TTCF = 1953784678, TAG_WOFF = 20
|
|
|
172002
172388
|
};
|
|
172003
172389
|
|
|
172004
172390
|
// ../sdk/src/compile/fontMacro.ts
|
|
172005
|
-
var FONT_ID, CODE_FILE, DECLARES_FONT, isIdentStart = (c2) => c2 >= "A" && c2 <= "Z" || c2 >= "a" && c2 <= "z" || c2 === "_" || c2 === "$", isIdentPart = (c2) => isIdentStart(c2) || c2 >= "0" && c2 <= "9",
|
|
172391
|
+
var FONT_ID, CODE_FILE, DECLARES_FONT, isIdentStart = (c2) => c2 >= "A" && c2 <= "Z" || c2 >= "a" && c2 <= "z" || c2 === "_" || c2 === "$", isIdentPart = (c2) => isIdentStart(c2) || c2 >= "0" && c2 <= "9", skipString2 = (src, start) => {
|
|
172006
172392
|
const quote = src[start];
|
|
172007
172393
|
const n = src.length;
|
|
172008
172394
|
let i = start + 1;
|
|
@@ -172028,7 +172414,7 @@ var FONT_ID, CODE_FILE, DECLARES_FONT, isIdentStart = (c2) => c2 >= "A" && c2 <=
|
|
|
172028
172414
|
while (i < n) {
|
|
172029
172415
|
const c2 = src[i];
|
|
172030
172416
|
if (c2 === '"' || c2 === "'" || c2 === "`") {
|
|
172031
|
-
i =
|
|
172417
|
+
i = skipString2(src, i);
|
|
172032
172418
|
continue;
|
|
172033
172419
|
}
|
|
172034
172420
|
if (c2 === "{")
|
|
@@ -172048,7 +172434,7 @@ var FONT_ID, CODE_FILE, DECLARES_FONT, isIdentStart = (c2) => c2 >= "A" && c2 <=
|
|
|
172048
172434
|
while (i < n) {
|
|
172049
172435
|
const c2 = src[i];
|
|
172050
172436
|
if (c2 === '"' || c2 === "'" || c2 === "`") {
|
|
172051
|
-
i =
|
|
172437
|
+
i = skipString2(src, i);
|
|
172052
172438
|
continue;
|
|
172053
172439
|
}
|
|
172054
172440
|
if (c2 === "/" && src[i + 1] === "/") {
|
|
@@ -172084,7 +172470,7 @@ var FONT_ID, CODE_FILE, DECLARES_FONT, isIdentStart = (c2) => c2 >= "A" && c2 <=
|
|
|
172084
172470
|
while (i < n) {
|
|
172085
172471
|
const c2 = text[i];
|
|
172086
172472
|
if (c2 === '"' || c2 === "'" || c2 === "`") {
|
|
172087
|
-
i =
|
|
172473
|
+
i = skipString2(text, i);
|
|
172088
172474
|
continue;
|
|
172089
172475
|
}
|
|
172090
172476
|
if (c2 === "(" || c2 === "[" || c2 === "{")
|
|
@@ -172124,7 +172510,7 @@ var FONT_ID, CODE_FILE, DECLARES_FONT, isIdentStart = (c2) => c2 >= "A" && c2 <=
|
|
|
172124
172510
|
while (i < n) {
|
|
172125
172511
|
const c2 = source[i];
|
|
172126
172512
|
if (c2 === '"' || c2 === "'" || c2 === "`") {
|
|
172127
|
-
i =
|
|
172513
|
+
i = skipString2(source, i);
|
|
172128
172514
|
continue;
|
|
172129
172515
|
}
|
|
172130
172516
|
if (c2 === "/" && source[i + 1] === "/") {
|
|
@@ -172412,7 +172798,7 @@ var init_fontMacro = __esm(() => {
|
|
|
172412
172798
|
});
|
|
172413
172799
|
|
|
172414
172800
|
// ../sdk/src/compile/assetIconMacro.ts
|
|
172415
|
-
var ICON_ID, isIdentStart2 = (c2) => c2 >= "A" && c2 <= "Z" || c2 >= "a" && c2 <= "z" || c2 === "_" || c2 === "$", isIdentPart2 = (c2) => isIdentStart2(c2) || c2 >= "0" && c2 <= "9",
|
|
172801
|
+
var ICON_ID, isIdentStart2 = (c2) => c2 >= "A" && c2 <= "Z" || c2 >= "a" && c2 <= "z" || c2 === "_" || c2 === "$", isIdentPart2 = (c2) => isIdentStart2(c2) || c2 >= "0" && c2 <= "9", skipString3 = (src, start) => {
|
|
172416
172802
|
const quote = src[start];
|
|
172417
172803
|
const n = src.length;
|
|
172418
172804
|
let i = start + 1;
|
|
@@ -172438,7 +172824,7 @@ var ICON_ID, isIdentStart2 = (c2) => c2 >= "A" && c2 <= "Z" || c2 >= "a" && c2 <
|
|
|
172438
172824
|
while (i < n) {
|
|
172439
172825
|
const c2 = src[i];
|
|
172440
172826
|
if (c2 === '"' || c2 === "'" || c2 === "`") {
|
|
172441
|
-
i =
|
|
172827
|
+
i = skipString3(src, i);
|
|
172442
172828
|
continue;
|
|
172443
172829
|
}
|
|
172444
172830
|
if (c2 === "{")
|
|
@@ -172458,7 +172844,7 @@ var ICON_ID, isIdentStart2 = (c2) => c2 >= "A" && c2 <= "Z" || c2 >= "a" && c2 <
|
|
|
172458
172844
|
while (i < n) {
|
|
172459
172845
|
const c2 = src[i];
|
|
172460
172846
|
if (c2 === '"' || c2 === "'" || c2 === "`") {
|
|
172461
|
-
i =
|
|
172847
|
+
i = skipString3(src, i);
|
|
172462
172848
|
continue;
|
|
172463
172849
|
}
|
|
172464
172850
|
if (c2 === "/" && src[i + 1] === "/") {
|
|
@@ -172494,7 +172880,7 @@ var ICON_ID, isIdentStart2 = (c2) => c2 >= "A" && c2 <= "Z" || c2 >= "a" && c2 <
|
|
|
172494
172880
|
while (i < n) {
|
|
172495
172881
|
const c2 = text[i];
|
|
172496
172882
|
if (c2 === '"' || c2 === "'" || c2 === "`") {
|
|
172497
|
-
i =
|
|
172883
|
+
i = skipString3(text, i);
|
|
172498
172884
|
continue;
|
|
172499
172885
|
}
|
|
172500
172886
|
if (c2 === "(" || c2 === "[" || c2 === "{")
|
|
@@ -172529,7 +172915,7 @@ var ICON_ID, isIdentStart2 = (c2) => c2 >= "A" && c2 <= "Z" || c2 >= "a" && c2 <
|
|
|
172529
172915
|
while (i < n) {
|
|
172530
172916
|
const c2 = source[i];
|
|
172531
172917
|
if (c2 === '"' || c2 === "'" || c2 === "`") {
|
|
172532
|
-
i =
|
|
172918
|
+
i = skipString3(source, i);
|
|
172533
172919
|
continue;
|
|
172534
172920
|
}
|
|
172535
172921
|
if (c2 === "/" && source[i + 1] === "/") {
|
|
@@ -172731,6 +173117,7 @@ var exports_compile = {};
|
|
|
172731
173117
|
__export(exports_compile, {
|
|
172732
173118
|
suggestIconNames: () => suggestIconNames,
|
|
172733
173119
|
stubServerEntries: () => stubServerEntries,
|
|
173120
|
+
shaderDefaults: () => shaderDefaults,
|
|
172734
173121
|
serverStubSource: () => serverStubSource,
|
|
172735
173122
|
serverModuleId: () => serverModuleId,
|
|
172736
173123
|
serverEntrySource: () => serverEntrySource,
|
|
@@ -172743,6 +173130,7 @@ __export(exports_compile, {
|
|
|
172743
173130
|
scanAssetIconCalls: () => scanAssetIconCalls,
|
|
172744
173131
|
sanitizeAssetName: () => sanitizeAssetName,
|
|
172745
173132
|
rewriteAssetIcons: () => rewriteAssetIcons,
|
|
173133
|
+
parseShaderSchema: () => parseShaderSchema,
|
|
172746
173134
|
parseSfnt: () => parseSfnt,
|
|
172747
173135
|
parseJsonish: () => parseJsonish,
|
|
172748
173136
|
offsetSourceMap: () => offsetSourceMap,
|
|
@@ -172771,17 +173159,21 @@ __export(exports_compile, {
|
|
|
172771
173159
|
buildFontsHeaderValue: () => buildFontsHeaderValue,
|
|
172772
173160
|
applyFontMacro: () => applyFontMacro,
|
|
172773
173161
|
applyAssetIconMacro: () => applyAssetIconMacro,
|
|
173162
|
+
TARGET_MATRIX_GENERATION: () => TARGET_MATRIX_GENERATION,
|
|
172774
173163
|
SERVER_INJECT: () => SERVER_INJECT,
|
|
172775
173164
|
SERVER_ENTRY_PATH: () => SERVER_ENTRY_PATH,
|
|
172776
173165
|
SCENE_HARNESS_DIR: () => SCENE_HARNESS_DIR,
|
|
172777
173166
|
SCENE_ENTRY_PATH: () => SCENE_ENTRY_PATH,
|
|
172778
173167
|
PLATFORM_BUILD: () => PLATFORM_BUILD,
|
|
173168
|
+
LOCAL_PLATFORM_BUILD: () => LOCAL_PLATFORM_BUILD,
|
|
173169
|
+
LOCAL_ONLY_PLATFORMS: () => LOCAL_ONLY_PLATFORMS,
|
|
172779
173170
|
ICON_ID: () => ICON_ID,
|
|
172780
173171
|
FONT_REGISTRY: () => FONT_REGISTRY,
|
|
172781
173172
|
EndpointTypeError: () => EndpointTypeError,
|
|
172782
173173
|
ENTRY_CONVENTION: () => ENTRY_CONVENTION,
|
|
172783
173174
|
DEFAULT_FONT_REGISTRY_BASE: () => DEFAULT_FONT_REGISTRY_BASE,
|
|
172784
|
-
CLIENT_INJECT: () => CLIENT_INJECT
|
|
173175
|
+
CLIENT_INJECT: () => CLIENT_INJECT,
|
|
173176
|
+
ALL_PLATFORM_BUILD: () => ALL_PLATFORM_BUILD
|
|
172785
173177
|
});
|
|
172786
173178
|
var init_compile = __esm(() => {
|
|
172787
173179
|
init_bundler();
|
|
@@ -172791,6 +173183,7 @@ var init_compile = __esm(() => {
|
|
|
172791
173183
|
init_assetName();
|
|
172792
173184
|
init_shaderTargets();
|
|
172793
173185
|
init_liteMaterial();
|
|
173186
|
+
init_shaderSchema();
|
|
172794
173187
|
init_compileProject();
|
|
172795
173188
|
init_sceneEditor();
|
|
172796
173189
|
init_fontMacro();
|
|
@@ -214358,6 +214751,10 @@ lecodes design snapshot <id> --state empty # one state → design/.snapshots/<i
|
|
|
214358
214751
|
lecodes design snapshot <id> --all-states # every state → design/.snapshots/<id>@<state>.png|.json
|
|
214359
214752
|
lecodes design snapshot # all screens → design/.snapshots/
|
|
214360
214753
|
lecodes design check # concept + flow coherence (spec.md links, states, untagged)
|
|
214754
|
+
|
|
214755
|
+
lecodes render <id> [--state <s>] # the same screen through the full render CLI, when you
|
|
214756
|
+
lecodes render <id> --png card.png --clip <name> # need one element cropped out, a device's
|
|
214757
|
+
lecodes render <id> --device iphone --png x.png # real safe-area insets, or a --script run
|
|
214361
214758
|
\`\`\`
|
|
214362
214759
|
|
|
214363
214760
|
Either way: after every screen you create or change, look at it and fix what's wrong (overflow,
|
|
@@ -214433,14 +214830,37 @@ var init_designTemplates = __esm(() => {
|
|
|
214433
214830
|
init_templates();
|
|
214434
214831
|
});
|
|
214435
214832
|
|
|
214833
|
+
// src/designMeta.ts
|
|
214834
|
+
import { readFileSync as readFileSync19 } from "node:fs";
|
|
214835
|
+
import { join as join23 } from "node:path";
|
|
214836
|
+
var PHONE_SIZE, readMetaFrames = (designDir) => {
|
|
214837
|
+
try {
|
|
214838
|
+
const meta = JSON.parse(readFileSync19(join23(designDir, "meta.json"), "utf8"));
|
|
214839
|
+
const d = meta?.defaultSize;
|
|
214840
|
+
const valid = Array.isArray(d) && d.length === 2 && d.every((n2) => typeof n2 === "number" && Number.isFinite(n2) && n2 >= 64 && n2 <= 8192);
|
|
214841
|
+
return {
|
|
214842
|
+
screens: meta?.screens ?? {},
|
|
214843
|
+
defaultSize: valid ? [Math.round(d[0]), Math.round(d[1])] : PHONE_SIZE
|
|
214844
|
+
};
|
|
214845
|
+
} catch {
|
|
214846
|
+
return { screens: {}, defaultSize: PHONE_SIZE };
|
|
214847
|
+
}
|
|
214848
|
+
}, screenFrame = (designDir, screenId) => {
|
|
214849
|
+
const frames = readMetaFrames(designDir);
|
|
214850
|
+
return frames.screens[screenId]?.size ?? frames.defaultSize;
|
|
214851
|
+
};
|
|
214852
|
+
var init_designMeta = __esm(() => {
|
|
214853
|
+
PHONE_SIZE = [390, 844];
|
|
214854
|
+
});
|
|
214855
|
+
|
|
214436
214856
|
// src/commands/design.ts
|
|
214437
214857
|
var exports_design = {};
|
|
214438
214858
|
__export(exports_design, {
|
|
214439
214859
|
flushDesignComments: () => flushDesignComments,
|
|
214440
214860
|
design: () => design
|
|
214441
214861
|
});
|
|
214442
|
-
import { existsSync as existsSync23, mkdirSync as mkdirSync17, readFileSync as
|
|
214443
|
-
import { basename as basename9, join as
|
|
214862
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync17, readFileSync as readFileSync20, readdirSync as readdirSync12, writeFileSync as writeFileSync18, appendFileSync } from "node:fs";
|
|
214863
|
+
import { basename as basename9, join as join24 } from "node:path";
|
|
214444
214864
|
var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
214445
214865
|
let root;
|
|
214446
214866
|
let manifest;
|
|
@@ -214457,7 +214877,7 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
214457
214877
|
manifest,
|
|
214458
214878
|
name: manifest?.name ?? basename9(root),
|
|
214459
214879
|
dirName,
|
|
214460
|
-
designDir:
|
|
214880
|
+
designDir: join24(root, dirName),
|
|
214461
214881
|
designKey: "/" + dirName.replace(/\\/g, "/")
|
|
214462
214882
|
};
|
|
214463
214883
|
}, loadDesignServer = async (ctx, needs = []) => {
|
|
@@ -214493,29 +214913,17 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
214493
214913
|
`).find((l2) => l2.trim())?.trim() ?? "";
|
|
214494
214914
|
return line.length > max ? line.slice(0, max - 1) + "…" : line;
|
|
214495
214915
|
}, listScreens = (designDir) => {
|
|
214496
|
-
const dir =
|
|
214916
|
+
const dir = join24(designDir, "screens");
|
|
214497
214917
|
if (!existsSync23(dir))
|
|
214498
214918
|
return [];
|
|
214499
214919
|
return readdirSync12(dir).filter((f3) => f3.endsWith(".ts") && !f3.startsWith(".")).map((f3) => f3.slice(0, -3)).sort();
|
|
214500
|
-
}, PHONE_SIZE, readMetaFrames = (designDir) => {
|
|
214501
|
-
try {
|
|
214502
|
-
const meta = JSON.parse(readFileSync19(join23(designDir, "meta.json"), "utf8"));
|
|
214503
|
-
const d = meta?.defaultSize;
|
|
214504
|
-
const valid = Array.isArray(d) && d.length === 2 && d.every((n2) => typeof n2 === "number" && Number.isFinite(n2) && n2 >= 64 && n2 <= 8192);
|
|
214505
|
-
return {
|
|
214506
|
-
screens: meta?.screens ?? {},
|
|
214507
|
-
defaultSize: valid ? [Math.round(d[0]), Math.round(d[1])] : PHONE_SIZE
|
|
214508
|
-
};
|
|
214509
|
-
} catch {
|
|
214510
|
-
return { screens: {}, defaultSize: PHONE_SIZE };
|
|
214511
|
-
}
|
|
214512
214920
|
}, writeMcpConfig = (ctx, port) => {
|
|
214513
|
-
const path2 =
|
|
214921
|
+
const path2 = join24(ctx.root, ".mcp.json");
|
|
214514
214922
|
const entry = { type: "http", url: `http://localhost:${port}/mcp` };
|
|
214515
214923
|
let config = {};
|
|
214516
214924
|
if (existsSync23(path2)) {
|
|
214517
214925
|
try {
|
|
214518
|
-
config = JSON.parse(
|
|
214926
|
+
config = JSON.parse(readFileSync20(path2, "utf8"));
|
|
214519
214927
|
} catch {
|
|
214520
214928
|
warnErr(".mcp.json exists but isn't valid JSON — left it untouched. Add the lecodes-design server by hand.");
|
|
214521
214929
|
return;
|
|
@@ -214537,15 +214945,15 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
214537
214945
|
["spec.md", SPEC_MD],
|
|
214538
214946
|
["README.md", README_HINT],
|
|
214539
214947
|
["CLAUDE.md", CLAUDE_MD],
|
|
214540
|
-
[
|
|
214541
|
-
[
|
|
214542
|
-
[
|
|
214948
|
+
[join24("shared", "tokens.ts"), TOKENS_TS],
|
|
214949
|
+
[join24("shared", "tabs.ts"), TABS_TS],
|
|
214950
|
+
[join24("screens", "home.ts"), HOME_SCREEN_TS]
|
|
214543
214951
|
];
|
|
214544
|
-
mkdirSync17(
|
|
214545
|
-
mkdirSync17(
|
|
214952
|
+
mkdirSync17(join24(ctx.designDir, "screens"), { recursive: true });
|
|
214953
|
+
mkdirSync17(join24(ctx.designDir, "shared"), { recursive: true });
|
|
214546
214954
|
let created = 0;
|
|
214547
214955
|
for (const [rel, content] of files) {
|
|
214548
|
-
const abs =
|
|
214956
|
+
const abs = join24(ctx.designDir, rel);
|
|
214549
214957
|
if (existsSync23(abs)) {
|
|
214550
214958
|
note(`kept ${ctx.dirName}/${rel.replace(/\\/g, "/")}`);
|
|
214551
214959
|
continue;
|
|
@@ -214554,9 +214962,9 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
214554
214962
|
log(`${c.green("created")} ${ctx.dirName}/${rel.replace(/\\/g, "/")}`);
|
|
214555
214963
|
created++;
|
|
214556
214964
|
}
|
|
214557
|
-
const ignorePath =
|
|
214965
|
+
const ignorePath = join24(ctx.root, ".lecodesignore");
|
|
214558
214966
|
const ignoreLines = [`${ctx.dirName}/.snapshots/`, `${ctx.dirName}/comments/`];
|
|
214559
|
-
const current = existsSync23(ignorePath) ?
|
|
214967
|
+
const current = existsSync23(ignorePath) ? readFileSync20(ignorePath, "utf8") : "";
|
|
214560
214968
|
const present = new Set(current.split(/\r?\n/));
|
|
214561
214969
|
const missing = ignoreLines.filter((line) => !present.has(line));
|
|
214562
214970
|
if (missing.length) {
|
|
@@ -214571,8 +214979,8 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
214571
214979
|
writeMcpConfig(ctx, Number(flagStr(args, "port")) || DEFAULT_PORT);
|
|
214572
214980
|
const wroteTypes = materializeTypesLocal(ctx.designDir);
|
|
214573
214981
|
writeTsconfig(ctx.designDir);
|
|
214574
|
-
mkdirSync17(
|
|
214575
|
-
writeFileSync18(
|
|
214982
|
+
mkdirSync17(join24(ctx.designDir, ".lecodes", "types"), { recursive: true });
|
|
214983
|
+
writeFileSync18(join24(ctx.designDir, ".lecodes", "types", "design-macros.d.ts"), DESIGN_MACROS_DTS);
|
|
214576
214984
|
log(`${c.green("created")} ${ctx.dirName}/tsconfig.json${wroteTypes ? ` + ${ctx.dirName}/.lecodes/types/` : ""} (IDE types)`);
|
|
214577
214985
|
if (!wroteTypes)
|
|
214578
214986
|
warnErr("Bundled SDK types not found — tsconfig written, but globals may show unresolved until the CLI is rebuilt with vendored types.");
|
|
@@ -214620,7 +215028,7 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
214620
215028
|
killer.unref?.();
|
|
214621
215029
|
const png2 = flagBool(args, "png") || flagStr(args, "png") !== undefined;
|
|
214622
215030
|
const frames = readMetaFrames(ctx.designDir);
|
|
214623
|
-
const outDir = flagStr(args, "out-dir") ??
|
|
215031
|
+
const outDir = flagStr(args, "out-dir") ?? join24(ctx.designDir, ".snapshots");
|
|
214624
215032
|
const single = targets.length === 1 && requested.length === 1 && !png2 && !allStates && !flagStr(args, "out-dir");
|
|
214625
215033
|
const onConsole = flagBool(args, "logs") ? (level, text) => logErr(`${c.dim(`[${level}]`)} ${text}`) : undefined;
|
|
214626
215034
|
if (!single)
|
|
@@ -214629,7 +215037,7 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
214629
215037
|
const [width, height] = frames.screens[id]?.size ?? frames.defaultSize;
|
|
214630
215038
|
let plan = [{ state: undefined, bases: [id] }];
|
|
214631
215039
|
if (discover) {
|
|
214632
|
-
const { states, canonical } = discover(
|
|
215040
|
+
const { states, canonical } = discover(readFileSync20(join24(ctx.designDir, "screens", `${id}.ts`), "utf8"));
|
|
214633
215041
|
if (stateFlag !== undefined) {
|
|
214634
215042
|
if (!states.includes(stateFlag)) {
|
|
214635
215043
|
if (requested.length === 1)
|
|
@@ -214670,11 +215078,11 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
214670
215078
|
for (const w of pngResult.warnings)
|
|
214671
215079
|
warnErr(`${tag}: ${w}`);
|
|
214672
215080
|
for (const base of bases) {
|
|
214673
|
-
writeFileSync18(
|
|
214674
|
-
log(`${c.green("✓")} ${tag} → ${
|
|
215081
|
+
writeFileSync18(join24(outDir, `${base}.json`), result.text);
|
|
215082
|
+
log(`${c.green("✓")} ${tag} → ${join24(outDir, `${base}.json`)}`);
|
|
214675
215083
|
if (pngResult) {
|
|
214676
|
-
writeFileSync18(
|
|
214677
|
-
log(`${c.green("✓")} ${tag} → ${
|
|
215084
|
+
writeFileSync18(join24(outDir, `${base}.png`), pngResult.png);
|
|
215085
|
+
log(`${c.green("✓")} ${tag} → ${join24(outDir, `${base}.png`)}`);
|
|
214678
215086
|
}
|
|
214679
215087
|
}
|
|
214680
215088
|
}
|
|
@@ -215096,7 +215504,7 @@ var init_design = __esm(() => {
|
|
|
215096
215504
|
init_config();
|
|
215097
215505
|
init_api();
|
|
215098
215506
|
init_types4();
|
|
215099
|
-
|
|
215507
|
+
init_designMeta();
|
|
215100
215508
|
});
|
|
215101
215509
|
|
|
215102
215510
|
// ../../node_modules/.bun/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRMode.js
|
|
@@ -216118,9 +216526,9 @@ var require_main = __commonJS((exports, module) => {
|
|
|
216118
216526
|
// package.json
|
|
216119
216527
|
var package_default = {
|
|
216120
216528
|
name: "lecodes-cli",
|
|
216121
|
-
version: "0.18.
|
|
216529
|
+
version: "0.18.1",
|
|
216122
216530
|
dependencies: {
|
|
216123
|
-
"@letary/chisel": "^0.
|
|
216531
|
+
"@letary/chisel": "^0.10.0",
|
|
216124
216532
|
jimp: "^1.6.1"
|
|
216125
216533
|
},
|
|
216126
216534
|
devDependencies: {
|
|
@@ -216134,8 +216542,8 @@ var package_default = {
|
|
|
216134
216542
|
},
|
|
216135
216543
|
peerDependencies: {
|
|
216136
216544
|
"lecodes-design": "^0.8.1",
|
|
216137
|
-
"lecodes-renderer": "^0.8.
|
|
216138
|
-
"lecodes-3d-editor": "^0.
|
|
216545
|
+
"lecodes-renderer": "^0.8.9",
|
|
216546
|
+
"lecodes-3d-editor": "^0.5.0",
|
|
216139
216547
|
"lecodes-assets": "^0.1.0"
|
|
216140
216548
|
},
|
|
216141
216549
|
bin: {
|
|
@@ -216486,15 +216894,27 @@ var matcIdentity = (exe) => {
|
|
|
216486
216894
|
const stat = statSync2(exe);
|
|
216487
216895
|
return `${stat.size}:${Math.trunc(stat.mtimeMs)}`;
|
|
216488
216896
|
};
|
|
216489
|
-
var shaderHash = (text, matcId) => createHash4("sha256").update(matcId
|
|
216897
|
+
var shaderHash = (text, matcId) => createHash4("sha256").update(`${TARGET_MATRIX_GENERATION}\x00${matcId}\x00${text}`).digest("hex").slice(0, 16);
|
|
216490
216898
|
var artifactFamily = (text) => {
|
|
216491
|
-
const matcPlatforms = matcPlatformsFor(text);
|
|
216899
|
+
const matcPlatforms = [...matcPlatformsFor(text), ...LOCAL_ONLY_PLATFORMS];
|
|
216492
216900
|
return [...matcPlatforms, ...matcPlatforms.includes("webgl") ? [] : ["webgl"], "webgl-lite"];
|
|
216493
216901
|
};
|
|
216494
216902
|
var shaderIsCompiled = (root, text, matcId) => {
|
|
216495
216903
|
const hash = shaderHash(text, matcId);
|
|
216496
216904
|
return artifactFamily(text).every((p) => existsSync11(join13(shadersCacheDir(root), `${hash}_${p}.filamat`)));
|
|
216497
216905
|
};
|
|
216906
|
+
var shaderCompiledPlatforms = (root, text, matcId) => {
|
|
216907
|
+
const hash = shaderHash(text, matcId);
|
|
216908
|
+
return artifactFamily(text).filter((p) => existsSync11(join13(shadersCacheDir(root), `${hash}_${p}.filamat`)));
|
|
216909
|
+
};
|
|
216910
|
+
var localShaderArtifact = (root, text, platform) => {
|
|
216911
|
+
const matcExe = resolveMatcExeCached();
|
|
216912
|
+
if (!matcExe)
|
|
216913
|
+
return null;
|
|
216914
|
+
const file = join13(shadersCacheDir(root), `${shaderHash(text, matcIdentity(matcExe))}_${platform}.filamat`);
|
|
216915
|
+
return existsSync11(file) ? file : null;
|
|
216916
|
+
};
|
|
216917
|
+
var allShaderPlatforms = () => [...Object.keys(ALL_PLATFORM_BUILD), "webgl-lite"];
|
|
216498
216918
|
var runMatc = (exe, args) => new Promise((resolve2) => {
|
|
216499
216919
|
execFile(exe, args, { windowsHide: true }, (error, _stdout, stderr) => {
|
|
216500
216920
|
const text = String(stderr ?? "") || (error ? error.message : "");
|
|
@@ -216509,6 +216929,8 @@ var cleanMatcOutput = (err, tempName, realName) => {
|
|
|
216509
216929
|
return (cut >= 0 ? output.slice(0, cut) : output).trim();
|
|
216510
216930
|
};
|
|
216511
216931
|
var NATIVE_PLATFORMS = ["opengl", "metal", "desktop"];
|
|
216932
|
+
var ALL_NATIVE_PLATFORMS = ["opengl", "metal", "desktop", "desktop-vulkan"];
|
|
216933
|
+
var desktopShaderPlatform = (platform, renderer) => platform === "win32" ? renderer === "vulkan" ? "desktop-vulkan" : "desktop" : "opengl";
|
|
216512
216934
|
var applyLocalShaders = async (entries, opts) => {
|
|
216513
216935
|
const shaderEntries = entries.filter((e) => e.type === "shader" && e.absPath && (opts.local || !e.shaders?.length));
|
|
216514
216936
|
if (shaderEntries.length === 0)
|
|
@@ -216528,14 +216950,21 @@ var applyLocalShaders = async (entries, opts) => {
|
|
|
216528
216950
|
for (const entry of shaderEntries) {
|
|
216529
216951
|
const text = readFileSync10(entry.absPath, "utf8");
|
|
216530
216952
|
const hash = shaderHash(text, matcId);
|
|
216531
|
-
const
|
|
216953
|
+
const family = artifactFamily(text);
|
|
216532
216954
|
const artifact = (platform) => `${hash}_${platform}.filamat`;
|
|
216533
|
-
|
|
216955
|
+
const has = (platform) => existsSync11(join13(cacheDir, artifact(platform)));
|
|
216956
|
+
const wanted = opts.platforms ? family.filter((p) => opts.platforms.includes(p)) : family;
|
|
216957
|
+
if (wanted.includes("webgl") && !matcPlatformsFor(text).includes("webgl") && !wanted.includes("opengl")) {
|
|
216958
|
+
wanted.push("opengl");
|
|
216959
|
+
}
|
|
216960
|
+
const missing = wanted.filter((p) => !has(p));
|
|
216961
|
+
const matcMissing = missing.filter((p) => p !== "webgl-lite" && !(p === "webgl" && !matcPlatformsFor(text).includes("webgl")));
|
|
216962
|
+
if (matcMissing.length > 0) {
|
|
216534
216963
|
const tempName = `lecodes-matc-${randomBytes(6).toString("hex")}.mat`;
|
|
216535
216964
|
const tempFile = join13(tmpdir(), tempName);
|
|
216536
216965
|
try {
|
|
216537
|
-
for (const platform of
|
|
216538
|
-
const build =
|
|
216966
|
+
for (const platform of matcMissing) {
|
|
216967
|
+
const build = ALL_PLATFORM_BUILD[platform];
|
|
216539
216968
|
writeFileSync7(tempFile, build.replaceExternal ? text.replace(externalSamplerReg, "type:sampler2d") : text);
|
|
216540
216969
|
const out = join13(cacheDir, artifact(platform));
|
|
216541
216970
|
const { ok, stderr } = await runMatc(matcExe, matcArgs(platform, out, tempFile));
|
|
@@ -216548,15 +216977,21 @@ ${cleanMatcOutput(stderr, tempName, basename3(entry.path))}`);
|
|
|
216548
216977
|
} finally {
|
|
216549
216978
|
rmSync2(tempFile, { force: true });
|
|
216550
216979
|
}
|
|
216551
|
-
|
|
216552
|
-
|
|
216553
|
-
|
|
216980
|
+
}
|
|
216981
|
+
if (missing.includes("webgl") && !matcPlatformsFor(text).includes("webgl")) {
|
|
216982
|
+
copyFileSync(join13(cacheDir, artifact("opengl")), join13(cacheDir, artifact("webgl")));
|
|
216983
|
+
}
|
|
216984
|
+
if (missing.includes("webgl-lite")) {
|
|
216554
216985
|
writeFileSync7(join13(cacheDir, artifact("webgl-lite")), JSON.stringify(buildLiteMaterialArtifact(text)));
|
|
216555
216986
|
}
|
|
216556
216987
|
if (opts.local) {
|
|
216557
|
-
|
|
216988
|
+
const staged = opts.platforms ? ALL_NATIVE_PLATFORMS.filter((p) => opts.platforms.includes(p)) : NATIVE_PLATFORMS;
|
|
216989
|
+
entry.shaders = [
|
|
216990
|
+
{ platform: "opengl", src: artifact("opengl") },
|
|
216991
|
+
...staged.filter((p) => p !== "opengl").map((platform) => ({ platform, src: artifact(platform) }))
|
|
216992
|
+
];
|
|
216558
216993
|
entry.shadersLocal = true;
|
|
216559
|
-
for (const platform of
|
|
216994
|
+
for (const platform of staged) {
|
|
216560
216995
|
artifacts.push({ name: artifact(platform), absPath: join13(cacheDir, artifact(platform)) });
|
|
216561
216996
|
}
|
|
216562
216997
|
} else {
|
|
@@ -216587,6 +217022,17 @@ var FIRST_PARTY_PLUGINS = {
|
|
|
216587
217022
|
};
|
|
216588
217023
|
var readAppConfigAt = (root) => readJson(join15(root, "app.json"));
|
|
216589
217024
|
var DESKTOP_DEFAULT_WINDOW = { width: 1280, height: 720 };
|
|
217025
|
+
var resolveDesktopRenderer = (root, args) => {
|
|
217026
|
+
const raw = (args ? flagStr(args, "renderer") : undefined) ?? process.env.LECODES_DESKTOP_RENDERER ?? readAppConfigAt(root)?.desktop?.renderer;
|
|
217027
|
+
if (raw === undefined)
|
|
217028
|
+
return "gl";
|
|
217029
|
+
const value = String(raw).toLowerCase();
|
|
217030
|
+
if (value === "gl" || value === "opengl")
|
|
217031
|
+
return "gl";
|
|
217032
|
+
if (value === "vulkan" || value === "vk")
|
|
217033
|
+
return "vulkan";
|
|
217034
|
+
throw new CliError(`Unknown desktop renderer "${raw}". Use "gl" or "vulkan".`);
|
|
217035
|
+
};
|
|
216590
217036
|
var desktopDefaultViewport = (root) => {
|
|
216591
217037
|
const cfg = readAppConfigAt(root);
|
|
216592
217038
|
if (!cfg?.desktop || cfg.ios || cfg.android)
|
|
@@ -216654,6 +217100,14 @@ var detectEngines = (bundleJs) => {
|
|
|
216654
217100
|
`);
|
|
216655
217101
|
return { gl: !/^\/\/ gl: disable$/m.test(header), g2: /^\/\/ 2d: enable$/m.test(header) };
|
|
216656
217102
|
};
|
|
217103
|
+
var assertRendererSupportsBundle = (renderer, bundleJs) => {
|
|
217104
|
+
if (renderer !== "vulkan")
|
|
217105
|
+
return;
|
|
217106
|
+
if (!detectEngines(bundleJs).g2)
|
|
217107
|
+
return;
|
|
217108
|
+
throw new CliError("This project uses the 2D engine, which the Vulkan desktop host doesn't include " + `(creator-2d renders through OpenGL and has no Vulkan path yet).
|
|
217109
|
+
` + " Build with the default renderer: drop `desktop.renderer` from app.json, or pass `--renderer gl`.");
|
|
217110
|
+
};
|
|
216657
217111
|
var resolveVariant = (requested, bundleJs, configKey = "ios.variant") => {
|
|
216658
217112
|
const uses = detectEngines(bundleJs);
|
|
216659
217113
|
const detected = uses.gl && uses.g2 ? "full" : uses.gl ? "3d" : uses.g2 ? "2d" : "core";
|
|
@@ -216866,7 +217320,8 @@ var buildBundle = async (args, shell, opts = {}) => {
|
|
|
216866
217320
|
}
|
|
216867
217321
|
let stagedShaders = [];
|
|
216868
217322
|
if (localAssets && (opts.localShaders ?? !shell)) {
|
|
216869
|
-
const
|
|
217323
|
+
const platforms = opts.desktopRenderer ? [desktopShaderPlatform(process.platform, opts.desktopRenderer)] : undefined;
|
|
217324
|
+
const local = await applyLocalShaders(entries, { root, local: true, platforms });
|
|
216870
217325
|
warnings.push(...local.warnings);
|
|
216871
217326
|
stagedShaders = local.artifacts;
|
|
216872
217327
|
}
|
|
@@ -216878,9 +217333,11 @@ var buildBundle = async (args, shell, opts = {}) => {
|
|
|
216878
217333
|
publicUrl: shell ? "" : publicUrl,
|
|
216879
217334
|
makeResourceEntry: (path, absPath) => ({ path, type: "resource", fileSrc: encodeURI(path), absPath })
|
|
216880
217335
|
});
|
|
217336
|
+
if (opts.extraEntries)
|
|
217337
|
+
entries.push(...opts.extraEntries);
|
|
216881
217338
|
const iconsHeader = await applyAppAssetIcons(entries);
|
|
216882
217339
|
note("Compiling…");
|
|
216883
|
-
const entryFlag = flagStr(args, "entry");
|
|
217340
|
+
const entryFlag = opts.entryOverride ?? flagStr(args, "entry");
|
|
216884
217341
|
if (!entryFlag) {
|
|
216885
217342
|
const ambiguous = ambiguousEntryWarning(entries);
|
|
216886
217343
|
if (ambiguous)
|
|
@@ -219373,9 +219830,13 @@ var makeStackDemangler = (bundleJs) => {
|
|
|
219373
219830
|
|
|
219374
219831
|
// src/desktopRenderer.ts
|
|
219375
219832
|
var DESKTOP_RENDERER_VERSION = "1.1.0";
|
|
219376
|
-
var
|
|
219833
|
+
var DESKTOP_RENDERER_MIN_VERSION = { gl: "0.0.0", vulkan: "1.4.0" };
|
|
219834
|
+
var platformKey2 = (renderer = "gl") => {
|
|
219377
219835
|
if (process.platform === "win32")
|
|
219378
|
-
return "windows-x64";
|
|
219836
|
+
return renderer === "vulkan" ? "windows-x64-vulkan" : "windows-x64";
|
|
219837
|
+
if (renderer === "vulkan") {
|
|
219838
|
+
throw new CliError(`The Vulkan desktop host is Windows-only (this is ${process.platform}). Use the default renderer.`);
|
|
219839
|
+
}
|
|
219379
219840
|
if (process.platform === "linux")
|
|
219380
219841
|
return "ubuntu-linux-x64";
|
|
219381
219842
|
throw new CliError(process.platform === "darwin" ? "`lecodes render --desktop` isn't available on macOS yet (the macOS host ships separately)." : `\`lecodes render --desktop\` isn't available on ${process.platform}.`);
|
|
@@ -219413,11 +219874,15 @@ var selectedDesktopVersion = () => {
|
|
|
219413
219874
|
return DESKTOP_RENDERER_VERSION;
|
|
219414
219875
|
};
|
|
219415
219876
|
var fetchDesktopManifest = () => fetchReleaseProduct("desktop", "desktop");
|
|
219416
|
-
var downloadDesktop = async (version, dest) => {
|
|
219417
|
-
const key = platformKey2();
|
|
219877
|
+
var downloadDesktop = async (version, dest, renderer = "gl") => {
|
|
219878
|
+
const key = platformKey2(renderer);
|
|
219418
219879
|
const entry = (await fetchDesktopManifest()).versions.find((v) => v.version === version);
|
|
219419
219880
|
const file = entry?.files[key];
|
|
219420
219881
|
if (!file) {
|
|
219882
|
+
if (renderer === "vulkan") {
|
|
219883
|
+
throw new CliError(`The Vulkan desktop host isn't published for LeCodes Desktop ${version} [${key}].
|
|
219884
|
+
` + ` Vulkan needs desktop >= ${DESKTOP_RENDERER_MIN_VERSION.vulkan}; run \`lecodes desktop update\`, ` + `set LECODES_DESKTOP_EXE to a locally built host, or drop desktop.renderer from app.json.`);
|
|
219885
|
+
}
|
|
219421
219886
|
throw new CliError(`LeCodes Desktop ${version} [${key}] isn't published yet — \`--desktop\` needs it for the ` + `headless render contract.
|
|
219422
219887
|
Set LECODES_DESKTOP_EXE to a locally built host, or update lecodes-cli.`);
|
|
219423
219888
|
}
|
|
@@ -219428,18 +219893,20 @@ Set LECODES_DESKTOP_EXE to a locally built host, or update lecodes-cli.`);
|
|
|
219428
219893
|
validate: (dir) => findExe(dir) ? null : "The downloaded archive doesn't contain the desktop host binary."
|
|
219429
219894
|
});
|
|
219430
219895
|
};
|
|
219431
|
-
var resolveDesktopExe = async () => {
|
|
219896
|
+
var resolveDesktopExe = async (renderer = "gl") => {
|
|
219432
219897
|
const override = process.env.LECODES_DESKTOP_EXE;
|
|
219433
219898
|
if (override) {
|
|
219434
219899
|
if (!existsSync17(override))
|
|
219435
219900
|
throw new CliError(`LECODES_DESKTOP_EXE points at "${override}" which doesn't exist.`);
|
|
219436
219901
|
return resolve5(override);
|
|
219437
219902
|
}
|
|
219903
|
+
if (renderer === "vulkan")
|
|
219904
|
+
platformKey2(renderer);
|
|
219438
219905
|
const version = selectedDesktopVersion();
|
|
219439
|
-
const dir = join18(cacheRoot2(), `desktop-${version}`);
|
|
219906
|
+
const dir = join18(cacheRoot2(), `desktop-${version}${renderer === "vulkan" ? "-vulkan" : ""}`);
|
|
219440
219907
|
let exe = existsSync17(dir) ? findExe(dir) : null;
|
|
219441
219908
|
if (!exe) {
|
|
219442
|
-
await downloadDesktop(version, dir);
|
|
219909
|
+
await downloadDesktop(version, dir, renderer);
|
|
219443
219910
|
exe = findExe(dir);
|
|
219444
219911
|
if (!exe)
|
|
219445
219912
|
throw new CliError(`Renderer cache at ${dir} is broken — delete the folder and retry.`);
|
|
@@ -219488,6 +219955,8 @@ var windowEnv = (opts) => {
|
|
|
219488
219955
|
if (opts.maxRenderSize && opts.maxRenderSize[0] > 0 && opts.maxRenderSize[1] > 0) {
|
|
219489
219956
|
env.CREATOR_RENDER_MAX = `${Math.round(opts.maxRenderSize[0])}x${Math.round(opts.maxRenderSize[1])}`;
|
|
219490
219957
|
}
|
|
219958
|
+
if (opts.fps)
|
|
219959
|
+
env.CREATOR_FPS = "1";
|
|
219491
219960
|
return env;
|
|
219492
219961
|
};
|
|
219493
219962
|
var stageBundle = (js, resources) => {
|
|
@@ -219517,7 +219986,7 @@ var pipeThroughDemangler = (src, dst, map) => {
|
|
|
219517
219986
|
});
|
|
219518
219987
|
};
|
|
219519
219988
|
var runDesktopApp = async (js, resources, opts = {}) => {
|
|
219520
|
-
const exe = await resolveDesktopExe();
|
|
219989
|
+
const exe = await resolveDesktopExe(opts.renderer);
|
|
219521
219990
|
const runDir = stageBundle(js, resources);
|
|
219522
219991
|
try {
|
|
219523
219992
|
const env = { ...process.env, ...windowEnv(opts) };
|
|
@@ -219541,7 +220010,7 @@ var runDesktopApp = async (js, resources, opts = {}) => {
|
|
|
219541
220010
|
}
|
|
219542
220011
|
};
|
|
219543
220012
|
var runDesktopDev = async (url, opts = {}) => {
|
|
219544
|
-
const exe = await resolveDesktopExe();
|
|
220013
|
+
const exe = await resolveDesktopExe(opts.renderer);
|
|
219545
220014
|
const env = { ...process.env, ...windowEnv(opts) };
|
|
219546
220015
|
await new Promise((done, fail) => {
|
|
219547
220016
|
const child = spawn2(exe, [url], { env, stdio: ["ignore", "ignore", "inherit"] });
|
|
@@ -219550,7 +220019,7 @@ var runDesktopDev = async (url, opts = {}) => {
|
|
|
219550
220019
|
});
|
|
219551
220020
|
};
|
|
219552
220021
|
var runDesktopRender = async (js, resources, opts) => {
|
|
219553
|
-
const exe = await resolveDesktopExe();
|
|
220022
|
+
const exe = await resolveDesktopExe(opts.renderer);
|
|
219554
220023
|
const runDir = stageBundle(js, resources);
|
|
219555
220024
|
try {
|
|
219556
220025
|
const appJs = join18(runDir, "app.js");
|
|
@@ -219577,11 +220046,16 @@ var runDesktopRender = async (js, resources, opts) => {
|
|
|
219577
220046
|
return await new Promise((done, fail) => {
|
|
219578
220047
|
const child = spawn2(exe, [appJs], { cwd: runDir, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
219579
220048
|
const chunks = [];
|
|
220049
|
+
let stopped = false;
|
|
219580
220050
|
const sink = (d) => {
|
|
219581
220051
|
const text = String(d);
|
|
219582
220052
|
chunks.push(text);
|
|
219583
220053
|
if (opts.logs)
|
|
219584
220054
|
process.stderr.write(demangle(text));
|
|
220055
|
+
if (!stopped && opts.stopOnLine?.(text)) {
|
|
220056
|
+
stopped = true;
|
|
220057
|
+
child.kill();
|
|
220058
|
+
}
|
|
219585
220059
|
};
|
|
219586
220060
|
child.stdout.on("data", sink);
|
|
219587
220061
|
child.stderr.on("data", sink);
|
|
@@ -219596,6 +220070,8 @@ var runDesktopRender = async (js, resources, opts) => {
|
|
|
219596
220070
|
child.on("exit", (code) => {
|
|
219597
220071
|
clearTimeout(killer);
|
|
219598
220072
|
const output = demangle(chunks.join(""));
|
|
220073
|
+
if (stopped)
|
|
220074
|
+
return done({ output, exitCode: 0 });
|
|
219599
220075
|
if (code === 0 && existsSync17(outAbs))
|
|
219600
220076
|
return done({ output, exitCode: code });
|
|
219601
220077
|
const tail = demangle(chunks.slice(-80).join("")).trimEnd();
|
|
@@ -219634,6 +220110,8 @@ var initDesktop = async (args) => {
|
|
|
219634
220110
|
log(`Next: ${c.bold("lecodes dev --desktop")} to develop, ${c.bold("lecodes app build desktop")} to package.`);
|
|
219635
220111
|
};
|
|
219636
220112
|
var cleanFileName = (name) => name.replace(/[<>:"/\\|?*\u0000-\u001f]/g, "").replace(/[. ]+$/, "").trim() || "app";
|
|
220113
|
+
var ASSETS_DIR = "assets";
|
|
220114
|
+
var RUNTIME_DIR = "runtime";
|
|
219637
220115
|
var buildDesktop = async (args) => {
|
|
219638
220116
|
const root = projectRoot();
|
|
219639
220117
|
const config = readJson(join19(root, "app.json")) ?? {};
|
|
@@ -219649,8 +220127,10 @@ var buildDesktop = async (args) => {
|
|
|
219649
220127
|
}
|
|
219650
220128
|
if (config.desktop?.entry && flagStr(args, "entry") === undefined)
|
|
219651
220129
|
args.flags.entry = config.desktop.entry;
|
|
219652
|
-
|
|
219653
|
-
|
|
220130
|
+
const renderer = resolveDesktopRenderer(root, args);
|
|
220131
|
+
note(`Building "${name}" for desktop${renderer === "vulkan" ? " (Vulkan)" : ""}…`);
|
|
220132
|
+
const { js, resources } = await buildBundle(args, { remotePatterns: config.resources?.remote ?? [] }, { localShaders: true, desktopRenderer: renderer });
|
|
220133
|
+
assertRendererSupportsBundle(renderer, js);
|
|
219654
220134
|
const wanted = new Set([...js.matchAll(/fetchLocal\("([^"]+)"\)/g)].map((m) => m[1]));
|
|
219655
220135
|
const shipped = resources.filter((r2) => !r2.remote && (r2.shader || wanted.has(r2.name)));
|
|
219656
220136
|
const byName = new Map;
|
|
@@ -219669,7 +220149,7 @@ var buildDesktop = async (args) => {
|
|
|
219669
220149
|
`)).join(`
|
|
219670
220150
|
`));
|
|
219671
220151
|
}
|
|
219672
|
-
const exe = await resolveDesktopExe();
|
|
220152
|
+
const exe = await resolveDesktopExe(renderer);
|
|
219673
220153
|
const hostDir = dirname11(exe);
|
|
219674
220154
|
const IMPLICIT_PRELOADS = new Set(["uberarchive", "capture"]);
|
|
219675
220155
|
const hostProvided = new Set;
|
|
@@ -219686,11 +220166,15 @@ var buildDesktop = async (args) => {
|
|
|
219686
220166
|
rmSync5(out, { recursive: true, force: true });
|
|
219687
220167
|
mkdirSync12(out, { recursive: true });
|
|
219688
220168
|
for (const entry of readdirSync7(hostDir, { withFileTypes: true })) {
|
|
219689
|
-
|
|
219690
|
-
|
|
220169
|
+
if (entry.isFile() && entry.name.toLowerCase().endsWith(".dll")) {
|
|
220170
|
+
cpSync2(join19(hostDir, entry.name), join19(out, entry.name));
|
|
220171
|
+
} else if (entry.isDirectory() && entry.name === "assets") {
|
|
220172
|
+
cpSync2(join19(hostDir, entry.name), join19(out, RUNTIME_DIR), { recursive: true });
|
|
220173
|
+
} else if (entry.isDirectory() && entry.name === "fonts") {
|
|
219691
220174
|
cpSync2(join19(hostDir, entry.name), join19(out, entry.name), { recursive: true });
|
|
220175
|
+
}
|
|
219692
220176
|
}
|
|
219693
|
-
if (!existsSync18(join19(out,
|
|
220177
|
+
if (!existsSync18(join19(out, RUNTIME_DIR, "initial.js"))) {
|
|
219694
220178
|
throw new CliError(`The host at ${hostDir} has no assets/initial.js next to it — a staged build can't boot. ` + `(LECODES_DESKTOP_EXE must point at an exe with its runtime assets/ beside it.)`);
|
|
219695
220179
|
}
|
|
219696
220180
|
const exeName2 = process.platform === "win32" ? `${cleanFileName(name)}.exe` : cleanFileName(name);
|
|
@@ -219698,16 +220182,18 @@ var buildDesktop = async (args) => {
|
|
|
219698
220182
|
note(` host: LeCodes Desktop ${version}`);
|
|
219699
220183
|
writeFileSync14(join19(out, "app.js"), js);
|
|
219700
220184
|
let assetBytes = 0, assetCount = 0;
|
|
220185
|
+
if (shipped.length > 0)
|
|
220186
|
+
mkdirSync12(join19(out, ASSETS_DIR), { recursive: true });
|
|
219701
220187
|
for (const r2 of shipped) {
|
|
219702
220188
|
if (!r2.absPath || !existsSync18(r2.absPath) || !statSync5(r2.absPath).isFile()) {
|
|
219703
220189
|
warn(`asset "${r2.name}" (${r2.path}) has no local file — skipped`);
|
|
219704
220190
|
continue;
|
|
219705
220191
|
}
|
|
219706
|
-
cpSync2(r2.absPath, join19(out, r2.name));
|
|
220192
|
+
cpSync2(r2.absPath, join19(out, ASSETS_DIR, r2.name));
|
|
219707
220193
|
assetBytes += statSync5(r2.absPath).size;
|
|
219708
220194
|
assetCount++;
|
|
219709
220195
|
}
|
|
219710
|
-
note(` assets: ${assetCount} files, ${mb(assetBytes)}
|
|
220196
|
+
note(` assets: ${assetCount} files, ${mb(assetBytes)} → ${ASSETS_DIR}/`);
|
|
219711
220197
|
writeJson(join19(out, "app.json"), { name, ...config.desktop ? { desktop: { window: config.desktop.window } } : {} });
|
|
219712
220198
|
if (config.icon) {
|
|
219713
220199
|
await writeIco(root, config, join19(out, "app.ico"));
|
|
@@ -219818,7 +220304,7 @@ var readmeText = (name, exeName2, version) => `${name}
|
|
|
219818
220304
|
${"=".repeat(Math.max(name.length, 4))}
|
|
219819
220305
|
|
|
219820
220306
|
A LeCodes desktop app. Nothing to install: unpack the WHOLE folder anywhere and run ${exeName2}.
|
|
219821
|
-
Keep the files together
|
|
220307
|
+
Keep the files together: assets/ holds the app's own content, runtime/ the engine it needs.
|
|
219822
220308
|
|
|
219823
220309
|
On first run Windows may show a SmartScreen prompt (any unsigned app gets one):
|
|
219824
220310
|
choose "More info", then "Run anyway".
|
|
@@ -220654,6 +221140,9 @@ from \`main.ts\`.
|
|
|
220654
221140
|
lecodes render # semantic JSON of the launched UI (structure, text, names, values)
|
|
220655
221141
|
lecodes render --png shot.png # pixels (needs @napi-rs/canvas installed)
|
|
220656
221142
|
lecodes render --png card.png --clip card # ONE component/region: a node name or x,y,w,h
|
|
221143
|
+
lecodes render src/screens/cart.ts # ONE screen module, without booting the app: any file that
|
|
221144
|
+
# \`export default\`s a UIScreen (or a (state) => UIScreen —
|
|
221145
|
+
# --state <name> picks the branch). Same flags as above.
|
|
220657
221146
|
lecodes render --desktop --png shot.png # NATIVE render: real Filament 3D + UI + 2D composited —
|
|
220658
221147
|
# use this to SEE 3D scenes / procedural geometry (headless
|
|
220659
221148
|
# renders record 3D as JSON only). Windows/Linux; downloads
|
|
@@ -220828,9 +221317,12 @@ var run = async (args) => {
|
|
|
220828
221317
|
}
|
|
220829
221318
|
const appCfg = readAppConfigAt(root);
|
|
220830
221319
|
const win = appCfg?.desktop?.window;
|
|
220831
|
-
const
|
|
220832
|
-
|
|
221320
|
+
const renderer = resolveDesktopRenderer(root, args);
|
|
221321
|
+
const { js, resources } = await buildBundle(args, undefined, { localAssets: true, desktopRenderer: renderer });
|
|
221322
|
+
assertRendererSupportsBundle(renderer, js);
|
|
221323
|
+
note(`Launching LeCodes Desktop${renderer === "vulkan" ? " (Vulkan)" : ""}… (close the window to return)`);
|
|
220833
221324
|
await runDesktopApp(js, resources, {
|
|
221325
|
+
renderer,
|
|
220834
221326
|
width: Number(flagStr(args, "width")) || win?.width,
|
|
220835
221327
|
height: Number(flagStr(args, "height")) || win?.height,
|
|
220836
221328
|
minWidth: win?.minWidth,
|
|
@@ -220840,7 +221332,8 @@ var run = async (args) => {
|
|
|
220840
221332
|
fullscreen: flagBool(args, "fullscreen") ? true : flagBool(args, "windowed") ? false : win?.fullscreen,
|
|
220841
221333
|
scale: resolveScale(flagStr(args, "scale"), win?.scale),
|
|
220842
221334
|
renderScale: resolveRenderScale(flagStr(args, "render-scale"), appCfg?.desktop?.renderScale),
|
|
220843
|
-
maxRenderSize: appCfg?.desktop?.maxRenderSize
|
|
221335
|
+
maxRenderSize: appCfg?.desktop?.maxRenderSize,
|
|
221336
|
+
fps: flagBool(args, "fps")
|
|
220844
221337
|
});
|
|
220845
221338
|
};
|
|
220846
221339
|
var status = async () => {
|
|
@@ -220865,9 +221358,11 @@ var desktop = async (args) => {
|
|
|
220865
221358
|
return status();
|
|
220866
221359
|
if (sub === "run")
|
|
220867
221360
|
return run(args);
|
|
221361
|
+
if (sub === "build")
|
|
221362
|
+
return buildDesktop(args);
|
|
220868
221363
|
if (sub === "update")
|
|
220869
221364
|
return updateDesktop();
|
|
220870
|
-
throw new CliError(`Unknown subcommand "desktop ${sub}". Use \`lecodes desktop\` (status), \`lecodes desktop run\` or \`lecodes desktop update\`.`);
|
|
221365
|
+
throw new CliError(`Unknown subcommand "desktop ${sub}". Use \`lecodes desktop\` (status), \`lecodes desktop run\`, \`lecodes desktop build\` or \`lecodes desktop update\`.`);
|
|
220871
221366
|
};
|
|
220872
221367
|
|
|
220873
221368
|
// src/index.ts
|
|
@@ -220876,16 +221371,16 @@ init_design();
|
|
|
220876
221371
|
// src/commands/dev.ts
|
|
220877
221372
|
init_manifest();
|
|
220878
221373
|
init_ignore();
|
|
220879
|
-
import { existsSync as existsSync24, mkdirSync as mkdirSync18, readFileSync as
|
|
221374
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync18, readFileSync as readFileSync21, watch, writeFileSync as writeFileSync19 } from "node:fs";
|
|
220880
221375
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
220881
221376
|
import { networkInterfaces } from "node:os";
|
|
220882
|
-
import { basename as basename10, join as
|
|
221377
|
+
import { basename as basename10, join as join26 } from "node:path";
|
|
220883
221378
|
|
|
220884
221379
|
// src/dev/devServer.ts
|
|
220885
221380
|
init_localFiles();
|
|
220886
221381
|
import { createServer as createServer2 } from "node:http";
|
|
220887
221382
|
import { createReadStream, statSync as statSync8 } from "node:fs";
|
|
220888
|
-
import { join as
|
|
221383
|
+
import { join as join25 } from "node:path";
|
|
220889
221384
|
init_projectCompile();
|
|
220890
221385
|
init_fonts();
|
|
220891
221386
|
init_assetIcons();
|
|
@@ -221271,7 +221766,7 @@ var startDevServer = async (options) => {
|
|
|
221271
221766
|
const name2 = route.slice("/shaders/".length);
|
|
221272
221767
|
if (!isShaderArtifactName(name2))
|
|
221273
221768
|
return sendText(res, 400, "text/plain", "Bad path");
|
|
221274
|
-
const abs =
|
|
221769
|
+
const abs = join25(shadersCacheDir(root), name2);
|
|
221275
221770
|
try {
|
|
221276
221771
|
if (!statSync8(abs).isFile())
|
|
221277
221772
|
throw new Error("not a file");
|
|
@@ -221385,14 +221880,14 @@ var lanCandidates = () => {
|
|
|
221385
221880
|
return out.sort((x2, y2) => x2.rank - y2.rank).map(({ address, iface }) => ({ address, iface }));
|
|
221386
221881
|
};
|
|
221387
221882
|
var loadDevToken = (root) => {
|
|
221388
|
-
const path2 =
|
|
221883
|
+
const path2 = join26(root, LECODES_DIR, "dev.json");
|
|
221389
221884
|
try {
|
|
221390
|
-
const token2 = JSON.parse(
|
|
221885
|
+
const token2 = JSON.parse(readFileSync21(path2, "utf8")).token;
|
|
221391
221886
|
if (token2 && /^[a-z0-9]{8,}$/.test(token2))
|
|
221392
221887
|
return token2;
|
|
221393
221888
|
} catch {}
|
|
221394
221889
|
const token = randomBytes3(6).toString("hex");
|
|
221395
|
-
mkdirSync18(
|
|
221890
|
+
mkdirSync18(join26(root, LECODES_DIR), { recursive: true });
|
|
221396
221891
|
writeFileSync19(path2, JSON.stringify({ token }, null, 2) + `
|
|
221397
221892
|
`);
|
|
221398
221893
|
return token;
|
|
@@ -221510,7 +222005,7 @@ ${result.error}`);
|
|
|
221510
222005
|
if (alternatives.length > 0) {
|
|
221511
222006
|
note(`Device can't reach it? This machine also has ${alternatives.map((a2) => `${a2.address} (${a2.iface})`).join(", ")} — retry with --host <ip>.`);
|
|
221512
222007
|
}
|
|
221513
|
-
if (existsSync24(
|
|
222008
|
+
if (existsSync24(join26(root, LECODES_DIR, "manifest.json")))
|
|
221514
222009
|
note(`Token stored in ${LECODES_DIR}/dev.json (delete it to rotate the URL).`);
|
|
221515
222010
|
log("");
|
|
221516
222011
|
log(`Watching ${root} — save a file to hot-reload. Ctrl+C to stop.`);
|
|
@@ -221525,7 +222020,8 @@ ${result.error}`);
|
|
|
221525
222020
|
minHeight: win?.minHeight,
|
|
221526
222021
|
resizable: win?.resizable,
|
|
221527
222022
|
title: win?.title ?? appCfg?.name ?? name,
|
|
221528
|
-
scale: resolveScale(undefined, win?.scale)
|
|
222023
|
+
scale: resolveScale(undefined, win?.scale),
|
|
222024
|
+
fps: flagBool(args, "fps")
|
|
221529
222025
|
}).then(() => note("Desktop window closed — the dev server keeps running (Ctrl+C to stop)."), (e2) => warnErr(`Desktop host: ${e2 instanceof Error ? e2.message : String(e2)}`));
|
|
221530
222026
|
}
|
|
221531
222027
|
};
|
|
@@ -221538,7 +222034,7 @@ init_localFiles();
|
|
|
221538
222034
|
|
|
221539
222035
|
// src/serverDiff.ts
|
|
221540
222036
|
init_localFiles();
|
|
221541
|
-
import { readFileSync as
|
|
222037
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
221542
222038
|
import { basename as basename11 } from "node:path";
|
|
221543
222039
|
var isTextAsset = (t2) => t2 === "text" || t2 === "shader" || t2 === "scene";
|
|
221544
222040
|
var computeServerDiff = (project, scan2) => {
|
|
@@ -221557,7 +222053,7 @@ var computeServerDiff = (project, scan2) => {
|
|
|
221557
222053
|
const local = localByPath.get(path2);
|
|
221558
222054
|
if (local && !server2) {
|
|
221559
222055
|
const text = isTextKind(classifyKind(basename11(path2)));
|
|
221560
|
-
entries.push({ path: path2, status: "added", binary: !text, serverText: "", localText: text ?
|
|
222056
|
+
entries.push({ path: path2, status: "added", binary: !text, serverText: "", localText: text ? readFileSync22(local.absPath, "utf8") : undefined });
|
|
221561
222057
|
continue;
|
|
221562
222058
|
}
|
|
221563
222059
|
if (server2 && !local) {
|
|
@@ -221568,12 +222064,12 @@ var computeServerDiff = (project, scan2) => {
|
|
|
221568
222064
|
if (server2 && local) {
|
|
221569
222065
|
if (isTextAsset(server2.type)) {
|
|
221570
222066
|
const serverText = server2.text ?? "";
|
|
221571
|
-
const localText =
|
|
222067
|
+
const localText = readFileSync22(local.absPath, "utf8");
|
|
221572
222068
|
if (serverText !== localText)
|
|
221573
222069
|
entries.push({ path: path2, status: "modified", binary: false, serverText, localText });
|
|
221574
222070
|
} else {
|
|
221575
222071
|
const serverSize = server2.file?.size;
|
|
221576
|
-
const localSize = Buffer.byteLength(
|
|
222072
|
+
const localSize = Buffer.byteLength(readFileSync22(local.absPath));
|
|
221577
222073
|
if (serverSize !== undefined && serverSize !== localSize) {
|
|
221578
222074
|
entries.push({ path: path2, status: "modified", binary: true });
|
|
221579
222075
|
}
|
|
@@ -221732,7 +222228,7 @@ init_manifest();
|
|
|
221732
222228
|
init_types4();
|
|
221733
222229
|
init_ignore();
|
|
221734
222230
|
import { existsSync as existsSync25, mkdirSync as mkdirSync19, writeFileSync as writeFileSync20 } from "node:fs";
|
|
221735
|
-
import { basename as basename12, dirname as dirname14, join as
|
|
222231
|
+
import { basename as basename12, dirname as dirname14, join as join27, resolve as resolve10 } from "node:path";
|
|
221736
222232
|
init_util();
|
|
221737
222233
|
var STARTER2 = `// Welcome to LeCodes — the whole SDK is available as globals, no imports needed.
|
|
221738
222234
|
// Run it live on a device with \`lecodes dev\`, or bundle it with \`lecodes compile -o app.js\`.
|
|
@@ -221845,7 +222341,7 @@ var init2 = async (args) => {
|
|
|
221845
222341
|
mkdirSync19(root, { recursive: true });
|
|
221846
222342
|
let created = 0;
|
|
221847
222343
|
const write = (rel, content) => {
|
|
221848
|
-
const abs =
|
|
222344
|
+
const abs = join27(root, rel);
|
|
221849
222345
|
if (existsSync25(abs)) {
|
|
221850
222346
|
note(`kept ${rel}`);
|
|
221851
222347
|
return;
|
|
@@ -221899,7 +222395,7 @@ init_ignore();
|
|
|
221899
222395
|
init_localFiles();
|
|
221900
222396
|
init_manifest();
|
|
221901
222397
|
import { existsSync as existsSync26 } from "node:fs";
|
|
221902
|
-
import { basename as basename14, join as
|
|
222398
|
+
import { basename as basename14, join as join28, resolve as resolve11 } from "node:path";
|
|
221903
222399
|
init_types4();
|
|
221904
222400
|
init_util();
|
|
221905
222401
|
|
|
@@ -221909,7 +222405,7 @@ init_config();
|
|
|
221909
222405
|
init_manifest();
|
|
221910
222406
|
init_localFiles();
|
|
221911
222407
|
import { basename as basename13 } from "node:path";
|
|
221912
|
-
import { readFileSync as
|
|
222408
|
+
import { readFileSync as readFileSync23 } from "node:fs";
|
|
221913
222409
|
init_util();
|
|
221914
222410
|
var push = async (args) => {
|
|
221915
222411
|
const root = findProjectRoot(process.cwd());
|
|
@@ -221970,9 +222466,9 @@ var push = async (args) => {
|
|
|
221970
222466
|
if (source)
|
|
221971
222467
|
entry.assetId = source.id;
|
|
221972
222468
|
if (isTextKind(kind)) {
|
|
221973
|
-
entry.text =
|
|
222469
|
+
entry.text = readFileSync23(file.absPath, "utf8");
|
|
221974
222470
|
} else if (!source || source.sha !== file.sha) {
|
|
221975
|
-
const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename13(file.path),
|
|
222471
|
+
const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename13(file.path), readFileSync23(file.absPath));
|
|
221976
222472
|
entry.blobFileId = blob.id;
|
|
221977
222473
|
uploaded++;
|
|
221978
222474
|
}
|
|
@@ -222082,7 +222578,7 @@ var link = async (args) => {
|
|
|
222082
222578
|
const root = dirArg ? resolve11(dirArg) : process.cwd();
|
|
222083
222579
|
if (!existsSync26(root))
|
|
222084
222580
|
throw new CliError(`No such directory: ${root}`);
|
|
222085
|
-
if (existsSync26(
|
|
222581
|
+
if (existsSync26(join28(root, LECODES_DIR, "manifest.json")) && !flagBool(args, "force")) {
|
|
222086
222582
|
throw new CliError("This folder is already linked (.lecodes/manifest.json exists) — use `lecodes push`, or `lecodes link --force` to re-link it to another project.");
|
|
222087
222583
|
}
|
|
222088
222584
|
let enclosing = null;
|
|
@@ -222144,6 +222640,173 @@ var link = async (args) => {
|
|
|
222144
222640
|
log(` ${c.dim("(add --publish to deploy it live in the same step)")}`);
|
|
222145
222641
|
};
|
|
222146
222642
|
|
|
222643
|
+
// src/commands/lightmap.ts
|
|
222644
|
+
init_manifest();
|
|
222645
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync3, rmSync as rmSync8, statSync as statSync9, unlinkSync, writeFileSync as writeFileSync21 } from "node:fs";
|
|
222646
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
222647
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
222648
|
+
import { join as join29, relative as relative4, resolve as resolve12 } from "node:path";
|
|
222649
|
+
init_util();
|
|
222650
|
+
var num = (args, key, fallback, min, max) => {
|
|
222651
|
+
const raw = flagStr(args, key);
|
|
222652
|
+
if (raw === undefined)
|
|
222653
|
+
return fallback;
|
|
222654
|
+
const n2 = Number(raw);
|
|
222655
|
+
if (!Number.isFinite(n2) || n2 < min || n2 > max)
|
|
222656
|
+
throw new CliError(`--${key} wants a number in ${min}..${max}, got "${raw}".`);
|
|
222657
|
+
return n2;
|
|
222658
|
+
};
|
|
222659
|
+
var findToktx = () => {
|
|
222660
|
+
const env = process.env.KTX_TOKTX;
|
|
222661
|
+
if (env && existsSync27(env))
|
|
222662
|
+
return env;
|
|
222663
|
+
for (const base of [process.env.LOCALAPPDATA && join29(process.env.LOCALAPPDATA, "Programs"), process.env.ProgramFiles]) {
|
|
222664
|
+
if (!base)
|
|
222665
|
+
continue;
|
|
222666
|
+
const guess = join29(base, "KTX-Software", "bin", "toktx.exe");
|
|
222667
|
+
if (existsSync27(guess))
|
|
222668
|
+
return guess;
|
|
222669
|
+
}
|
|
222670
|
+
return "toktx";
|
|
222671
|
+
};
|
|
222672
|
+
var checkToktx = (toktx) => {
|
|
222673
|
+
try {
|
|
222674
|
+
const p2 = spawnSync4(toktx, ["--version"], { encoding: "utf8" });
|
|
222675
|
+
if (p2.status === 0)
|
|
222676
|
+
return (p2.stdout ?? "").toString().trim() || "toktx";
|
|
222677
|
+
} catch {}
|
|
222678
|
+
return null;
|
|
222679
|
+
};
|
|
222680
|
+
var requireToktx = () => {
|
|
222681
|
+
const toktx = findToktx();
|
|
222682
|
+
if (!checkToktx(toktx)) {
|
|
222683
|
+
throw new CliError(`lecodes lightmap bake needs KTX-Software's toktx — the atlas ships as KTX2 (GPU-compressed, mipmapped).
|
|
222684
|
+
Install it from https://github.com/KhronosGroup/KTX-Software/releases (make sure toktx is on PATH) or set KTX_TOKTX=<path to toktx>.`);
|
|
222685
|
+
}
|
|
222686
|
+
return toktx;
|
|
222687
|
+
};
|
|
222688
|
+
var encodeKtx2 = (toktx, png2, ktx2) => {
|
|
222689
|
+
const args = ["--t2", "--encode", "uastc", "--uastc_quality", "2", "--zcmp", "18", "--genmipmap", "--assign_oetf", "srgb", ktx2, png2];
|
|
222690
|
+
const p2 = spawnSync4(toktx, args, { encoding: "utf8" });
|
|
222691
|
+
if (p2.status !== 0) {
|
|
222692
|
+
throw new CliError(`toktx failed (${p2.status ?? p2.error?.message ?? "?"}) while encoding ${ktx2}:
|
|
222693
|
+
${(p2.stderr || p2.stdout || "").trim()}`);
|
|
222694
|
+
}
|
|
222695
|
+
};
|
|
222696
|
+
var bake = async (args) => {
|
|
222697
|
+
let root;
|
|
222698
|
+
try {
|
|
222699
|
+
root = findProjectRoot(process.cwd());
|
|
222700
|
+
} catch {
|
|
222701
|
+
root = process.cwd();
|
|
222702
|
+
}
|
|
222703
|
+
const toktx = requireToktx();
|
|
222704
|
+
const outDir = resolve12(root, flagStr(args, "out") ?? "assets/lightmap");
|
|
222705
|
+
const entryFlag = flagStr(args, "entry");
|
|
222706
|
+
const entryFile = entryFlag ?? "main.ts";
|
|
222707
|
+
if (!existsSync27(resolve12(root, entryFile))) {
|
|
222708
|
+
throw new CliError(`No ${entryFile} in ${root} — pass --entry <file> (the file that builds the scene and calls Lightmap.load).`);
|
|
222709
|
+
}
|
|
222710
|
+
const cfg = {
|
|
222711
|
+
outDir: outDir.replace(/\\/g, "/"),
|
|
222712
|
+
size: num(args, "size", 4096, 256, 16384),
|
|
222713
|
+
texel: num(args, "texel", 0.02, 0.001, 2),
|
|
222714
|
+
sunRays: num(args, "sun-rays", 32, 1, 1024),
|
|
222715
|
+
aoRays: num(args, "ao-rays", 64, 1, 4096),
|
|
222716
|
+
aoDistance: num(args, "ao-distance", 2, 0.01, 1000),
|
|
222717
|
+
bias: num(args, "bias", 0.02, 0.0001, 1),
|
|
222718
|
+
sunAngle: num(args, "sun-angle", 0.5, 0.01, 30)
|
|
222719
|
+
};
|
|
222720
|
+
const frames = num(args, "frames", 900, 30, 1e5);
|
|
222721
|
+
const win = readAppConfigAt(root)?.desktop?.window;
|
|
222722
|
+
const width = Number(flagStr(args, "width")) || win?.width || 1280;
|
|
222723
|
+
const height = Number(flagStr(args, "height")) || win?.height || 720;
|
|
222724
|
+
const logs = flagBool(args, "logs");
|
|
222725
|
+
const importPath = "./" + entryFile.replace(/\\/g, "/").replace(/\.tsx?$/, "");
|
|
222726
|
+
const entryPath = "/.lightmap-entry.ts";
|
|
222727
|
+
const entryText = `globalThis.__lecodesLightmap = ${JSON.stringify(cfg)}
|
|
222728
|
+
import ${JSON.stringify(importPath)}
|
|
222729
|
+
`;
|
|
222730
|
+
mkdirSync20(outDir, { recursive: true });
|
|
222731
|
+
ensurePlaceholders(outDir);
|
|
222732
|
+
const desktopRenderer = resolveDesktopRenderer(root, args);
|
|
222733
|
+
const { js, resources } = await buildBundle(args, undefined, {
|
|
222734
|
+
localAssets: true,
|
|
222735
|
+
desktopRenderer,
|
|
222736
|
+
extraEntries: [{ path: entryPath, type: "text", text: entryText }],
|
|
222737
|
+
entryOverride: entryPath
|
|
222738
|
+
});
|
|
222739
|
+
const scratch = mkdtempSync3(join29(tmpdir3(), "lecodes-lightmap-"));
|
|
222740
|
+
const outPng = join29(scratch, "frame.png");
|
|
222741
|
+
note(`Baking ${cfg.size}² at ${(cfg.texel * 100).toFixed(1)} cm texels (${cfg.sunRays} sun + ${cfg.aoRays} AO rays) → ${relative4(root, outDir) || "."}…`);
|
|
222742
|
+
let output = "";
|
|
222743
|
+
try {
|
|
222744
|
+
const timeoutMs = Number(flagStr(args, "timeout")) || 600000;
|
|
222745
|
+
const res = await runDesktopRender(js, resources, {
|
|
222746
|
+
renderer: desktopRenderer,
|
|
222747
|
+
width,
|
|
222748
|
+
height,
|
|
222749
|
+
frames,
|
|
222750
|
+
outPng,
|
|
222751
|
+
logs,
|
|
222752
|
+
timeoutMs,
|
|
222753
|
+
stopOnLine: (chunk) => chunk.includes("[lightmap] done") || chunk.includes("[lightmap] error")
|
|
222754
|
+
});
|
|
222755
|
+
output = res.output;
|
|
222756
|
+
} catch (e2) {
|
|
222757
|
+
const msg = e2 instanceof Error ? e2.message : String(e2);
|
|
222758
|
+
if (!/\[lightmap\] done/.test(msg))
|
|
222759
|
+
throw e2;
|
|
222760
|
+
output = msg;
|
|
222761
|
+
} finally {
|
|
222762
|
+
rmSync8(scratch, { recursive: true, force: true });
|
|
222763
|
+
}
|
|
222764
|
+
const lines = output.split(/\r?\n/).filter((l2) => l2.startsWith("[lightmap]"));
|
|
222765
|
+
for (const l2 of lines) {
|
|
222766
|
+
const text = l2.slice("[lightmap]".length).trim();
|
|
222767
|
+
if (text === "done")
|
|
222768
|
+
continue;
|
|
222769
|
+
logErr(text.startsWith("error") ? c.red(`✗ ${text}`) : c.dim(` ${text}`));
|
|
222770
|
+
}
|
|
222771
|
+
if (/\[lightmap\] done/.test(output)) {
|
|
222772
|
+
const png2 = join29(outDir, "lightmap.png");
|
|
222773
|
+
const ktx2 = join29(outDir, "lightmap.ktx2");
|
|
222774
|
+
if (!existsSync27(png2))
|
|
222775
|
+
throw new CliError(`The host reported a bake but ${png2} is missing.`);
|
|
222776
|
+
const t0 = Date.now();
|
|
222777
|
+
encodeKtx2(toktx, png2, ktx2);
|
|
222778
|
+
const pngKb = Math.round(statSync9(png2).size / 1024), ktxKb = Math.round(statSync9(ktx2).size / 1024);
|
|
222779
|
+
unlinkSync(png2);
|
|
222780
|
+
logErr(c.dim(` encoded lightmap.ktx2 (UASTC + mips, ${ktxKb} KB from a ${pngKb} KB raster) in ${((Date.now() - t0) / 1000).toFixed(1)} s`));
|
|
222781
|
+
logErr(c.green(`✓ baked → ${relative4(root, outDir) || "."}/lightmap.ktx2 + lightmap.bake`));
|
|
222782
|
+
logErr(c.dim(` apply it with Lightmap.load(scene, { data: asset('<rel>/${relative4(root, join29(outDir, "lightmap.bake")).replace(/\\/g, "/")}'), texture: asset('<rel>/${relative4(root, ktx2).replace(/\\/g, "/")}') }) — asset() paths are relative to the calling file`));
|
|
222783
|
+
return;
|
|
222784
|
+
}
|
|
222785
|
+
if (lines.some((l2) => /no bake module/.test(l2))) {
|
|
222786
|
+
throw new CliError("This desktop host has no bake module. Build packages/desktop (CREATOR_GL_LIGHTMAP is on there) and point LECODES_DESKTOP_EXE at build/lecodes-desktop.exe, or update: lecodes desktop update.");
|
|
222787
|
+
}
|
|
222788
|
+
if (lines.some((l2) => /error/.test(l2)))
|
|
222789
|
+
throw new CliError("The bake failed — see the lines above.");
|
|
222790
|
+
warnErr("The host ran to its frame cap without baking. Make sure the app calls Lightmap.load(...) after the static scene is built, and skips menus when Lightmap.baking is true (a title screen never reaches the scene). More load headroom: --frames <n>. Host output: --logs.");
|
|
222791
|
+
throw new CliError("Nothing was baked.");
|
|
222792
|
+
};
|
|
222793
|
+
var PLACEHOLDER_KTX2 = Buffer.from("q0tUWCAyMLsNChoKAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAAaAAAACwAAACUAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAQAQAAAAAAABAAAAAAAAAAEAAAAAAAAAAsAAAAAAAAAAIAKACmAQIAAwMAABAAAAAAAAAAAAB/AAAAAAAAAAAA/////xEAAABLVFhvcmllbnRhdGlvbgByAAAAACcAAABLVFh3cml0ZXIAdG9rdHggdjQuNC4yIC8gbGlia3R4IHY0LjQuMgAAMwAAAEtUWHdyaXRlclNjUGFyYW1zAC0tZW5jb2RlIHVhc3RjIC0tdWFzdGNfcXVhbGl0eSAwAAD3////H3zvAQAAAAAAAAAA", "base64");
|
|
222794
|
+
var ensurePlaceholders = (outDir) => {
|
|
222795
|
+
const bake2 = join29(outDir, "lightmap.bake");
|
|
222796
|
+
const ktx2 = join29(outDir, "lightmap.ktx2");
|
|
222797
|
+
if (!existsSync27(bake2))
|
|
222798
|
+
writeFileSync21(bake2, JSON.stringify({ version: 1, placeholder: true, size: 1, texel: 0, instances: [] }) + `
|
|
222799
|
+
`);
|
|
222800
|
+
if (!existsSync27(ktx2))
|
|
222801
|
+
writeFileSync21(ktx2, PLACEHOLDER_KTX2);
|
|
222802
|
+
};
|
|
222803
|
+
var lightmap = async (args) => {
|
|
222804
|
+
const sub = args._[0];
|
|
222805
|
+
if (sub === "bake")
|
|
222806
|
+
return bake({ ...args, _: args._.slice(1) });
|
|
222807
|
+
throw new CliError("Usage: lecodes lightmap bake [--out assets/lightmap] [--size 4096] [--texel 0.02] [--sun-rays 32] [--ao-rays 64] [--ao-distance 2] [--bias 0.02] [--sun-angle 0.5] [--frames 900] [--entry main.ts] [--logs] (needs KTX-Software's toktx)");
|
|
222808
|
+
};
|
|
222809
|
+
|
|
222147
222810
|
// src/commands/login.ts
|
|
222148
222811
|
init_api();
|
|
222149
222812
|
init_config();
|
|
@@ -222161,8 +222824,9 @@ var login = async (args) => {
|
|
|
222161
222824
|
|
|
222162
222825
|
// src/commands/shaders.ts
|
|
222163
222826
|
init_manifest();
|
|
222164
|
-
import { readFileSync as
|
|
222827
|
+
import { readFileSync as readFileSync24 } from "node:fs";
|
|
222165
222828
|
init_util();
|
|
222829
|
+
init_compile();
|
|
222166
222830
|
var collectShaders = () => {
|
|
222167
222831
|
let root;
|
|
222168
222832
|
let manifest;
|
|
@@ -222188,8 +222852,27 @@ var status2 = async () => {
|
|
|
222188
222852
|
log(`shaders ${c.dim(`(compiled = up-to-date artifacts in ${shadersCacheDir(root)})`)}`);
|
|
222189
222853
|
const width = Math.max(...shaders.map((s2) => s2.path.length));
|
|
222190
222854
|
for (const shader of shaders) {
|
|
222191
|
-
|
|
222855
|
+
let state;
|
|
222856
|
+
if (matcId === null)
|
|
222857
|
+
state = c.dim("unknown (matc not downloaded)");
|
|
222858
|
+
else {
|
|
222859
|
+
const text = readFileSync24(shader.absPath, "utf8");
|
|
222860
|
+
if (shaderIsCompiled(root, text, matcId))
|
|
222861
|
+
state = "compiled";
|
|
222862
|
+
else {
|
|
222863
|
+
const present = shaderCompiledPlatforms(root, text, matcId);
|
|
222864
|
+
state = present.length === 0 ? c.yellow("not compiled — run `lecodes shaders compile`") : c.yellow(`partial (${present.join(", ")}) — \`lecodes shaders compile\` for the rest`);
|
|
222865
|
+
}
|
|
222866
|
+
}
|
|
222192
222867
|
log(` ${shader.path.padEnd(width)} ${state}`);
|
|
222868
|
+
const schema = parseShaderSchema(readFileSync24(shader.absPath, "utf8"));
|
|
222869
|
+
for (const p2 of schema.params) {
|
|
222870
|
+
const range = p2.min !== undefined ? ` [${p2.min}..${p2.max}]` : "";
|
|
222871
|
+
const def = p2.default !== null && p2.default !== undefined ? ` = ${JSON.stringify(p2.default)}` : "";
|
|
222872
|
+
log(` ${c.dim(p2.type.padEnd(10))} ${p2.name}${c.dim(` ${p2.editor}${range}${def}`)}${p2.tooltip ? c.dim(` — ${p2.tooltip}`) : ""}`);
|
|
222873
|
+
}
|
|
222874
|
+
for (const e2 of schema.errors)
|
|
222875
|
+
log(` ${c.yellow(e2)}`);
|
|
222193
222876
|
}
|
|
222194
222877
|
}
|
|
222195
222878
|
try {
|
|
@@ -222201,7 +222884,23 @@ var status2 = async () => {
|
|
|
222201
222884
|
log(`published ${c.dim(e2 instanceof CliError ? e2.message : "unknown (couldn't reach the release manifest)")}`);
|
|
222202
222885
|
}
|
|
222203
222886
|
};
|
|
222204
|
-
var
|
|
222887
|
+
var parsePlatforms = (args) => {
|
|
222888
|
+
const raw = flagStr(args, "platform", "platforms");
|
|
222889
|
+
if (raw === undefined)
|
|
222890
|
+
return;
|
|
222891
|
+
const known = allShaderPlatforms();
|
|
222892
|
+
const asked = raw.split(",").map((p2) => p2.trim()).filter(Boolean);
|
|
222893
|
+
if (asked.length === 0)
|
|
222894
|
+
throw new CliError(`--platform needs at least one target. Known: ${known.join(", ")}`);
|
|
222895
|
+
const bad = asked.filter((p2) => !known.includes(p2));
|
|
222896
|
+
if (bad.length > 0) {
|
|
222897
|
+
throw new CliError(`Unknown shader platform${bad.length > 1 ? "s" : ""}: ${bad.join(", ")}
|
|
222898
|
+
Known: ${known.join(", ")}`);
|
|
222899
|
+
}
|
|
222900
|
+
return asked;
|
|
222901
|
+
};
|
|
222902
|
+
var compile3 = async (args) => {
|
|
222903
|
+
const platforms = parsePlatforms(args);
|
|
222205
222904
|
const { root, entries, shaders } = collectShaders();
|
|
222206
222905
|
if (shaders.length === 0) {
|
|
222207
222906
|
note("No .mat shaders in this project.");
|
|
@@ -222209,11 +222908,11 @@ var compile3 = async () => {
|
|
|
222209
222908
|
}
|
|
222210
222909
|
const matcExe = await resolveMatcExe();
|
|
222211
222910
|
const matcId = matcIdentity(matcExe);
|
|
222212
|
-
const wasCompiled = new Map(shaders.map((s2) => [s2.path, shaderIsCompiled(root,
|
|
222911
|
+
const wasCompiled = new Map(shaders.map((s2) => [s2.path, shaderIsCompiled(root, readFileSync24(s2.absPath, "utf8"), matcId)]));
|
|
222213
222912
|
const errors2 = [];
|
|
222214
222913
|
for (const shader of shaders) {
|
|
222215
222914
|
try {
|
|
222216
|
-
const { warnings } = await applyLocalShaders([shader], { root, local: true });
|
|
222915
|
+
const { warnings } = await applyLocalShaders([shader], { root, local: true, platforms });
|
|
222217
222916
|
for (const w of warnings)
|
|
222218
222917
|
warnErr(w);
|
|
222219
222918
|
log(` ${shader.path} ${wasCompiled.get(shader.path) ? c.dim("compiled (cached)") : "compiled"}`);
|
|
@@ -222228,22 +222927,23 @@ var compile3 = async () => {
|
|
|
222228
222927
|
${c.red("✗")} ${e2}`);
|
|
222229
222928
|
if (errors2.length > 0)
|
|
222230
222929
|
throw new CliError(`${errors2.length} of ${shaders.length} shader${shaders.length === 1 ? "" : "s"} failed to compile.`);
|
|
222231
|
-
|
|
222930
|
+
const scope = platforms ? ` [${platforms.join(", ")}]` : "";
|
|
222931
|
+
note(`${shaders.length} shader${shaders.length === 1 ? "" : "s"} compiled${scope} → ${shadersCacheDir(root)}`);
|
|
222232
222932
|
};
|
|
222233
222933
|
var shaders = async (args) => {
|
|
222234
222934
|
const sub = args._[0];
|
|
222235
222935
|
if (sub === undefined)
|
|
222236
222936
|
return status2();
|
|
222237
222937
|
if (sub === "compile")
|
|
222238
|
-
return compile3();
|
|
222938
|
+
return compile3(args);
|
|
222239
222939
|
if (sub === "update")
|
|
222240
222940
|
return updateMatc();
|
|
222241
|
-
throw new CliError(`Unknown subcommand "shaders ${sub}". Use \`lecodes shaders\` (status), \`lecodes shaders compile\` or \`lecodes shaders update\`.`);
|
|
222941
|
+
throw new CliError(`Unknown subcommand "shaders ${sub}". Use \`lecodes shaders\` (status), \`lecodes shaders compile [--platform a,b]\` or \`lecodes shaders update\`.`);
|
|
222242
222942
|
};
|
|
222243
222943
|
|
|
222244
222944
|
// src/commands/render.ts
|
|
222245
|
-
import { mkdirSync as
|
|
222246
|
-
import { dirname as dirname15, resolve as
|
|
222945
|
+
import { mkdirSync as mkdirSync21, readFileSync as readFileSync26, writeFileSync as writeFileSync22 } from "node:fs";
|
|
222946
|
+
import { dirname as dirname15, resolve as resolve15 } from "node:path";
|
|
222247
222947
|
|
|
222248
222948
|
// src/compile/headlessBundle.ts
|
|
222249
222949
|
init_api();
|
|
@@ -222254,7 +222954,7 @@ init_fonts();
|
|
|
222254
222954
|
init_assetIcons();
|
|
222255
222955
|
init_util();
|
|
222256
222956
|
var LOCAL_ASSET_SCHEME2 = "lecodesfile://";
|
|
222257
|
-
var compileHeadlessBundle = async (args) => {
|
|
222957
|
+
var compileHeadlessBundle = async (args, opts = {}) => {
|
|
222258
222958
|
let root;
|
|
222259
222959
|
let manifest;
|
|
222260
222960
|
try {
|
|
@@ -222289,7 +222989,9 @@ var compileHeadlessBundle = async (args) => {
|
|
|
222289
222989
|
warnings.push("This is a local project (no server) — assets always load from the working tree.");
|
|
222290
222990
|
}
|
|
222291
222991
|
}
|
|
222292
|
-
const compileEntries = useLocalAssets ? entries.map((e2) => e2.type === "resource" && e2.absPath ? { ...e2, fileSrc: encodeURIComponent(e2.absPath) } : e2) : entries;
|
|
222992
|
+
const compileEntries = useLocalAssets ? entries.map((e2) => e2.type === "resource" && e2.absPath ? { ...e2, fileSrc: encodeURIComponent(e2.absPath) } : e2) : [...entries];
|
|
222993
|
+
if (opts.screen)
|
|
222994
|
+
compileEntries.push(opts.screen.entry);
|
|
222293
222995
|
await applyProjectFonts(compileEntries, useLocalAssets ? {
|
|
222294
222996
|
mode: "vendor",
|
|
222295
222997
|
root,
|
|
@@ -222299,7 +223001,7 @@ var compileHeadlessBundle = async (args) => {
|
|
|
222299
223001
|
} : { mode: "cdn", root });
|
|
222300
223002
|
await applyAppAssetIcons(compileEntries);
|
|
222301
223003
|
note("Compiling…");
|
|
222302
|
-
const entryFlag = flagStr(args, "entry");
|
|
223004
|
+
const entryFlag = opts.screen?.entryPath ?? flagStr(args, "entry");
|
|
222303
223005
|
if (!entryFlag) {
|
|
222304
223006
|
const ambiguous = ambiguousEntryWarning(compileEntries);
|
|
222305
223007
|
if (ambiguous)
|
|
@@ -222322,12 +223024,84 @@ var compileHeadlessBundle = async (args) => {
|
|
|
222322
223024
|
return { js, root, warnings, useLocalAssets };
|
|
222323
223025
|
};
|
|
222324
223026
|
|
|
223027
|
+
// src/compile/screenEntry.ts
|
|
223028
|
+
init_designMeta();
|
|
223029
|
+
init_util();
|
|
223030
|
+
import { existsSync as existsSync28, readFileSync as readFileSync25, statSync as statSync10 } from "node:fs";
|
|
223031
|
+
import { isAbsolute, join as join30, relative as relative5, resolve as resolve13 } from "node:path";
|
|
223032
|
+
var SCREEN_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs"];
|
|
223033
|
+
var SCREEN_ENTRY_PATH = "/.screen-entry.ts";
|
|
223034
|
+
var DEFAULT_DESIGN_DIR = "design";
|
|
223035
|
+
var screenArg = (args) => args._[0];
|
|
223036
|
+
var hasSeparator = (file) => file.includes("/") || file.includes("\\");
|
|
223037
|
+
var findScreenFile = (root, file, dirFlag) => {
|
|
223038
|
+
const candidates = [resolve13(process.cwd(), file)];
|
|
223039
|
+
if (!hasSeparator(file)) {
|
|
223040
|
+
candidates.push(join30(root, (dirFlag ?? DEFAULT_DESIGN_DIR).replace(/[/\\]+$/, ""), "screens", file));
|
|
223041
|
+
}
|
|
223042
|
+
for (const base of candidates) {
|
|
223043
|
+
if (existsSync28(base) && statSync10(base).isFile())
|
|
223044
|
+
return base;
|
|
223045
|
+
for (const ext2 of SCREEN_EXTS)
|
|
223046
|
+
if (existsSync28(base + ext2))
|
|
223047
|
+
return base + ext2;
|
|
223048
|
+
}
|
|
223049
|
+
throw new CliError(`No screen file at "${file}".` + (hasSeparator(file) ? "" : ` Pass a path, or a screen id that exists in ${dirFlag ?? DEFAULT_DESIGN_DIR}/screens/.`));
|
|
223050
|
+
};
|
|
223051
|
+
var designDirOf = (root, key) => {
|
|
223052
|
+
const m = /^\/(.*)screens\/([^/]+)\.[^./]+$/.exec(key);
|
|
223053
|
+
if (!m)
|
|
223054
|
+
return null;
|
|
223055
|
+
const dir = join30(root, m[1]);
|
|
223056
|
+
return existsSync28(join30(dir, "meta.json")) ? { dir, id: m[2] } : null;
|
|
223057
|
+
};
|
|
223058
|
+
var resolveScreenTarget = (root, file, opts = {}) => {
|
|
223059
|
+
const abs = findScreenFile(root, file, opts.dir);
|
|
223060
|
+
const rel = relative5(root, abs);
|
|
223061
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
223062
|
+
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.`);
|
|
223063
|
+
}
|
|
223064
|
+
const key = toKey(rel);
|
|
223065
|
+
if (!/^\s*export\s+default\b/m.test(readFileSync25(abs, "utf8"))) {
|
|
223066
|
+
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.`);
|
|
223067
|
+
}
|
|
223068
|
+
const invoke = opts.state !== undefined ? `screen(${JSON.stringify(opts.state)})` : "screen()";
|
|
223069
|
+
const spec = "." + key.replace(/\.[^./]+$/, "");
|
|
223070
|
+
const design2 = designDirOf(root, key);
|
|
223071
|
+
return {
|
|
223072
|
+
entryPath: SCREEN_ENTRY_PATH,
|
|
223073
|
+
entry: {
|
|
223074
|
+
path: SCREEN_ENTRY_PATH,
|
|
223075
|
+
type: "text",
|
|
223076
|
+
text: `import screen from ${JSON.stringify(spec)}
|
|
223077
|
+
` + `const __screen = typeof screen === "function" ? ${invoke} : screen
|
|
223078
|
+
` + `__screen.open()
|
|
223079
|
+
`
|
|
223080
|
+
},
|
|
223081
|
+
frame: design2 ? screenFrame(design2.dir, design2.id) : undefined,
|
|
223082
|
+
label: (design2 ? design2.id : key.slice(1)) + (opts.state !== undefined ? `@${opts.state}` : "")
|
|
223083
|
+
};
|
|
223084
|
+
};
|
|
223085
|
+
var screenTargetFromArgs = (root, args) => {
|
|
223086
|
+
const file = screenArg(args);
|
|
223087
|
+
if (file === undefined) {
|
|
223088
|
+
if (flagStr(args, "state") !== undefined) {
|
|
223089
|
+
throw new CliError("--state picks a screen's state — name the screen file too: lecodes render <file> --state <name>");
|
|
223090
|
+
}
|
|
223091
|
+
return;
|
|
223092
|
+
}
|
|
223093
|
+
if (flagStr(args, "entry") !== undefined) {
|
|
223094
|
+
throw new CliError("Use either a screen file or --entry, not both — each names the module to compile from.");
|
|
223095
|
+
}
|
|
223096
|
+
return resolveScreenTarget(root, file, { state: flagStr(args, "state"), dir: flagStr(args, "dir") });
|
|
223097
|
+
};
|
|
223098
|
+
|
|
222325
223099
|
// src/commands/render.ts
|
|
222326
223100
|
init_manifest();
|
|
222327
223101
|
|
|
222328
223102
|
// src/desktopScript.ts
|
|
222329
223103
|
init_util();
|
|
222330
|
-
import { resolve as
|
|
223104
|
+
import { resolve as resolve14 } from "node:path";
|
|
222331
223105
|
var isPoint = (v) => typeof v === "object" && v !== null && typeof v.x === "number" && typeof v.y === "number";
|
|
222332
223106
|
var FRAME_MS = 16;
|
|
222333
223107
|
var ACTIONS = ["tap", "type", "fill", "scroll", "wait", "frames", "drag", "hold", "key", "keyDown", "keyUp", "look", "mouseTo", "wheel", "stick", "gamepad", "camera", "view"];
|
|
@@ -222355,7 +223129,7 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
222355
223129
|
}
|
|
222356
223130
|
throw new CliError(`${at}: "${key}" must be { "x", "y" }.`);
|
|
222357
223131
|
};
|
|
222358
|
-
const
|
|
223132
|
+
const num2 = (at, key, v, def) => {
|
|
222359
223133
|
if (v === undefined && def !== undefined)
|
|
222360
223134
|
return def;
|
|
222361
223135
|
if (typeof v !== "number")
|
|
@@ -222382,7 +223156,7 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
222382
223156
|
}
|
|
222383
223157
|
if (action === "view") {
|
|
222384
223158
|
const v = s2.view;
|
|
222385
|
-
const fov = s2.fov !== undefined ?
|
|
223159
|
+
const fov = s2.fov !== undefined ? num2(at, "fov", s2.fov) : undefined;
|
|
222386
223160
|
if (v === "off") {
|
|
222387
223161
|
plan.lines.push(emit("view off"));
|
|
222388
223162
|
inspecting = false;
|
|
@@ -222402,7 +223176,7 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
222402
223176
|
parts.push("camera off");
|
|
222403
223177
|
} else if (typeof cam === "object" && cam !== null && isVec3(cam.at) && isVec3(cam.lookAt)) {
|
|
222404
223178
|
const { at: eye, lookAt, fov } = cam;
|
|
222405
|
-
const fovN = fov !== undefined ?
|
|
223179
|
+
const fovN = fov !== undefined ? num2(at, "camera.fov", fov) : undefined;
|
|
222406
223180
|
plan.lines.push(emit(`camera ${eye.join(" ")} ${lookAt.join(" ")}${fovN !== undefined ? ` ${fovN}` : ""}`));
|
|
222407
223181
|
inspecting = true;
|
|
222408
223182
|
parts.push(`camera at (${eye.join(",")}) → (${lookAt.join(",")})`);
|
|
@@ -222419,20 +223193,20 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
222419
223193
|
parts.push(`tap ${pt(p2)}`);
|
|
222420
223194
|
} else if (action === "drag") {
|
|
222421
223195
|
const a2 = coord(at, "drag", s2.drag), b = coord(at, "to", s2.to);
|
|
222422
|
-
const ms =
|
|
223196
|
+
const ms = num2(at, "ms", s2.ms, 300);
|
|
222423
223197
|
plan.lines.push(emit(`drag ${a2.x} ${a2.y} ${b.x} ${b.y} ${Math.round(ms)}`));
|
|
222424
223198
|
budgetMs += ms + 4 * FRAME_MS;
|
|
222425
223199
|
parts.push(`drag ${pt(a2)} → ${pt(b)} over ${ms}ms`);
|
|
222426
223200
|
} else if (action === "hold") {
|
|
222427
223201
|
const p2 = coord(at, "hold", s2.hold);
|
|
222428
|
-
const ms =
|
|
223202
|
+
const ms = num2(at, "ms", s2.ms);
|
|
222429
223203
|
plan.lines.push(emit(`hold ${p2.x} ${p2.y} ${Math.round(ms)}`));
|
|
222430
223204
|
budgetMs += ms + 4 * FRAME_MS;
|
|
222431
223205
|
parts.push(`hold ${pt(p2)} ${ms}ms`);
|
|
222432
223206
|
} else if (action === "key") {
|
|
222433
223207
|
if (typeof s2.key !== "string")
|
|
222434
223208
|
throw new CliError(`${at}: "key" must be a KeyboardEvent.code string.`);
|
|
222435
|
-
const ms =
|
|
223209
|
+
const ms = num2(at, "ms", s2.ms);
|
|
222436
223210
|
plan.lines.push(emit(`key ${s2.key} ${Math.round(ms)}`));
|
|
222437
223211
|
budgetMs += ms + 2 * FRAME_MS;
|
|
222438
223212
|
parts.push(`key ${s2.key} ${ms}ms`);
|
|
@@ -222447,7 +223221,7 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
222447
223221
|
const v = s2.look;
|
|
222448
223222
|
if (!Array.isArray(v) || v.length !== 2 || !v.every((n2) => typeof n2 === "number"))
|
|
222449
223223
|
throw new CliError(`${at}: "look" must be [dx, dy] (logical px of mouse motion).`);
|
|
222450
|
-
const ms =
|
|
223224
|
+
const ms = num2(at, "ms", s2.ms, FRAME_MS);
|
|
222451
223225
|
plan.lines.push(emit(`look ${v[0]} ${v[1]} ${Math.round(ms)}`));
|
|
222452
223226
|
budgetMs += ms + 2 * FRAME_MS;
|
|
222453
223227
|
parts.push(`look ${v[0]},${v[1]} over ${ms}ms`);
|
|
@@ -222493,12 +223267,12 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
222493
223267
|
budgetMs += 2 * FRAME_MS;
|
|
222494
223268
|
parts.push(`gamepad ${s2.gamepad} #${pad}`);
|
|
222495
223269
|
} else if (action === "wait") {
|
|
222496
|
-
const ms =
|
|
223270
|
+
const ms = num2(at, "wait", s2.wait);
|
|
222497
223271
|
plan.lines.push(emit(`wait ${Math.round(ms)}`));
|
|
222498
223272
|
budgetMs += ms + FRAME_MS;
|
|
222499
223273
|
parts.push(`wait ${ms}ms`);
|
|
222500
223274
|
} else if (action === "frames") {
|
|
222501
|
-
const n2 =
|
|
223275
|
+
const n2 = num2(at, "frames", s2.frames);
|
|
222502
223276
|
plan.lines.push(emit(`frames ${Math.round(n2)}`));
|
|
222503
223277
|
budgetMs += n2 * FRAME_MS + FRAME_MS;
|
|
222504
223278
|
parts.push(`advance ${n2} frames`);
|
|
@@ -222528,7 +223302,7 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
222528
223302
|
if (s2.screenshot !== undefined) {
|
|
222529
223303
|
if (typeof s2.screenshot !== "string")
|
|
222530
223304
|
throw new CliError(`${at}: "screenshot" must be a path.`);
|
|
222531
|
-
plan.screenshot =
|
|
223305
|
+
plan.screenshot = resolve14(opts.outDir, s2.screenshot);
|
|
222532
223306
|
plan.lines.push(emit(`frames 2`));
|
|
222533
223307
|
plan.lines.push(emit(`shot ${plan.screenshot}`));
|
|
222534
223308
|
budgetMs += 3 * FRAME_MS;
|
|
@@ -222637,9 +223411,9 @@ var requireRenderer = async () => {
|
|
|
222637
223411
|
};
|
|
222638
223412
|
var fileScenarioIO = (dir) => ({
|
|
222639
223413
|
write: (relPath, data) => {
|
|
222640
|
-
const abs =
|
|
222641
|
-
|
|
222642
|
-
|
|
223414
|
+
const abs = resolve15(dir, relPath);
|
|
223415
|
+
mkdirSync21(dirname15(abs), { recursive: true });
|
|
223416
|
+
writeFileSync22(abs, data);
|
|
222643
223417
|
}
|
|
222644
223418
|
});
|
|
222645
223419
|
var resolveFixtures = (value, baseDir) => {
|
|
@@ -222647,9 +223421,9 @@ var resolveFixtures = (value, baseDir) => {
|
|
|
222647
223421
|
return;
|
|
222648
223422
|
let map = value;
|
|
222649
223423
|
if (typeof value === "string") {
|
|
222650
|
-
const abs =
|
|
223424
|
+
const abs = resolve15(baseDir, value);
|
|
222651
223425
|
try {
|
|
222652
|
-
map = JSON.parse(
|
|
223426
|
+
map = JSON.parse(readFileSync26(abs, "utf8"));
|
|
222653
223427
|
} catch (e2) {
|
|
222654
223428
|
throw new CliError(`Can't read fixtures "${value}": ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
222655
223429
|
}
|
|
@@ -222662,7 +223436,7 @@ var resolveFixtures = (value, baseDir) => {
|
|
|
222662
223436
|
var readScenarioFile = (path2) => {
|
|
222663
223437
|
let raw;
|
|
222664
223438
|
try {
|
|
222665
|
-
raw = JSON.parse(
|
|
223439
|
+
raw = JSON.parse(readFileSync26(resolve15(path2), "utf8"));
|
|
222666
223440
|
} catch (e2) {
|
|
222667
223441
|
throw new CliError(`Can't read scenario "${path2}": ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
222668
223442
|
}
|
|
@@ -222697,10 +223471,11 @@ var renderDesktop = async (args) => {
|
|
|
222697
223471
|
root = process.cwd();
|
|
222698
223472
|
}
|
|
222699
223473
|
const win = readAppConfigAt(root)?.desktop?.window;
|
|
223474
|
+
const screen2 = screenTargetFromArgs(root, args);
|
|
222700
223475
|
const scriptFlag = flagStr(args, "script");
|
|
222701
223476
|
const scenario = scriptFlag !== undefined ? readScenarioFile(scriptFlag) : null;
|
|
222702
|
-
const width = Number(flagStr(args, "width")) || scenario?.width || win?.width || 390;
|
|
222703
|
-
const height = Number(flagStr(args, "height")) || scenario?.height || win?.height || 844;
|
|
223477
|
+
const width = Number(flagStr(args, "width")) || scenario?.width || screen2?.frame?.[0] || win?.width || 390;
|
|
223478
|
+
const height = Number(flagStr(args, "height")) || scenario?.height || screen2?.frame?.[1] || win?.height || 844;
|
|
222704
223479
|
const frames = Number(flagStr(args, "frames")) || 60;
|
|
222705
223480
|
const pngFlag = flagStr(args, "png") ?? "screenshot.png";
|
|
222706
223481
|
const logs = flagBool(args, "logs");
|
|
@@ -222711,17 +223486,24 @@ var renderDesktop = async (args) => {
|
|
|
222711
223486
|
const { camera, view } = resolveInspectionCamera(args);
|
|
222712
223487
|
if ((camera || view) && scenario)
|
|
222713
223488
|
throw new CliError('--camera/--view apply to a plain render; in a --script run use a { "view": "iso" } or { "camera": { "at", "lookAt" } } step before the screenshot.');
|
|
222714
|
-
const
|
|
222715
|
-
|
|
223489
|
+
const desktopRenderer = resolveDesktopRenderer(root, args);
|
|
223490
|
+
const { js, resources } = await buildBundle(args, undefined, {
|
|
223491
|
+
localAssets: true,
|
|
223492
|
+
desktopRenderer,
|
|
223493
|
+
extraEntries: screen2 ? [screen2.entry] : undefined,
|
|
223494
|
+
entryOverride: screen2?.entryPath
|
|
223495
|
+
});
|
|
223496
|
+
mkdirSync21(dirname15(resolve15(pngFlag)), { recursive: true });
|
|
222716
223497
|
if (scenario) {
|
|
222717
223498
|
const outDir = flagStr(args, "out-dir") ?? ".";
|
|
222718
223499
|
const plan = translateScenario(scenario.steps, { outDir, headroomFrames: frames });
|
|
222719
223500
|
for (const st of plan.steps)
|
|
222720
223501
|
if (st.screenshot)
|
|
222721
|
-
|
|
223502
|
+
mkdirSync21(dirname15(st.screenshot), { recursive: true });
|
|
222722
223503
|
const timeoutMs2 = Number(flagStr(args, "timeout")) || 60000 + plan.steps.length * 5000;
|
|
222723
223504
|
note(`Running ${plan.steps.length} step${plan.steps.length === 1 ? "" : "s"} (native)…`);
|
|
222724
223505
|
const { output } = await runDesktopRender(js, resources, {
|
|
223506
|
+
renderer: desktopRenderer,
|
|
222725
223507
|
width,
|
|
222726
223508
|
height,
|
|
222727
223509
|
frames: plan.frameCap,
|
|
@@ -222751,8 +223533,9 @@ var renderDesktop = async (args) => {
|
|
|
222751
223533
|
process.exit(result.ok ? 0 : 1);
|
|
222752
223534
|
}
|
|
222753
223535
|
const timeoutMs = Number(flagStr(args, "timeout")) || 60000;
|
|
222754
|
-
note("Rendering (native)…");
|
|
223536
|
+
note(screen2 ? `Rendering ${screen2.label} (native)…` : "Rendering (native)…");
|
|
222755
223537
|
await runDesktopRender(js, resources, {
|
|
223538
|
+
renderer: desktopRenderer,
|
|
222756
223539
|
width,
|
|
222757
223540
|
height,
|
|
222758
223541
|
frames,
|
|
@@ -222798,6 +223581,13 @@ var render = async (args) => {
|
|
|
222798
223581
|
return renderDesktop(args);
|
|
222799
223582
|
const scriptFlag = flagStr(args, "script");
|
|
222800
223583
|
const scenario = scriptFlag !== undefined ? readScenarioFile(scriptFlag) : null;
|
|
223584
|
+
let projectRoot2;
|
|
223585
|
+
try {
|
|
223586
|
+
projectRoot2 = findProjectRoot(process.cwd());
|
|
223587
|
+
} catch {
|
|
223588
|
+
projectRoot2 = process.cwd();
|
|
223589
|
+
}
|
|
223590
|
+
const screen2 = screenTargetFromArgs(projectRoot2, args);
|
|
222801
223591
|
const stepCount = scenario && Array.isArray(scenario.steps) ? scenario.steps.length : 0;
|
|
222802
223592
|
const timeoutMs = Number(flagStr(args, "timeout")) || 15000 + stepCount * 3000;
|
|
222803
223593
|
const killer = setTimeout(() => {
|
|
@@ -222805,15 +223595,15 @@ var render = async (args) => {
|
|
|
222805
223595
|
process.exit(1);
|
|
222806
223596
|
}, timeoutMs);
|
|
222807
223597
|
killer.unref?.();
|
|
222808
|
-
const { js, warnings, useLocalAssets, root } = await compileHeadlessBundle(args);
|
|
223598
|
+
const { js, warnings, useLocalAssets, root } = await compileHeadlessBundle(args, { screen: screen2 });
|
|
222809
223599
|
const renderer = await requireRenderer();
|
|
222810
223600
|
const projectViewport = desktopDefaultViewport(root);
|
|
222811
|
-
const width = Number(flagStr(args, "width")) || scenario?.width || projectViewport?.width || 390;
|
|
222812
|
-
const height = Number(flagStr(args, "height")) || scenario?.height || projectViewport?.height || 844;
|
|
223601
|
+
const width = Number(flagStr(args, "width")) || scenario?.width || screen2?.frame?.[0] || projectViewport?.width || 390;
|
|
223602
|
+
const height = Number(flagStr(args, "height")) || scenario?.height || screen2?.frame?.[1] || projectViewport?.height || 844;
|
|
222813
223603
|
const settleStr = flagStr(args, "settle") ?? (scenario?.settle !== undefined ? String(scenario.settle) : undefined);
|
|
222814
223604
|
const timeStr = flagStr(args, "time");
|
|
222815
223605
|
const onConsole = flagBool(args, "logs") ? (level, text2) => logErr(`${c.dim(`[${level}]`)} ${text2}`) : undefined;
|
|
222816
|
-
const envelopeFixtures = resolveFixtures(scenario?.fixtures, scriptFlag !== undefined ? dirname15(
|
|
223606
|
+
const envelopeFixtures = resolveFixtures(scenario?.fixtures, scriptFlag !== undefined ? dirname15(resolve15(scriptFlag)) : ".");
|
|
222817
223607
|
const flagFixtures = resolveFixtures(flagStr(args, "fixtures"), ".");
|
|
222818
223608
|
const fixtures = envelopeFixtures || flagFixtures ? { ...envelopeFixtures, ...flagFixtures } : undefined;
|
|
222819
223609
|
if (fixtures) {
|
|
@@ -222852,7 +223642,7 @@ var render = async (args) => {
|
|
|
222852
223642
|
${c.red(st.error ?? "failed")}`);
|
|
222853
223643
|
}
|
|
222854
223644
|
if (!result2.ok && result2.failureArtifacts.length > 0) {
|
|
222855
|
-
logErr(c.dim(` failing frame: ${result2.failureArtifacts.map((p2) =>
|
|
223645
|
+
logErr(c.dim(` failing frame: ${result2.failureArtifacts.map((p2) => resolve15(outDir, p2)).join(", ")}`));
|
|
222856
223646
|
}
|
|
222857
223647
|
logErr(result2.ok ? c.green(`✓ ${result2.steps.length}/${steps.length} steps passed`) : c.red(`✗ failed at step ${result2.steps.length} of ${steps.length}`));
|
|
222858
223648
|
return done((cb) => cb(), result2.ok ? 0 : 1);
|
|
@@ -222862,7 +223652,7 @@ var render = async (args) => {
|
|
|
222862
223652
|
}
|
|
222863
223653
|
const pngFlag = flagStr(args, "png") ?? (flagBool(args, "png") ? "screenshot.png" : undefined);
|
|
222864
223654
|
if (pngFlag !== undefined) {
|
|
222865
|
-
note("Rendering PNG…");
|
|
223655
|
+
note(screen2 ? `Rendering ${screen2.label} (PNG)…` : "Rendering PNG…");
|
|
222866
223656
|
const clip = parseClip(flagStr(args, "clip"));
|
|
222867
223657
|
const { png: png2, width: outW, height: outH, warnings: pngWarnings } = await renderer.renderToPng(js, {
|
|
222868
223658
|
width,
|
|
@@ -222876,12 +223666,13 @@ var render = async (args) => {
|
|
|
222876
223666
|
if (isNullSink(pngFlag)) {
|
|
222877
223667
|
note(`Discarded PNG (${pngFlag})`);
|
|
222878
223668
|
} else {
|
|
222879
|
-
|
|
223669
|
+
mkdirSync21(dirname15(resolve15(pngFlag)), { recursive: true });
|
|
223670
|
+
writeFileSync22(resolve15(pngFlag), png2);
|
|
222880
223671
|
note(`Wrote ${pngFlag} (${png2.length} bytes, ${outW}x${outH}${clip !== undefined ? ", clipped" : ""})`);
|
|
222881
223672
|
}
|
|
222882
223673
|
return done((cb) => cb());
|
|
222883
223674
|
}
|
|
222884
|
-
note("Rendering…");
|
|
223675
|
+
note(screen2 ? `Rendering ${screen2.label}…` : "Rendering…");
|
|
222885
223676
|
const result = await renderer.renderToJson(js, { width, height, ...timing });
|
|
222886
223677
|
for (const w of [...warnings, ...result.warnings])
|
|
222887
223678
|
warnErr(w);
|
|
@@ -222891,7 +223682,7 @@ var render = async (args) => {
|
|
|
222891
223682
|
return done((cb) => cb());
|
|
222892
223683
|
}
|
|
222893
223684
|
if (out) {
|
|
222894
|
-
|
|
223685
|
+
writeFileSync22(resolve15(out), result.text);
|
|
222895
223686
|
note(`Wrote ${out} (${result.text.length} bytes)`);
|
|
222896
223687
|
return done((cb) => cb());
|
|
222897
223688
|
}
|
|
@@ -222906,8 +223697,8 @@ init_api();
|
|
|
222906
223697
|
init_config();
|
|
222907
223698
|
init_manifest();
|
|
222908
223699
|
init_util();
|
|
222909
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
222910
|
-
import { join as
|
|
223700
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync29, readFileSync as readFileSync27, writeFileSync as writeFileSync23 } from "node:fs";
|
|
223701
|
+
import { join as join31 } from "node:path";
|
|
222911
223702
|
var ENV_VAR = "LECODES_PUSH_KEY";
|
|
222912
223703
|
var context = () => {
|
|
222913
223704
|
const root = findProjectRoot(process.cwd());
|
|
@@ -223051,11 +223842,11 @@ var pnKeysList = async (ctx) => {
|
|
|
223051
223842
|
};
|
|
223052
223843
|
var writeEnv = (root, key) => {
|
|
223053
223844
|
const touched = [];
|
|
223054
|
-
const envPath =
|
|
223845
|
+
const envPath = join31(root, ".env");
|
|
223055
223846
|
const line = `${ENV_VAR}=${key}`;
|
|
223056
|
-
const existing =
|
|
223847
|
+
const existing = existsSync29(envPath) ? readFileSync27(envPath, "utf8") : "";
|
|
223057
223848
|
if (new RegExp(`^${ENV_VAR}=`, "m").test(existing)) {
|
|
223058
|
-
|
|
223849
|
+
writeFileSync23(envPath, existing.replace(new RegExp(`^${ENV_VAR}=.*$`, "m"), line));
|
|
223059
223850
|
} else {
|
|
223060
223851
|
const separator = !existing || existing.endsWith(`
|
|
223061
223852
|
`) ? "" : `
|
|
@@ -223064,8 +223855,8 @@ var writeEnv = (root, key) => {
|
|
|
223064
223855
|
`);
|
|
223065
223856
|
}
|
|
223066
223857
|
touched.push(".env");
|
|
223067
|
-
const ignorePath =
|
|
223068
|
-
const ignore =
|
|
223858
|
+
const ignorePath = join31(root, ".gitignore");
|
|
223859
|
+
const ignore = existsSync29(ignorePath) ? readFileSync27(ignorePath, "utf8") : "";
|
|
223069
223860
|
if (!ignore.split(`
|
|
223070
223861
|
`).some((l2) => l2.trim() === ".env")) {
|
|
223071
223862
|
const separator = !ignore || ignore.endsWith(`
|
|
@@ -223164,8 +223955,8 @@ var pn = async (args) => {
|
|
|
223164
223955
|
init_api();
|
|
223165
223956
|
init_config();
|
|
223166
223957
|
init_manifest();
|
|
223167
|
-
import { existsSync as
|
|
223168
|
-
import { dirname as dirname16, join as
|
|
223958
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync22, rmSync as rmSync9, writeFileSync as writeFileSync24 } from "node:fs";
|
|
223959
|
+
import { dirname as dirname16, join as join32 } from "node:path";
|
|
223169
223960
|
init_types4();
|
|
223170
223961
|
init_localFiles();
|
|
223171
223962
|
init_util();
|
|
@@ -223188,9 +223979,9 @@ var pull = async (args) => {
|
|
|
223188
223979
|
const remotePaths = new Set([...buildPaths(project.assets).entries()].filter(([id]) => project.assets.find((a2) => a2.id === id)?.type !== "folder").map(([, path2]) => path2));
|
|
223189
223980
|
for (const path2 of Object.keys(manifest.files)) {
|
|
223190
223981
|
if (!remotePaths.has(path2)) {
|
|
223191
|
-
const abs =
|
|
223192
|
-
if (
|
|
223193
|
-
|
|
223982
|
+
const abs = join32(root, ...path2.replace(/^\//, "").split("/"));
|
|
223983
|
+
if (existsSync30(abs))
|
|
223984
|
+
rmSync9(abs);
|
|
223194
223985
|
}
|
|
223195
223986
|
}
|
|
223196
223987
|
const files = await materializeAssets(root, project, apiUrl, token, progress);
|
|
@@ -223227,18 +224018,18 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
|
|
|
223227
224018
|
const files = { ...manifest.files };
|
|
223228
224019
|
for (const asset of targets) {
|
|
223229
224020
|
const key = pathById.get(asset.id);
|
|
223230
|
-
const abs =
|
|
223231
|
-
|
|
224021
|
+
const abs = join32(root, ...key.replace(/^\//, "").split("/"));
|
|
224022
|
+
mkdirSync22(dirname16(abs), { recursive: true });
|
|
223232
224023
|
let sha;
|
|
223233
224024
|
if (asset.type === "file") {
|
|
223234
224025
|
if (!asset.file?.src)
|
|
223235
224026
|
continue;
|
|
223236
224027
|
const bytes = await downloadBlob(apiUrl, asset.file.src);
|
|
223237
|
-
|
|
224028
|
+
writeFileSync24(abs, bytes);
|
|
223238
224029
|
sha = sha256(bytes);
|
|
223239
224030
|
} else {
|
|
223240
224031
|
const text = asset.text ?? "";
|
|
223241
|
-
|
|
224032
|
+
writeFileSync24(abs, text);
|
|
223242
224033
|
sha = sha256(text);
|
|
223243
224034
|
}
|
|
223244
224035
|
files[key] = { id: asset.id, type: kindOf3(asset.type), sha };
|
|
@@ -223251,8 +224042,8 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
|
|
|
223251
224042
|
|
|
223252
224043
|
// src/commands/scene.ts
|
|
223253
224044
|
init_manifest();
|
|
223254
|
-
import { existsSync as
|
|
223255
|
-
import { basename as basename15, dirname as dirname17, join as
|
|
224045
|
+
import { existsSync as existsSync32, readFileSync as readFileSync29, readdirSync as readdirSync13, statSync as statSync11, writeFileSync as writeFileSync26 } from "node:fs";
|
|
224046
|
+
import { basename as basename15, dirname as dirname17, join as join34, resolve as resolve16 } from "node:path";
|
|
223256
224047
|
|
|
223257
224048
|
// src/compile/sceneCompile.ts
|
|
223258
224049
|
init_localFiles();
|
|
@@ -223260,18 +224051,19 @@ init_ignore();
|
|
|
223260
224051
|
init_distRoot();
|
|
223261
224052
|
init_util();
|
|
223262
224053
|
init_projectCompile();
|
|
223263
|
-
import {
|
|
223264
|
-
import {
|
|
224054
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
224055
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync23, readFileSync as readFileSync28, writeFileSync as writeFileSync25 } from "node:fs";
|
|
224056
|
+
import { join as join33 } from "node:path";
|
|
223265
224057
|
var TEXT_EXT = /\.(ts|js|json|svg|txt|md)$/i;
|
|
223266
224058
|
var rel = (key) => key.replace(/^\//, "");
|
|
223267
224059
|
var resolveHarness = (loadSceneHarness2) => {
|
|
223268
|
-
const inRepo =
|
|
224060
|
+
const inRepo = join33(distRoot, "..", "projects", "src", "scene-editor");
|
|
223269
224061
|
try {
|
|
223270
|
-
return loadSceneHarness2(
|
|
224062
|
+
return loadSceneHarness2(existsSync31(inRepo) ? inRepo : undefined);
|
|
223271
224063
|
} catch {
|
|
223272
|
-
const vendored =
|
|
223273
|
-
if (
|
|
223274
|
-
return JSON.parse(
|
|
224064
|
+
const vendored = join33(distRoot, "runtime", "scene-harness.json");
|
|
224065
|
+
if (existsSync31(vendored))
|
|
224066
|
+
return JSON.parse(readFileSync28(vendored, "utf8"));
|
|
223275
224067
|
throw new CliError("scene-editor harness sources not found — reinstall the CLI or set LECODES_SCENE_HARNESS_DIR.");
|
|
223276
224068
|
}
|
|
223277
224069
|
};
|
|
@@ -223283,11 +224075,32 @@ var compileSceneEditor = async (opts) => {
|
|
|
223283
224075
|
for (const file of scan2.files) {
|
|
223284
224076
|
const path2 = "./" + rel(file.path);
|
|
223285
224077
|
if (TEXT_EXT.test(path2)) {
|
|
223286
|
-
entries.push({ path: path2, type: "text", text:
|
|
224078
|
+
entries.push({ path: path2, type: "text", text: readFileSync28(file.absPath, "utf8") });
|
|
224079
|
+
} else if (/\.mat$/i.test(path2)) {
|
|
224080
|
+
entries.push({ path: path2, type: "shader", text: readFileSync28(file.absPath, "utf8"), absPath: file.absPath });
|
|
223287
224081
|
} else {
|
|
223288
224082
|
entries.push({ path: path2, type: "resource", fileSrc: "/project/" + rel(file.path) });
|
|
223289
224083
|
}
|
|
223290
224084
|
}
|
|
224085
|
+
const shaderEntries = entries.filter((e2) => e2.type === "shader");
|
|
224086
|
+
if (shaderEntries.length > 0) {
|
|
224087
|
+
const { warnings } = await applyLocalShaders(shaderEntries, { root, srcPrefix: "/project/.lecodes/shaders", platforms: ["webgl", "webgl-lite"] });
|
|
224088
|
+
for (const w of warnings)
|
|
224089
|
+
warnErr(w);
|
|
224090
|
+
const { buildLiteMaterialArtifact: buildLiteMaterialArtifact2 } = await Promise.resolve().then(() => (init_compile(), exports_compile));
|
|
224091
|
+
for (const e2 of shaderEntries) {
|
|
224092
|
+
if (e2.shaders?.length)
|
|
224093
|
+
continue;
|
|
224094
|
+
const hash = createHash8("sha256").update(e2.text ?? "").digest("hex").slice(0, 16);
|
|
224095
|
+
const dir = shadersCacheDir(root);
|
|
224096
|
+
mkdirSync23(dir, { recursive: true });
|
|
224097
|
+
const liteName = `${hash}_webgl-lite.filamat`;
|
|
224098
|
+
if (!existsSync31(join33(dir, liteName)))
|
|
224099
|
+
writeFileSync25(join33(dir, liteName), JSON.stringify(buildLiteMaterialArtifact2(e2.text ?? "")));
|
|
224100
|
+
const src = `/project/.lecodes/shaders/${hash}_opengl.filamat`;
|
|
224101
|
+
e2.shaders = ["opengl", "webgl", "webgl-lite"].map((platform) => ({ platform, src }));
|
|
224102
|
+
}
|
|
224103
|
+
}
|
|
223291
224104
|
const scene = scenePath.startsWith("./") ? scenePath : "./" + rel(scenePath);
|
|
223292
224105
|
if (!entries.some((e2) => e2.path === scene && e2.type === "text")) {
|
|
223293
224106
|
return { js: null, error: `Scene file not found: ${scene}` };
|
|
@@ -223317,7 +224130,7 @@ var DEFAULT_PORT3 = 4488;
|
|
|
223317
224130
|
var findAppRoot = (start) => {
|
|
223318
224131
|
let dir = start;
|
|
223319
224132
|
while (true) {
|
|
223320
|
-
if (
|
|
224133
|
+
if (existsSync32(join34(dir, "app.json")))
|
|
223321
224134
|
return dir;
|
|
223322
224135
|
const parent = dirname17(dir);
|
|
223323
224136
|
if (parent === dir)
|
|
@@ -223336,10 +224149,10 @@ var getContext2 = (args) => {
|
|
|
223336
224149
|
manifest = null;
|
|
223337
224150
|
const pathArg = args._[0];
|
|
223338
224151
|
if (pathArg) {
|
|
223339
|
-
const abs =
|
|
223340
|
-
if (
|
|
224152
|
+
const abs = resolve16(process.cwd(), pathArg);
|
|
224153
|
+
if (existsSync32(abs) && statSync11(abs).isDirectory())
|
|
223341
224154
|
root = abs;
|
|
223342
|
-
else if (abs.endsWith(".scene.ts") &&
|
|
224155
|
+
else if (abs.endsWith(".scene.ts") && existsSync32(abs))
|
|
223343
224156
|
root = findAppRoot(dirname17(abs)) ?? dirname17(abs);
|
|
223344
224157
|
else
|
|
223345
224158
|
throw new CliError(`No such folder or .scene.ts: ${pathArg}`);
|
|
@@ -223350,12 +224163,12 @@ var getContext2 = (args) => {
|
|
|
223350
224163
|
return { root, manifest, name: manifest?.name ?? basename15(root) };
|
|
223351
224164
|
};
|
|
223352
224165
|
var writeMcpConfig2 = (root, port) => {
|
|
223353
|
-
const path2 =
|
|
224166
|
+
const path2 = join34(root, ".mcp.json");
|
|
223354
224167
|
const entry = { type: "http", url: `http://localhost:${port}/mcp` };
|
|
223355
224168
|
let config = {};
|
|
223356
|
-
if (
|
|
224169
|
+
if (existsSync32(path2)) {
|
|
223357
224170
|
try {
|
|
223358
|
-
config = JSON.parse(
|
|
224171
|
+
config = JSON.parse(readFileSync29(path2, "utf8"));
|
|
223359
224172
|
} catch {
|
|
223360
224173
|
warnErr(".mcp.json exists but isn't valid JSON — left it untouched. Add the lecodes-scene server by hand.");
|
|
223361
224174
|
return;
|
|
@@ -223368,7 +224181,7 @@ var writeMcpConfig2 = (root, port) => {
|
|
|
223368
224181
|
return;
|
|
223369
224182
|
}
|
|
223370
224183
|
config.mcpServers["lecodes-scene"] = entry;
|
|
223371
|
-
|
|
224184
|
+
writeFileSync26(path2, JSON.stringify(config, null, 2) + `
|
|
223372
224185
|
`);
|
|
223373
224186
|
note(`${existing ? "updated" : "created"} .mcp.json (lecodes-scene MCP → ${entry.url})`);
|
|
223374
224187
|
};
|
|
@@ -223377,8 +224190,8 @@ var countScenes = (dir) => {
|
|
|
223377
224190
|
for (const name of readdirSync13(dir)) {
|
|
223378
224191
|
if (name === "node_modules" || name.startsWith("."))
|
|
223379
224192
|
continue;
|
|
223380
|
-
const full =
|
|
223381
|
-
if (
|
|
224193
|
+
const full = join34(dir, name);
|
|
224194
|
+
if (statSync11(full).isDirectory())
|
|
223382
224195
|
n2 += countScenes(full);
|
|
223383
224196
|
else if (name.endsWith(".scene.ts"))
|
|
223384
224197
|
n2++;
|
|
@@ -223406,7 +224219,8 @@ var scene = async (args) => {
|
|
|
223406
224219
|
manifest: ctx.manifest,
|
|
223407
224220
|
name: ctx.name,
|
|
223408
224221
|
scenePath
|
|
223409
|
-
})
|
|
224222
|
+
}),
|
|
224223
|
+
shaderArtifact: (shaderText, platform) => localShaderArtifact(ctx.root, shaderText, platform)
|
|
223410
224224
|
});
|
|
223411
224225
|
if (!flagBool(args, "no-mcp"))
|
|
223412
224226
|
writeMcpConfig2(ctx.root, port);
|
|
@@ -223420,6 +224234,79 @@ var scene = async (args) => {
|
|
|
223420
224234
|
|
|
223421
224235
|
// src/commands/assets.ts
|
|
223422
224236
|
init_util();
|
|
224237
|
+
|
|
224238
|
+
// src/cmgenTool.ts
|
|
224239
|
+
import { existsSync as existsSync33, readdirSync as readdirSync14 } from "node:fs";
|
|
224240
|
+
import { homedir as homedir8 } from "node:os";
|
|
224241
|
+
import { join as join35, resolve as resolve17 } from "node:path";
|
|
224242
|
+
init_util();
|
|
224243
|
+
var exeName2 = () => process.platform === "win32" ? "cmgen.exe" : "cmgen";
|
|
224244
|
+
var platformKey3 = () => {
|
|
224245
|
+
if (process.platform === "win32")
|
|
224246
|
+
return "windows-x64";
|
|
224247
|
+
if (process.platform === "linux")
|
|
224248
|
+
return "linux-x64";
|
|
224249
|
+
throw new CliError(process.platform === "darwin" ? "cmgen isn't published for macOS yet — build filament's host tools locally and set LECODES_CMGEN." : `cmgen isn't published for ${process.platform}.`);
|
|
224250
|
+
};
|
|
224251
|
+
var cacheRoot3 = () => join35(homedir8(), ".lecodes", "cmgen");
|
|
224252
|
+
var findCmgenIn = (dir) => {
|
|
224253
|
+
for (const entry of readdirSync14(dir, { withFileTypes: true })) {
|
|
224254
|
+
const p2 = join35(dir, entry.name);
|
|
224255
|
+
if (entry.isFile() && entry.name === exeName2())
|
|
224256
|
+
return p2;
|
|
224257
|
+
if (entry.isDirectory()) {
|
|
224258
|
+
const found = findCmgenIn(p2);
|
|
224259
|
+
if (found)
|
|
224260
|
+
return found;
|
|
224261
|
+
}
|
|
224262
|
+
}
|
|
224263
|
+
return null;
|
|
224264
|
+
};
|
|
224265
|
+
var installedCmgenVersions = () => {
|
|
224266
|
+
try {
|
|
224267
|
+
return readdirSync14(cacheRoot3(), { withFileTypes: true }).filter((e2) => e2.isDirectory() && e2.name.startsWith("cmgen-")).map((e2) => e2.name.slice("cmgen-".length)).filter((v) => findCmgenIn(join35(cacheRoot3(), `cmgen-${v}`)) !== null).sort(semverNewer);
|
|
224268
|
+
} catch {
|
|
224269
|
+
return [];
|
|
224270
|
+
}
|
|
224271
|
+
};
|
|
224272
|
+
var fetchCmgenManifest = () => fetchReleaseProduct("cmgen", "cmgen (environment tool)");
|
|
224273
|
+
var downloadCmgen = async (version, versions, dest) => {
|
|
224274
|
+
const key = platformKey3();
|
|
224275
|
+
const file = versions.find((v) => v.version === version)?.files[key];
|
|
224276
|
+
if (!file) {
|
|
224277
|
+
throw new CliError(`cmgen ${version} [${key}] isn't published — build filament's host tools locally and point ` + `LECODES_CMGEN at the binary.`);
|
|
224278
|
+
}
|
|
224279
|
+
note(`Downloading the environment tool (cmgen ${version}, ${(file.size / 1024 / 1024).toFixed(1)} MB)…`);
|
|
224280
|
+
await downloadReleaseArchive(file, {
|
|
224281
|
+
cacheRoot: cacheRoot3(),
|
|
224282
|
+
dest,
|
|
224283
|
+
validate: (dir) => findCmgenIn(dir) ? null : "The downloaded archive doesn't contain the cmgen binary."
|
|
224284
|
+
});
|
|
224285
|
+
};
|
|
224286
|
+
var resolveCmgenExeCached = () => {
|
|
224287
|
+
const override = process.env.LECODES_CMGEN;
|
|
224288
|
+
if (override) {
|
|
224289
|
+
if (!existsSync33(override))
|
|
224290
|
+
throw new CliError(`LECODES_CMGEN points at "${override}" which doesn't exist.`);
|
|
224291
|
+
return resolve17(override);
|
|
224292
|
+
}
|
|
224293
|
+
const cached = installedCmgenVersions()[0];
|
|
224294
|
+
return cached ? findCmgenIn(join35(cacheRoot3(), `cmgen-${cached}`)) : null;
|
|
224295
|
+
};
|
|
224296
|
+
var resolveCmgenExe = async () => {
|
|
224297
|
+
const cached = resolveCmgenExeCached();
|
|
224298
|
+
if (cached)
|
|
224299
|
+
return cached;
|
|
224300
|
+
const { latest, versions } = await fetchCmgenManifest();
|
|
224301
|
+
const dir = join35(cacheRoot3(), `cmgen-${latest}`);
|
|
224302
|
+
await downloadCmgen(latest, versions, dir);
|
|
224303
|
+
const exe = findCmgenIn(dir);
|
|
224304
|
+
if (!exe)
|
|
224305
|
+
throw new CliError(`cmgen cache at ${dir} is broken — delete the folder and retry.`);
|
|
224306
|
+
return exe;
|
|
224307
|
+
};
|
|
224308
|
+
|
|
224309
|
+
// src/commands/assets.ts
|
|
223423
224310
|
var assets = async (args) => {
|
|
223424
224311
|
let mod;
|
|
223425
224312
|
try {
|
|
@@ -223428,6 +224315,9 @@ var assets = async (args) => {
|
|
|
223428
224315
|
throw new CliError("The asset pipeline isn't installed. Run: npm install -g lecodes-assets");
|
|
223429
224316
|
}
|
|
223430
224317
|
const raw = args._raw ?? args._;
|
|
224318
|
+
if (raw[0] === "sky" && !process.env.LECODES_CMGEN) {
|
|
224319
|
+
process.env.LECODES_CMGEN = await resolveCmgenExe();
|
|
224320
|
+
}
|
|
223431
224321
|
const code = await mod.runAssetsCli(raw);
|
|
223432
224322
|
if (code !== 0)
|
|
223433
224323
|
process.exitCode = code;
|
|
@@ -223466,8 +224356,8 @@ var status3 = async (_args) => {
|
|
|
223466
224356
|
};
|
|
223467
224357
|
|
|
223468
224358
|
// src/commands/test.ts
|
|
223469
|
-
import { existsSync as
|
|
223470
|
-
import { dirname as dirname18, resolve as
|
|
224359
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync24, readFileSync as readFileSync30, readdirSync as readdirSync15 } from "node:fs";
|
|
224360
|
+
import { dirname as dirname18, resolve as resolve18 } from "node:path";
|
|
223471
224361
|
init_manifest();
|
|
223472
224362
|
init_util();
|
|
223473
224363
|
var deriveDesignFlows = (meta, entryScreen) => {
|
|
@@ -223530,7 +224420,7 @@ var slug3 = (s2) => s2.replace(/[^a-zA-Z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").
|
|
|
223530
224420
|
var readFlowFile = (abs, displayName) => {
|
|
223531
224421
|
let raw;
|
|
223532
224422
|
try {
|
|
223533
|
-
raw = JSON.parse(
|
|
224423
|
+
raw = JSON.parse(readFileSync30(abs, "utf8"));
|
|
223534
224424
|
} catch (e2) {
|
|
223535
224425
|
throw new CliError(`Can't read "${displayName}": ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
223536
224426
|
}
|
|
@@ -223556,7 +224446,14 @@ var test = async (args) => {
|
|
|
223556
224446
|
const desktop2 = flagBool(args, "desktop");
|
|
223557
224447
|
if (desktop2 && flagBool(args, "flows"))
|
|
223558
224448
|
throw new CliError("--flows needs the headless renderer (design flows tap by name) — drop --desktop.");
|
|
223559
|
-
|
|
224449
|
+
let desktopRoot;
|
|
224450
|
+
try {
|
|
224451
|
+
desktopRoot = findProjectRoot(process.cwd());
|
|
224452
|
+
} catch {
|
|
224453
|
+
desktopRoot = process.cwd();
|
|
224454
|
+
}
|
|
224455
|
+
const desktopRenderer = desktop2 ? resolveDesktopRenderer(desktopRoot, args) : undefined;
|
|
224456
|
+
const native = desktop2 ? await buildBundle(args, undefined, { localAssets: true, desktopRenderer }) : null;
|
|
223560
224457
|
const headless = desktop2 ? null : await compileHeadlessBundle(args);
|
|
223561
224458
|
const warnings = headless?.warnings ?? [];
|
|
223562
224459
|
const useLocalAssets = headless?.useLocalAssets ?? false;
|
|
@@ -223578,40 +224475,40 @@ var test = async (args) => {
|
|
|
223578
224475
|
if (!Number.isFinite(nativeFixedDt) || nativeFixedDt < 0 || nativeFixedDt > 1000)
|
|
223579
224476
|
throw new CliError(`--fixed-dt wants a number of ms (0 = wall clock, max 1000), got "${fixedDtFlag}".`);
|
|
223580
224477
|
const projectViewport = desktopDefaultViewport(root);
|
|
223581
|
-
const testsDir =
|
|
223582
|
-
const designMetaPath =
|
|
224478
|
+
const testsDir = resolve18(root, flagStr(args, "dir") ?? "tests");
|
|
224479
|
+
const designMetaPath = resolve18(root, flagStr(args, "design-dir") ?? "design", "meta.json");
|
|
223583
224480
|
const asJson = flagBool(args, "json");
|
|
223584
224481
|
const out = asJson ? logErr : log;
|
|
223585
224482
|
const cases = [];
|
|
223586
224483
|
if (args._.length > 0) {
|
|
223587
224484
|
for (const given of args._) {
|
|
223588
|
-
const candidates = [
|
|
223589
|
-
const abs = candidates.find((p2) =>
|
|
224485
|
+
const candidates = [resolve18(given), resolve18(testsDir, given), resolve18(testsDir, `${given}.flow.json`)];
|
|
224486
|
+
const abs = candidates.find((p2) => existsSync34(p2));
|
|
223590
224487
|
if (!abs)
|
|
223591
224488
|
throw new CliError(`No such test: "${given}" (looked in ${testsDir}).`);
|
|
223592
224489
|
cases.push(readFlowFile(abs, given));
|
|
223593
224490
|
}
|
|
223594
|
-
} else if (
|
|
223595
|
-
for (const f3 of
|
|
223596
|
-
cases.push(readFlowFile(
|
|
224491
|
+
} else if (existsSync34(testsDir)) {
|
|
224492
|
+
for (const f3 of readdirSync15(testsDir).filter((f4) => f4.endsWith(".flow.json")).sort()) {
|
|
224493
|
+
cases.push(readFlowFile(resolve18(testsDir, f3), f3));
|
|
223597
224494
|
}
|
|
223598
224495
|
}
|
|
223599
224496
|
const fixturesFlag = flagStr(args, "fixtures");
|
|
223600
|
-
const defaultPack =
|
|
223601
|
-
const flowFixtures = fixturesFlag !== undefined ? resolveFixtures(fixturesFlag, ".") :
|
|
224497
|
+
const defaultPack = resolve18(testsDir, "fixtures.json");
|
|
224498
|
+
const flowFixtures = fixturesFlag !== undefined ? resolveFixtures(fixturesFlag, ".") : existsSync34(defaultPack) ? resolveFixtures("fixtures.json", testsDir) : undefined;
|
|
223602
224499
|
const flowsOnly = flagBool(args, "flows");
|
|
223603
224500
|
const wantFlows = !flagBool(args, "no-flows") && args._.length === 0;
|
|
223604
224501
|
if (flowsOnly)
|
|
223605
224502
|
cases.length = 0;
|
|
223606
224503
|
let skipped = [];
|
|
223607
224504
|
let entryScreen = null;
|
|
223608
|
-
if (desktop2 && wantFlows &&
|
|
224505
|
+
if (desktop2 && wantFlows && existsSync34(designMetaPath)) {
|
|
223609
224506
|
note("Design-derived flows skipped with --desktop (they tap by name; run `lecodes test` headless for them).");
|
|
223610
224507
|
}
|
|
223611
|
-
if (!desktop2 && (wantFlows || flowsOnly) &&
|
|
224508
|
+
if (!desktop2 && (wantFlows || flowsOnly) && existsSync34(designMetaPath)) {
|
|
223612
224509
|
let meta;
|
|
223613
224510
|
try {
|
|
223614
|
-
meta = JSON.parse(
|
|
224511
|
+
meta = JSON.parse(readFileSync30(designMetaPath, "utf8"));
|
|
223615
224512
|
} catch (e2) {
|
|
223616
224513
|
throw new CliError(`Can't read ${designMetaPath}: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
223617
224514
|
}
|
|
@@ -223643,17 +224540,18 @@ var test = async (args) => {
|
|
|
223643
224540
|
}, timeoutMs);
|
|
223644
224541
|
killer.unref?.();
|
|
223645
224542
|
const onConsole = flagBool(args, "logs") ? (level, text) => logErr(`${c.dim(`[${level}]`)} ${text}`) : undefined;
|
|
223646
|
-
const artifactsRoot =
|
|
224543
|
+
const artifactsRoot = resolve18(root, flagStr(args, "artifacts") ?? resolve18(testsDir, ".artifacts"));
|
|
223647
224544
|
const results = [];
|
|
223648
224545
|
const runNativeCase = async (kase) => {
|
|
223649
|
-
const caseDir =
|
|
224546
|
+
const caseDir = resolve18(artifactsRoot, slug3(kase.name));
|
|
223650
224547
|
const plan = translateScenario(kase.steps, { outDir: caseDir, headroomFrames: nativeFrames });
|
|
223651
224548
|
for (const st of plan.steps)
|
|
223652
224549
|
if (st.screenshot)
|
|
223653
|
-
|
|
223654
|
-
|
|
223655
|
-
const finalPng =
|
|
224550
|
+
mkdirSync24(dirname18(st.screenshot), { recursive: true });
|
|
224551
|
+
mkdirSync24(caseDir, { recursive: true });
|
|
224552
|
+
const finalPng = resolve18(caseDir, "final.png");
|
|
223656
224553
|
const { output } = await runDesktopRender(native.js, native.resources, {
|
|
224554
|
+
renderer: desktopRenderer,
|
|
223657
224555
|
width: Number(flagStr(args, "width")) || kase.width || projectViewport?.width || 390,
|
|
223658
224556
|
height: Number(flagStr(args, "height")) || kase.height || projectViewport?.height || 844,
|
|
223659
224557
|
frames: plan.frameCap,
|
|
@@ -223703,14 +224601,14 @@ var test = async (args) => {
|
|
|
223703
224601
|
onConsole
|
|
223704
224602
|
});
|
|
223705
224603
|
try {
|
|
223706
|
-
const io = fileScenarioIO(
|
|
224604
|
+
const io = fileScenarioIO(resolve18(artifactsRoot, slug3(kase.name)));
|
|
223707
224605
|
const outcome = await renderer.runScenario(session, steps, io);
|
|
223708
224606
|
result = {
|
|
223709
224607
|
name: kase.name,
|
|
223710
224608
|
kind: kase.kind,
|
|
223711
224609
|
ok: outcome.ok,
|
|
223712
224610
|
steps: outcome.steps.map((s2) => ({ desc: s2.desc, ok: s2.ok, error: s2.error })),
|
|
223713
|
-
artifacts: outcome.failureArtifacts.map((p2) =>
|
|
224611
|
+
artifacts: outcome.failureArtifacts.map((p2) => resolve18(artifactsRoot, slug3(kase.name), p2))
|
|
223714
224612
|
};
|
|
223715
224613
|
} finally {
|
|
223716
224614
|
session.dispose();
|
|
@@ -223778,11 +224676,11 @@ init_config();
|
|
|
223778
224676
|
init_manifest();
|
|
223779
224677
|
init_types4();
|
|
223780
224678
|
init_util();
|
|
223781
|
-
import { resolve as
|
|
224679
|
+
import { resolve as resolve19 } from "node:path";
|
|
223782
224680
|
var label = (bundle) => `${Object.keys(bundle).length} file(s)`;
|
|
223783
224681
|
var types4 = async (args) => {
|
|
223784
224682
|
const dirArg = args._[0];
|
|
223785
|
-
const root = dirArg ?
|
|
224683
|
+
const root = dirArg ? resolve19(dirArg) : (() => {
|
|
223786
224684
|
try {
|
|
223787
224685
|
return findProjectRoot(process.cwd());
|
|
223788
224686
|
} catch {
|
|
@@ -223841,23 +224739,23 @@ var types4 = async (args) => {
|
|
|
223841
224739
|
};
|
|
223842
224740
|
|
|
223843
224741
|
// src/index.ts
|
|
223844
|
-
import { existsSync as
|
|
224742
|
+
import { existsSync as existsSync36, readFileSync as readFileSync32, realpathSync as realpathSync2 } from "node:fs";
|
|
223845
224743
|
import { createRequire as createRequire4 } from "node:module";
|
|
223846
|
-
import { delimiter as delimiter2, dirname as dirname20, join as
|
|
224744
|
+
import { delimiter as delimiter2, dirname as dirname20, join as join37 } from "node:path";
|
|
223847
224745
|
|
|
223848
224746
|
// src/commands/install.ts
|
|
223849
224747
|
init_util();
|
|
223850
224748
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
223851
|
-
import { appendFileSync as appendFileSync3, chmodSync as chmodSync2, cpSync as cpSync4, existsSync as
|
|
223852
|
-
import { homedir as
|
|
223853
|
-
import { basename as basename16, delimiter, dirname as dirname19, join as
|
|
224749
|
+
import { appendFileSync as appendFileSync3, chmodSync as chmodSync2, cpSync as cpSync4, existsSync as existsSync35, mkdirSync as mkdirSync25, readFileSync as readFileSync31, realpathSync, rmSync as rmSync10, symlinkSync } from "node:fs";
|
|
224750
|
+
import { homedir as homedir9 } from "node:os";
|
|
224751
|
+
import { basename as basename16, delimiter, dirname as dirname19, join as join36, resolve as resolve20, sep as sep2 } from "node:path";
|
|
223854
224752
|
var isWin = process.platform === "win32";
|
|
223855
|
-
var HOME =
|
|
224753
|
+
var HOME = homedir9();
|
|
223856
224754
|
var safeRealpath = (p2) => {
|
|
223857
224755
|
try {
|
|
223858
224756
|
return realpathSync(p2);
|
|
223859
224757
|
} catch {
|
|
223860
|
-
return
|
|
224758
|
+
return resolve20(p2);
|
|
223861
224759
|
}
|
|
223862
224760
|
};
|
|
223863
224761
|
var onPath = (dir) => {
|
|
@@ -223874,24 +224772,24 @@ var onPath = (dir) => {
|
|
|
223874
224772
|
};
|
|
223875
224773
|
var install = async (args) => {
|
|
223876
224774
|
const bundle = dirname19(process.execPath);
|
|
223877
|
-
const
|
|
224775
|
+
const exeName3 = basename16(process.execPath);
|
|
223878
224776
|
const chiselName = isWin ? "chisel.exe" : "chisel";
|
|
223879
|
-
if (!
|
|
224777
|
+
if (!existsSync35(join36(bundle, "runtime")) || !existsSync35(join36(bundle, chiselName))) {
|
|
223880
224778
|
throw new CliError("`lecodes install` only works from the packaged release build (the archive containing lecodes + " + "chisel + runtime/). You appear to be running from source or an npm install — install globally " + "with your package manager there instead.");
|
|
223881
224779
|
}
|
|
223882
|
-
const companions = [
|
|
224780
|
+
const companions = [exeName3, chiselName, "runtime", "README.txt"].filter((f3) => existsSync35(join36(bundle, f3)));
|
|
223883
224781
|
if (isWin)
|
|
223884
224782
|
return installWindows(args, bundle, companions);
|
|
223885
224783
|
return installUnix(args, bundle, companions);
|
|
223886
224784
|
};
|
|
223887
224785
|
var copyBundle = (bundle, target, companions) => {
|
|
223888
|
-
|
|
224786
|
+
mkdirSync25(target, { recursive: true });
|
|
223889
224787
|
if (safeRealpath(target) === safeRealpath(bundle))
|
|
223890
224788
|
return;
|
|
223891
224789
|
for (const f3 of companions) {
|
|
223892
|
-
const dest =
|
|
223893
|
-
|
|
223894
|
-
cpSync4(
|
|
224790
|
+
const dest = join36(target, f3);
|
|
224791
|
+
rmSync10(dest, { recursive: true, force: true });
|
|
224792
|
+
cpSync4(join36(bundle, f3), dest, { recursive: true });
|
|
223895
224793
|
}
|
|
223896
224794
|
};
|
|
223897
224795
|
var permError = (e2, dir, system) => {
|
|
@@ -223903,41 +224801,41 @@ var permError = (e2, dir, system) => {
|
|
|
223903
224801
|
};
|
|
223904
224802
|
var installUnix = (args, bundle, companions) => {
|
|
223905
224803
|
const system = flagBool(args, "system");
|
|
223906
|
-
const libDir =
|
|
223907
|
-
const binDir =
|
|
223908
|
-
const link2 =
|
|
224804
|
+
const libDir = resolve20(flagStr(args, "dir") ?? (system ? "/usr/local/lib/lecodes-cli" : join36(HOME, ".local", "lib", "lecodes-cli")));
|
|
224805
|
+
const binDir = resolve20(flagStr(args, "bin") ?? (system ? "/usr/local/bin" : join36(HOME, ".local", "bin")));
|
|
224806
|
+
const link2 = join36(binDir, "lecodes");
|
|
223909
224807
|
try {
|
|
223910
224808
|
copyBundle(bundle, libDir, companions);
|
|
223911
|
-
chmodSync2(
|
|
223912
|
-
chmodSync2(
|
|
224809
|
+
chmodSync2(join36(libDir, "lecodes"), 493);
|
|
224810
|
+
chmodSync2(join36(libDir, "chisel"), 493);
|
|
223913
224811
|
} catch (e2) {
|
|
223914
224812
|
throw permError(e2, libDir, system);
|
|
223915
224813
|
}
|
|
223916
224814
|
try {
|
|
223917
|
-
|
|
223918
|
-
|
|
223919
|
-
symlinkSync(
|
|
224815
|
+
mkdirSync25(binDir, { recursive: true });
|
|
224816
|
+
rmSync10(link2, { force: true });
|
|
224817
|
+
symlinkSync(join36(libDir, "lecodes"), link2);
|
|
223920
224818
|
} catch (e2) {
|
|
223921
224819
|
throw permError(e2, binDir, system);
|
|
223922
224820
|
}
|
|
223923
224821
|
success(`Installed to ${c.bold(libDir)}`);
|
|
223924
|
-
log(` ${c.dim(link2 + " → " +
|
|
224822
|
+
log(` ${c.dim(link2 + " → " + join36(libDir, "lecodes"))}`);
|
|
223925
224823
|
finishPathUnix(args, binDir);
|
|
223926
224824
|
};
|
|
223927
224825
|
var shellRc = () => {
|
|
223928
224826
|
const shell = basename16(process.env.SHELL ?? "");
|
|
223929
224827
|
if (shell === "zsh")
|
|
223930
|
-
return { file:
|
|
224828
|
+
return { file: join36(HOME, ".zshrc"), name: "~/.zshrc" };
|
|
223931
224829
|
if (shell === "bash")
|
|
223932
|
-
return { file:
|
|
223933
|
-
return { file:
|
|
224830
|
+
return { file: join36(HOME, ".bashrc"), name: "~/.bashrc" };
|
|
224831
|
+
return { file: join36(HOME, ".profile"), name: "~/.profile" };
|
|
223934
224832
|
};
|
|
223935
224833
|
var PATH_MARKER = "# Added by `lecodes install`";
|
|
223936
224834
|
var appendPathToShellRc = (binDir) => {
|
|
223937
224835
|
const { file, name } = shellRc();
|
|
223938
224836
|
const line = `export PATH="${binDir}:$PATH"`;
|
|
223939
224837
|
try {
|
|
223940
|
-
const existing =
|
|
224838
|
+
const existing = existsSync35(file) ? readFileSync31(file, "utf8") : "";
|
|
223941
224839
|
if (existing.includes(line))
|
|
223942
224840
|
return name;
|
|
223943
224841
|
appendFileSync3(file, `${existing && !existing.endsWith(`
|
|
@@ -223975,7 +224873,7 @@ var finishPathUnix = (args, binDir) => {
|
|
|
223975
224873
|
log(` • or install system-wide (no PATH edit): ${c.bold("sudo lecodes install --system")}`);
|
|
223976
224874
|
};
|
|
223977
224875
|
var installWindows = (args, bundle, companions) => {
|
|
223978
|
-
const target =
|
|
224876
|
+
const target = resolve20(flagStr(args, "dir") ?? join36(process.env.LOCALAPPDATA ?? join36(HOME, "AppData", "Local"), "Programs", "lecodes-cli"));
|
|
223979
224877
|
try {
|
|
223980
224878
|
copyBundle(bundle, target, companions);
|
|
223981
224879
|
} catch (e2) {
|
|
@@ -224015,7 +224913,7 @@ init_util();
|
|
|
224015
224913
|
var STANDALONE = (() => {
|
|
224016
224914
|
const dir = dirname20(process.execPath);
|
|
224017
224915
|
const chisel = process.platform === "win32" ? "chisel.exe" : "chisel";
|
|
224018
|
-
return
|
|
224916
|
+
return existsSync36(join37(dir, "runtime")) && existsSync36(join37(dir, chisel));
|
|
224019
224917
|
})();
|
|
224020
224918
|
var INSTALLED = STANDALONE && (() => {
|
|
224021
224919
|
const exe = process.platform === "win32" ? "lecodes.exe" : "lecodes";
|
|
@@ -224030,8 +224928,8 @@ var INSTALLED = STANDALONE && (() => {
|
|
|
224030
224928
|
if (!d)
|
|
224031
224929
|
return false;
|
|
224032
224930
|
try {
|
|
224033
|
-
const cand =
|
|
224034
|
-
return
|
|
224931
|
+
const cand = join37(d, exe);
|
|
224932
|
+
return existsSync36(cand) && realpathSync2(cand) === self2;
|
|
224035
224933
|
} catch {
|
|
224036
224934
|
return false;
|
|
224037
224935
|
}
|
|
@@ -224102,6 +225000,7 @@ ${c.bold("Commands:")}
|
|
|
224102
225000
|
--desktop also open the app in the native desktop host
|
|
224103
225001
|
(same hot reload; window from app.json's
|
|
224104
225002
|
desktop block; closing it keeps the server)
|
|
225003
|
+
--fps (--desktop) draw the host's frame counter
|
|
224105
225004
|
compile Compile the local project to JS (prints to stdout)
|
|
224106
225005
|
(was 'lecodes preview' — the old name still works)
|
|
224107
225006
|
-o, --out <file> write to a file instead of stdout
|
|
@@ -224110,8 +225009,16 @@ ${c.bold("Commands:")}
|
|
|
224110
225009
|
--no-minify keep the bundle readable (no compress/mangle)
|
|
224111
225010
|
--no-sourcemap don't generate/inline the source map
|
|
224112
225011
|
--public-url <url> asset URL prefix (default: the API origin)
|
|
224113
|
-
render
|
|
225012
|
+
render [file] Render the local UI project to a semantic JSON description
|
|
224114
225013
|
(needs the optional 'lecodes-renderer' package; UI projects only)
|
|
225014
|
+
[file] render ONE screen module instead of the app:
|
|
225015
|
+
a file that default-exports a UIScreen (or a
|
|
225016
|
+
(state) => UIScreen) — every 'lecodes design'
|
|
225017
|
+
screen is written that way. A bare id
|
|
225018
|
+
("login") resolves in design/screens/, and a
|
|
225019
|
+
design screen renders at its meta.json frame
|
|
225020
|
+
--state <name> ([file]) the state to pass a
|
|
225021
|
+
(state) => UIScreen screen
|
|
224115
225022
|
-o, --out <file> write JSON to a file instead of stdout
|
|
224116
225023
|
--png [file] rasterize a PNG (default screenshot.png;
|
|
224117
225024
|
needs @napi-rs/canvas)
|
|
@@ -224204,7 +225111,7 @@ ${c.bold("Commands:")}
|
|
|
224204
225111
|
add "<text>" [--screen <id>] [--state <s>] [--element <name>]
|
|
224205
225112
|
reply <number> "<text>" · resolve <number> [--reply "<text>"] [--reopen]
|
|
224206
225113
|
(--ai signs a write as Claude; sends with the next "lecodes push")
|
|
224207
|
-
desktop [run|update]
|
|
225114
|
+
desktop [run|build|update] The desktop target — the host 'lecodes render --desktop' uses too:
|
|
224208
225115
|
no args shows the version status (selected / downloaded /
|
|
224209
225116
|
newest published)
|
|
224210
225117
|
run compile the current project and open it in a real
|
|
@@ -224214,11 +225121,35 @@ ${c.bold("Commands:")}
|
|
|
224214
225121
|
app.json fullscreen; --scale <0.5..4|fit> overrides
|
|
224215
225122
|
the display scale — "fit" sizes a 1080p-designed app
|
|
224216
225123
|
to the monitor with the density raised to match;
|
|
224217
|
-
--entry overrides the entrypoint;
|
|
225124
|
+
--entry overrides the entrypoint; --fps draws the
|
|
225125
|
+
host's frame counter in the corner — composited fps,
|
|
225126
|
+
plus the 3D fps when they diverge; asset() resources
|
|
224218
225127
|
load from the working tree — --remote-assets or
|
|
224219
225128
|
--public-url <url> uses the server's copies instead)
|
|
225129
|
+
build stage a portable app folder (default dist/<name>/):
|
|
225130
|
+
the cached host renamed to <Name>.exe + app.js +
|
|
225131
|
+
assets/ (the project's files) + runtime/ (the engine's)
|
|
225132
|
+
+ app.json (the host reads title/size/fullscreen from
|
|
225133
|
+
it) + app.ico from app.json "icon". Duplicate asset
|
|
225134
|
+
basenames are a build error (they silently alias in
|
|
225135
|
+
the bundle). Also spelled 'lecodes app build desktop'.
|
|
225136
|
+
-o <dir> output folder (default dist/<name>)
|
|
225137
|
+
--slug <name> folder/zip name override (Latin-only)
|
|
225138
|
+
--renderer <gl|vulkan> host + shader variant
|
|
225139
|
+
--probe headless-render the staged folder to PNG
|
|
225140
|
+
--zip <out>.zip, UTF-8/forward-slash entries
|
|
224220
225141
|
update move to the newest published release
|
|
224221
225142
|
(cached in ~/.lecodes/renderer)
|
|
225143
|
+
lightmap bake Bake static shadows + ambient occlusion into a lightmap atlas
|
|
225144
|
+
(assets/lightmap/lightmap.{png,bake}) by running the app in
|
|
225145
|
+
the desktop host: the statics are what the app registers
|
|
225146
|
+
with Lightmap.add, the bake fires at its Lightmap.load call.
|
|
225147
|
+
Props need UVs first: lecodes assets doctor … --lightmap-uv
|
|
225148
|
+
--out <dir> where the atlas lands (assets/lightmap)
|
|
225149
|
+
--size <px> atlas edge (4096) --texel <m> density (0.02)
|
|
225150
|
+
--sun-rays/--ao-rays <n> quality (32 / 64)
|
|
225151
|
+
--ao-distance <m> AO reach (2) --sun-angle <deg> (0.5)
|
|
225152
|
+
--frames <n> load headroom (900) --logs host output
|
|
224222
225153
|
shaders [compile|update] The project's custom shaders (.mat) and their local compile:
|
|
224223
225154
|
no args lists the shaders + their compile state; compile
|
|
224224
225155
|
compiles them all into .lecodes/shaders (the same pass
|
|
@@ -224252,6 +225183,11 @@ ${c.bold("Commands:")}
|
|
|
224252
225183
|
Skeleton, clips (+ root motion / first-frame-vs-rest per clip), meshes,
|
|
224253
225184
|
materials, textures, bind sanity, duplicate names; --filmstrip renders
|
|
224254
225185
|
a CPU-skinned PNG strip per clip (no GPU)
|
|
225186
|
+
assets sky <env.exr|.hdr> [-o dir] [--size N] [--ibl-size N] [--ibl-only]
|
|
225187
|
+
Equirectangular HDR → the two cubemaps a scene needs: sky.ktx (sharp,
|
|
225188
|
+
for 'skybox: { texture }') and ibl.ktx (prefiltered + SH, lights the
|
|
225189
|
+
scene) — they are NOT interchangeable. Reports where the sun is in
|
|
225190
|
+
the environment so the scene's light can be aimed to match
|
|
224255
225191
|
assets doctor <in.glb|dir> [--fix] [--ktx2] [--dump]
|
|
224256
225192
|
Analyze / fix a GLB against engine limits (bone cap 256, texture
|
|
224257
225193
|
formats/size, KTX2 via toktx)
|
|
@@ -224300,15 +225236,8 @@ ${c.bold("Commands:")}
|
|
|
224300
225236
|
--configuration <c> Release (default) or Debug
|
|
224301
225237
|
--no-aab skip the .aab (APK only)
|
|
224302
225238
|
--no-sync skip the lecodes app sync step
|
|
224303
|
-
app build desktop
|
|
224304
|
-
|
|
224305
|
-
app.json (the host ≥1.3.0 reads title/size/fullscreen from it)
|
|
224306
|
-
+ app.ico from app.json "icon". Duplicate asset basenames are
|
|
224307
|
-
a build error (they silently alias in the bundle).
|
|
224308
|
-
-o <dir> output folder (default dist/<name>)
|
|
224309
|
-
--slug <name> folder/zip name override (e.g. Latin-only)
|
|
224310
|
-
--probe headless-render the staged folder to a PNG
|
|
224311
|
-
--zip <out>.zip, UTF-8/forward-slash entries
|
|
225239
|
+
app build desktop Same as 'lecodes desktop build' (kept for symmetry with the
|
|
225240
|
+
ios/android shells) — see the desktop group above.
|
|
224312
225241
|
app upload ios Upload the archive to App Store Connect (then bumps buildNumber)
|
|
224313
225242
|
--key-path <p8> --key-id <id> --issuer-id <id>
|
|
224314
225243
|
ASC API key; stored in ~/.lecodes/asc.json
|
|
@@ -224323,7 +225252,7 @@ ${c.bold("Commands:")}
|
|
|
224323
225252
|
— 'new --env' banks it in .env), revoke <id>
|
|
224324
225253
|
|
|
224325
225254
|
Config lives in ~/.lecodes/config.json (override with LECODES_API / LECODES_TOKEN).`;
|
|
224326
|
-
var commands = { init: init2, login, create, clone: clone3, link, status: status3, diff: diff2, pull, push, compile: compile2, preview: previewLegacy, render, test, types: types4, design, scene, dev, app, pn, desktop, shaders, assets };
|
|
225255
|
+
var commands = { init: init2, login, create, clone: clone3, link, status: status3, diff: diff2, pull, push, compile: compile2, preview: previewLegacy, render, test, types: types4, design, scene, dev, app, pn, desktop, shaders, assets, lightmap };
|
|
224327
225256
|
if (STANDALONE)
|
|
224328
225257
|
commands.install = install;
|
|
224329
225258
|
var readVersion = () => package_default.version ?? "unknown";
|
|
@@ -224338,9 +225267,9 @@ var peerVersion = (name, probe) => {
|
|
|
224338
225267
|
const require2 = createRequire4(import.meta.url);
|
|
224339
225268
|
let dir = dirname20(require2.resolve(probe));
|
|
224340
225269
|
for (let depth = 0;depth < 16; depth++) {
|
|
224341
|
-
const manifest =
|
|
224342
|
-
if (
|
|
224343
|
-
const parsed = JSON.parse(
|
|
225270
|
+
const manifest = join37(dir, "package.json");
|
|
225271
|
+
if (existsSync36(manifest)) {
|
|
225272
|
+
const parsed = JSON.parse(readFileSync32(manifest, "utf8"));
|
|
224344
225273
|
if (parsed.name === name)
|
|
224345
225274
|
return parsed.version ?? "unknown";
|
|
224346
225275
|
}
|