lecodes-cli 0.18.0 → 0.18.2
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 +2013 -564
- package/package.json +4 -4
- package/runtime/scene-harness.json +1 -1
- 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 +40 -1
- package/runtime/sdk/gl/CameraPlace.ts +52 -51
- 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 +161 -5
- package/runtime/sdk/gl/Shape.ts +42 -3
- package/runtime/sdk/gl/Trigger.ts +45 -50
- package/runtime/sdk/gl/Vehicle.ts +276 -322
- package/runtime/sdk/gl/Wheel.ts +240 -0
- package/runtime/sdk/gl/scenarios.ts +4 -30
- package/runtime/sdk/gl/state.ts +6 -6
- package/runtime/sdk/inject.ts +186 -171
- package/runtime/sdk/runtime/device.ts +102 -1
- package/runtime/sdk/scene/defineScene.ts +108 -53
- package/runtime/sdk/scene/gizmos.ts +31 -11
- package/runtime/sdk/scene/material.ts +188 -0
- package/runtime/sdk-types.json +1 -1
- package/runtime/sdk/compile/aspectMacro.ts +0 -42
- package/runtime/sdk/compile/assetIconMacro.ts +0 -384
- package/runtime/sdk/compile/assetMacro.ts +0 -45
- package/runtime/sdk/compile/assetName.ts +0 -50
- package/runtime/sdk/compile/bundler.ts +0 -252
- package/runtime/sdk/compile/compileProject.ts +0 -129
- package/runtime/sdk/compile/detectEntry.ts +0 -128
- package/runtime/sdk/compile/fontMacro.ts +0 -459
- package/runtime/sdk/compile/fontRegistry.ts +0 -78
- package/runtime/sdk/compile/header.ts +0 -67
- package/runtime/sdk/compile/index.ts +0 -85
- package/runtime/sdk/compile/libraryImports.ts +0 -52
- package/runtime/sdk/compile/liteMaterial.ts +0 -247
- package/runtime/sdk/compile/sceneEditor.ts +0 -78
- package/runtime/sdk/compile/serverSplit.ts +0 -233
- package/runtime/sdk/compile/serverTypes.ts +0 -227
- package/runtime/sdk/compile/sfnt.ts +0 -98
- package/runtime/sdk/compile/shaderTargets.ts +0 -42
- package/runtime/sdk/compile/sourcemap.ts +0 -25
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/", ".env", ".env.*"];
|
|
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++;
|
|
598
|
+
}
|
|
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++;
|
|
658
|
+
}
|
|
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}`);
|
|
575
681
|
}
|
|
576
|
-
return name;
|
|
577
|
-
});
|
|
578
|
-
if (/(?<![.\w$])asset\(/.test(out)) {
|
|
579
|
-
throw new Error(`asset() expects a string literal, found a dynamic argument in: ${filePath}`);
|
|
580
682
|
}
|
|
581
|
-
|
|
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({
|
|
@@ -2983,7 +3174,7 @@ var require_source_map_support = __commonJS((exports, module) => {
|
|
|
2983
3174
|
|
|
2984
3175
|
// ../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/typescript.js
|
|
2985
3176
|
var require_typescript = __commonJS((exports, module) => {
|
|
2986
|
-
var __dirname = "
|
|
3177
|
+
var __dirname = "/Users/den/Desktop/Ingenuity/le-codes/node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib", __filename = "/Users/den/Desktop/Ingenuity/le-codes/node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/typescript.js";
|
|
2987
3178
|
/*! *****************************************************************************
|
|
2988
3179
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
2989
3180
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
@@ -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();
|
|
@@ -172957,7 +173350,7 @@ var init_fonts = __esm(() => {
|
|
|
172957
173350
|
|
|
172958
173351
|
// src/compile/assetIcons.ts
|
|
172959
173352
|
import { createHash as createHash5 } from "node:crypto";
|
|
172960
|
-
import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as
|
|
173353
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
|
|
172961
173354
|
import { homedir as homedir3 } from "node:os";
|
|
172962
173355
|
import { dirname as dirname7, join as join14 } from "node:path";
|
|
172963
173356
|
var DEFAULT_REGISTRY, isHttp2 = (s) => /^https?:/i.test(s), sha122 = (s) => createHash5("sha256").update(s).digest("hex").slice(0, 12), readText = async (registry, rel) => {
|
|
@@ -172970,13 +173363,13 @@ var DEFAULT_REGISTRY, isHttp2 = (s) => /^https?:/i.test(s), sha122 = (s) => crea
|
|
|
172970
173363
|
}
|
|
172971
173364
|
}
|
|
172972
173365
|
try {
|
|
172973
|
-
return
|
|
173366
|
+
return readFileSync12(join14(registry, rel), "utf8");
|
|
172974
173367
|
} catch {
|
|
172975
173368
|
return null;
|
|
172976
173369
|
}
|
|
172977
173370
|
}, safeRead = (path) => {
|
|
172978
173371
|
try {
|
|
172979
|
-
return
|
|
173372
|
+
return readFileSync12(path, "utf8");
|
|
172980
173373
|
} catch {
|
|
172981
173374
|
return null;
|
|
172982
173375
|
}
|
|
@@ -172998,7 +173391,7 @@ var DEFAULT_REGISTRY, isHttp2 = (s) => /^https?:/i.test(s), sha122 = (s) => crea
|
|
|
172998
173391
|
return JSON.parse(raw);
|
|
172999
173392
|
}, readLock2 = (path) => {
|
|
173000
173393
|
try {
|
|
173001
|
-
const lock = JSON.parse(
|
|
173394
|
+
const lock = JSON.parse(readFileSync12(path, "utf8"));
|
|
173002
173395
|
if (lock && typeof lock === "object" && lock.icons && lock.packs)
|
|
173003
173396
|
return lock;
|
|
173004
173397
|
} catch {}
|
|
@@ -213727,65 +214120,65 @@ var init_esm30 = __esm(() => {
|
|
|
213727
214120
|
});
|
|
213728
214121
|
|
|
213729
214122
|
// src/types.ts
|
|
213730
|
-
import { dirname as dirname13, join as
|
|
213731
|
-
import { existsSync as
|
|
214123
|
+
import { dirname as dirname13, join as join22, relative as relative3, sep } from "node:path";
|
|
214124
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync15, readdirSync as readdirSync10, readFileSync as readFileSync18, rmSync as rmSync8, statSync as statSync8, writeFileSync as writeFileSync17 } from "node:fs";
|
|
213732
214125
|
var TSCONFIG, listBundleFiles = (dir, base = dir) => {
|
|
213733
|
-
if (!
|
|
214126
|
+
if (!existsSync21(dir))
|
|
213734
214127
|
return [];
|
|
213735
214128
|
const out = [];
|
|
213736
|
-
for (const name of
|
|
213737
|
-
const path2 =
|
|
213738
|
-
if (
|
|
214129
|
+
for (const name of readdirSync10(dir)) {
|
|
214130
|
+
const path2 = join22(dir, name);
|
|
214131
|
+
if (statSync8(path2).isDirectory())
|
|
213739
214132
|
out.push(...listBundleFiles(path2, base));
|
|
213740
214133
|
else
|
|
213741
214134
|
out.push(relative3(base, path2).split(sep).join("/"));
|
|
213742
214135
|
}
|
|
213743
214136
|
return out;
|
|
213744
214137
|
}, pruneEmptyDirs = (dir, root) => {
|
|
213745
|
-
if (!
|
|
214138
|
+
if (!existsSync21(dir) || !statSync8(dir).isDirectory())
|
|
213746
214139
|
return;
|
|
213747
|
-
for (const name of
|
|
213748
|
-
pruneEmptyDirs(
|
|
213749
|
-
if (dir !== root &&
|
|
213750
|
-
|
|
214140
|
+
for (const name of readdirSync10(dir))
|
|
214141
|
+
pruneEmptyDirs(join22(dir, name), root);
|
|
214142
|
+
if (dir !== root && readdirSync10(dir).length === 0)
|
|
214143
|
+
rmSync8(dir, { recursive: true });
|
|
213751
214144
|
}, writeTypeBundle = (root, bundle) => {
|
|
213752
|
-
const typesDir =
|
|
214145
|
+
const typesDir = join22(root, LECODES_DIR, "types");
|
|
213753
214146
|
const before = new Set(listBundleFiles(typesDir));
|
|
213754
214147
|
const stats = { added: 0, updated: 0, removed: 0, total: Object.keys(bundle).length };
|
|
213755
214148
|
for (const [path2, text] of Object.entries(bundle)) {
|
|
213756
|
-
const dest =
|
|
214149
|
+
const dest = join22(typesDir, path2);
|
|
213757
214150
|
if (!before.has(path2))
|
|
213758
214151
|
stats.added++;
|
|
213759
|
-
else if (
|
|
214152
|
+
else if (readFileSync18(dest, "utf8") !== text)
|
|
213760
214153
|
stats.updated++;
|
|
213761
214154
|
else
|
|
213762
214155
|
continue;
|
|
213763
|
-
|
|
213764
|
-
|
|
214156
|
+
mkdirSync15(dirname13(dest), { recursive: true });
|
|
214157
|
+
writeFileSync17(dest, text);
|
|
213765
214158
|
}
|
|
213766
214159
|
for (const path2 of before) {
|
|
213767
214160
|
if (path2 in bundle)
|
|
213768
214161
|
continue;
|
|
213769
|
-
|
|
214162
|
+
rmSync8(join22(typesDir, path2));
|
|
213770
214163
|
stats.removed++;
|
|
213771
214164
|
}
|
|
213772
214165
|
pruneEmptyDirs(typesDir, typesDir);
|
|
213773
214166
|
return stats;
|
|
213774
214167
|
}, bundleHash = (bundle) => sha256(Object.keys(bundle).sort().map((k) => `${k}\x00${bundle[k]}`).join("\x00")), readProjectTypeBundle = (root) => {
|
|
213775
|
-
const typesDir =
|
|
214168
|
+
const typesDir = join22(root, LECODES_DIR, "types");
|
|
213776
214169
|
const bundle = {};
|
|
213777
214170
|
for (const path2 of listBundleFiles(typesDir))
|
|
213778
|
-
bundle[path2] =
|
|
214171
|
+
bundle[path2] = readFileSync18(join22(typesDir, path2), "utf8");
|
|
213779
214172
|
return bundle;
|
|
213780
214173
|
}, materializeTypes = async (root, apiUrl) => writeTypeBundle(root, await getTypes(apiUrl)), readLocalTypeBundle = () => {
|
|
213781
214174
|
const candidates = [
|
|
213782
|
-
|
|
213783
|
-
|
|
214175
|
+
join22(distRoot, "runtime", "sdk-types.json"),
|
|
214176
|
+
join22(distRoot, "..", "sdk", "dist", "types.json")
|
|
213784
214177
|
];
|
|
213785
214178
|
for (const path2 of candidates) {
|
|
213786
|
-
if (
|
|
214179
|
+
if (existsSync21(path2)) {
|
|
213787
214180
|
try {
|
|
213788
|
-
return JSON.parse(
|
|
214181
|
+
return JSON.parse(readFileSync18(path2, "utf8"));
|
|
213789
214182
|
} catch {}
|
|
213790
214183
|
}
|
|
213791
214184
|
}
|
|
@@ -213797,7 +214190,7 @@ var TSCONFIG, listBundleFiles = (dir, base = dir) => {
|
|
|
213797
214190
|
writeTypeBundle(root, bundle);
|
|
213798
214191
|
return true;
|
|
213799
214192
|
}, writeTsconfig = (root) => {
|
|
213800
|
-
|
|
214193
|
+
writeFileSync17(join22(root, "tsconfig.json"), JSON.stringify(TSCONFIG, null, 2));
|
|
213801
214194
|
};
|
|
213802
214195
|
var init_types4 = __esm(() => {
|
|
213803
214196
|
init_api();
|
|
@@ -213823,14 +214216,14 @@ var init_types4 = __esm(() => {
|
|
|
213823
214216
|
});
|
|
213824
214217
|
|
|
213825
214218
|
// src/compile/designCompile.ts
|
|
213826
|
-
import { readFileSync as
|
|
213827
|
-
import { basename as
|
|
214219
|
+
import { readFileSync as readFileSync19 } from "node:fs";
|
|
214220
|
+
import { basename as basename9 } from "node:path";
|
|
213828
214221
|
var LOCAL_ASSET_SCHEME = "lecodesfile://", compileDesignScreen = async (opts) => {
|
|
213829
214222
|
const { root, manifest, designDir, designKey, screenId, state } = opts;
|
|
213830
214223
|
const entries = [];
|
|
213831
214224
|
const scan2 = scanLocal(root, { ignore: loadIgnore(root), tracked: new Set(Object.keys(manifest?.files ?? {})) });
|
|
213832
214225
|
for (const file of scan2.files) {
|
|
213833
|
-
const type = manifest?.files[file.path]?.type ?? classifyKind(
|
|
214226
|
+
const type = manifest?.files[file.path]?.type ?? classifyKind(basename9(file.path));
|
|
213834
214227
|
if (type === "resource") {
|
|
213835
214228
|
entries.push({
|
|
213836
214229
|
path: file.path,
|
|
@@ -213841,7 +214234,7 @@ var LOCAL_ASSET_SCHEME = "lecodesfile://", compileDesignScreen = async (opts) =>
|
|
|
213841
214234
|
} else if (type === "shader") {
|
|
213842
214235
|
entries.push({ path: file.path, type, shaders: [] });
|
|
213843
214236
|
} else {
|
|
213844
|
-
entries.push({ path: file.path, type, text:
|
|
214237
|
+
entries.push({ path: file.path, type, text: readFileSync19(file.absPath, "utf8") });
|
|
213845
214238
|
}
|
|
213846
214239
|
}
|
|
213847
214240
|
const invoke = state !== undefined ? `screen(${JSON.stringify(state)})` : "screen()";
|
|
@@ -213886,7 +214279,7 @@ var init_designCompile = __esm(() => {
|
|
|
213886
214279
|
|
|
213887
214280
|
// src/browserAuth.ts
|
|
213888
214281
|
import { createServer } from "node:http";
|
|
213889
|
-
import { spawn as
|
|
214282
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
213890
214283
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
213891
214284
|
import { hostname } from "node:os";
|
|
213892
214285
|
var SUCCESS_HTML = `<!doctype html><meta charset="utf-8"><title>lecodes</title>
|
|
@@ -213911,16 +214304,16 @@ var SUCCESS_HTML = `<!doctype html><meta charset="utf-8"><title>lecodes</title>
|
|
|
213911
214304
|
args = [url];
|
|
213912
214305
|
}
|
|
213913
214306
|
try {
|
|
213914
|
-
const child =
|
|
214307
|
+
const child = spawn5(cmd, args, { stdio: "ignore", detached: true });
|
|
213915
214308
|
child.on("error", () => {});
|
|
213916
214309
|
child.unref();
|
|
213917
214310
|
} catch {}
|
|
213918
214311
|
}, browserLogin = async (webBase, timeoutMs = 5 * 60 * 1000, open = openBrowser) => {
|
|
213919
214312
|
const state = randomBytes2(16).toString("hex");
|
|
213920
|
-
let
|
|
214313
|
+
let resolve11;
|
|
213921
214314
|
let reject;
|
|
213922
214315
|
const tokenPromise = new Promise((res, rej) => {
|
|
213923
|
-
|
|
214316
|
+
resolve11 = res;
|
|
213924
214317
|
reject = rej;
|
|
213925
214318
|
});
|
|
213926
214319
|
const server2 = createServer((req, res) => {
|
|
@@ -213938,7 +214331,7 @@ var SUCCESS_HTML = `<!doctype html><meta charset="utf-8"><title>lecodes</title>
|
|
|
213938
214331
|
}
|
|
213939
214332
|
res.writeHead(200, { "content-type": "text/html" });
|
|
213940
214333
|
res.end(SUCCESS_HTML);
|
|
213941
|
-
|
|
214334
|
+
resolve11(token);
|
|
213942
214335
|
});
|
|
213943
214336
|
await new Promise((res, rej) => {
|
|
213944
214337
|
server2.once("error", rej);
|
|
@@ -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 readFileSync20 } from "node:fs";
|
|
214835
|
+
import { join as join24 } from "node:path";
|
|
214836
|
+
var PHONE_SIZE, readMetaFrames = (designDir) => {
|
|
214837
|
+
try {
|
|
214838
|
+
const meta = JSON.parse(readFileSync20(join24(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
|
|
214443
|
-
import { basename as
|
|
214862
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync18, readFileSync as readFileSync21, readdirSync as readdirSync13, writeFileSync as writeFileSync19, appendFileSync } from "node:fs";
|
|
214863
|
+
import { basename as basename10, join as join25 } from "node:path";
|
|
214444
214864
|
var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
214445
214865
|
let root;
|
|
214446
214866
|
let manifest;
|
|
@@ -214455,13 +214875,13 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
214455
214875
|
return {
|
|
214456
214876
|
root,
|
|
214457
214877
|
manifest,
|
|
214458
|
-
name: manifest?.name ??
|
|
214878
|
+
name: manifest?.name ?? basename10(root),
|
|
214459
214879
|
dirName,
|
|
214460
|
-
designDir:
|
|
214880
|
+
designDir: join25(root, dirName),
|
|
214461
214881
|
designKey: "/" + dirName.replace(/\\/g, "/")
|
|
214462
214882
|
};
|
|
214463
214883
|
}, loadDesignServer = async (ctx, needs = []) => {
|
|
214464
|
-
if (!
|
|
214884
|
+
if (!existsSync24(ctx.designDir)) {
|
|
214465
214885
|
throw new CliError(`No ${ctx.dirName}/ folder here. Run "lecodes design init" to scaffold it.`);
|
|
214466
214886
|
}
|
|
214467
214887
|
let mod;
|
|
@@ -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 =
|
|
214497
|
-
if (!
|
|
214916
|
+
const dir = join25(designDir, "screens");
|
|
214917
|
+
if (!existsSync24(dir))
|
|
214498
214918
|
return [];
|
|
214499
|
-
return
|
|
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
|
-
}
|
|
214919
|
+
return readdirSync13(dir).filter((f3) => f3.endsWith(".ts") && !f3.startsWith(".")).map((f3) => f3.slice(0, -3)).sort();
|
|
214512
214920
|
}, writeMcpConfig = (ctx, port) => {
|
|
214513
|
-
const path2 =
|
|
214921
|
+
const path2 = join25(ctx.root, ".mcp.json");
|
|
214514
214922
|
const entry = { type: "http", url: `http://localhost:${port}/mcp` };
|
|
214515
214923
|
let config = {};
|
|
214516
|
-
if (
|
|
214924
|
+
if (existsSync24(path2)) {
|
|
214517
214925
|
try {
|
|
214518
|
-
config = JSON.parse(
|
|
214926
|
+
config = JSON.parse(readFileSync21(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;
|
|
@@ -214528,7 +214936,7 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
214528
214936
|
return;
|
|
214529
214937
|
}
|
|
214530
214938
|
config.mcpServers["lecodes-design"] = entry;
|
|
214531
|
-
|
|
214939
|
+
writeFileSync19(path2, JSON.stringify(config, null, 2) + `
|
|
214532
214940
|
`);
|
|
214533
214941
|
log(`${c.green(existing ? "updated" : "created")} .mcp.json (lecodes-design MCP → ${entry.url})`);
|
|
214534
214942
|
}, init = async (ctx, args) => {
|
|
@@ -214537,26 +214945,26 @@ 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
|
+
[join25("shared", "tokens.ts"), TOKENS_TS],
|
|
214949
|
+
[join25("shared", "tabs.ts"), TABS_TS],
|
|
214950
|
+
[join25("screens", "home.ts"), HOME_SCREEN_TS]
|
|
214543
214951
|
];
|
|
214544
|
-
|
|
214545
|
-
|
|
214952
|
+
mkdirSync18(join25(ctx.designDir, "screens"), { recursive: true });
|
|
214953
|
+
mkdirSync18(join25(ctx.designDir, "shared"), { recursive: true });
|
|
214546
214954
|
let created = 0;
|
|
214547
214955
|
for (const [rel, content] of files) {
|
|
214548
|
-
const abs =
|
|
214549
|
-
if (
|
|
214956
|
+
const abs = join25(ctx.designDir, rel);
|
|
214957
|
+
if (existsSync24(abs)) {
|
|
214550
214958
|
note(`kept ${ctx.dirName}/${rel.replace(/\\/g, "/")}`);
|
|
214551
214959
|
continue;
|
|
214552
214960
|
}
|
|
214553
|
-
|
|
214961
|
+
writeFileSync19(abs, content);
|
|
214554
214962
|
log(`${c.green("created")} ${ctx.dirName}/${rel.replace(/\\/g, "/")}`);
|
|
214555
214963
|
created++;
|
|
214556
214964
|
}
|
|
214557
|
-
const ignorePath =
|
|
214965
|
+
const ignorePath = join25(ctx.root, ".lecodesignore");
|
|
214558
214966
|
const ignoreLines = [`${ctx.dirName}/.snapshots/`, `${ctx.dirName}/comments/`];
|
|
214559
|
-
const current =
|
|
214967
|
+
const current = existsSync24(ignorePath) ? readFileSync21(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
|
-
|
|
214575
|
-
|
|
214982
|
+
mkdirSync18(join25(ctx.designDir, ".lecodes", "types"), { recursive: true });
|
|
214983
|
+
writeFileSync19(join25(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,16 +215028,16 @@ 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") ?? join25(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)
|
|
214627
|
-
|
|
215035
|
+
mkdirSync18(outDir, { recursive: true });
|
|
214628
215036
|
for (const id of targets) {
|
|
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(readFileSync21(join25(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
|
-
|
|
214674
|
-
log(`${c.green("✓")} ${tag} → ${
|
|
215081
|
+
writeFileSync19(join25(outDir, `${base}.json`), result.text);
|
|
215082
|
+
log(`${c.green("✓")} ${tag} → ${join25(outDir, `${base}.json`)}`);
|
|
214675
215083
|
if (pngResult) {
|
|
214676
|
-
|
|
214677
|
-
log(`${c.green("✓")} ${tag} → ${
|
|
215084
|
+
writeFileSync19(join25(outDir, `${base}.png`), pngResult.png);
|
|
215085
|
+
log(`${c.green("✓")} ${tag} → ${join25(outDir, `${base}.png`)}`);
|
|
214678
215086
|
}
|
|
214679
215087
|
}
|
|
214680
215088
|
}
|
|
@@ -214976,7 +215384,7 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
214976
215384
|
note("Read them with: lecodes design comments");
|
|
214977
215385
|
}, flushDesignComments = async (args) => {
|
|
214978
215386
|
const ctx = getContext(args);
|
|
214979
|
-
if (!
|
|
215387
|
+
if (!existsSync24(ctx.designDir))
|
|
214980
215388
|
return null;
|
|
214981
215389
|
const platform = optionalPlatform(ctx);
|
|
214982
215390
|
if (!platform)
|
|
@@ -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.2",
|
|
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: {
|
|
@@ -216185,10 +216593,10 @@ var package_default = {
|
|
|
216185
216593
|
// src/commands/app.ts
|
|
216186
216594
|
init_manifest();
|
|
216187
216595
|
init_config();
|
|
216188
|
-
import { execFileSync as
|
|
216189
|
-
import { cpSync as
|
|
216596
|
+
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
216597
|
+
import { cpSync as cpSync4, existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync17, readdirSync as readdirSync9, rmSync as rmSync7, statSync as statSync7, writeFileSync as writeFileSync16 } from "node:fs";
|
|
216190
216598
|
import { homedir as homedir7 } from "node:os";
|
|
216191
|
-
import { basename as
|
|
216599
|
+
import { basename as basename8, dirname as dirname12, join as join21, resolve as resolve8 } from "node:path";
|
|
216192
216600
|
|
|
216193
216601
|
// src/commands/compile.ts
|
|
216194
216602
|
init_api();
|
|
@@ -216313,7 +216721,7 @@ init_fonts();
|
|
|
216313
216721
|
init_compile();
|
|
216314
216722
|
import { execFile } from "node:child_process";
|
|
216315
216723
|
import { createHash as createHash4, randomBytes } from "node:crypto";
|
|
216316
|
-
import { copyFileSync, existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as
|
|
216724
|
+
import { copyFileSync, existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync11, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
216317
216725
|
import { tmpdir } from "node:os";
|
|
216318
216726
|
import { basename as basename3, join as join13 } from "node:path";
|
|
216319
216727
|
|
|
@@ -216325,10 +216733,18 @@ import { join as join12, resolve } from "node:path";
|
|
|
216325
216733
|
// src/releases.ts
|
|
216326
216734
|
init_util();
|
|
216327
216735
|
import { createHash as createHash3 } from "node:crypto";
|
|
216328
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync5, mkdtempSync, renameSync, rmSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
216736
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, mkdtempSync, readFileSync as readFileSync10, renameSync, rmSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
216329
216737
|
import { join as join11 } from "node:path";
|
|
216330
216738
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
216331
216739
|
var MANIFEST_URL = "https://storage.yandexcloud.net/le-codes/releases/versions.json";
|
|
216740
|
+
var ARTIFACT_STAMP = ".lecodes-artifact.json";
|
|
216741
|
+
var cachedArtifactSha = (dir) => {
|
|
216742
|
+
try {
|
|
216743
|
+
return JSON.parse(readFileSync10(join11(dir, ARTIFACT_STAMP), "utf8")).sha256 ?? null;
|
|
216744
|
+
} catch {
|
|
216745
|
+
return null;
|
|
216746
|
+
}
|
|
216747
|
+
};
|
|
216332
216748
|
var semverNewer = (a, b) => {
|
|
216333
216749
|
const pa = a.split(/[.-]/).map(Number), pb = b.split(/[.-]/).map(Number);
|
|
216334
216750
|
for (let i = 0;i < 3; i++)
|
|
@@ -216370,15 +216786,20 @@ var downloadReleaseArchive = async (file, opts) => {
|
|
|
216370
216786
|
const archiveName = file.url.split("/").pop() ?? "archive";
|
|
216371
216787
|
const archive = join11(stage, archiveName);
|
|
216372
216788
|
writeFileSync6(archive, bytes);
|
|
216373
|
-
|
|
216374
|
-
|
|
216375
|
-
|
|
216376
|
-
|
|
216377
|
-
|
|
216789
|
+
if (opts.extract) {
|
|
216790
|
+
opts.extract(archive, stage);
|
|
216791
|
+
} else {
|
|
216792
|
+
const winTar = join11(process.env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe");
|
|
216793
|
+
const tarExe = process.platform === "win32" && existsSync9(winTar) ? winTar : "tar";
|
|
216794
|
+
const tar = spawnSync2(tarExe, ["-xf", archiveName], { cwd: stage, stdio: ["ignore", "ignore", "pipe"] });
|
|
216795
|
+
if (tar.status !== 0)
|
|
216796
|
+
throw new CliError(`Couldn't extract the archive: ${tar.stderr?.toString() ?? "tar failed"}`);
|
|
216797
|
+
}
|
|
216378
216798
|
rmSync(archive);
|
|
216379
216799
|
const bad = opts.validate(stage);
|
|
216380
216800
|
if (bad)
|
|
216381
216801
|
throw new CliError(bad);
|
|
216802
|
+
writeFileSync6(join11(stage, ARTIFACT_STAMP), JSON.stringify({ sha256: file.sha256, url: file.url }, null, 2));
|
|
216382
216803
|
try {
|
|
216383
216804
|
renameSync(stage, opts.dest);
|
|
216384
216805
|
} catch {
|
|
@@ -216404,7 +216825,13 @@ var platformKey = () => {
|
|
|
216404
216825
|
return "windows-x64";
|
|
216405
216826
|
if (process.platform === "linux")
|
|
216406
216827
|
return "linux-x64";
|
|
216407
|
-
|
|
216828
|
+
if (process.platform === "darwin") {
|
|
216829
|
+
if (process.arch !== "arm64") {
|
|
216830
|
+
throw new CliError("matc isn't published for Intel macOS — set LECODES_MATC to a locally built matc " + "(creator-gl/out/cmake-release/filament/tools/matc), or push the project once so shaders " + "compile on the server.");
|
|
216831
|
+
}
|
|
216832
|
+
return "macos-arm64";
|
|
216833
|
+
}
|
|
216834
|
+
throw new CliError(`matc isn't published for ${process.platform}.`);
|
|
216408
216835
|
};
|
|
216409
216836
|
var cacheRoot = () => join12(homedir2(), ".lecodes", "matc");
|
|
216410
216837
|
var findMatc = (dir) => {
|
|
@@ -216486,15 +216913,27 @@ var matcIdentity = (exe) => {
|
|
|
216486
216913
|
const stat = statSync2(exe);
|
|
216487
216914
|
return `${stat.size}:${Math.trunc(stat.mtimeMs)}`;
|
|
216488
216915
|
};
|
|
216489
|
-
var shaderHash = (text, matcId) => createHash4("sha256").update(matcId
|
|
216916
|
+
var shaderHash = (text, matcId) => createHash4("sha256").update(`${TARGET_MATRIX_GENERATION}\x00${matcId}\x00${text}`).digest("hex").slice(0, 16);
|
|
216490
216917
|
var artifactFamily = (text) => {
|
|
216491
|
-
const matcPlatforms = matcPlatformsFor(text);
|
|
216918
|
+
const matcPlatforms = [...matcPlatformsFor(text), ...LOCAL_ONLY_PLATFORMS];
|
|
216492
216919
|
return [...matcPlatforms, ...matcPlatforms.includes("webgl") ? [] : ["webgl"], "webgl-lite"];
|
|
216493
216920
|
};
|
|
216494
216921
|
var shaderIsCompiled = (root, text, matcId) => {
|
|
216495
216922
|
const hash = shaderHash(text, matcId);
|
|
216496
216923
|
return artifactFamily(text).every((p) => existsSync11(join13(shadersCacheDir(root), `${hash}_${p}.filamat`)));
|
|
216497
216924
|
};
|
|
216925
|
+
var shaderCompiledPlatforms = (root, text, matcId) => {
|
|
216926
|
+
const hash = shaderHash(text, matcId);
|
|
216927
|
+
return artifactFamily(text).filter((p) => existsSync11(join13(shadersCacheDir(root), `${hash}_${p}.filamat`)));
|
|
216928
|
+
};
|
|
216929
|
+
var localShaderArtifact = (root, text, platform) => {
|
|
216930
|
+
const matcExe = resolveMatcExeCached();
|
|
216931
|
+
if (!matcExe)
|
|
216932
|
+
return null;
|
|
216933
|
+
const file = join13(shadersCacheDir(root), `${shaderHash(text, matcIdentity(matcExe))}_${platform}.filamat`);
|
|
216934
|
+
return existsSync11(file) ? file : null;
|
|
216935
|
+
};
|
|
216936
|
+
var allShaderPlatforms = () => [...Object.keys(ALL_PLATFORM_BUILD), "webgl-lite"];
|
|
216498
216937
|
var runMatc = (exe, args) => new Promise((resolve2) => {
|
|
216499
216938
|
execFile(exe, args, { windowsHide: true }, (error, _stdout, stderr) => {
|
|
216500
216939
|
const text = String(stderr ?? "") || (error ? error.message : "");
|
|
@@ -216509,6 +216948,8 @@ var cleanMatcOutput = (err, tempName, realName) => {
|
|
|
216509
216948
|
return (cut >= 0 ? output.slice(0, cut) : output).trim();
|
|
216510
216949
|
};
|
|
216511
216950
|
var NATIVE_PLATFORMS = ["opengl", "metal", "desktop"];
|
|
216951
|
+
var ALL_NATIVE_PLATFORMS = ["opengl", "metal", "desktop", "desktop-vulkan"];
|
|
216952
|
+
var desktopShaderPlatform = (platform, renderer) => platform === "win32" ? renderer === "vulkan" ? "desktop-vulkan" : "desktop" : platform === "darwin" ? "metal" : "opengl";
|
|
216512
216953
|
var applyLocalShaders = async (entries, opts) => {
|
|
216513
216954
|
const shaderEntries = entries.filter((e) => e.type === "shader" && e.absPath && (opts.local || !e.shaders?.length));
|
|
216514
216955
|
if (shaderEntries.length === 0)
|
|
@@ -216526,16 +216967,23 @@ var applyLocalShaders = async (entries, opts) => {
|
|
|
216526
216967
|
mkdirSync6(cacheDir, { recursive: true });
|
|
216527
216968
|
const artifacts = [];
|
|
216528
216969
|
for (const entry of shaderEntries) {
|
|
216529
|
-
const text =
|
|
216970
|
+
const text = readFileSync11(entry.absPath, "utf8");
|
|
216530
216971
|
const hash = shaderHash(text, matcId);
|
|
216531
|
-
const
|
|
216972
|
+
const family = artifactFamily(text);
|
|
216532
216973
|
const artifact = (platform) => `${hash}_${platform}.filamat`;
|
|
216533
|
-
|
|
216974
|
+
const has = (platform) => existsSync11(join13(cacheDir, artifact(platform)));
|
|
216975
|
+
const wanted = opts.platforms ? family.filter((p) => opts.platforms.includes(p)) : family;
|
|
216976
|
+
if (wanted.includes("webgl") && !matcPlatformsFor(text).includes("webgl") && !wanted.includes("opengl")) {
|
|
216977
|
+
wanted.push("opengl");
|
|
216978
|
+
}
|
|
216979
|
+
const missing = wanted.filter((p) => !has(p));
|
|
216980
|
+
const matcMissing = missing.filter((p) => p !== "webgl-lite" && !(p === "webgl" && !matcPlatformsFor(text).includes("webgl")));
|
|
216981
|
+
if (matcMissing.length > 0) {
|
|
216534
216982
|
const tempName = `lecodes-matc-${randomBytes(6).toString("hex")}.mat`;
|
|
216535
216983
|
const tempFile = join13(tmpdir(), tempName);
|
|
216536
216984
|
try {
|
|
216537
|
-
for (const platform of
|
|
216538
|
-
const build =
|
|
216985
|
+
for (const platform of matcMissing) {
|
|
216986
|
+
const build = ALL_PLATFORM_BUILD[platform];
|
|
216539
216987
|
writeFileSync7(tempFile, build.replaceExternal ? text.replace(externalSamplerReg, "type:sampler2d") : text);
|
|
216540
216988
|
const out = join13(cacheDir, artifact(platform));
|
|
216541
216989
|
const { ok, stderr } = await runMatc(matcExe, matcArgs(platform, out, tempFile));
|
|
@@ -216548,15 +216996,21 @@ ${cleanMatcOutput(stderr, tempName, basename3(entry.path))}`);
|
|
|
216548
216996
|
} finally {
|
|
216549
216997
|
rmSync2(tempFile, { force: true });
|
|
216550
216998
|
}
|
|
216551
|
-
|
|
216552
|
-
|
|
216553
|
-
|
|
216999
|
+
}
|
|
217000
|
+
if (missing.includes("webgl") && !matcPlatformsFor(text).includes("webgl")) {
|
|
217001
|
+
copyFileSync(join13(cacheDir, artifact("opengl")), join13(cacheDir, artifact("webgl")));
|
|
217002
|
+
}
|
|
217003
|
+
if (missing.includes("webgl-lite")) {
|
|
216554
217004
|
writeFileSync7(join13(cacheDir, artifact("webgl-lite")), JSON.stringify(buildLiteMaterialArtifact(text)));
|
|
216555
217005
|
}
|
|
216556
217006
|
if (opts.local) {
|
|
216557
|
-
|
|
217007
|
+
const staged = opts.platforms ? ALL_NATIVE_PLATFORMS.filter((p) => opts.platforms.includes(p)) : NATIVE_PLATFORMS;
|
|
217008
|
+
entry.shaders = [
|
|
217009
|
+
{ platform: "opengl", src: artifact("opengl") },
|
|
217010
|
+
...staged.filter((p) => p !== "opengl").map((platform) => ({ platform, src: artifact(platform) }))
|
|
217011
|
+
];
|
|
216558
217012
|
entry.shadersLocal = true;
|
|
216559
|
-
for (const platform of
|
|
217013
|
+
for (const platform of staged) {
|
|
216560
217014
|
artifacts.push({ name: artifact(platform), absPath: join13(cacheDir, artifact(platform)) });
|
|
216561
217015
|
}
|
|
216562
217016
|
} else {
|
|
@@ -216576,7 +217030,7 @@ init_manifest();
|
|
|
216576
217030
|
init_ignore();
|
|
216577
217031
|
init_util();
|
|
216578
217032
|
import { execFileSync } from "node:child_process";
|
|
216579
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as
|
|
217033
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "node:fs";
|
|
216580
217034
|
import { homedir as homedir4 } from "node:os";
|
|
216581
217035
|
import { dirname as dirname8, join as join15, resolve as resolve2 } from "node:path";
|
|
216582
217036
|
var FIRST_PARTY_PLUGINS = {
|
|
@@ -216587,6 +217041,22 @@ var FIRST_PARTY_PLUGINS = {
|
|
|
216587
217041
|
};
|
|
216588
217042
|
var readAppConfigAt = (root) => readJson(join15(root, "app.json"));
|
|
216589
217043
|
var DESKTOP_DEFAULT_WINDOW = { width: 1280, height: 720 };
|
|
217044
|
+
var resolveDesktopRenderer = (root, args) => {
|
|
217045
|
+
const raw = (args ? flagStr(args, "renderer") : undefined) ?? process.env.LECODES_DESKTOP_RENDERER ?? readAppConfigAt(root)?.desktop?.renderer;
|
|
217046
|
+
if (raw === undefined)
|
|
217047
|
+
return "gl";
|
|
217048
|
+
const value = String(raw).toLowerCase();
|
|
217049
|
+
if (value === "gl" || value === "opengl")
|
|
217050
|
+
return "gl";
|
|
217051
|
+
if (value === "vulkan" || value === "vk") {
|
|
217052
|
+
if (process.platform === "darwin") {
|
|
217053
|
+
note('desktop.renderer "vulkan" is Windows-only — the macOS host renders with Metal.');
|
|
217054
|
+
return "gl";
|
|
217055
|
+
}
|
|
217056
|
+
return "vulkan";
|
|
217057
|
+
}
|
|
217058
|
+
throw new CliError(`Unknown desktop renderer "${raw}". Use "gl" or "vulkan".`);
|
|
217059
|
+
};
|
|
216590
217060
|
var desktopDefaultViewport = (root) => {
|
|
216591
217061
|
const cfg = readAppConfigAt(root);
|
|
216592
217062
|
if (!cfg?.desktop || cfg.ios || cfg.android)
|
|
@@ -216600,7 +217070,7 @@ var readJson = (path) => {
|
|
|
216600
217070
|
if (!existsSync13(path))
|
|
216601
217071
|
return null;
|
|
216602
217072
|
try {
|
|
216603
|
-
return JSON.parse(
|
|
217073
|
+
return JSON.parse(readFileSync13(path, "utf8").replace(/^\uFEFF/, ""));
|
|
216604
217074
|
} catch {
|
|
216605
217075
|
throw new CliError(`Invalid JSON in ${path}`);
|
|
216606
217076
|
}
|
|
@@ -216631,7 +217101,7 @@ ${tail}`);
|
|
|
216631
217101
|
};
|
|
216632
217102
|
var ensureShellIgnored = (root, dir) => {
|
|
216633
217103
|
const ignorePath = join15(root, ".lecodesignore");
|
|
216634
|
-
const ignore = existsSync13(ignorePath) ?
|
|
217104
|
+
const ignore = existsSync13(ignorePath) ? readFileSync13(ignorePath, "utf8") : DEFAULT_IGNORE_FILE;
|
|
216635
217105
|
if (new RegExp(`^${dir}\\/$`, "m").test(ignore))
|
|
216636
217106
|
return;
|
|
216637
217107
|
const comment = ignore.includes("# Native app shells") ? "" : "# Native app shells — generated by `lecodes app init`, never pushed to LeCodes.\n";
|
|
@@ -216654,6 +217124,14 @@ var detectEngines = (bundleJs) => {
|
|
|
216654
217124
|
`);
|
|
216655
217125
|
return { gl: !/^\/\/ gl: disable$/m.test(header), g2: /^\/\/ 2d: enable$/m.test(header) };
|
|
216656
217126
|
};
|
|
217127
|
+
var assertRendererSupportsBundle = (renderer, bundleJs) => {
|
|
217128
|
+
if (renderer !== "vulkan")
|
|
217129
|
+
return;
|
|
217130
|
+
if (!detectEngines(bundleJs).g2)
|
|
217131
|
+
return;
|
|
217132
|
+
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).
|
|
217133
|
+
` + " Build with the default renderer: drop `desktop.renderer` from app.json, or pass `--renderer gl`.");
|
|
217134
|
+
};
|
|
216657
217135
|
var resolveVariant = (requested, bundleJs, configKey = "ios.variant") => {
|
|
216658
217136
|
const uses = detectEngines(bundleJs);
|
|
216659
217137
|
const detected = uses.gl && uses.g2 ? "full" : uses.gl ? "3d" : uses.g2 ? "2d" : "core";
|
|
@@ -216866,7 +217344,8 @@ var buildBundle = async (args, shell, opts = {}) => {
|
|
|
216866
217344
|
}
|
|
216867
217345
|
let stagedShaders = [];
|
|
216868
217346
|
if (localAssets && (opts.localShaders ?? !shell)) {
|
|
216869
|
-
const
|
|
217347
|
+
const platforms = opts.desktopRenderer ? [desktopShaderPlatform(process.platform, opts.desktopRenderer)] : undefined;
|
|
217348
|
+
const local = await applyLocalShaders(entries, { root, local: true, platforms });
|
|
216870
217349
|
warnings.push(...local.warnings);
|
|
216871
217350
|
stagedShaders = local.artifacts;
|
|
216872
217351
|
}
|
|
@@ -216878,9 +217357,11 @@ var buildBundle = async (args, shell, opts = {}) => {
|
|
|
216878
217357
|
publicUrl: shell ? "" : publicUrl,
|
|
216879
217358
|
makeResourceEntry: (path, absPath) => ({ path, type: "resource", fileSrc: encodeURI(path), absPath })
|
|
216880
217359
|
});
|
|
217360
|
+
if (opts.extraEntries)
|
|
217361
|
+
entries.push(...opts.extraEntries);
|
|
216881
217362
|
const iconsHeader = await applyAppAssetIcons(entries);
|
|
216882
217363
|
note("Compiling…");
|
|
216883
|
-
const entryFlag = flagStr(args, "entry");
|
|
217364
|
+
const entryFlag = opts.entryOverride ?? flagStr(args, "entry");
|
|
216884
217365
|
if (!entryFlag) {
|
|
216885
217366
|
const ambiguous = ambiguousEntryWarning(entries);
|
|
216886
217367
|
if (ambiguous)
|
|
@@ -217760,14 +218241,14 @@ buildNumber) — edit it there, then re-run \`lecodes app sync\`. Plugins are ma
|
|
|
217760
218241
|
init_manifest();
|
|
217761
218242
|
import { execFileSync as execFileSync2, spawn, spawnSync as spawnSync3 } from "node:child_process";
|
|
217762
218243
|
import { createHash as createHash6 } from "node:crypto";
|
|
217763
|
-
import { chmodSync, cpSync, existsSync as existsSync16, mkdirSync as mkdirSync10, readFileSync as
|
|
218244
|
+
import { chmodSync, cpSync, existsSync as existsSync16, mkdirSync as mkdirSync10, readFileSync as readFileSync15, readdirSync as readdirSync5, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync12 } from "node:fs";
|
|
217764
218245
|
import { homedir as homedir5 } from "node:os";
|
|
217765
218246
|
import { basename as basename5, dirname as dirname10, join as join17 } from "node:path";
|
|
217766
218247
|
init_util();
|
|
217767
218248
|
|
|
217768
218249
|
// src/commands/appIcon.ts
|
|
217769
218250
|
init_util();
|
|
217770
|
-
import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as
|
|
218251
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync14, writeFileSync as writeFileSync11 } from "node:fs";
|
|
217771
218252
|
import { dirname as dirname9, join as join16, resolve as resolve4 } from "node:path";
|
|
217772
218253
|
var MASTER_PX = 1024;
|
|
217773
218254
|
var DENSITIES = [
|
|
@@ -217897,7 +218378,7 @@ var restampManifestIcon = (androidDir) => {
|
|
|
217897
218378
|
const path2 = join16(androidDir, "app", "src", "main", "AndroidManifest.xml");
|
|
217898
218379
|
if (!existsSync15(path2))
|
|
217899
218380
|
return;
|
|
217900
|
-
const content =
|
|
218381
|
+
const content = readFileSync14(path2, "utf8");
|
|
217901
218382
|
if (content.includes(`android:icon="@mipmap/ic_launcher"`))
|
|
217902
218383
|
return;
|
|
217903
218384
|
if (!/android:icon="[^"]*"/.test(content)) {
|
|
@@ -218817,7 +219298,7 @@ var shellSdkVersionAndroid = (androidDir) => {
|
|
|
218817
219298
|
const path2 = join17(androidDir, "lecodes-runtime", "build.gradle.kts");
|
|
218818
219299
|
if (!existsSync16(path2))
|
|
218819
219300
|
return null;
|
|
218820
|
-
const m = /io\.letary:lecodes-sdk(?:-(?:core|2d|3d))?:(\d+\.\d+\.\d+)@aar/.exec(
|
|
219301
|
+
const m = /io\.letary:lecodes-sdk(?:-(?:core|2d|3d))?:(\d+\.\d+\.\d+)@aar/.exec(readFileSync15(path2, "utf8"));
|
|
218821
219302
|
return m ? m[1] : null;
|
|
218822
219303
|
};
|
|
218823
219304
|
var GRADLE_WRAPPER_JAR_URL = "https://raw.githubusercontent.com/gradle/gradle/v8.13.0/gradle/wrapper/gradle-wrapper.jar";
|
|
@@ -218825,7 +219306,7 @@ var GRADLE_WRAPPER_JAR_SHA256 = "81a82aaea5abcc8ff68b3dfcb58b3c3c429378efd98e743
|
|
|
218825
219306
|
var ensureGradleWrapperJar = async () => {
|
|
218826
219307
|
const cached = join17(homedir5(), ".lecodes", "gradle-wrapper", GRADLE_VERSION, "gradle-wrapper.jar");
|
|
218827
219308
|
if (existsSync16(cached))
|
|
218828
|
-
return new Uint8Array(
|
|
219309
|
+
return new Uint8Array(readFileSync15(cached));
|
|
218829
219310
|
try {
|
|
218830
219311
|
const res = await fetch(GRADLE_WRAPPER_JAR_URL);
|
|
218831
219312
|
if (!res.ok)
|
|
@@ -218952,7 +219433,7 @@ var syncAndroid = async (root, config, prior) => {
|
|
|
218952
219433
|
resources = prior.resources;
|
|
218953
219434
|
} else {
|
|
218954
219435
|
resources = await compileShell({ _: [], flags: { o: join17(assetsDir, "app.js") } }, { remotePatterns });
|
|
218955
|
-
bundleJs =
|
|
219436
|
+
bundleJs = readFileSync15(join17(assetsDir, "app.js"), "utf8");
|
|
218956
219437
|
}
|
|
218957
219438
|
const requestedVariant = config.android?.variant ?? "auto";
|
|
218958
219439
|
if (requestedVariant !== "auto" && !SDK_VARIANTS.includes(requestedVariant)) {
|
|
@@ -219089,7 +219570,7 @@ var restampAppBuildGradle = (androidDir, config, gradlePlugins) => {
|
|
|
219089
219570
|
warn("android/app/build.gradle.kts is missing — run `lecodes app init android` on a clean tree to regenerate the shell.");
|
|
219090
219571
|
return;
|
|
219091
219572
|
}
|
|
219092
|
-
let content =
|
|
219573
|
+
let content = readFileSync15(path2, "utf8");
|
|
219093
219574
|
const before = content;
|
|
219094
219575
|
const versionName = config.version ?? "1.0.0";
|
|
219095
219576
|
const versionCode = config.android?.versionCode ?? 1;
|
|
@@ -219262,13 +219743,13 @@ var doctorAndroidChecks = async (root, config, check) => {
|
|
|
219262
219743
|
};
|
|
219263
219744
|
|
|
219264
219745
|
// src/commands/appDesktop.ts
|
|
219265
|
-
import { execFileSync as
|
|
219266
|
-
import { cpSync as
|
|
219267
|
-
import { basename as
|
|
219746
|
+
import { execFileSync as execFileSync5, spawn as spawn4 } from "node:child_process";
|
|
219747
|
+
import { cpSync as cpSync3, existsSync as existsSync19, mkdirSync as mkdirSync13, readdirSync as readdirSync8, rmSync as rmSync6, statSync as statSync6, writeFileSync as writeFileSync15 } from "node:fs";
|
|
219748
|
+
import { basename as basename7, dirname as dirname11, join as join20, resolve as resolve7 } from "node:path";
|
|
219268
219749
|
|
|
219269
219750
|
// src/desktopRenderer.ts
|
|
219270
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
219271
|
-
import { copyFileSync as copyFileSync2, existsSync as existsSync17, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync2, readdirSync as readdirSync6, readFileSync as
|
|
219751
|
+
import { execFileSync as execFileSync3, spawn as spawn2, spawnSync as spawnSync4 } from "node:child_process";
|
|
219752
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync17, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync2, readdirSync as readdirSync6, readFileSync as readFileSync16, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync13 } from "node:fs";
|
|
219272
219753
|
import { homedir as homedir6, tmpdir as tmpdir2 } from "node:os";
|
|
219273
219754
|
import { join as join18, resolve as resolve5 } from "node:path";
|
|
219274
219755
|
init_util();
|
|
@@ -219373,14 +219854,21 @@ var makeStackDemangler = (bundleJs) => {
|
|
|
219373
219854
|
|
|
219374
219855
|
// src/desktopRenderer.ts
|
|
219375
219856
|
var DESKTOP_RENDERER_VERSION = "1.1.0";
|
|
219376
|
-
var
|
|
219857
|
+
var DESKTOP_RENDERER_MIN_VERSION = { gl: "0.0.0", vulkan: "1.4.0" };
|
|
219858
|
+
var platformKey2 = (renderer = "gl") => {
|
|
219377
219859
|
if (process.platform === "win32")
|
|
219378
|
-
return "windows-x64";
|
|
219860
|
+
return renderer === "vulkan" ? "windows-x64-vulkan" : "windows-x64";
|
|
219861
|
+
if (renderer === "vulkan") {
|
|
219862
|
+
throw new CliError(`The Vulkan desktop host is Windows-only (this is ${process.platform}). Use the default renderer.`);
|
|
219863
|
+
}
|
|
219379
219864
|
if (process.platform === "linux")
|
|
219380
219865
|
return "ubuntu-linux-x64";
|
|
219381
|
-
|
|
219866
|
+
if (process.platform === "darwin")
|
|
219867
|
+
return process.arch === "arm64" ? "macos-arm64" : "macos-x64";
|
|
219868
|
+
throw new CliError(`The LeCodes Desktop host isn't available on ${process.platform}.`);
|
|
219382
219869
|
};
|
|
219383
|
-
var
|
|
219870
|
+
var isMac = () => process.platform === "darwin";
|
|
219871
|
+
var exeName = () => process.platform === "win32" ? "lecodes-desktop.exe" : isMac() ? "lecodes-app" : "lecodes-desktop";
|
|
219384
219872
|
var findExe = (dir) => {
|
|
219385
219873
|
const name = exeName();
|
|
219386
219874
|
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
@@ -219406,40 +219894,110 @@ var installedDesktopVersions = () => {
|
|
|
219406
219894
|
};
|
|
219407
219895
|
var selectedDesktopVersion = () => {
|
|
219408
219896
|
try {
|
|
219409
|
-
const saved = JSON.parse(
|
|
219897
|
+
const saved = JSON.parse(readFileSync16(currentFile(), "utf8")).version;
|
|
219410
219898
|
if (saved && /^\d+\.\d+\.\d+/.test(saved) && semverNewer(DESKTOP_RENDERER_VERSION, saved) > 0)
|
|
219411
219899
|
return saved;
|
|
219412
219900
|
} catch {}
|
|
219413
219901
|
return DESKTOP_RENDERER_VERSION;
|
|
219414
219902
|
};
|
|
219415
219903
|
var fetchDesktopManifest = () => fetchReleaseProduct("desktop", "desktop");
|
|
219416
|
-
var
|
|
219417
|
-
|
|
219904
|
+
var pickMacVersion = (versions, wanted, key = "macos-arm64") => {
|
|
219905
|
+
if (versions.find((v) => v.version === wanted)?.files[key])
|
|
219906
|
+
return wanted;
|
|
219907
|
+
const eligible = versions.filter((v) => v.files[key] && semverNewer(DESKTOP_RENDERER_VERSION, v.version) >= 0).map((v) => v.version).sort(semverNewer);
|
|
219908
|
+
return eligible[0] ?? null;
|
|
219909
|
+
};
|
|
219910
|
+
var extractDmg = (archive, stage) => {
|
|
219911
|
+
const mount = join18(stage, "mnt");
|
|
219912
|
+
mkdirSync11(mount, { recursive: true });
|
|
219913
|
+
execFileSync3("hdiutil", ["attach", "-nobrowse", "-readonly", "-quiet", "-mountpoint", mount, archive]);
|
|
219914
|
+
try {
|
|
219915
|
+
const app = readdirSync6(mount).find((n2) => n2.endsWith(".app"));
|
|
219916
|
+
if (!app)
|
|
219917
|
+
throw new CliError("The downloaded DMG holds no .app bundle.");
|
|
219918
|
+
execFileSync3("cp", ["-R", join18(mount, app), join18(stage, app)]);
|
|
219919
|
+
} finally {
|
|
219920
|
+
spawnSync4("hdiutil", ["detach", "-quiet", "-force", mount]);
|
|
219921
|
+
rmSync4(mount, { recursive: true, force: true });
|
|
219922
|
+
}
|
|
219923
|
+
};
|
|
219924
|
+
var downloadDesktop = async (version, dest, renderer = "gl") => {
|
|
219925
|
+
const key = platformKey2(renderer);
|
|
219418
219926
|
const entry = (await fetchDesktopManifest()).versions.find((v) => v.version === version);
|
|
219419
219927
|
const file = entry?.files[key];
|
|
219420
219928
|
if (!file) {
|
|
219929
|
+
if (renderer === "vulkan") {
|
|
219930
|
+
throw new CliError(`The Vulkan desktop host isn't published for LeCodes Desktop ${version} [${key}].
|
|
219931
|
+
` + ` 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.`);
|
|
219932
|
+
}
|
|
219421
219933
|
throw new CliError(`LeCodes Desktop ${version} [${key}] isn't published yet — \`--desktop\` needs it for the ` + `headless render contract.
|
|
219422
219934
|
Set LECODES_DESKTOP_EXE to a locally built host, or update lecodes-cli.`);
|
|
219423
219935
|
}
|
|
219424
|
-
note(`Downloading
|
|
219936
|
+
note(`Downloading LeCodes Desktop ${version} [${key}] (${(file.size / 1024 / 1024).toFixed(1)} MB)…`);
|
|
219425
219937
|
await downloadReleaseArchive(file, {
|
|
219426
219938
|
cacheRoot: cacheRoot2(),
|
|
219427
219939
|
dest,
|
|
219428
|
-
validate: (dir) => findExe(dir) ? null : "The downloaded archive doesn't contain the desktop host binary."
|
|
219940
|
+
validate: (dir) => findExe(dir) ? null : "The downloaded archive doesn't contain the desktop host binary.",
|
|
219941
|
+
extract: file.url.toLowerCase().endsWith(".dmg") ? extractDmg : undefined
|
|
219429
219942
|
});
|
|
219430
219943
|
};
|
|
219431
|
-
var
|
|
219944
|
+
var findMacHostExe = () => {
|
|
219945
|
+
const candidates = [
|
|
219946
|
+
...["/Applications", join18(homedir6(), "Applications")].flatMap((root) => ["LeCodes Desktop.app", "LeCodes.app"].map((app) => join18(root, app)))
|
|
219947
|
+
].map((app) => join18(app, "Contents", "MacOS", "lecodes-app"));
|
|
219948
|
+
return candidates.find((exe) => existsSync17(exe)) ?? null;
|
|
219949
|
+
};
|
|
219950
|
+
var resolveMacExe = async (selected) => {
|
|
219951
|
+
const cached = findExe(join18(cacheRoot2(), `desktop-${selected}`));
|
|
219952
|
+
if (cached)
|
|
219953
|
+
return cached;
|
|
219954
|
+
try {
|
|
219955
|
+
const { versions } = await fetchDesktopManifest();
|
|
219956
|
+
const version = pickMacVersion(versions, selected, platformKey2());
|
|
219957
|
+
if (!version) {
|
|
219958
|
+
throw new CliError(`No LeCodes Desktop release for ${platformKey2()} is published at or above ${DESKTOP_RENDERER_VERSION}.`);
|
|
219959
|
+
}
|
|
219960
|
+
const dir = join18(cacheRoot2(), `desktop-${version}`);
|
|
219961
|
+
let exe = findExe(dir);
|
|
219962
|
+
if (!exe) {
|
|
219963
|
+
await downloadDesktop(version, dir);
|
|
219964
|
+
exe = findExe(dir);
|
|
219965
|
+
if (!exe)
|
|
219966
|
+
throw new CliError(`Renderer cache at ${dir} is broken — delete the folder and retry.`);
|
|
219967
|
+
}
|
|
219968
|
+
if (version !== selected) {
|
|
219969
|
+
note(`LeCodes Desktop ${selected} has no macOS build — using ${version}.`);
|
|
219970
|
+
mkdirSync11(cacheRoot2(), { recursive: true });
|
|
219971
|
+
writeFileSync13(currentFile(), JSON.stringify({ version }, null, 2));
|
|
219972
|
+
}
|
|
219973
|
+
return exe;
|
|
219974
|
+
} catch (e2) {
|
|
219975
|
+
const installed = findMacHostExe();
|
|
219976
|
+
if (installed) {
|
|
219977
|
+
warnErr(`${e2 instanceof Error ? e2.message : String(e2)}
|
|
219978
|
+
Falling back to the installed host: ${installed}`);
|
|
219979
|
+
return installed;
|
|
219980
|
+
}
|
|
219981
|
+
throw new CliError(`${e2 instanceof Error ? e2.message : String(e2)}
|
|
219982
|
+
` + " No LeCodes Desktop.app is installed either — retry online (`lecodes desktop update`), install the DMG into\n" + " /Applications, or point LECODES_DESKTOP_EXE at a build (…/LeCodes Desktop.app/Contents/MacOS/lecodes-app).");
|
|
219983
|
+
}
|
|
219984
|
+
};
|
|
219985
|
+
var resolveDesktopExe = async (renderer = "gl") => {
|
|
219432
219986
|
const override = process.env.LECODES_DESKTOP_EXE;
|
|
219433
219987
|
if (override) {
|
|
219434
219988
|
if (!existsSync17(override))
|
|
219435
219989
|
throw new CliError(`LECODES_DESKTOP_EXE points at "${override}" which doesn't exist.`);
|
|
219436
219990
|
return resolve5(override);
|
|
219437
219991
|
}
|
|
219992
|
+
if (renderer === "vulkan")
|
|
219993
|
+
platformKey2(renderer);
|
|
219438
219994
|
const version = selectedDesktopVersion();
|
|
219439
|
-
|
|
219995
|
+
if (isMac())
|
|
219996
|
+
return resolveMacExe(version);
|
|
219997
|
+
const dir = join18(cacheRoot2(), `desktop-${version}${renderer === "vulkan" ? "-vulkan" : ""}`);
|
|
219440
219998
|
let exe = existsSync17(dir) ? findExe(dir) : null;
|
|
219441
219999
|
if (!exe) {
|
|
219442
|
-
await downloadDesktop(version, dir);
|
|
220000
|
+
await downloadDesktop(version, dir, renderer);
|
|
219443
220001
|
exe = findExe(dir);
|
|
219444
220002
|
if (!exe)
|
|
219445
220003
|
throw new CliError(`Renderer cache at ${dir} is broken — delete the folder and retry.`);
|
|
@@ -219447,13 +220005,23 @@ var resolveDesktopExe = async () => {
|
|
|
219447
220005
|
return exe;
|
|
219448
220006
|
};
|
|
219449
220007
|
var updateDesktop = async () => {
|
|
219450
|
-
const
|
|
220008
|
+
const manifest = await fetchDesktopManifest();
|
|
220009
|
+
const latest = isMac() ? pickMacVersion(manifest.versions, manifest.latest, platformKey2()) : manifest.latest;
|
|
220010
|
+
if (!latest)
|
|
220011
|
+
throw new CliError(`No LeCodes Desktop release for ${platformKey2()} is published at or above ${DESKTOP_RENDERER_VERSION}.`);
|
|
219451
220012
|
if (semverNewer(latest, DESKTOP_RENDERER_VERSION) > 0) {
|
|
219452
220013
|
throw new CliError(`The newest published LeCodes Desktop is ${latest}, but this CLI needs ≥ ${DESKTOP_RENDERER_VERSION} ` + `for the render contract. Publish the desktop release first.`);
|
|
219453
220014
|
}
|
|
219454
220015
|
const before = selectedDesktopVersion();
|
|
219455
220016
|
const dir = join18(cacheRoot2(), `desktop-${latest}`);
|
|
219456
|
-
const
|
|
220017
|
+
const published = (await fetchDesktopManifest()).versions.find((v) => v.version === latest)?.files[platformKey2()];
|
|
220018
|
+
const cached = existsSync17(dir) && findExe(dir) !== null;
|
|
220019
|
+
const republished = cached && published !== undefined && cachedArtifactSha(dir) !== published.sha256;
|
|
220020
|
+
if (republished) {
|
|
220021
|
+
note(`LeCodes Desktop ${latest} was republished since this machine cached it — downloading the current build.`);
|
|
220022
|
+
rmSync4(dir, { recursive: true, force: true });
|
|
220023
|
+
}
|
|
220024
|
+
const hadIt = cached && !republished;
|
|
219457
220025
|
if (!hadIt)
|
|
219458
220026
|
await downloadDesktop(latest, dir);
|
|
219459
220027
|
mkdirSync11(cacheRoot2(), { recursive: true });
|
|
@@ -219488,6 +220056,8 @@ var windowEnv = (opts) => {
|
|
|
219488
220056
|
if (opts.maxRenderSize && opts.maxRenderSize[0] > 0 && opts.maxRenderSize[1] > 0) {
|
|
219489
220057
|
env.CREATOR_RENDER_MAX = `${Math.round(opts.maxRenderSize[0])}x${Math.round(opts.maxRenderSize[1])}`;
|
|
219490
220058
|
}
|
|
220059
|
+
if (opts.fps)
|
|
220060
|
+
env.CREATOR_FPS = "1";
|
|
219491
220061
|
return env;
|
|
219492
220062
|
};
|
|
219493
220063
|
var stageBundle = (js, resources) => {
|
|
@@ -219517,7 +220087,7 @@ var pipeThroughDemangler = (src, dst, map) => {
|
|
|
219517
220087
|
});
|
|
219518
220088
|
};
|
|
219519
220089
|
var runDesktopApp = async (js, resources, opts = {}) => {
|
|
219520
|
-
const exe = await resolveDesktopExe();
|
|
220090
|
+
const exe = await resolveDesktopExe(opts.renderer);
|
|
219521
220091
|
const runDir = stageBundle(js, resources);
|
|
219522
220092
|
try {
|
|
219523
220093
|
const env = { ...process.env, ...windowEnv(opts) };
|
|
@@ -219541,7 +220111,7 @@ var runDesktopApp = async (js, resources, opts = {}) => {
|
|
|
219541
220111
|
}
|
|
219542
220112
|
};
|
|
219543
220113
|
var runDesktopDev = async (url, opts = {}) => {
|
|
219544
|
-
const exe = await resolveDesktopExe();
|
|
220114
|
+
const exe = await resolveDesktopExe(opts.renderer);
|
|
219545
220115
|
const env = { ...process.env, ...windowEnv(opts) };
|
|
219546
220116
|
await new Promise((done, fail) => {
|
|
219547
220117
|
const child = spawn2(exe, [url], { env, stdio: ["ignore", "ignore", "inherit"] });
|
|
@@ -219550,7 +220120,10 @@ var runDesktopDev = async (url, opts = {}) => {
|
|
|
219550
220120
|
});
|
|
219551
220121
|
};
|
|
219552
220122
|
var runDesktopRender = async (js, resources, opts) => {
|
|
219553
|
-
|
|
220123
|
+
if (isMac()) {
|
|
220124
|
+
throw new CliError("`lecodes render --desktop` isn't available on macOS yet — the macOS host has no headless mode " + "(CREATOR_HIDDEN / CREATOR_DUMP_PNG). `lecodes desktop run` and `lecodes desktop build` do work there.");
|
|
220125
|
+
}
|
|
220126
|
+
const exe = await resolveDesktopExe(opts.renderer);
|
|
219554
220127
|
const runDir = stageBundle(js, resources);
|
|
219555
220128
|
try {
|
|
219556
220129
|
const appJs = join18(runDir, "app.js");
|
|
@@ -219577,11 +220150,16 @@ var runDesktopRender = async (js, resources, opts) => {
|
|
|
219577
220150
|
return await new Promise((done, fail) => {
|
|
219578
220151
|
const child = spawn2(exe, [appJs], { cwd: runDir, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
219579
220152
|
const chunks = [];
|
|
220153
|
+
let stopped = false;
|
|
219580
220154
|
const sink = (d) => {
|
|
219581
220155
|
const text = String(d);
|
|
219582
220156
|
chunks.push(text);
|
|
219583
220157
|
if (opts.logs)
|
|
219584
220158
|
process.stderr.write(demangle(text));
|
|
220159
|
+
if (!stopped && opts.stopOnLine?.(text)) {
|
|
220160
|
+
stopped = true;
|
|
220161
|
+
child.kill();
|
|
220162
|
+
}
|
|
219585
220163
|
};
|
|
219586
220164
|
child.stdout.on("data", sink);
|
|
219587
220165
|
child.stderr.on("data", sink);
|
|
@@ -219596,6 +220174,8 @@ var runDesktopRender = async (js, resources, opts) => {
|
|
|
219596
220174
|
child.on("exit", (code) => {
|
|
219597
220175
|
clearTimeout(killer);
|
|
219598
220176
|
const output = demangle(chunks.join(""));
|
|
220177
|
+
if (stopped)
|
|
220178
|
+
return done({ output, exitCode: 0 });
|
|
219599
220179
|
if (code === 0 && existsSync17(outAbs))
|
|
219600
220180
|
return done({ output, exitCode: code });
|
|
219601
220181
|
const tail = demangle(chunks.slice(-80).join("")).trimEnd();
|
|
@@ -219611,15 +220191,333 @@ ${tail}` : "";
|
|
|
219611
220191
|
|
|
219612
220192
|
// src/commands/appDesktop.ts
|
|
219613
220193
|
init_util();
|
|
220194
|
+
|
|
220195
|
+
// src/commands/appDesktopMac.ts
|
|
220196
|
+
init_util();
|
|
220197
|
+
import { execFileSync as execFileSync4, spawn as spawn3, spawnSync as spawnSync5 } from "node:child_process";
|
|
220198
|
+
import { cpSync as cpSync2, existsSync as existsSync18, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync3, readdirSync as readdirSync7, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync14 } from "node:fs";
|
|
220199
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
220200
|
+
import { basename as basename6, join as join19, resolve as resolve6 } from "node:path";
|
|
219614
220201
|
var mb = (bytes) => `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
219615
220202
|
var dirSize = (dir) => readdirSync7(dir, { withFileTypes: true }).reduce((sum, e2) => {
|
|
219616
220203
|
const p2 = join19(dir, e2.name);
|
|
219617
|
-
return sum + (e2.isDirectory() ? dirSize(p2) : statSync5(p2).size);
|
|
220204
|
+
return sum + (e2.isDirectory() ? dirSize(p2) : e2.isSymbolicLink() ? 0 : statSync5(p2).size);
|
|
220205
|
+
}, 0);
|
|
220206
|
+
var cleanBundleName = (name) => name.replace(/[/:]/g, "").trim() || "App";
|
|
220207
|
+
var macHostAppOf = (exe) => {
|
|
220208
|
+
const app = resolve6(exe, "..", "..", "..");
|
|
220209
|
+
if (!app.endsWith(".app") || !existsSync18(join19(app, "Contents", "Info.plist"))) {
|
|
220210
|
+
throw new CliError(`The macOS host must be an .app bundle (got ${exe}). Install LeCodes Desktop.app, or point ` + `LECODES_DESKTOP_EXE at <Build>.app/Contents/MacOS/lecodes-app — a bare SwiftPM binary has no ` + `resource bundles and can't be re-signed into an app.`);
|
|
220211
|
+
}
|
|
220212
|
+
return app;
|
|
220213
|
+
};
|
|
220214
|
+
var macHostProvidedNames = (exe) => {
|
|
220215
|
+
const names3 = new Set;
|
|
220216
|
+
const walk = (dir) => {
|
|
220217
|
+
for (const e2 of readdirSync7(dir, { withFileTypes: true })) {
|
|
220218
|
+
if (e2.isDirectory())
|
|
220219
|
+
walk(join19(dir, e2.name));
|
|
220220
|
+
else
|
|
220221
|
+
names3.add(e2.name);
|
|
220222
|
+
}
|
|
220223
|
+
};
|
|
220224
|
+
const res = join19(macHostAppOf(exe), "Contents", "Resources");
|
|
220225
|
+
if (existsSync18(res))
|
|
220226
|
+
walk(res);
|
|
220227
|
+
return names3;
|
|
220228
|
+
};
|
|
220229
|
+
var plistRead = (plist, key) => {
|
|
220230
|
+
const r2 = spawnSync5("/usr/libexec/PlistBuddy", ["-c", `Print :${key}`, plist], { encoding: "utf8" });
|
|
220231
|
+
return r2.status === 0 ? r2.stdout.trim() : null;
|
|
220232
|
+
};
|
|
220233
|
+
var plistSet = (plist, key, value) => {
|
|
220234
|
+
const quoted = `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
220235
|
+
const set = spawnSync5("/usr/libexec/PlistBuddy", ["-c", `Set :${key} ${quoted}`, plist], { stdio: "ignore" });
|
|
220236
|
+
if (set.status !== 0)
|
|
220237
|
+
execFileSync4("/usr/libexec/PlistBuddy", ["-c", `Add :${key} string ${quoted}`, plist], { stdio: "ignore" });
|
|
220238
|
+
};
|
|
220239
|
+
var macHostVersion = (exe) => {
|
|
220240
|
+
const plist = join19(macHostAppOf(exe), "Contents", "Info.plist");
|
|
220241
|
+
return plistRead(plist, "LeCodesHostVersion") ?? plistRead(plist, "CFBundleShortVersionString") ?? "unknown";
|
|
220242
|
+
};
|
|
220243
|
+
var macHostFeatures = (exe) => {
|
|
220244
|
+
const raw = plistRead(join19(macHostAppOf(exe), "Contents", "Info.plist"), "LeCodesHostFeatures") ?? "";
|
|
220245
|
+
return new Set(raw.split(`
|
|
220246
|
+
`).map((l2) => l2.trim()).filter((l2) => l2 && l2 !== "Array {" && l2 !== "}"));
|
|
220247
|
+
};
|
|
220248
|
+
var assertMacHostCanBundle = (exe) => {
|
|
220249
|
+
if (macHostFeatures(exe).has("project-bundle"))
|
|
220250
|
+
return;
|
|
220251
|
+
throw new CliError(`This LeCodes Desktop host (${macHostAppOf(exe)}) predates project bundles — an app built from it would ` + `open the host's home screen instead of the project.
|
|
220252
|
+
` + " Run `lecodes desktop update` (needs a published macOS host with project-bundle support), or point " + "LECODES_DESKTOP_EXE at a current build (…/LeCodes Desktop.app/Contents/MacOS/lecodes-app).");
|
|
220253
|
+
};
|
|
220254
|
+
var developerIdInKeychain = () => {
|
|
220255
|
+
const r2 = spawnSync5("security", ["find-identity", "-v", "-p", "codesigning"], { encoding: "utf8" });
|
|
220256
|
+
if (r2.status !== 0)
|
|
220257
|
+
return null;
|
|
220258
|
+
return /"(Developer ID Application: [^"]+)"/.exec(r2.stdout)?.[1] ?? null;
|
|
220259
|
+
};
|
|
220260
|
+
var resolveMacSigning = (cfg, overrides, keychainDeveloperId) => {
|
|
220261
|
+
let identity = overrides.sign ?? overrides.env.LECODES_MACOS_SIGN_ID ?? cfg?.signing?.identity;
|
|
220262
|
+
if (identity === undefined || identity === "" || identity === "auto")
|
|
220263
|
+
identity = keychainDeveloperId() ?? "-";
|
|
220264
|
+
if (identity === "adhoc" || identity === "ad-hoc")
|
|
220265
|
+
identity = "-";
|
|
220266
|
+
const notaryProfile = overrides.noNotarize ? null : overrides.notarize ?? overrides.env.LECODES_NOTARY_PROFILE ?? cfg?.notarize?.profile ?? null;
|
|
220267
|
+
if (notaryProfile && identity === "-") {
|
|
220268
|
+
throw new CliError(`Notarization needs a Developer ID signature — an ad-hoc-signed app can't be notarized.
|
|
220269
|
+
` + ' Set desktop.macos.signing.identity (or --sign) to a "Developer ID Application: …" certificate, ' + "or drop the notarize profile (--no-notarize).");
|
|
220270
|
+
}
|
|
220271
|
+
return { identity, notaryProfile: notaryProfile || null };
|
|
220272
|
+
};
|
|
220273
|
+
var FALLBACK_ENTITLEMENTS = `<?xml version="1.0" encoding="UTF-8"?>
|
|
220274
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
220275
|
+
<plist version="1.0">
|
|
220276
|
+
<dict>
|
|
220277
|
+
<key>com.apple.security.cs.allow-jit</key>
|
|
220278
|
+
<true/>
|
|
220279
|
+
</dict>
|
|
220280
|
+
</plist>
|
|
220281
|
+
`;
|
|
220282
|
+
var entitlementsFor = (hostApp) => {
|
|
220283
|
+
const r2 = spawnSync5("codesign", ["-d", "--entitlements", "-", "--xml", hostApp], { encoding: "utf8" });
|
|
220284
|
+
const xml = r2.status === 0 ? r2.stdout.trim() : "";
|
|
220285
|
+
if (xml.startsWith("<?xml") && xml.includes("com.apple.security.cs.allow-jit"))
|
|
220286
|
+
return xml + `
|
|
220287
|
+
`;
|
|
220288
|
+
return FALLBACK_ENTITLEMENTS;
|
|
220289
|
+
};
|
|
220290
|
+
var codesignRetrying = async (args) => {
|
|
220291
|
+
for (let attempt = 1;; attempt++) {
|
|
220292
|
+
const r2 = spawnSync5("codesign", args, { encoding: "utf8" });
|
|
220293
|
+
if (r2.status === 0)
|
|
220294
|
+
return;
|
|
220295
|
+
const out = (r2.stdout + `
|
|
220296
|
+
` + r2.stderr).trim();
|
|
220297
|
+
if (!/timestamp/i.test(out) || attempt >= 5)
|
|
220298
|
+
throw new CliError(`codesign failed:
|
|
220299
|
+
${out}`);
|
|
220300
|
+
const delay = attempt * 15;
|
|
220301
|
+
warn(`timestamp service unavailable (attempt ${attempt}/5) — retrying in ${delay}s…`);
|
|
220302
|
+
await new Promise((r3) => setTimeout(r3, delay * 1000));
|
|
220303
|
+
}
|
|
220304
|
+
};
|
|
220305
|
+
var signApp = async (app, signing, hostApp, tmp) => {
|
|
220306
|
+
if (signing.identity === "-") {
|
|
220307
|
+
note("Signing ad-hoc (runs on this Mac only — pass a Developer ID identity to distribute)…");
|
|
220308
|
+
await codesignRetrying(["--force", "--sign", "-", app]);
|
|
220309
|
+
return;
|
|
220310
|
+
}
|
|
220311
|
+
note(`Signing with "${signing.identity}" (Hardened Runtime + JIT entitlement)…`);
|
|
220312
|
+
const ent = join19(tmp, "entitlements.plist");
|
|
220313
|
+
writeFileSync14(ent, entitlementsFor(hostApp));
|
|
220314
|
+
await codesignRetrying(["--force", "--options", "runtime", "--timestamp", "--entitlements", ent, "--sign", signing.identity, app]);
|
|
220315
|
+
const verify = spawnSync5("codesign", ["--verify", "--strict", "--verbose=2", app], { encoding: "utf8" });
|
|
220316
|
+
if (verify.status !== 0)
|
|
220317
|
+
throw new CliError(`The signature doesn't verify:
|
|
220318
|
+
${(verify.stdout + verify.stderr).trim()}`);
|
|
220319
|
+
};
|
|
220320
|
+
var notarizeApp = (app, profile, tmp) => {
|
|
220321
|
+
const zip = join19(tmp, `${basename6(app, ".app")}.zip`);
|
|
220322
|
+
execFileSync4("ditto", ["-c", "-k", "--keepParent", app, zip]);
|
|
220323
|
+
note(`Notarizing with profile "${profile}" — this waits on Apple (usually 1–5 minutes)…`);
|
|
220324
|
+
const r2 = spawnSync5("xcrun", ["notarytool", "submit", zip, "--keychain-profile", profile, "--wait"], { encoding: "utf8" });
|
|
220325
|
+
const out = (r2.stdout + `
|
|
220326
|
+
` + r2.stderr).trim();
|
|
220327
|
+
const id = /id: ([0-9a-f-]{36})/.exec(out)?.[1];
|
|
220328
|
+
if (r2.status !== 0 || !/status: Accepted/.test(out)) {
|
|
220329
|
+
throw new CliError(`Notarization failed:
|
|
220330
|
+
${out}
|
|
220331
|
+
` + (id ? ` Details: xcrun notarytool log ${id} --keychain-profile ${profile}` : ""));
|
|
220332
|
+
}
|
|
220333
|
+
const staple = spawnSync5("xcrun", ["stapler", "staple", app], { encoding: "utf8" });
|
|
220334
|
+
if (staple.status !== 0)
|
|
220335
|
+
throw new CliError(`Stapling failed:
|
|
220336
|
+
${(staple.stdout + staple.stderr).trim()}`);
|
|
220337
|
+
success("Notarized + stapled.");
|
|
220338
|
+
};
|
|
220339
|
+
var gatekeeperVerdict = (app) => {
|
|
220340
|
+
const r2 = spawnSync5("spctl", ["-a", "-t", "exec", "-vv", app], { encoding: "utf8" });
|
|
220341
|
+
const line = (r2.stdout + r2.stderr).trim().split(`
|
|
220342
|
+
`).filter((l2) => l2.includes("source=") || l2.includes("rejected") || l2.includes("accepted")).join("; ");
|
|
220343
|
+
return { accepted: r2.status === 0, line: line || (r2.stdout + r2.stderr).trim() };
|
|
220344
|
+
};
|
|
220345
|
+
var writeIcns = async (root, config, dest, tmp) => {
|
|
220346
|
+
const master = await loadIconMaster(root, config);
|
|
220347
|
+
const { Jimp: Jimp2 } = await Promise.resolve().then(() => (init_esm30(), exports_esm2));
|
|
220348
|
+
const SIZE = 1024, INSET = 100, RADIUS = 185;
|
|
220349
|
+
const tileSize = SIZE - 2 * INSET;
|
|
220350
|
+
const tile = new Jimp2({ width: tileSize, height: tileSize, color: master.background });
|
|
220351
|
+
const art = master.image.clone();
|
|
220352
|
+
art.resize({ w: tileSize, h: tileSize });
|
|
220353
|
+
tile.composite(art, 0, 0);
|
|
220354
|
+
const { data } = tile.bitmap;
|
|
220355
|
+
for (let y2 = 0;y2 < tileSize; y2++) {
|
|
220356
|
+
for (let x2 = 0;x2 < tileSize; x2++) {
|
|
220357
|
+
const cx = x2 < RADIUS ? RADIUS : x2 >= tileSize - RADIUS ? tileSize - RADIUS - 1 : x2;
|
|
220358
|
+
const cy = y2 < RADIUS ? RADIUS : y2 >= tileSize - RADIUS ? tileSize - RADIUS - 1 : y2;
|
|
220359
|
+
const dx = x2 - cx, dy = y2 - cy;
|
|
220360
|
+
if (dx * dx + dy * dy > RADIUS * RADIUS)
|
|
220361
|
+
data[(y2 * tileSize + x2) * 4 + 3] = 0;
|
|
220362
|
+
}
|
|
220363
|
+
}
|
|
220364
|
+
const canvas = new Jimp2({ width: SIZE, height: SIZE, color: 0 });
|
|
220365
|
+
canvas.composite(tile, INSET, INSET);
|
|
220366
|
+
const iconset = join19(tmp, "AppIcon.iconset");
|
|
220367
|
+
mkdirSync12(iconset, { recursive: true });
|
|
220368
|
+
for (const pt of [16, 32, 128, 256, 512]) {
|
|
220369
|
+
for (const scale of [1, 2]) {
|
|
220370
|
+
const px = pt * scale;
|
|
220371
|
+
const frame = canvas.clone();
|
|
220372
|
+
frame.resize({ w: px, h: px });
|
|
220373
|
+
writeFileSync14(join19(iconset, `icon_${pt}x${pt}${scale === 2 ? "@2x" : ""}.png`), await frame.getBuffer("image/png"));
|
|
220374
|
+
}
|
|
220375
|
+
}
|
|
220376
|
+
execFileSync4("iconutil", ["-c", "icns", "-o", dest, iconset]);
|
|
220377
|
+
};
|
|
220378
|
+
var probeMacApp = async (app, out, args) => {
|
|
220379
|
+
const after = Number(flagStr(args, "after")) || 8;
|
|
220380
|
+
const shot = join19(out, `${basename6(app, ".app")}-probe.png`);
|
|
220381
|
+
rmSync5(shot, { force: true });
|
|
220382
|
+
note(`Probe: launching the built app for ${after + 2}s (the window will show)…`);
|
|
220383
|
+
await new Promise((done, fail) => {
|
|
220384
|
+
const child = spawn3(join19(app, "Contents", "MacOS", "lecodes-app"), [], {
|
|
220385
|
+
cwd: "/",
|
|
220386
|
+
env: { ...process.env, LECODES_SNAPSHOT: shot, LECODES_SNAPSHOT_AFTER: String(after), LECODES_EXIT_AFTER: String(after + 2) },
|
|
220387
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
220388
|
+
});
|
|
220389
|
+
const tail = [];
|
|
220390
|
+
const sink = (d) => {
|
|
220391
|
+
tail.push(String(d));
|
|
220392
|
+
while (tail.length > 60)
|
|
220393
|
+
tail.shift();
|
|
220394
|
+
};
|
|
220395
|
+
child.stdout.on("data", sink);
|
|
220396
|
+
child.stderr.on("data", sink);
|
|
220397
|
+
const killer = setTimeout(() => {
|
|
220398
|
+
child.kill();
|
|
220399
|
+
fail(new CliError("The probe timed out after 120s."));
|
|
220400
|
+
}, 120000);
|
|
220401
|
+
child.on("error", (e2) => {
|
|
220402
|
+
clearTimeout(killer);
|
|
220403
|
+
fail(new CliError(`Probe couldn't run: ${e2.message}`));
|
|
220404
|
+
});
|
|
220405
|
+
child.on("exit", (code) => {
|
|
220406
|
+
clearTimeout(killer);
|
|
220407
|
+
const detail = tail.length > 0 ? `
|
|
220408
|
+
${tail.join("").trimEnd()}` : "";
|
|
220409
|
+
if (code === 0 && existsSync18(shot)) {
|
|
220410
|
+
success(`Probe: ${shot}`);
|
|
220411
|
+
done();
|
|
220412
|
+
} else
|
|
220413
|
+
fail(new CliError(`Probe failed (exit ${code}).${detail}`));
|
|
220414
|
+
});
|
|
220415
|
+
});
|
|
220416
|
+
};
|
|
220417
|
+
var buildMacApp = async (input) => {
|
|
220418
|
+
const { root, config, name, slug, out, js, shipped, hostExe, args } = input;
|
|
220419
|
+
const hostApp = macHostAppOf(hostExe);
|
|
220420
|
+
assertMacHostCanBundle(hostExe);
|
|
220421
|
+
const mac = config.desktop?.macos ?? {};
|
|
220422
|
+
const appName = cleanBundleName(name);
|
|
220423
|
+
const app = join19(out, `${appName}.app`);
|
|
220424
|
+
const tmp = mkdtempSync3(join19(tmpdir3(), "lecodes-macapp-"));
|
|
220425
|
+
try {
|
|
220426
|
+
note(` host: LeCodes Desktop ${macHostVersion(hostExe)} (${hostApp})`);
|
|
220427
|
+
rmSync5(out, { recursive: true, force: true });
|
|
220428
|
+
mkdirSync12(out, { recursive: true });
|
|
220429
|
+
execFileSync4("cp", ["-R", hostApp, app]);
|
|
220430
|
+
execFileSync4("xattr", ["-cr", app]);
|
|
220431
|
+
rmSync5(join19(app, "Contents", "_CodeSignature"), { recursive: true, force: true });
|
|
220432
|
+
rmSync5(join19(app, "Contents", "CodeResources"), { force: true });
|
|
220433
|
+
const proj = join19(app, "Contents", "Resources", "project");
|
|
220434
|
+
mkdirSync12(join19(proj, "assets"), { recursive: true });
|
|
220435
|
+
writeFileSync14(join19(proj, "app.js"), js);
|
|
220436
|
+
let assetBytes = 0, assetCount = 0;
|
|
220437
|
+
for (const r2 of shipped) {
|
|
220438
|
+
if (!r2.absPath || !existsSync18(r2.absPath) || !statSync5(r2.absPath).isFile()) {
|
|
220439
|
+
warn(`asset "${r2.name}" (${r2.path}) has no local file — skipped`);
|
|
220440
|
+
continue;
|
|
220441
|
+
}
|
|
220442
|
+
cpSync2(r2.absPath, join19(proj, "assets", r2.name));
|
|
220443
|
+
assetBytes += statSync5(r2.absPath).size;
|
|
220444
|
+
assetCount++;
|
|
220445
|
+
}
|
|
220446
|
+
note(` assets: ${assetCount} files, ${mb(assetBytes)} → Contents/Resources/project/assets/`);
|
|
220447
|
+
writeJson(join19(proj, "app.json"), { name, ...config.desktop ? { desktop: { window: config.desktop.window } } : {} });
|
|
220448
|
+
const plist = join19(app, "Contents", "Info.plist");
|
|
220449
|
+
const bundleId = mac.bundleId ?? `codes.le.app.${slug.toLowerCase().replace(/[^a-z0-9.-]+/g, "-")}`;
|
|
220450
|
+
plistSet(plist, "CFBundleName", appName);
|
|
220451
|
+
plistSet(plist, "CFBundleDisplayName", appName);
|
|
220452
|
+
plistSet(plist, "CFBundleIdentifier", bundleId);
|
|
220453
|
+
plistSet(plist, "CFBundleShortVersionString", config.version ?? "1.0");
|
|
220454
|
+
plistSet(plist, "CFBundleVersion", String(mac.buildNumber ?? 1));
|
|
220455
|
+
if (mac.category)
|
|
220456
|
+
plistSet(plist, "LSApplicationCategoryType", mac.category);
|
|
220457
|
+
if (config.icon) {
|
|
220458
|
+
await writeIcns(root, config, join19(app, "Contents", "Resources", "AppIcon.icns"), tmp);
|
|
220459
|
+
plistSet(plist, "CFBundleIconFile", "AppIcon");
|
|
220460
|
+
note(" icon: AppIcon.icns rendered from " + config.icon);
|
|
220461
|
+
}
|
|
220462
|
+
note(` bundle: ${bundleId} ${config.version ?? "1.0"} (${mac.buildNumber ?? 1})`);
|
|
220463
|
+
const signing = resolveMacSigning(mac, {
|
|
220464
|
+
sign: flagStr(args, "sign"),
|
|
220465
|
+
notarize: flagStr(args, "notarize"),
|
|
220466
|
+
noNotarize: flagBool(args, "no-notarize"),
|
|
220467
|
+
env: process.env
|
|
220468
|
+
}, developerIdInKeychain);
|
|
220469
|
+
await signApp(app, signing, hostApp, tmp);
|
|
220470
|
+
if (signing.notaryProfile) {
|
|
220471
|
+
notarizeApp(app, signing.notaryProfile, tmp);
|
|
220472
|
+
} else if (signing.identity !== "-") {
|
|
220473
|
+
warn("Signed with Developer ID but NOT notarized — Gatekeeper refuses a downloaded copy on every other Mac.");
|
|
220474
|
+
warn(" Set desktop.macos.notarize.profile in app.json (a `xcrun notarytool store-credentials` profile) or pass --notarize <profile>.");
|
|
220475
|
+
}
|
|
220476
|
+
const verdict = gatekeeperVerdict(app);
|
|
220477
|
+
log(`${verdict.accepted ? c.green("gatekeeper") : c.yellow("gatekeeper")} ${verdict.line}`);
|
|
220478
|
+
if (signing.notaryProfile && !verdict.accepted) {
|
|
220479
|
+
throw new CliError("The app was notarized but Gatekeeper still rejects it — see the spctl line above.");
|
|
220480
|
+
}
|
|
220481
|
+
writeFileSync14(join19(out, "README.txt"), readmeText(appName, !!signing.notaryProfile, signing.identity !== "-"));
|
|
220482
|
+
success(`App: ${app} (${mb(dirSize(app))})`);
|
|
220483
|
+
if (flagBool(args, "probe"))
|
|
220484
|
+
await probeMacApp(app, out, args);
|
|
220485
|
+
if (flagBool(args, "zip")) {
|
|
220486
|
+
const zip = join19(out, `${appName}.zip`);
|
|
220487
|
+
rmSync5(zip, { force: true });
|
|
220488
|
+
execFileSync4("ditto", ["-c", "-k", "--keepParent", app, zip]);
|
|
220489
|
+
success(`Zip: ${zip} (${mb(statSync5(zip).size)})`);
|
|
220490
|
+
} else {
|
|
220491
|
+
note("Tip: --zip packs the .app for sending (ditto, structure-preserving).");
|
|
220492
|
+
}
|
|
220493
|
+
} finally {
|
|
220494
|
+
rmSync5(tmp, { recursive: true, force: true });
|
|
220495
|
+
}
|
|
220496
|
+
};
|
|
220497
|
+
var readmeText = (name, notarized, developerId) => `${name}
|
|
220498
|
+
${"=".repeat(Math.max(name.length, 4))}
|
|
220499
|
+
|
|
220500
|
+
A LeCodes desktop app for macOS (Apple Silicon). Nothing to install: unzip and double-click
|
|
220501
|
+
${name}.app — or drag it to Applications first.
|
|
220502
|
+
|
|
220503
|
+
${notarized ? `The app is signed and notarized by Apple: macOS asks once whether to open a downloaded app,
|
|
220504
|
+
then it runs.` : developerId ? `The app is signed but NOT notarized: macOS will refuse to open the downloaded copy. To run it
|
|
220505
|
+
anyway: System Settings › Privacy & Security › "Open Anyway" (after the first refused launch),
|
|
220506
|
+
or remove the quarantine flag in Terminal: xattr -dr com.apple.quarantine "` + name + '.app"' : `The app is not signed for distribution: macOS will refuse to open the downloaded copy. To run it
|
|
220507
|
+
anyway: System Settings › Privacy & Security › "Open Anyway" (after the first refused launch),
|
|
220508
|
+
or remove the quarantine flag in Terminal: xattr -dr com.apple.quarantine "` + name + '.app"'}
|
|
220509
|
+
`;
|
|
220510
|
+
|
|
220511
|
+
// src/commands/appDesktop.ts
|
|
220512
|
+
var mb2 = (bytes) => `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
220513
|
+
var dirSize2 = (dir) => readdirSync8(dir, { withFileTypes: true }).reduce((sum, e2) => {
|
|
220514
|
+
const p2 = join20(dir, e2.name);
|
|
220515
|
+
return sum + (e2.isDirectory() ? dirSize2(p2) : statSync6(p2).size);
|
|
219618
220516
|
}, 0);
|
|
219619
220517
|
var initDesktop = async (args) => {
|
|
219620
220518
|
const root = projectRoot();
|
|
219621
|
-
const existing = readJson(
|
|
219622
|
-
const name = flagStr(args, "name") ?? existing.name ??
|
|
220519
|
+
const existing = readJson(join20(root, "app.json")) ?? {};
|
|
220520
|
+
const name = flagStr(args, "name") ?? existing.name ?? basename7(root);
|
|
219623
220521
|
const window2 = {
|
|
219624
220522
|
width: 1280,
|
|
219625
220523
|
height: 800,
|
|
@@ -219629,28 +220527,33 @@ var initDesktop = async (args) => {
|
|
|
219629
220527
|
...flagBool(args, "fullscreen") ? { fullscreen: true } : {}
|
|
219630
220528
|
};
|
|
219631
220529
|
const config = { ...existing, name, desktop: { ...existing.desktop, window: window2 } };
|
|
219632
|
-
writeJson(
|
|
220530
|
+
writeJson(join20(root, "app.json"), config);
|
|
219633
220531
|
log(`${c.green("written")} app.json (desktop.window ${window2.width}×${window2.height}${window2.fullscreen ? ", fullscreen" : ""})`);
|
|
219634
220532
|
log(`Next: ${c.bold("lecodes dev --desktop")} to develop, ${c.bold("lecodes app build desktop")} to package.`);
|
|
219635
220533
|
};
|
|
219636
220534
|
var cleanFileName = (name) => name.replace(/[<>:"/\\|?*\u0000-\u001f]/g, "").replace(/[. ]+$/, "").trim() || "app";
|
|
220535
|
+
var ASSETS_DIR = "assets";
|
|
220536
|
+
var RUNTIME_DIR = "runtime";
|
|
219637
220537
|
var buildDesktop = async (args) => {
|
|
219638
220538
|
const root = projectRoot();
|
|
219639
|
-
const config = readJson(
|
|
219640
|
-
const name = (config.name ??
|
|
220539
|
+
const config = readJson(join20(root, "app.json")) ?? {};
|
|
220540
|
+
const name = (config.name ?? basename7(root)).trim() || "app";
|
|
219641
220541
|
const slug = flagStr(args, "slug") ?? cleanFileName(name);
|
|
219642
|
-
const out =
|
|
219643
|
-
if (
|
|
219644
|
-
const entries =
|
|
219645
|
-
const looksLikeBuild = entries.length === 0 || entries.includes("app.js");
|
|
220542
|
+
const out = resolve7(root, flagStr(args, "o", "out") ?? join20(root, "dist", slug));
|
|
220543
|
+
if (existsSync19(out)) {
|
|
220544
|
+
const entries = readdirSync8(out);
|
|
220545
|
+
const looksLikeBuild = entries.length === 0 || entries.includes("app.js") || entries.some((e2) => e2.endsWith(".app"));
|
|
219646
220546
|
if (!looksLikeBuild) {
|
|
219647
220547
|
throw new CliError(`${out} exists and doesn't look like a previous desktop build — pick another -o or clear it yourself.`);
|
|
219648
220548
|
}
|
|
219649
220549
|
}
|
|
219650
220550
|
if (config.desktop?.entry && flagStr(args, "entry") === undefined)
|
|
219651
220551
|
args.flags.entry = config.desktop.entry;
|
|
219652
|
-
|
|
219653
|
-
const
|
|
220552
|
+
const renderer = resolveDesktopRenderer(root, args);
|
|
220553
|
+
const mac = process.platform === "darwin";
|
|
220554
|
+
note(`Building "${name}" for ${mac ? "macOS" : "desktop"}${renderer === "vulkan" ? " (Vulkan)" : ""}…`);
|
|
220555
|
+
const { js, resources } = await buildBundle(args, { remotePatterns: config.resources?.remote ?? [] }, { localShaders: true, desktopRenderer: renderer });
|
|
220556
|
+
assertRendererSupportsBundle(renderer, js);
|
|
219654
220557
|
const wanted = new Set([...js.matchAll(/fetchLocal\("([^"]+)"\)/g)].map((m) => m[1]));
|
|
219655
220558
|
const shipped = resources.filter((r2) => !r2.remote && (r2.shader || wanted.has(r2.name)));
|
|
219656
220559
|
const byName = new Map;
|
|
@@ -219669,52 +220572,62 @@ var buildDesktop = async (args) => {
|
|
|
219669
220572
|
`)).join(`
|
|
219670
220573
|
`));
|
|
219671
220574
|
}
|
|
219672
|
-
const exe = await resolveDesktopExe();
|
|
220575
|
+
const exe = await resolveDesktopExe(renderer);
|
|
219673
220576
|
const hostDir = dirname11(exe);
|
|
219674
220577
|
const IMPLICIT_PRELOADS = new Set(["uberarchive", "capture"]);
|
|
219675
|
-
const hostProvided = new Set;
|
|
219676
|
-
|
|
219677
|
-
|
|
219678
|
-
|
|
219679
|
-
|
|
220578
|
+
const hostProvided = mac ? macHostProvidedNames(exe) : new Set;
|
|
220579
|
+
if (!mac) {
|
|
220580
|
+
try {
|
|
220581
|
+
for (const f3 of readdirSync8(join20(hostDir, "assets")))
|
|
220582
|
+
hostProvided.add(f3);
|
|
220583
|
+
} catch {}
|
|
220584
|
+
}
|
|
219680
220585
|
const shippedNames = new Set(shipped.map((r2) => r2.name));
|
|
219681
220586
|
const missing = [...wanted].filter((n2) => !shippedNames.has(n2) && !hostProvided.has(n2) && !IMPLICIT_PRELOADS.has(n2));
|
|
219682
220587
|
if (missing.length > 0) {
|
|
219683
220588
|
throw new CliError(`The bundle references assets with no local file: ${missing.join(", ")}`);
|
|
219684
220589
|
}
|
|
220590
|
+
if (mac)
|
|
220591
|
+
return buildMacApp({ root, config, name, slug, out, js, shipped, hostExe: exe, args });
|
|
219685
220592
|
const version = process.env.LECODES_DESKTOP_EXE ? "local build" : selectedDesktopVersion();
|
|
219686
|
-
|
|
219687
|
-
|
|
219688
|
-
for (const entry of
|
|
219689
|
-
|
|
219690
|
-
|
|
219691
|
-
|
|
219692
|
-
|
|
219693
|
-
|
|
220593
|
+
rmSync6(out, { recursive: true, force: true });
|
|
220594
|
+
mkdirSync13(out, { recursive: true });
|
|
220595
|
+
for (const entry of readdirSync8(hostDir, { withFileTypes: true })) {
|
|
220596
|
+
if (entry.isFile() && entry.name.toLowerCase().endsWith(".dll")) {
|
|
220597
|
+
cpSync3(join20(hostDir, entry.name), join20(out, entry.name));
|
|
220598
|
+
} else if (entry.isDirectory() && entry.name === "assets") {
|
|
220599
|
+
cpSync3(join20(hostDir, entry.name), join20(out, RUNTIME_DIR), { recursive: true });
|
|
220600
|
+
} else if (entry.isDirectory() && entry.name === "fonts") {
|
|
220601
|
+
cpSync3(join20(hostDir, entry.name), join20(out, entry.name), { recursive: true });
|
|
220602
|
+
}
|
|
220603
|
+
}
|
|
220604
|
+
if (!existsSync19(join20(out, RUNTIME_DIR, "initial.js"))) {
|
|
219694
220605
|
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
220606
|
}
|
|
219696
220607
|
const exeName2 = process.platform === "win32" ? `${cleanFileName(name)}.exe` : cleanFileName(name);
|
|
219697
|
-
|
|
220608
|
+
cpSync3(exe, join20(out, exeName2));
|
|
219698
220609
|
note(` host: LeCodes Desktop ${version}`);
|
|
219699
|
-
|
|
220610
|
+
writeFileSync15(join20(out, "app.js"), js);
|
|
219700
220611
|
let assetBytes = 0, assetCount = 0;
|
|
220612
|
+
if (shipped.length > 0)
|
|
220613
|
+
mkdirSync13(join20(out, ASSETS_DIR), { recursive: true });
|
|
219701
220614
|
for (const r2 of shipped) {
|
|
219702
|
-
if (!r2.absPath || !
|
|
220615
|
+
if (!r2.absPath || !existsSync19(r2.absPath) || !statSync6(r2.absPath).isFile()) {
|
|
219703
220616
|
warn(`asset "${r2.name}" (${r2.path}) has no local file — skipped`);
|
|
219704
220617
|
continue;
|
|
219705
220618
|
}
|
|
219706
|
-
|
|
219707
|
-
assetBytes +=
|
|
220619
|
+
cpSync3(r2.absPath, join20(out, ASSETS_DIR, r2.name));
|
|
220620
|
+
assetBytes += statSync6(r2.absPath).size;
|
|
219708
220621
|
assetCount++;
|
|
219709
220622
|
}
|
|
219710
|
-
note(` assets: ${assetCount} files, ${
|
|
219711
|
-
writeJson(
|
|
220623
|
+
note(` assets: ${assetCount} files, ${mb2(assetBytes)} → ${ASSETS_DIR}/`);
|
|
220624
|
+
writeJson(join20(out, "app.json"), { name, ...config.desktop ? { desktop: { window: config.desktop.window } } : {} });
|
|
219712
220625
|
if (config.icon) {
|
|
219713
|
-
await writeIco(root, config,
|
|
220626
|
+
await writeIco(root, config, join20(out, "app.ico"));
|
|
219714
220627
|
note(" app.ico rendered from " + config.icon);
|
|
219715
220628
|
}
|
|
219716
|
-
|
|
219717
|
-
success(`Portable build: ${out} (${
|
|
220629
|
+
writeFileSync15(join20(out, "README.txt"), "\uFEFF" + readmeText2(name, exeName2, version));
|
|
220630
|
+
success(`Portable build: ${out} (${mb2(dirSize2(out))})`);
|
|
219718
220631
|
if (flagBool(args, "probe"))
|
|
219719
220632
|
await probeBuild(out, exeName2, config, args);
|
|
219720
220633
|
if (flagBool(args, "zip"))
|
|
@@ -219746,15 +220659,15 @@ var writeIco = async (root, config, dest) => {
|
|
|
219746
220659
|
header.writeUInt32LE(offset, e2 + 12);
|
|
219747
220660
|
offset += pngs[i2].length;
|
|
219748
220661
|
});
|
|
219749
|
-
|
|
220662
|
+
writeFileSync15(dest, Buffer.concat([header, ...pngs]));
|
|
219750
220663
|
};
|
|
219751
220664
|
var probeBuild = async (out, exeName2, config, args) => {
|
|
219752
220665
|
const win = config.desktop?.window;
|
|
219753
220666
|
const frames = Number(flagStr(args, "frames")) || 90;
|
|
219754
|
-
const shot =
|
|
220667
|
+
const shot = join20(dirname11(out), `${basename7(out)}-probe.png`);
|
|
219755
220668
|
note(`Probe: hidden render of the staged folder (${frames} frames)…`);
|
|
219756
220669
|
await new Promise((done, fail) => {
|
|
219757
|
-
const child =
|
|
220670
|
+
const child = spawn4(join20(out, exeName2), [], {
|
|
219758
220671
|
cwd: out,
|
|
219759
220672
|
env: {
|
|
219760
220673
|
...process.env,
|
|
@@ -219783,7 +220696,7 @@ var probeBuild = async (out, exeName2, config, args) => {
|
|
|
219783
220696
|
});
|
|
219784
220697
|
child.on("exit", (code) => {
|
|
219785
220698
|
clearTimeout(killer);
|
|
219786
|
-
if (code === 0 &&
|
|
220699
|
+
if (code === 0 && existsSync19(shot)) {
|
|
219787
220700
|
success(`Probe: ${shot}`);
|
|
219788
220701
|
done();
|
|
219789
220702
|
} else {
|
|
@@ -219796,11 +220709,11 @@ ${tail.join("").trimEnd()}` : "";
|
|
|
219796
220709
|
};
|
|
219797
220710
|
var zipBuild = (out) => {
|
|
219798
220711
|
const zip = `${out}.zip`;
|
|
219799
|
-
|
|
220712
|
+
rmSync6(zip, { force: true });
|
|
219800
220713
|
note("Zipping…");
|
|
219801
220714
|
if (process.platform === "win32") {
|
|
219802
220715
|
const q = (s2) => s2.replaceAll("'", "''");
|
|
219803
|
-
|
|
220716
|
+
execFileSync5("powershell", ["-NoProfile", "-Command", [
|
|
219804
220717
|
"Add-Type -AssemblyName System.IO.Compression.FileSystem;",
|
|
219805
220718
|
`$src='${q(out)}'; $base=Split-Path $src -Parent;`,
|
|
219806
220719
|
`$zip=[System.IO.Compression.ZipFile]::Open('${q(zip)}','Create');`,
|
|
@@ -219810,15 +220723,15 @@ var zipBuild = (out) => {
|
|
|
219810
220723
|
"$zip.Dispose()"
|
|
219811
220724
|
].join(" ")], { stdio: ["ignore", "ignore", "pipe"] });
|
|
219812
220725
|
} else {
|
|
219813
|
-
|
|
220726
|
+
execFileSync5("zip", ["-rq", zip, basename7(out)], { cwd: dirname11(out) });
|
|
219814
220727
|
}
|
|
219815
|
-
success(`Zip: ${zip} (${
|
|
220728
|
+
success(`Zip: ${zip} (${mb2(statSync6(zip).size)})`);
|
|
219816
220729
|
};
|
|
219817
|
-
var
|
|
220730
|
+
var readmeText2 = (name, exeName2, version) => `${name}
|
|
219818
220731
|
${"=".repeat(Math.max(name.length, 4))}
|
|
219819
220732
|
|
|
219820
220733
|
A LeCodes desktop app. Nothing to install: unpack the WHOLE folder anywhere and run ${exeName2}.
|
|
219821
|
-
Keep the files together
|
|
220734
|
+
Keep the files together: assets/ holds the app's own content, runtime/ the engine it needs.
|
|
219822
220735
|
|
|
219823
220736
|
On first run Windows may show a SmartScreen prompt (any unsigned app gets one):
|
|
219824
220737
|
choose "More info", then "Run anyway".
|
|
@@ -219833,10 +220746,10 @@ var SDK_REPO = "letary/lecodes-ios-sdk";
|
|
|
219833
220746
|
var SDK_MIN_VERSION = "1.2.0";
|
|
219834
220747
|
var SDK_IDENTITY_VERSION = "1.6.0";
|
|
219835
220748
|
var shellSdkVersion = (iosDir) => {
|
|
219836
|
-
const path2 =
|
|
219837
|
-
if (!
|
|
220749
|
+
const path2 = join21(iosDir, "LeCodesRuntime", "Package.swift");
|
|
220750
|
+
if (!existsSync20(path2))
|
|
219838
220751
|
return null;
|
|
219839
|
-
const m = /lecodes-ios-sdk\.git",\s*(?:exact|from):\s*"([^"]+)"/.exec(
|
|
220752
|
+
const m = /lecodes-ios-sdk\.git",\s*(?:exact|from):\s*"([^"]+)"/.exec(readFileSync17(path2, "utf8"));
|
|
219840
220753
|
return m && VERSION_RE.test(m[1]) ? m[1] : null;
|
|
219841
220754
|
};
|
|
219842
220755
|
var IOS_SDK_OPTS = { repo: SDK_REPO, minVersion: SDK_MIN_VERSION, configKey: "ios.sdk" };
|
|
@@ -219850,11 +220763,11 @@ var appInit = async (args) => {
|
|
|
219850
220763
|
throw new CliError(platform ? `Unsupported platform "${platform}" — use ios, android or desktop.` : "Usage: lecodes app init <ios|android|desktop> [--name <display name>] [--bundle-id <id>] [--team <team id>] [--variant auto|core|2d|3d|full] [--app-id <application id>] [--sdk <version|latest>]");
|
|
219851
220764
|
}
|
|
219852
220765
|
const root = projectRoot();
|
|
219853
|
-
const iosDir =
|
|
219854
|
-
if (
|
|
220766
|
+
const iosDir = join21(root, "ios");
|
|
220767
|
+
if (existsSync20(iosDir)) {
|
|
219855
220768
|
throw new CliError("ios/ already exists — run `lecodes app sync` to update it.");
|
|
219856
220769
|
}
|
|
219857
|
-
const existing = readJson(
|
|
220770
|
+
const existing = readJson(join21(root, "app.json")) ?? {};
|
|
219858
220771
|
const variant = flagStr(args, "variant") ?? existing.ios?.variant ?? "auto";
|
|
219859
220772
|
if (variant !== "auto" && !SDK_VARIANTS.includes(variant)) {
|
|
219860
220773
|
throw new CliError(`--variant must be auto, ${SDK_VARIANTS.join(", ")} (got "${variant}")`);
|
|
@@ -219862,7 +220775,7 @@ var appInit = async (args) => {
|
|
|
219862
220775
|
const sdkFlag = flagStr(args, "sdk") ?? existing.ios?.sdk;
|
|
219863
220776
|
const sdkVersion = await resolveSdkVersion(process.env.LECODES_IOS_SDK ?? sdkFlag ?? "latest", null, IOS_SDK_OPTS);
|
|
219864
220777
|
const sdk = sdkFlag === "latest" ? "latest" : sdkVersion;
|
|
219865
|
-
const name = flagStr(args, "name") ?? existing.name ??
|
|
220778
|
+
const name = flagStr(args, "name") ?? existing.name ?? basename8(root);
|
|
219866
220779
|
const config = {
|
|
219867
220780
|
...existing,
|
|
219868
220781
|
name,
|
|
@@ -219876,7 +220789,7 @@ var appInit = async (args) => {
|
|
|
219876
220789
|
buildNumber: existing.ios?.buildNumber ?? 1
|
|
219877
220790
|
}
|
|
219878
220791
|
};
|
|
219879
|
-
writeJson(
|
|
220792
|
+
writeJson(join21(root, "app.json"), config);
|
|
219880
220793
|
log(`${c.green("written")} app.json`);
|
|
219881
220794
|
ensureShellIgnored(root, "ios");
|
|
219882
220795
|
const identity = {
|
|
@@ -219887,9 +220800,9 @@ var appInit = async (args) => {
|
|
|
219887
220800
|
buildNumber: config.ios.buildNumber
|
|
219888
220801
|
};
|
|
219889
220802
|
const write = (rel, content) => {
|
|
219890
|
-
const abs =
|
|
219891
|
-
|
|
219892
|
-
|
|
220803
|
+
const abs = join21(iosDir, rel);
|
|
220804
|
+
mkdirSync14(dirname12(abs), { recursive: true });
|
|
220805
|
+
writeFileSync16(abs, content);
|
|
219893
220806
|
log(`${c.green("created")} ios/${rel}`);
|
|
219894
220807
|
};
|
|
219895
220808
|
write("README.md", IOS_README);
|
|
@@ -219917,23 +220830,23 @@ var ensureSdkResourcesBundle = async (sdkVersion) => {
|
|
|
219917
220830
|
const override = process.env.LECODES_SDK_RESOURCES;
|
|
219918
220831
|
if (override) {
|
|
219919
220832
|
note(`Using local resources bundle ${override} (LECODES_SDK_RESOURCES)`);
|
|
219920
|
-
return
|
|
220833
|
+
return resolve8(override);
|
|
219921
220834
|
}
|
|
219922
|
-
const dir =
|
|
219923
|
-
const bundle =
|
|
219924
|
-
if (
|
|
220835
|
+
const dir = join21(homedir7(), ".lecodes", "sdk-resources", sdkVersion);
|
|
220836
|
+
const bundle = join21(dir, "LeCodesSDKResources.bundle");
|
|
220837
|
+
if (existsSync20(bundle))
|
|
219925
220838
|
return bundle;
|
|
219926
220839
|
const url = `https://github.com/${SDK_REPO}/releases/download/${sdkVersion}/LeCodesSDKResources.bundle.zip`;
|
|
219927
220840
|
note(`Downloading LeCodesSDKResources.bundle (SDK ${sdkVersion})…`);
|
|
219928
220841
|
const res = await fetch(url);
|
|
219929
220842
|
if (!res.ok)
|
|
219930
220843
|
throw new CliError(`Resources bundle download failed (HTTP ${res.status}) — ${url}`);
|
|
219931
|
-
|
|
219932
|
-
const zip =
|
|
219933
|
-
|
|
219934
|
-
|
|
219935
|
-
|
|
219936
|
-
if (!
|
|
220844
|
+
mkdirSync14(dir, { recursive: true });
|
|
220845
|
+
const zip = join21(dir, "LeCodesSDKResources.bundle.zip");
|
|
220846
|
+
writeFileSync16(zip, new Uint8Array(await res.arrayBuffer()));
|
|
220847
|
+
execFileSync6("ditto", ["-x", "-k", zip, dir]);
|
|
220848
|
+
rmSync7(zip);
|
|
220849
|
+
if (!existsSync20(bundle))
|
|
219937
220850
|
throw new CliError(`No LeCodesSDKResources.bundle inside the release asset ${url}`);
|
|
219938
220851
|
return bundle;
|
|
219939
220852
|
};
|
|
@@ -219958,13 +220871,13 @@ var resolveUpdateUrl = (update, root) => {
|
|
|
219958
220871
|
};
|
|
219959
220872
|
var appSync = async (args) => {
|
|
219960
220873
|
const root = projectRoot();
|
|
219961
|
-
const config = readJson(
|
|
220874
|
+
const config = readJson(join21(root, "app.json"));
|
|
219962
220875
|
const platform = args._[1];
|
|
219963
220876
|
if (platform !== undefined && platform !== "ios" && platform !== "android") {
|
|
219964
220877
|
throw new CliError(`Unknown platform "${platform}" — use ios or android.`);
|
|
219965
220878
|
}
|
|
219966
|
-
const hasIos = !!config?.ios &&
|
|
219967
|
-
const hasAndroid = !!config?.android &&
|
|
220879
|
+
const hasIos = !!config?.ios && existsSync20(join21(root, "ios"));
|
|
220880
|
+
const hasAndroid = !!config?.android && existsSync20(join21(root, "android"));
|
|
219968
220881
|
if (platform === "ios" && !hasIos)
|
|
219969
220882
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
219970
220883
|
if (platform === "android" && !hasAndroid)
|
|
@@ -219979,7 +220892,7 @@ var appSync = async (args) => {
|
|
|
219979
220892
|
await syncAndroid(root, config, iosArtifacts);
|
|
219980
220893
|
};
|
|
219981
220894
|
var syncIos = async (root, config) => {
|
|
219982
|
-
const iosDir =
|
|
220895
|
+
const iosDir = join21(root, "ios");
|
|
219983
220896
|
if (!config.ios)
|
|
219984
220897
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
219985
220898
|
const requestedVariant = config.ios.variant ?? "auto";
|
|
@@ -219989,14 +220902,14 @@ var syncIos = async (root, config) => {
|
|
|
219989
220902
|
const linkedSdk = shellSdkVersion(iosDir);
|
|
219990
220903
|
const sdkPin = process.env.LECODES_IOS_SDK ?? config.ios.sdk ?? linkedSdk ?? "latest";
|
|
219991
220904
|
const sdkVersion = await resolveSdkVersion(sdkPin, linkedSdk, IOS_SDK_OPTS);
|
|
219992
|
-
const lockPath =
|
|
220905
|
+
const lockPath = join21(root, "plugins.lock.json");
|
|
219993
220906
|
const oldLock = readJson(lockPath)?.plugins ?? {};
|
|
219994
220907
|
const pluginSpecs = Object.entries(config.plugins ?? {});
|
|
219995
220908
|
const runtimePlugins = [];
|
|
219996
220909
|
const infoValues = {};
|
|
219997
220910
|
const entitlementValues = {};
|
|
219998
220911
|
const newLock = {};
|
|
219999
|
-
const sourcesDir =
|
|
220912
|
+
const sourcesDir = join21(iosDir, "LeCodesRuntime", "Sources");
|
|
220000
220913
|
for (const [id, spec] of pluginSpecs) {
|
|
220001
220914
|
const plugin = resolvePlugin(id, spec, oldLock[id], "ios");
|
|
220002
220915
|
const ios = plugin.manifest.ios;
|
|
@@ -220004,12 +220917,12 @@ var syncIos = async (root, config) => {
|
|
|
220004
220917
|
const register = ios.register;
|
|
220005
220918
|
if (!module || !register)
|
|
220006
220919
|
throw new CliError(`Plugin "${id}": manifest ios.module / ios.register missing`);
|
|
220007
|
-
const src =
|
|
220008
|
-
if (!
|
|
220920
|
+
const src = join21(plugin.dir, ios.sources ?? "ios");
|
|
220921
|
+
if (!existsSync20(src))
|
|
220009
220922
|
throw new CliError(`Plugin "${id}": sources dir ${src} missing`);
|
|
220010
|
-
const dest =
|
|
220011
|
-
|
|
220012
|
-
|
|
220923
|
+
const dest = join21(sourcesDir, module);
|
|
220924
|
+
rmSync7(dest, { recursive: true, force: true });
|
|
220925
|
+
cpSync4(src, dest, { recursive: true });
|
|
220013
220926
|
if (ios.minSdk && VERSION_RE.test(ios.minSdk) && cmpVersion(sdkVersion, ios.minSdk) < 0) {
|
|
220014
220927
|
throw new CliError(`Plugin "${id}" needs lecodes-ios-sdk ${ios.minSdk} but the shell links ${sdkVersion} — run \`lecodes app update\` (or set app.json ios.sdk).`);
|
|
220015
220928
|
}
|
|
@@ -220024,9 +220937,9 @@ var syncIos = async (root, config) => {
|
|
|
220024
220937
|
log(`${c.green("plugin")} ${id}@${plugin.lock.version} (${plugin.lock.tag} → Sources/${module})`);
|
|
220025
220938
|
}
|
|
220026
220939
|
const keep = new Set(["LeCodesRuntime", ...runtimePlugins.map((p2) => p2.module)]);
|
|
220027
|
-
for (const entry of
|
|
220028
|
-
if (!keep.has(entry) &&
|
|
220029
|
-
|
|
220940
|
+
for (const entry of existsSync20(sourcesDir) ? readdirSync9(sourcesDir) : []) {
|
|
220941
|
+
if (!keep.has(entry) && statSync7(join21(sourcesDir, entry)).isDirectory()) {
|
|
220942
|
+
rmSync7(join21(sourcesDir, entry), { recursive: true });
|
|
220030
220943
|
log(`${c.yellow("removed")} ios/LeCodesRuntime/Sources/${entry} (plugin uninstalled)`);
|
|
220031
220944
|
}
|
|
220032
220945
|
}
|
|
@@ -220034,11 +220947,11 @@ var syncIos = async (root, config) => {
|
|
|
220034
220947
|
if (!Array.isArray(remotePatterns) || remotePatterns.some((p2) => typeof p2 !== "string")) {
|
|
220035
220948
|
throw new CliError('app.json "resources.remote" must be an array of glob strings (e.g. ["videos/**"]).');
|
|
220036
220949
|
}
|
|
220037
|
-
const resourcesDir =
|
|
220038
|
-
|
|
220039
|
-
|
|
220040
|
-
const resources = await compileShell({ _: [], flags: { o:
|
|
220041
|
-
const bundleJs =
|
|
220950
|
+
const resourcesDir = join21(iosDir, "App", "Resources");
|
|
220951
|
+
rmSync7(resourcesDir, { recursive: true, force: true });
|
|
220952
|
+
mkdirSync14(resourcesDir, { recursive: true });
|
|
220953
|
+
const resources = await compileShell({ _: [], flags: { o: join21(resourcesDir, "app.js") } }, { remotePatterns });
|
|
220954
|
+
const bundleJs = readFileSync17(join21(resourcesDir, "app.js"), "utf8");
|
|
220042
220955
|
const { variant, detected } = resolveVariant(requestedVariant, bundleJs);
|
|
220043
220956
|
const WHY = {
|
|
220044
220957
|
core: "UI only",
|
|
@@ -220052,8 +220965,8 @@ var syncIos = async (root, config) => {
|
|
|
220052
220965
|
const sdkProvided = new Set;
|
|
220053
220966
|
if (variant === "3d" || variant === "full") {
|
|
220054
220967
|
const sdkResources = await ensureSdkResourcesBundle(sdkVersion);
|
|
220055
|
-
|
|
220056
|
-
for (const f3 of
|
|
220968
|
+
cpSync4(sdkResources, join21(resourcesDir, "LeCodesSDKResources.bundle"), { recursive: true });
|
|
220969
|
+
for (const f3 of readdirSync9(sdkResources))
|
|
220057
220970
|
if (f3 !== "Info.plist")
|
|
220058
220971
|
sdkProvided.add(f3);
|
|
220059
220972
|
log(`${c.green("resources")} LeCodesSDKResources.bundle (filament) → ios/App/Resources/`);
|
|
@@ -220080,7 +220993,7 @@ var syncIos = async (root, config) => {
|
|
|
220080
220993
|
}
|
|
220081
220994
|
if (candidates.length > 1)
|
|
220082
220995
|
warn(`asset name collision: ${name} (${candidates.map((r2) => r2.path).join(", ")}) — embedding ${candidates[0].path}`);
|
|
220083
|
-
|
|
220996
|
+
cpSync4(candidates[0].absPath, join21(resourcesDir, name));
|
|
220084
220997
|
embedded++;
|
|
220085
220998
|
}
|
|
220086
220999
|
if (embedded > 0)
|
|
@@ -220100,14 +221013,14 @@ var syncIos = async (root, config) => {
|
|
|
220100
221013
|
}
|
|
220101
221014
|
if (shellUuid)
|
|
220102
221015
|
log(`${c.green("identity")} project ${shellUuid} baked into the shell (world identity)`);
|
|
220103
|
-
|
|
220104
|
-
|
|
220105
|
-
|
|
221016
|
+
writeFileSync16(join21(iosDir, "README.md"), IOS_README);
|
|
221017
|
+
writeFileSync16(join21(iosDir, "LeCodesRuntime", "Package.swift"), runtimePackageTemplate(sdkVersion, variant, runtimePlugins));
|
|
221018
|
+
writeFileSync16(join21(sourcesDir, "LeCodesRuntime", "LeCodesRuntime.swift"), runtimeSwiftTemplate(runtimePlugins, shellUuid));
|
|
220106
221019
|
const mergedKeys = {};
|
|
220107
221020
|
for (const [key, values] of Object.entries(infoValues))
|
|
220108
221021
|
mergedKeys[key] = [...new Set(values)].join(" ");
|
|
220109
221022
|
const updateUrl = resolveUpdateUrl(config.update, root);
|
|
220110
|
-
|
|
221023
|
+
writeFileSync16(join21(iosDir, "App", "Info.plist"), infoPlistTemplate(mergedKeys, updateUrl));
|
|
220111
221024
|
if (updateUrl)
|
|
220112
221025
|
log(`${c.green("update")} OTA source ${updateUrl} — checked natively at launch, new code applies next launch`);
|
|
220113
221026
|
else if (!config.update) {
|
|
@@ -220118,10 +221031,10 @@ var syncIos = async (root, config) => {
|
|
|
220118
221031
|
if (manifest)
|
|
220119
221032
|
note(`tip: add "update": true to app.json for over-the-air JS updates — ship with \`lecodes push --publish\`, no store release`);
|
|
220120
221033
|
}
|
|
220121
|
-
if (pluginSpecs.length > 0 ||
|
|
221034
|
+
if (pluginSpecs.length > 0 || existsSync20(lockPath))
|
|
220122
221035
|
writeJson(lockPath, { plugins: newLock });
|
|
220123
|
-
const pbxPath =
|
|
220124
|
-
let pbx =
|
|
221036
|
+
const pbxPath = join21(iosDir, "App.xcodeproj", "project.pbxproj");
|
|
221037
|
+
let pbx = readFileSync17(pbxPath, "utf8").replace(/MARKETING_VERSION = [^;]+;/g, `MARKETING_VERSION = ${config.version ?? "1.0.0"};`).replace(/CURRENT_PROJECT_VERSION = [^;]+;/g, `CURRENT_PROJECT_VERSION = ${config.ios.buildNumber ?? 1};`);
|
|
220125
221038
|
const mergedEntitlements = {};
|
|
220126
221039
|
for (const [key, values] of Object.entries(entitlementValues)) {
|
|
220127
221040
|
if (values.every((v) => Array.isArray(v))) {
|
|
@@ -220142,7 +221055,7 @@ var syncIos = async (root, config) => {
|
|
|
220142
221055
|
}
|
|
220143
221056
|
const hasEntitlements = Object.keys(mergedEntitlements).length > 0;
|
|
220144
221057
|
if (pbx.includes("CODE_SIGN_ENTITLEMENTS") || hasEntitlements) {
|
|
220145
|
-
|
|
221058
|
+
writeFileSync16(join21(iosDir, "App", "App.entitlements"), entitlementsTemplate(mergedEntitlements));
|
|
220146
221059
|
if (hasEntitlements)
|
|
220147
221060
|
log(`${c.green("caps")} App/App.entitlements: ${Object.keys(mergedEntitlements).sort().join(", ")}`);
|
|
220148
221061
|
if (!pbx.includes("CODE_SIGN_ENTITLEMENTS")) {
|
|
@@ -220158,7 +221071,7 @@ $2Info.plist,`);
|
|
|
220158
221071
|
}
|
|
220159
221072
|
}
|
|
220160
221073
|
}
|
|
220161
|
-
|
|
221074
|
+
writeFileSync16(pbxPath, pbx);
|
|
220162
221075
|
if (config.icon) {
|
|
220163
221076
|
await writeIosAppIcon(iosDir, await loadIconMaster(root, config));
|
|
220164
221077
|
}
|
|
@@ -220170,7 +221083,7 @@ $2Info.plist,`);
|
|
|
220170
221083
|
var appPlugin = async (args) => {
|
|
220171
221084
|
const sub = args._[1];
|
|
220172
221085
|
const root = projectRoot();
|
|
220173
|
-
const configPath =
|
|
221086
|
+
const configPath = join21(root, "app.json");
|
|
220174
221087
|
const config = readJson(configPath);
|
|
220175
221088
|
if (!config)
|
|
220176
221089
|
throw new CliError("No app.json here — run `lecodes app init <ios|android>` first.");
|
|
@@ -220181,7 +221094,7 @@ var appPlugin = async (args) => {
|
|
|
220181
221094
|
log("No plugins installed.");
|
|
220182
221095
|
return;
|
|
220183
221096
|
}
|
|
220184
|
-
const lock = readJson(
|
|
221097
|
+
const lock = readJson(join21(root, "plugins.lock.json"))?.plugins ?? {};
|
|
220185
221098
|
for (const [id2, spec] of entries) {
|
|
220186
221099
|
const pin = lock[id2] ? c.dim(` ${lock[id2].repo} ${lock[id2].tag} (${lock[id2].commit.slice(0, 7)})`) : c.dim(" (not synced)");
|
|
220187
221100
|
log(`${c.bold(id2)} ${spec}${pin}`);
|
|
@@ -220234,14 +221147,14 @@ var exportOptionsPlist = (method, teamId, destination) => `<?xml version="1.0" e
|
|
|
220234
221147
|
`;
|
|
220235
221148
|
var requireShell = () => {
|
|
220236
221149
|
const root = projectRoot();
|
|
220237
|
-
const config = readJson(
|
|
220238
|
-
if (!config?.ios || !
|
|
221150
|
+
const config = readJson(join21(root, "app.json"));
|
|
221151
|
+
if (!config?.ios || !existsSync20(join21(root, "ios"))) {
|
|
220239
221152
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
220240
221153
|
}
|
|
220241
221154
|
return { root, config };
|
|
220242
221155
|
};
|
|
220243
221156
|
var updateSdkPin = async (root, config, args, checkOnly, platform) => {
|
|
220244
|
-
const [label, repo, minVersion, shellVersion] = platform === "ios" ? ["lecodes-ios-sdk", SDK_REPO, SDK_MIN_VERSION, () => shellSdkVersion(
|
|
221157
|
+
const [label, repo, minVersion, shellVersion] = platform === "ios" ? ["lecodes-ios-sdk", SDK_REPO, SDK_MIN_VERSION, () => shellSdkVersion(join21(root, "ios"))] : ["lecodes-android-sdk", ANDROID_SDK_REPO, ANDROID_SDK_MIN_VERSION, () => shellSdkVersionAndroid(join21(root, "android"))];
|
|
220245
221158
|
const section = config[platform];
|
|
220246
221159
|
const wanted = flagStr(args, "sdk");
|
|
220247
221160
|
let changed = false;
|
|
@@ -220272,19 +221185,19 @@ var updateSdkPin = async (root, config, args, checkOnly, platform) => {
|
|
|
220272
221185
|
}
|
|
220273
221186
|
if (!checkOnly && nextPin !== pin) {
|
|
220274
221187
|
section.sdk = nextPin;
|
|
220275
|
-
writeJson(
|
|
221188
|
+
writeJson(join21(root, "app.json"), config);
|
|
220276
221189
|
}
|
|
220277
221190
|
return changed;
|
|
220278
221191
|
};
|
|
220279
221192
|
var appUpdate = async (args) => {
|
|
220280
221193
|
const root = projectRoot();
|
|
220281
|
-
const config = readJson(
|
|
221194
|
+
const config = readJson(join21(root, "app.json"));
|
|
220282
221195
|
const platform = args._[1];
|
|
220283
221196
|
if (platform !== undefined && platform !== "ios" && platform !== "android") {
|
|
220284
221197
|
throw new CliError(`Unknown platform "${platform}" — use ios or android.`);
|
|
220285
221198
|
}
|
|
220286
|
-
const hasIos = !!config?.ios &&
|
|
220287
|
-
const hasAndroid = !!config?.android &&
|
|
221199
|
+
const hasIos = !!config?.ios && existsSync20(join21(root, "ios"));
|
|
221200
|
+
const hasAndroid = !!config?.android && existsSync20(join21(root, "android"));
|
|
220288
221201
|
if (platform === "ios" && !hasIos)
|
|
220289
221202
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
220290
221203
|
if (platform === "android" && !hasAndroid)
|
|
@@ -220302,7 +221215,7 @@ var appUpdate = async (args) => {
|
|
|
220302
221215
|
changed = await updateSdkPin(root, config, args, checkOnly, "ios") || changed;
|
|
220303
221216
|
if (doAndroid)
|
|
220304
221217
|
changed = await updateSdkPin(root, config, args, checkOnly, "android") || changed;
|
|
220305
|
-
const lockPath =
|
|
221218
|
+
const lockPath = join21(root, "plugins.lock.json");
|
|
220306
221219
|
const lock = readJson(lockPath)?.plugins ?? {};
|
|
220307
221220
|
const specs = Object.entries(config.plugins ?? {});
|
|
220308
221221
|
if (specs.length > 0 && !flagBool(args, "no-plugins")) {
|
|
@@ -220331,7 +221244,7 @@ var appUpdate = async (args) => {
|
|
|
220331
221244
|
log(`${c.green("plugin")} ${id} ${entry?.tag ?? "—"} → ${c.bold(tag)}`);
|
|
220332
221245
|
changed = true;
|
|
220333
221246
|
}
|
|
220334
|
-
if (!checkOnly &&
|
|
221247
|
+
if (!checkOnly && existsSync20(lockPath))
|
|
220335
221248
|
writeJson(lockPath, { plugins: kept });
|
|
220336
221249
|
}
|
|
220337
221250
|
log("");
|
|
@@ -220351,9 +221264,9 @@ var appUpdate = async (args) => {
|
|
|
220351
221264
|
};
|
|
220352
221265
|
var appDoctor = async (_args) => {
|
|
220353
221266
|
const root = projectRoot();
|
|
220354
|
-
const config = readJson(
|
|
220355
|
-
const hasIos = !!config?.ios &&
|
|
220356
|
-
const hasAndroid = !!config?.android &&
|
|
221267
|
+
const config = readJson(join21(root, "app.json"));
|
|
221268
|
+
const hasIos = !!config?.ios && existsSync20(join21(root, "ios"));
|
|
221269
|
+
const hasAndroid = !!config?.android && existsSync20(join21(root, "android"));
|
|
220357
221270
|
if (!hasIos && !hasAndroid) {
|
|
220358
221271
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
220359
221272
|
}
|
|
@@ -220364,14 +221277,14 @@ var appDoctor = async (_args) => {
|
|
|
220364
221277
|
failed++;
|
|
220365
221278
|
};
|
|
220366
221279
|
if (config?.icon) {
|
|
220367
|
-
check(
|
|
221280
|
+
check(existsSync20(join21(root, config.icon)), `Icon master ${config.icon}`, `app.json "icon" points at a missing file — sync will fail`);
|
|
220368
221281
|
}
|
|
220369
221282
|
if (hasIos)
|
|
220370
221283
|
await doctorIosChecks(root, config, check);
|
|
220371
221284
|
if (hasAndroid) {
|
|
220372
221285
|
await doctorAndroidChecks(root, config, check);
|
|
220373
221286
|
if (!hasIos) {
|
|
220374
|
-
const lock = readJson(
|
|
221287
|
+
const lock = readJson(join21(root, "plugins.lock.json"))?.plugins ?? {};
|
|
220375
221288
|
const wanted = Object.keys(config.plugins ?? {});
|
|
220376
221289
|
const inSync = wanted.every((id) => lock[id]) && Object.keys(lock).every((id) => wanted.includes(id));
|
|
220377
221290
|
check(wanted.length === 0 || inSync, "Plugins synced", "run `lecodes app sync`");
|
|
@@ -220390,22 +221303,22 @@ var doctorIosChecks = async (root, config, check) => {
|
|
|
220390
221303
|
} catch {}
|
|
220391
221304
|
check(xcodeVersion !== "", `Xcode CLI (${xcodeVersion || "not found"})`, "install Xcode + command line tools");
|
|
220392
221305
|
check(!!config.ios.teamId, "Development team set", "lecodes app init wrote app.json — fill ios.teamId", false);
|
|
220393
|
-
const iconDir =
|
|
220394
|
-
const hasIcon =
|
|
221306
|
+
const iconDir = join21(root, "ios", "App", "Assets.xcassets", "AppIcon.appiconset");
|
|
221307
|
+
const hasIcon = existsSync20(iconDir) && readdirSync9(iconDir).some((f3) => f3.endsWith(".png"));
|
|
220395
221308
|
check(hasIcon, "App icon present", "drop a 1024×1024 PNG into ios/App/Assets.xcassets/AppIcon.appiconset/ (App Store requires it)", false);
|
|
220396
|
-
const bundle =
|
|
220397
|
-
check(
|
|
221309
|
+
const bundle = join21(root, "ios", "App", "Resources", "app.js");
|
|
221310
|
+
check(existsSync20(bundle), "Bundled app.js", "run `lecodes app sync`");
|
|
220398
221311
|
const requestedVariant = config.ios.variant ?? "auto";
|
|
220399
221312
|
const validVariant = requestedVariant === "auto" || SDK_VARIANTS.includes(requestedVariant);
|
|
220400
221313
|
check(validVariant, `ios.variant "${requestedVariant}"`, `use "auto" or one of ${SDK_VARIANTS.join(", ")}`);
|
|
220401
|
-
if (
|
|
220402
|
-
const { variant } = resolveVariant(requestedVariant,
|
|
220403
|
-
const pkg =
|
|
221314
|
+
if (existsSync20(bundle) && validVariant) {
|
|
221315
|
+
const { variant } = resolveVariant(requestedVariant, readFileSync17(bundle, "utf8"));
|
|
221316
|
+
const pkg = readFileSync17(join21(root, "ios", "LeCodesRuntime", "Package.swift"), "utf8");
|
|
220404
221317
|
const needsResources = variant === "3d" || variant === "full";
|
|
220405
|
-
const hasResources =
|
|
221318
|
+
const hasResources = existsSync20(join21(root, "ios", "App", "Resources", "LeCodesSDKResources.bundle"));
|
|
220406
221319
|
check(pkg.includes(`"LeCodesSDK-${variant}"`) && (!needsResources || hasResources), `SDK variant ${variant}${needsResources ? " (+ filament resources)" : ""}`, "run `lecodes app sync`");
|
|
220407
221320
|
}
|
|
220408
|
-
const linked = shellSdkVersion(
|
|
221321
|
+
const linked = shellSdkVersion(join21(root, "ios"));
|
|
220409
221322
|
const pin = config.ios.sdk ?? "latest";
|
|
220410
221323
|
check(!!linked, `SDK ${linked ?? "version unreadable"}`, "run `lecodes app sync`");
|
|
220411
221324
|
if (linked && pin !== "latest") {
|
|
@@ -220417,27 +221330,27 @@ var doctorIosChecks = async (root, config, check) => {
|
|
|
220417
221330
|
check(cmpVersion(linked, latest) >= 0, `SDK is the latest release (${latest})`, "run `lecodes app update`", false);
|
|
220418
221331
|
} catch {}
|
|
220419
221332
|
}
|
|
220420
|
-
const lock = readJson(
|
|
221333
|
+
const lock = readJson(join21(root, "plugins.lock.json"))?.plugins ?? {};
|
|
220421
221334
|
const wanted = Object.keys(config.plugins ?? {});
|
|
220422
221335
|
const inSync = wanted.every((id) => lock[id]) && Object.keys(lock).every((id) => wanted.includes(id));
|
|
220423
221336
|
check(wanted.length === 0 || inSync, "Plugins synced", "run `lecodes app sync`");
|
|
220424
|
-
const pbxFile =
|
|
220425
|
-
if (
|
|
220426
|
-
check(
|
|
221337
|
+
const pbxFile = join21(root, "ios", "App.xcodeproj", "project.pbxproj");
|
|
221338
|
+
if (existsSync20(pbxFile) && readFileSync17(pbxFile, "utf8").includes("CODE_SIGN_ENTITLEMENTS")) {
|
|
221339
|
+
check(existsSync20(join21(root, "ios", "App", "App.entitlements")), "App/App.entitlements present", "run `lecodes app sync`");
|
|
220427
221340
|
}
|
|
220428
221341
|
if (wanted.includes("push")) {
|
|
220429
|
-
const runtimeSwift =
|
|
220430
|
-
const baked =
|
|
221342
|
+
const runtimeSwift = join21(root, "ios", "LeCodesRuntime", "Sources", "LeCodesRuntime", "LeCodesRuntime.swift");
|
|
221343
|
+
const baked = existsSync20(runtimeSwift) && readFileSync17(runtimeSwift, "utf8").includes("bootProjectUuid");
|
|
220431
221344
|
check(baked, "Push world identity baked", `without it Push.register() rejects "unavailable" — needs a pushed project (uuid), lecodes-ios-sdk ${SDK_IDENTITY_VERSION}+ and \`lecodes app sync\``, false);
|
|
220432
221345
|
}
|
|
220433
221346
|
if (config.update) {
|
|
220434
|
-
const plist =
|
|
221347
|
+
const plist = readFileSync17(join21(root, "ios", "App", "Info.plist"), "utf8");
|
|
220435
221348
|
check(plist.includes("<key>LeCodesUpdateURL</key>"), "OTA update URL in Info.plist", "run `lecodes app sync`", false);
|
|
220436
|
-
const appDir =
|
|
220437
|
-
const callsUpdater =
|
|
221349
|
+
const appDir = join21(root, "ios", "App");
|
|
221350
|
+
const callsUpdater = readdirSync9(appDir).some((f3) => f3.endsWith(".swift") && readFileSync17(join21(appDir, f3), "utf8").includes("LeCodesUpdater.activeBundle"));
|
|
220438
221351
|
check(callsUpdater, "App code launches via LeCodesUpdater", `no App/*.swift calls LeCodesUpdater.activeBundle() — downloaded updates would never run; see "Over-the-air updates" in ios/README.md for the ViewController snippet`, false);
|
|
220439
221352
|
}
|
|
220440
|
-
const asc = readJson(
|
|
221353
|
+
const asc = readJson(join21(homedir7(), ".lecodes", "asc.json"));
|
|
220441
221354
|
check(!!asc?.keyPath, "App Store Connect API key configured", "pass --key-path/--key-id/--issuer-id to `lecodes app upload` once to store it", false);
|
|
220442
221355
|
};
|
|
220443
221356
|
var ARCHIVE_PATH = "ios/build/App.xcarchive";
|
|
@@ -220473,8 +221386,8 @@ var appBuild = async (args) => {
|
|
|
220473
221386
|
success(`Archive: ios/build/App.xcarchive`);
|
|
220474
221387
|
if (flagBool(args, "no-export"))
|
|
220475
221388
|
return;
|
|
220476
|
-
const optionsPath =
|
|
220477
|
-
|
|
221389
|
+
const optionsPath = join21(root, "ios", "build", "ExportOptions.plist");
|
|
221390
|
+
writeFileSync16(optionsPath, exportOptionsPlist(method, config.ios.teamId ?? ""));
|
|
220478
221391
|
note(`Exporting (.ipa, method ${method})…`);
|
|
220479
221392
|
runTool(root, "xcodebuild", [
|
|
220480
221393
|
"-exportArchive",
|
|
@@ -220493,10 +221406,10 @@ var appUpload = async (args) => {
|
|
|
220493
221406
|
throw new CliError("Play upload isn't wired yet — upload android/app/build/outputs/bundle/release/*.aab in the Play Console (`lecodes app build android` produces it).");
|
|
220494
221407
|
}
|
|
220495
221408
|
const { root, config } = requireShell();
|
|
220496
|
-
if (!
|
|
221409
|
+
if (!existsSync20(join21(root, ARCHIVE_PATH))) {
|
|
220497
221410
|
throw new CliError("No archive at ios/build/App.xcarchive — run `lecodes app build ios` first.");
|
|
220498
221411
|
}
|
|
220499
|
-
const ascPath =
|
|
221412
|
+
const ascPath = join21(homedir7(), ".lecodes", "asc.json");
|
|
220500
221413
|
const stored = readJson(ascPath) ?? {};
|
|
220501
221414
|
const keyPath = flagStr(args, "key-path") ?? stored.keyPath;
|
|
220502
221415
|
const keyId = flagStr(args, "key-id") ?? stored.keyId;
|
|
@@ -220505,15 +221418,15 @@ var appUpload = async (args) => {
|
|
|
220505
221418
|
throw new CliError(`App Store Connect API key needed: --key-path <AuthKey.p8> --key-id <id> --issuer-id <id>
|
|
220506
221419
|
(create one at appstoreconnect.apple.com → Users and Access → Integrations; stored in ~/.lecodes/asc.json after first use)`);
|
|
220507
221420
|
}
|
|
220508
|
-
if (!
|
|
221421
|
+
if (!existsSync20(keyPath))
|
|
220509
221422
|
throw new CliError(`API key file not found: ${keyPath}`);
|
|
220510
221423
|
if (flagStr(args, "key-path")) {
|
|
220511
|
-
|
|
221424
|
+
mkdirSync14(dirname12(ascPath), { recursive: true });
|
|
220512
221425
|
writeJson(ascPath, { keyPath, keyId, issuerId });
|
|
220513
221426
|
note("Stored the key reference in ~/.lecodes/asc.json");
|
|
220514
221427
|
}
|
|
220515
|
-
const optionsPath =
|
|
220516
|
-
|
|
221428
|
+
const optionsPath = join21(root, "ios", "build", "UploadOptions.plist");
|
|
221429
|
+
writeFileSync16(optionsPath, exportOptionsPlist("app-store-connect", config.ios.teamId ?? "", "upload"));
|
|
220517
221430
|
note("Uploading to App Store Connect… (processing continues on Apple's side)");
|
|
220518
221431
|
runTool(root, "xcodebuild", [
|
|
220519
221432
|
"-exportArchive",
|
|
@@ -220524,14 +221437,14 @@ var appUpload = async (args) => {
|
|
|
220524
221437
|
"-exportPath",
|
|
220525
221438
|
"ios/build/upload",
|
|
220526
221439
|
"-authenticationKeyPath",
|
|
220527
|
-
|
|
221440
|
+
resolve8(keyPath),
|
|
220528
221441
|
"-authenticationKeyID",
|
|
220529
221442
|
keyId,
|
|
220530
221443
|
"-authenticationKeyIssuerID",
|
|
220531
221444
|
issuerId,
|
|
220532
221445
|
"-allowProvisioningUpdates"
|
|
220533
221446
|
]);
|
|
220534
|
-
const configPath =
|
|
221447
|
+
const configPath = join21(root, "app.json");
|
|
220535
221448
|
const fresh = readJson(configPath);
|
|
220536
221449
|
fresh.ios.buildNumber = (fresh.ios.buildNumber ?? 1) + 1;
|
|
220537
221450
|
writeJson(configPath, fresh);
|
|
@@ -220542,19 +221455,19 @@ var appOpen = async (args) => {
|
|
|
220542
221455
|
const root = projectRoot();
|
|
220543
221456
|
let platform = args._[1];
|
|
220544
221457
|
if (!platform) {
|
|
220545
|
-
const hasIos =
|
|
220546
|
-
const hasAndroid =
|
|
221458
|
+
const hasIos = existsSync20(join21(root, "ios"));
|
|
221459
|
+
const hasAndroid = existsSync20(join21(root, "android"));
|
|
220547
221460
|
if (hasIos && hasAndroid)
|
|
220548
221461
|
throw new CliError("Both shells exist — `lecodes app open ios` or `lecodes app open android`.");
|
|
220549
221462
|
platform = hasAndroid ? "android" : "ios";
|
|
220550
221463
|
}
|
|
220551
221464
|
if (platform === "ios") {
|
|
220552
|
-
const proj =
|
|
220553
|
-
if (!
|
|
221465
|
+
const proj = join21(root, "ios", "App.xcodeproj");
|
|
221466
|
+
if (!existsSync20(proj))
|
|
220554
221467
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
220555
221468
|
if (process.platform !== "darwin")
|
|
220556
221469
|
throw new CliError("Opening Xcode requires macOS.");
|
|
220557
|
-
|
|
221470
|
+
execFileSync6("open", [proj], { stdio: "ignore" });
|
|
220558
221471
|
success("Opened ios/App.xcodeproj in Xcode.");
|
|
220559
221472
|
return;
|
|
220560
221473
|
}
|
|
@@ -220587,8 +221500,8 @@ var app = async (args) => {
|
|
|
220587
221500
|
init_api();
|
|
220588
221501
|
init_config();
|
|
220589
221502
|
init_manifest();
|
|
220590
|
-
import { existsSync as
|
|
220591
|
-
import { resolve as
|
|
221503
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync16, readdirSync as readdirSync11 } from "node:fs";
|
|
221504
|
+
import { resolve as resolve9 } from "node:path";
|
|
220592
221505
|
init_types4();
|
|
220593
221506
|
init_ignore();
|
|
220594
221507
|
init_util();
|
|
@@ -220609,11 +221522,11 @@ var clone3 = async (args) => {
|
|
|
220609
221522
|
const uuid = parseUuid(args._[0]);
|
|
220610
221523
|
const project = await getProject(apiUrl, token, uuid);
|
|
220611
221524
|
const dir = args._[1] ?? slug(project.name);
|
|
220612
|
-
const root =
|
|
220613
|
-
if (
|
|
221525
|
+
const root = resolve9(dir);
|
|
221526
|
+
if (existsSync22(root) && readdirSync11(root).length > 0) {
|
|
220614
221527
|
throw new CliError(`Directory "${dir}" already exists and is not empty.`);
|
|
220615
221528
|
}
|
|
220616
|
-
|
|
221529
|
+
mkdirSync16(root, { recursive: true });
|
|
220617
221530
|
info(c.dim(`Fetching project ${c.bold(project.name)}…`));
|
|
220618
221531
|
const files = await materializeAssets(root, project, apiUrl, token, progress);
|
|
220619
221532
|
endProgress();
|
|
@@ -220636,8 +221549,8 @@ init_config();
|
|
|
220636
221549
|
init_manifest();
|
|
220637
221550
|
init_types4();
|
|
220638
221551
|
init_ignore();
|
|
220639
|
-
import { existsSync as
|
|
220640
|
-
import { join as
|
|
221552
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync17, readdirSync as readdirSync12, writeFileSync as writeFileSync18 } from "node:fs";
|
|
221553
|
+
import { join as join23, resolve as resolve10 } from "node:path";
|
|
220641
221554
|
|
|
220642
221555
|
// src/commands/projectTemplates.ts
|
|
220643
221556
|
var appClaudeMd = (name) => `# ${name} — LeCodes app
|
|
@@ -220654,6 +221567,9 @@ from \`main.ts\`.
|
|
|
220654
221567
|
lecodes render # semantic JSON of the launched UI (structure, text, names, values)
|
|
220655
221568
|
lecodes render --png shot.png # pixels (needs @napi-rs/canvas installed)
|
|
220656
221569
|
lecodes render --png card.png --clip card # ONE component/region: a node name or x,y,w,h
|
|
221570
|
+
lecodes render src/screens/cart.ts # ONE screen module, without booting the app: any file that
|
|
221571
|
+
# \`export default\`s a UIScreen (or a (state) => UIScreen —
|
|
221572
|
+
# --state <name> picks the branch). Same flags as above.
|
|
220657
221573
|
lecodes render --desktop --png shot.png # NATIVE render: real Filament 3D + UI + 2D composited —
|
|
220658
221574
|
# use this to SEE 3D scenes / procedural geometry (headless
|
|
220659
221575
|
# renders record 3D as JSON only). Windows/Linux; downloads
|
|
@@ -220774,21 +221690,21 @@ var create = async (args) => {
|
|
|
220774
221690
|
if (!name)
|
|
220775
221691
|
throw new CliError("Usage: lecodes create <name> [directory]");
|
|
220776
221692
|
const dir = args._[1] ?? slug2(name);
|
|
220777
|
-
const root =
|
|
220778
|
-
if (
|
|
221693
|
+
const root = resolve10(dir);
|
|
221694
|
+
if (existsSync23(root) && readdirSync12(root).length > 0) {
|
|
220779
221695
|
throw new CliError(`Directory "${dir}" already exists and is not empty.`);
|
|
220780
221696
|
}
|
|
220781
|
-
|
|
221697
|
+
mkdirSync17(root, { recursive: true });
|
|
220782
221698
|
info(c.dim(`Creating project ${c.bold(name)}…`));
|
|
220783
221699
|
const { uuid } = await createProject(apiUrl, token, name, flagStr(args, "team"));
|
|
220784
221700
|
info(c.dim("Fetching type definitions…"));
|
|
220785
221701
|
await materializeTypes(root, apiUrl);
|
|
220786
221702
|
writeTsconfig(root);
|
|
220787
221703
|
writeDefaultIgnore(root);
|
|
220788
|
-
|
|
220789
|
-
|
|
220790
|
-
|
|
220791
|
-
|
|
221704
|
+
writeFileSync18(join23(root, "main.ts"), STARTER);
|
|
221705
|
+
writeFileSync18(join23(root, "CLAUDE.md"), appClaudeMd(name));
|
|
221706
|
+
mkdirSync17(join23(root, ".claude"), { recursive: true });
|
|
221707
|
+
writeFileSync18(join23(root, ".claude", "settings.json"), CLAUDE_SETTINGS);
|
|
220792
221708
|
const commits = await getCommits(apiUrl, token, uuid);
|
|
220793
221709
|
writeManifest(root, { uuid, apiUrl, name, baseHeadId: commits.headId, files: {} });
|
|
220794
221710
|
success(`Created ${c.bold(name)} in ${c.bold(dir)}/`);
|
|
@@ -220828,9 +221744,12 @@ var run = async (args) => {
|
|
|
220828
221744
|
}
|
|
220829
221745
|
const appCfg = readAppConfigAt(root);
|
|
220830
221746
|
const win = appCfg?.desktop?.window;
|
|
220831
|
-
const
|
|
220832
|
-
|
|
221747
|
+
const renderer = resolveDesktopRenderer(root, args);
|
|
221748
|
+
const { js, resources } = await buildBundle(args, undefined, { localAssets: true, desktopRenderer: renderer });
|
|
221749
|
+
assertRendererSupportsBundle(renderer, js);
|
|
221750
|
+
note(`Launching LeCodes Desktop${renderer === "vulkan" ? " (Vulkan)" : ""}… (close the window to return)`);
|
|
220833
221751
|
await runDesktopApp(js, resources, {
|
|
221752
|
+
renderer,
|
|
220834
221753
|
width: Number(flagStr(args, "width")) || win?.width,
|
|
220835
221754
|
height: Number(flagStr(args, "height")) || win?.height,
|
|
220836
221755
|
minWidth: win?.minWidth,
|
|
@@ -220840,7 +221759,8 @@ var run = async (args) => {
|
|
|
220840
221759
|
fullscreen: flagBool(args, "fullscreen") ? true : flagBool(args, "windowed") ? false : win?.fullscreen,
|
|
220841
221760
|
scale: resolveScale(flagStr(args, "scale"), win?.scale),
|
|
220842
221761
|
renderScale: resolveRenderScale(flagStr(args, "render-scale"), appCfg?.desktop?.renderScale),
|
|
220843
|
-
maxRenderSize: appCfg?.desktop?.maxRenderSize
|
|
221762
|
+
maxRenderSize: appCfg?.desktop?.maxRenderSize,
|
|
221763
|
+
fps: flagBool(args, "fps")
|
|
220844
221764
|
});
|
|
220845
221765
|
};
|
|
220846
221766
|
var status = async () => {
|
|
@@ -220851,6 +221771,10 @@ var status = async () => {
|
|
|
220851
221771
|
log(`downloaded ${installed.length > 0 ? installed.join(", ") : c.dim("none")} ${c.dim("(~/.lecodes/renderer)")}`);
|
|
220852
221772
|
if (process.env.LECODES_DESKTOP_EXE)
|
|
220853
221773
|
log(`override LECODES_DESKTOP_EXE=${process.env.LECODES_DESKTOP_EXE}`);
|
|
221774
|
+
if (process.platform === "darwin") {
|
|
221775
|
+
const installed2 = findMacHostExe();
|
|
221776
|
+
log(`installed ${installed2 ?? c.dim("none")} ${c.dim("(offline fallback only)")}`);
|
|
221777
|
+
}
|
|
220854
221778
|
try {
|
|
220855
221779
|
const { latest } = await fetchDesktopManifest();
|
|
220856
221780
|
const hint = semverNewer(selected, latest) > 0 ? c.yellow("(newer — run `lecodes desktop update`)") : latest === selected ? c.dim("(up to date)") : c.dim("(older than the CLI's minimum — this desktop release isn't published yet)");
|
|
@@ -220865,9 +221789,11 @@ var desktop = async (args) => {
|
|
|
220865
221789
|
return status();
|
|
220866
221790
|
if (sub === "run")
|
|
220867
221791
|
return run(args);
|
|
221792
|
+
if (sub === "build")
|
|
221793
|
+
return buildDesktop(args);
|
|
220868
221794
|
if (sub === "update")
|
|
220869
221795
|
return updateDesktop();
|
|
220870
|
-
throw new CliError(`Unknown subcommand "desktop ${sub}". Use \`lecodes desktop\` (status), \`lecodes desktop run\` or \`lecodes desktop update\`.`);
|
|
221796
|
+
throw new CliError(`Unknown subcommand "desktop ${sub}". Use \`lecodes desktop\` (status), \`lecodes desktop run\`, \`lecodes desktop build\` or \`lecodes desktop update\`.`);
|
|
220871
221797
|
};
|
|
220872
221798
|
|
|
220873
221799
|
// src/index.ts
|
|
@@ -220876,16 +221802,16 @@ init_design();
|
|
|
220876
221802
|
// src/commands/dev.ts
|
|
220877
221803
|
init_manifest();
|
|
220878
221804
|
init_ignore();
|
|
220879
|
-
import { existsSync as
|
|
221805
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync19, readFileSync as readFileSync22, watch, writeFileSync as writeFileSync20 } from "node:fs";
|
|
220880
221806
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
220881
221807
|
import { networkInterfaces } from "node:os";
|
|
220882
|
-
import { basename as
|
|
221808
|
+
import { basename as basename11, join as join27 } from "node:path";
|
|
220883
221809
|
|
|
220884
221810
|
// src/dev/devServer.ts
|
|
220885
221811
|
init_localFiles();
|
|
220886
221812
|
import { createServer as createServer2 } from "node:http";
|
|
220887
|
-
import { createReadStream, statSync as
|
|
220888
|
-
import { join as
|
|
221813
|
+
import { createReadStream, statSync as statSync9 } from "node:fs";
|
|
221814
|
+
import { join as join26 } from "node:path";
|
|
220889
221815
|
init_projectCompile();
|
|
220890
221816
|
init_fonts();
|
|
220891
221817
|
init_assetIcons();
|
|
@@ -221159,14 +222085,14 @@ var sendText = (res, status2, type, body) => {
|
|
|
221159
222085
|
res.end(body);
|
|
221160
222086
|
};
|
|
221161
222087
|
var sendJson = (res, status2, body) => sendText(res, status2, "application/json", JSON.stringify(body));
|
|
221162
|
-
var readBody = (req) => new Promise((
|
|
222088
|
+
var readBody = (req) => new Promise((resolve11) => {
|
|
221163
222089
|
let body = "";
|
|
221164
222090
|
req.on("data", (c4) => {
|
|
221165
222091
|
if (body.length < 256 * 1024)
|
|
221166
222092
|
body += c4.toString("utf8");
|
|
221167
222093
|
});
|
|
221168
|
-
req.on("end", () =>
|
|
221169
|
-
req.on("error", () =>
|
|
222094
|
+
req.on("end", () => resolve11(body));
|
|
222095
|
+
req.on("error", () => resolve11(body));
|
|
221170
222096
|
});
|
|
221171
222097
|
var startDevServer = async (options) => {
|
|
221172
222098
|
const { root, manifest, name, origin, token, entryOverride } = options;
|
|
@@ -221271,9 +222197,9 @@ var startDevServer = async (options) => {
|
|
|
221271
222197
|
const name2 = route.slice("/shaders/".length);
|
|
221272
222198
|
if (!isShaderArtifactName(name2))
|
|
221273
222199
|
return sendText(res, 400, "text/plain", "Bad path");
|
|
221274
|
-
const abs =
|
|
222200
|
+
const abs = join26(shadersCacheDir(root), name2);
|
|
221275
222201
|
try {
|
|
221276
|
-
if (!
|
|
222202
|
+
if (!statSync9(abs).isFile())
|
|
221277
222203
|
throw new Error("not a file");
|
|
221278
222204
|
} catch {
|
|
221279
222205
|
return sendText(res, 404, "text/plain", "Not found");
|
|
@@ -221290,7 +222216,7 @@ var startDevServer = async (options) => {
|
|
|
221290
222216
|
return sendText(res, 400, "text/plain", "Bad path");
|
|
221291
222217
|
}
|
|
221292
222218
|
const abs = resolveLocalPath(root, key);
|
|
221293
|
-
if (!abs || !
|
|
222219
|
+
if (!abs || !statSync9(abs).isFile())
|
|
221294
222220
|
return sendText(res, 404, "text/plain", "Not found");
|
|
221295
222221
|
res.writeHead(200, { "content-type": contentType(key), "cache-control": "no-store" });
|
|
221296
222222
|
createReadStream(abs).pipe(res);
|
|
@@ -221333,11 +222259,11 @@ var startDevServer = async (options) => {
|
|
|
221333
222259
|
if (conn)
|
|
221334
222260
|
clients.add(conn);
|
|
221335
222261
|
});
|
|
221336
|
-
await new Promise((
|
|
222262
|
+
await new Promise((resolve11, reject) => {
|
|
221337
222263
|
server2.once("error", reject);
|
|
221338
222264
|
server2.listen(options.port, options.bind ?? "0.0.0.0", () => {
|
|
221339
222265
|
server2.removeListener("error", reject);
|
|
221340
|
-
|
|
222266
|
+
resolve11();
|
|
221341
222267
|
});
|
|
221342
222268
|
});
|
|
221343
222269
|
const address = server2.address();
|
|
@@ -221385,24 +222311,24 @@ var lanCandidates = () => {
|
|
|
221385
222311
|
return out.sort((x2, y2) => x2.rank - y2.rank).map(({ address, iface }) => ({ address, iface }));
|
|
221386
222312
|
};
|
|
221387
222313
|
var loadDevToken = (root) => {
|
|
221388
|
-
const path2 =
|
|
222314
|
+
const path2 = join27(root, LECODES_DIR, "dev.json");
|
|
221389
222315
|
try {
|
|
221390
|
-
const token2 = JSON.parse(
|
|
222316
|
+
const token2 = JSON.parse(readFileSync22(path2, "utf8")).token;
|
|
221391
222317
|
if (token2 && /^[a-z0-9]{8,}$/.test(token2))
|
|
221392
222318
|
return token2;
|
|
221393
222319
|
} catch {}
|
|
221394
222320
|
const token = randomBytes3(6).toString("hex");
|
|
221395
|
-
|
|
221396
|
-
|
|
222321
|
+
mkdirSync19(join27(root, LECODES_DIR), { recursive: true });
|
|
222322
|
+
writeFileSync20(path2, JSON.stringify({ token }, null, 2) + `
|
|
221397
222323
|
`);
|
|
221398
222324
|
return token;
|
|
221399
222325
|
};
|
|
221400
222326
|
var printQr = async (url) => {
|
|
221401
222327
|
try {
|
|
221402
222328
|
const qrcode = (await Promise.resolve().then(() => __toESM(require_main(), 1))).default;
|
|
221403
|
-
await new Promise((
|
|
222329
|
+
await new Promise((resolve11) => qrcode.generate(url, { small: true }, (out) => {
|
|
221404
222330
|
log(out);
|
|
221405
|
-
|
|
222331
|
+
resolve11();
|
|
221406
222332
|
}));
|
|
221407
222333
|
} catch {
|
|
221408
222334
|
note("(couldn't render a QR code — open the URL below on the device)");
|
|
@@ -221418,7 +222344,7 @@ var dev = async (args) => {
|
|
|
221418
222344
|
root = process.cwd();
|
|
221419
222345
|
manifest = null;
|
|
221420
222346
|
}
|
|
221421
|
-
const name = manifest?.name ??
|
|
222347
|
+
const name = manifest?.name ?? basename11(root);
|
|
221422
222348
|
if (typeof globalThis.Bun !== "undefined") {
|
|
221423
222349
|
warnErr("Running under Bun: the websocket reload channel doesn't work here — run `lecodes dev` with Node (or use ?t=poll on the device URL).");
|
|
221424
222350
|
}
|
|
@@ -221510,7 +222436,7 @@ ${result.error}`);
|
|
|
221510
222436
|
if (alternatives.length > 0) {
|
|
221511
222437
|
note(`Device can't reach it? This machine also has ${alternatives.map((a2) => `${a2.address} (${a2.iface})`).join(", ")} — retry with --host <ip>.`);
|
|
221512
222438
|
}
|
|
221513
|
-
if (
|
|
222439
|
+
if (existsSync25(join27(root, LECODES_DIR, "manifest.json")))
|
|
221514
222440
|
note(`Token stored in ${LECODES_DIR}/dev.json (delete it to rotate the URL).`);
|
|
221515
222441
|
log("");
|
|
221516
222442
|
log(`Watching ${root} — save a file to hot-reload. Ctrl+C to stop.`);
|
|
@@ -221525,7 +222451,8 @@ ${result.error}`);
|
|
|
221525
222451
|
minHeight: win?.minHeight,
|
|
221526
222452
|
resizable: win?.resizable,
|
|
221527
222453
|
title: win?.title ?? appCfg?.name ?? name,
|
|
221528
|
-
scale: resolveScale(undefined, win?.scale)
|
|
222454
|
+
scale: resolveScale(undefined, win?.scale),
|
|
222455
|
+
fps: flagBool(args, "fps")
|
|
221529
222456
|
}).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
222457
|
}
|
|
221531
222458
|
};
|
|
@@ -221538,8 +222465,8 @@ init_localFiles();
|
|
|
221538
222465
|
|
|
221539
222466
|
// src/serverDiff.ts
|
|
221540
222467
|
init_localFiles();
|
|
221541
|
-
import { readFileSync as
|
|
221542
|
-
import { basename as
|
|
222468
|
+
import { readFileSync as readFileSync23 } from "node:fs";
|
|
222469
|
+
import { basename as basename12 } from "node:path";
|
|
221543
222470
|
var isTextAsset = (t2) => t2 === "text" || t2 === "shader" || t2 === "scene";
|
|
221544
222471
|
var computeServerDiff = (project, scan2) => {
|
|
221545
222472
|
const pathById = buildPaths(project.assets);
|
|
@@ -221556,8 +222483,8 @@ var computeServerDiff = (project, scan2) => {
|
|
|
221556
222483
|
const server2 = serverByPath.get(path2);
|
|
221557
222484
|
const local = localByPath.get(path2);
|
|
221558
222485
|
if (local && !server2) {
|
|
221559
|
-
const text = isTextKind(classifyKind(
|
|
221560
|
-
entries.push({ path: path2, status: "added", binary: !text, serverText: "", localText: text ?
|
|
222486
|
+
const text = isTextKind(classifyKind(basename12(path2)));
|
|
222487
|
+
entries.push({ path: path2, status: "added", binary: !text, serverText: "", localText: text ? readFileSync23(local.absPath, "utf8") : undefined });
|
|
221561
222488
|
continue;
|
|
221562
222489
|
}
|
|
221563
222490
|
if (server2 && !local) {
|
|
@@ -221568,12 +222495,12 @@ var computeServerDiff = (project, scan2) => {
|
|
|
221568
222495
|
if (server2 && local) {
|
|
221569
222496
|
if (isTextAsset(server2.type)) {
|
|
221570
222497
|
const serverText = server2.text ?? "";
|
|
221571
|
-
const localText =
|
|
222498
|
+
const localText = readFileSync23(local.absPath, "utf8");
|
|
221572
222499
|
if (serverText !== localText)
|
|
221573
222500
|
entries.push({ path: path2, status: "modified", binary: false, serverText, localText });
|
|
221574
222501
|
} else {
|
|
221575
222502
|
const serverSize = server2.file?.size;
|
|
221576
|
-
const localSize = Buffer.byteLength(
|
|
222503
|
+
const localSize = Buffer.byteLength(readFileSync23(local.absPath));
|
|
221577
222504
|
if (serverSize !== undefined && serverSize !== localSize) {
|
|
221578
222505
|
entries.push({ path: path2, status: "modified", binary: true });
|
|
221579
222506
|
}
|
|
@@ -221731,8 +222658,8 @@ var statusLabel = (e2) => e2.status === "added" ? c.green(" added ") : e2.st
|
|
|
221731
222658
|
init_manifest();
|
|
221732
222659
|
init_types4();
|
|
221733
222660
|
init_ignore();
|
|
221734
|
-
import { existsSync as
|
|
221735
|
-
import { basename as
|
|
222661
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync20, writeFileSync as writeFileSync21 } from "node:fs";
|
|
222662
|
+
import { basename as basename13, dirname as dirname14, join as join28, resolve as resolve11 } from "node:path";
|
|
221736
222663
|
init_util();
|
|
221737
222664
|
var STARTER2 = `// Welcome to LeCodes — the whole SDK is available as globals, no imports needed.
|
|
221738
222665
|
// Run it live on a device with \`lecodes dev\`, or bundle it with \`lecodes compile -o app.js\`.
|
|
@@ -221840,18 +222767,18 @@ Full command list: \`lecodes --help\`.
|
|
|
221840
222767
|
`;
|
|
221841
222768
|
var init2 = async (args) => {
|
|
221842
222769
|
const dirArg = (args._[0] ?? ".").replace(/[/\\]+$/, "") || ".";
|
|
221843
|
-
const root =
|
|
221844
|
-
const name =
|
|
221845
|
-
|
|
222770
|
+
const root = resolve11(dirArg);
|
|
222771
|
+
const name = basename13(root);
|
|
222772
|
+
mkdirSync20(root, { recursive: true });
|
|
221846
222773
|
let created = 0;
|
|
221847
222774
|
const write = (rel, content) => {
|
|
221848
|
-
const abs =
|
|
221849
|
-
if (
|
|
222775
|
+
const abs = join28(root, rel);
|
|
222776
|
+
if (existsSync26(abs)) {
|
|
221850
222777
|
note(`kept ${rel}`);
|
|
221851
222778
|
return;
|
|
221852
222779
|
}
|
|
221853
|
-
|
|
221854
|
-
|
|
222780
|
+
mkdirSync20(dirname14(abs), { recursive: true });
|
|
222781
|
+
writeFileSync21(abs, content);
|
|
221855
222782
|
log(`${c.green("created")} ${rel}`);
|
|
221856
222783
|
created++;
|
|
221857
222784
|
};
|
|
@@ -221898,8 +222825,8 @@ init_config();
|
|
|
221898
222825
|
init_ignore();
|
|
221899
222826
|
init_localFiles();
|
|
221900
222827
|
init_manifest();
|
|
221901
|
-
import { existsSync as
|
|
221902
|
-
import { basename as
|
|
222828
|
+
import { existsSync as existsSync27 } from "node:fs";
|
|
222829
|
+
import { basename as basename15, join as join29, resolve as resolve12 } from "node:path";
|
|
221903
222830
|
init_types4();
|
|
221904
222831
|
init_util();
|
|
221905
222832
|
|
|
@@ -221908,8 +222835,8 @@ init_api();
|
|
|
221908
222835
|
init_config();
|
|
221909
222836
|
init_manifest();
|
|
221910
222837
|
init_localFiles();
|
|
221911
|
-
import { basename as
|
|
221912
|
-
import { readFileSync as
|
|
222838
|
+
import { basename as basename14 } from "node:path";
|
|
222839
|
+
import { readFileSync as readFileSync24 } from "node:fs";
|
|
221913
222840
|
init_util();
|
|
221914
222841
|
var push = async (args) => {
|
|
221915
222842
|
const root = findProjectRoot(process.cwd());
|
|
@@ -221965,14 +222892,14 @@ var push = async (args) => {
|
|
|
221965
222892
|
let uploaded = 0;
|
|
221966
222893
|
for (const file of scan2.files) {
|
|
221967
222894
|
const source = sourceFor(file.path);
|
|
221968
|
-
const kind = source?.type ?? classifyKind(
|
|
222895
|
+
const kind = source?.type ?? classifyKind(basename14(file.path));
|
|
221969
222896
|
const entry = { path: file.path, type: kind };
|
|
221970
222897
|
if (source)
|
|
221971
222898
|
entry.assetId = source.id;
|
|
221972
222899
|
if (isTextKind(kind)) {
|
|
221973
|
-
entry.text =
|
|
222900
|
+
entry.text = readFileSync24(file.absPath, "utf8");
|
|
221974
222901
|
} else if (!source || source.sha !== file.sha) {
|
|
221975
|
-
const blob = await uploadBlob(apiUrl, token, manifest.uuid,
|
|
222902
|
+
const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename14(file.path), readFileSync24(file.absPath));
|
|
221976
222903
|
entry.blobFileId = blob.id;
|
|
221977
222904
|
uploaded++;
|
|
221978
222905
|
}
|
|
@@ -222079,10 +223006,10 @@ var link = async (args) => {
|
|
|
222079
223006
|
const apiUrl = requireApiUrl(config);
|
|
222080
223007
|
const token = requireToken(config);
|
|
222081
223008
|
const dirArg = args._[0];
|
|
222082
|
-
const root = dirArg ?
|
|
222083
|
-
if (!
|
|
223009
|
+
const root = dirArg ? resolve12(dirArg) : process.cwd();
|
|
223010
|
+
if (!existsSync27(root))
|
|
222084
223011
|
throw new CliError(`No such directory: ${root}`);
|
|
222085
|
-
if (
|
|
223012
|
+
if (existsSync27(join29(root, LECODES_DIR, "manifest.json")) && !flagBool(args, "force")) {
|
|
222086
223013
|
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
223014
|
}
|
|
222088
223015
|
let enclosing = null;
|
|
@@ -222109,7 +223036,7 @@ var link = async (args) => {
|
|
|
222109
223036
|
}
|
|
222110
223037
|
name = project.name;
|
|
222111
223038
|
} else {
|
|
222112
|
-
name = flagStr(args, "name") ??
|
|
223039
|
+
name = flagStr(args, "name") ?? basename15(root);
|
|
222113
223040
|
info(c.dim(`Creating project ${c.bold(name)}…`));
|
|
222114
223041
|
uuid = (await createProject(apiUrl, token, name, flagStr(args, "team"))).uuid;
|
|
222115
223042
|
files = {};
|
|
@@ -222144,6 +223071,173 @@ var link = async (args) => {
|
|
|
222144
223071
|
log(` ${c.dim("(add --publish to deploy it live in the same step)")}`);
|
|
222145
223072
|
};
|
|
222146
223073
|
|
|
223074
|
+
// src/commands/lightmap.ts
|
|
223075
|
+
init_manifest();
|
|
223076
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync21, mkdtempSync as mkdtempSync4, rmSync as rmSync9, statSync as statSync10, unlinkSync, writeFileSync as writeFileSync22 } from "node:fs";
|
|
223077
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
223078
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
223079
|
+
import { join as join30, relative as relative4, resolve as resolve13 } from "node:path";
|
|
223080
|
+
init_util();
|
|
223081
|
+
var num = (args, key, fallback, min, max) => {
|
|
223082
|
+
const raw = flagStr(args, key);
|
|
223083
|
+
if (raw === undefined)
|
|
223084
|
+
return fallback;
|
|
223085
|
+
const n2 = Number(raw);
|
|
223086
|
+
if (!Number.isFinite(n2) || n2 < min || n2 > max)
|
|
223087
|
+
throw new CliError(`--${key} wants a number in ${min}..${max}, got "${raw}".`);
|
|
223088
|
+
return n2;
|
|
223089
|
+
};
|
|
223090
|
+
var findToktx = () => {
|
|
223091
|
+
const env = process.env.KTX_TOKTX;
|
|
223092
|
+
if (env && existsSync28(env))
|
|
223093
|
+
return env;
|
|
223094
|
+
for (const base of [process.env.LOCALAPPDATA && join30(process.env.LOCALAPPDATA, "Programs"), process.env.ProgramFiles]) {
|
|
223095
|
+
if (!base)
|
|
223096
|
+
continue;
|
|
223097
|
+
const guess = join30(base, "KTX-Software", "bin", "toktx.exe");
|
|
223098
|
+
if (existsSync28(guess))
|
|
223099
|
+
return guess;
|
|
223100
|
+
}
|
|
223101
|
+
return "toktx";
|
|
223102
|
+
};
|
|
223103
|
+
var checkToktx = (toktx) => {
|
|
223104
|
+
try {
|
|
223105
|
+
const p2 = spawnSync6(toktx, ["--version"], { encoding: "utf8" });
|
|
223106
|
+
if (p2.status === 0)
|
|
223107
|
+
return (p2.stdout ?? "").toString().trim() || "toktx";
|
|
223108
|
+
} catch {}
|
|
223109
|
+
return null;
|
|
223110
|
+
};
|
|
223111
|
+
var requireToktx = () => {
|
|
223112
|
+
const toktx = findToktx();
|
|
223113
|
+
if (!checkToktx(toktx)) {
|
|
223114
|
+
throw new CliError(`lecodes lightmap bake needs KTX-Software's toktx — the atlas ships as KTX2 (GPU-compressed, mipmapped).
|
|
223115
|
+
Install it from https://github.com/KhronosGroup/KTX-Software/releases (make sure toktx is on PATH) or set KTX_TOKTX=<path to toktx>.`);
|
|
223116
|
+
}
|
|
223117
|
+
return toktx;
|
|
223118
|
+
};
|
|
223119
|
+
var encodeKtx2 = (toktx, png2, ktx2) => {
|
|
223120
|
+
const args = ["--t2", "--encode", "uastc", "--uastc_quality", "2", "--zcmp", "18", "--genmipmap", "--assign_oetf", "srgb", ktx2, png2];
|
|
223121
|
+
const p2 = spawnSync6(toktx, args, { encoding: "utf8" });
|
|
223122
|
+
if (p2.status !== 0) {
|
|
223123
|
+
throw new CliError(`toktx failed (${p2.status ?? p2.error?.message ?? "?"}) while encoding ${ktx2}:
|
|
223124
|
+
${(p2.stderr || p2.stdout || "").trim()}`);
|
|
223125
|
+
}
|
|
223126
|
+
};
|
|
223127
|
+
var bake = async (args) => {
|
|
223128
|
+
let root;
|
|
223129
|
+
try {
|
|
223130
|
+
root = findProjectRoot(process.cwd());
|
|
223131
|
+
} catch {
|
|
223132
|
+
root = process.cwd();
|
|
223133
|
+
}
|
|
223134
|
+
const toktx = requireToktx();
|
|
223135
|
+
const outDir = resolve13(root, flagStr(args, "out") ?? "assets/lightmap");
|
|
223136
|
+
const entryFlag = flagStr(args, "entry");
|
|
223137
|
+
const entryFile = entryFlag ?? "main.ts";
|
|
223138
|
+
if (!existsSync28(resolve13(root, entryFile))) {
|
|
223139
|
+
throw new CliError(`No ${entryFile} in ${root} — pass --entry <file> (the file that builds the scene and calls Lightmap.load).`);
|
|
223140
|
+
}
|
|
223141
|
+
const cfg = {
|
|
223142
|
+
outDir: outDir.replace(/\\/g, "/"),
|
|
223143
|
+
size: num(args, "size", 4096, 256, 16384),
|
|
223144
|
+
texel: num(args, "texel", 0.02, 0.001, 2),
|
|
223145
|
+
sunRays: num(args, "sun-rays", 32, 1, 1024),
|
|
223146
|
+
aoRays: num(args, "ao-rays", 64, 1, 4096),
|
|
223147
|
+
aoDistance: num(args, "ao-distance", 2, 0.01, 1000),
|
|
223148
|
+
bias: num(args, "bias", 0.02, 0.0001, 1),
|
|
223149
|
+
sunAngle: num(args, "sun-angle", 0.5, 0.01, 30)
|
|
223150
|
+
};
|
|
223151
|
+
const frames = num(args, "frames", 900, 30, 1e5);
|
|
223152
|
+
const win = readAppConfigAt(root)?.desktop?.window;
|
|
223153
|
+
const width = Number(flagStr(args, "width")) || win?.width || 1280;
|
|
223154
|
+
const height = Number(flagStr(args, "height")) || win?.height || 720;
|
|
223155
|
+
const logs = flagBool(args, "logs");
|
|
223156
|
+
const importPath = "./" + entryFile.replace(/\\/g, "/").replace(/\.tsx?$/, "");
|
|
223157
|
+
const entryPath = "/.lightmap-entry.ts";
|
|
223158
|
+
const entryText = `globalThis.__lecodesLightmap = ${JSON.stringify(cfg)}
|
|
223159
|
+
import ${JSON.stringify(importPath)}
|
|
223160
|
+
`;
|
|
223161
|
+
mkdirSync21(outDir, { recursive: true });
|
|
223162
|
+
ensurePlaceholders(outDir);
|
|
223163
|
+
const desktopRenderer = resolveDesktopRenderer(root, args);
|
|
223164
|
+
const { js, resources } = await buildBundle(args, undefined, {
|
|
223165
|
+
localAssets: true,
|
|
223166
|
+
desktopRenderer,
|
|
223167
|
+
extraEntries: [{ path: entryPath, type: "text", text: entryText }],
|
|
223168
|
+
entryOverride: entryPath
|
|
223169
|
+
});
|
|
223170
|
+
const scratch = mkdtempSync4(join30(tmpdir4(), "lecodes-lightmap-"));
|
|
223171
|
+
const outPng = join30(scratch, "frame.png");
|
|
223172
|
+
note(`Baking ${cfg.size}² at ${(cfg.texel * 100).toFixed(1)} cm texels (${cfg.sunRays} sun + ${cfg.aoRays} AO rays) → ${relative4(root, outDir) || "."}…`);
|
|
223173
|
+
let output = "";
|
|
223174
|
+
try {
|
|
223175
|
+
const timeoutMs = Number(flagStr(args, "timeout")) || 600000;
|
|
223176
|
+
const res = await runDesktopRender(js, resources, {
|
|
223177
|
+
renderer: desktopRenderer,
|
|
223178
|
+
width,
|
|
223179
|
+
height,
|
|
223180
|
+
frames,
|
|
223181
|
+
outPng,
|
|
223182
|
+
logs,
|
|
223183
|
+
timeoutMs,
|
|
223184
|
+
stopOnLine: (chunk) => chunk.includes("[lightmap] done") || chunk.includes("[lightmap] error")
|
|
223185
|
+
});
|
|
223186
|
+
output = res.output;
|
|
223187
|
+
} catch (e2) {
|
|
223188
|
+
const msg = e2 instanceof Error ? e2.message : String(e2);
|
|
223189
|
+
if (!/\[lightmap\] done/.test(msg))
|
|
223190
|
+
throw e2;
|
|
223191
|
+
output = msg;
|
|
223192
|
+
} finally {
|
|
223193
|
+
rmSync9(scratch, { recursive: true, force: true });
|
|
223194
|
+
}
|
|
223195
|
+
const lines = output.split(/\r?\n/).filter((l2) => l2.startsWith("[lightmap]"));
|
|
223196
|
+
for (const l2 of lines) {
|
|
223197
|
+
const text = l2.slice("[lightmap]".length).trim();
|
|
223198
|
+
if (text === "done")
|
|
223199
|
+
continue;
|
|
223200
|
+
logErr(text.startsWith("error") ? c.red(`✗ ${text}`) : c.dim(` ${text}`));
|
|
223201
|
+
}
|
|
223202
|
+
if (/\[lightmap\] done/.test(output)) {
|
|
223203
|
+
const png2 = join30(outDir, "lightmap.png");
|
|
223204
|
+
const ktx2 = join30(outDir, "lightmap.ktx2");
|
|
223205
|
+
if (!existsSync28(png2))
|
|
223206
|
+
throw new CliError(`The host reported a bake but ${png2} is missing.`);
|
|
223207
|
+
const t0 = Date.now();
|
|
223208
|
+
encodeKtx2(toktx, png2, ktx2);
|
|
223209
|
+
const pngKb = Math.round(statSync10(png2).size / 1024), ktxKb = Math.round(statSync10(ktx2).size / 1024);
|
|
223210
|
+
unlinkSync(png2);
|
|
223211
|
+
logErr(c.dim(` encoded lightmap.ktx2 (UASTC + mips, ${ktxKb} KB from a ${pngKb} KB raster) in ${((Date.now() - t0) / 1000).toFixed(1)} s`));
|
|
223212
|
+
logErr(c.green(`✓ baked → ${relative4(root, outDir) || "."}/lightmap.ktx2 + lightmap.bake`));
|
|
223213
|
+
logErr(c.dim(` apply it with Lightmap.load(scene, { data: asset('<rel>/${relative4(root, join30(outDir, "lightmap.bake")).replace(/\\/g, "/")}'), texture: asset('<rel>/${relative4(root, ktx2).replace(/\\/g, "/")}') }) — asset() paths are relative to the calling file`));
|
|
223214
|
+
return;
|
|
223215
|
+
}
|
|
223216
|
+
if (lines.some((l2) => /no bake module/.test(l2))) {
|
|
223217
|
+
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.");
|
|
223218
|
+
}
|
|
223219
|
+
if (lines.some((l2) => /error/.test(l2)))
|
|
223220
|
+
throw new CliError("The bake failed — see the lines above.");
|
|
223221
|
+
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.");
|
|
223222
|
+
throw new CliError("Nothing was baked.");
|
|
223223
|
+
};
|
|
223224
|
+
var PLACEHOLDER_KTX2 = Buffer.from("q0tUWCAyMLsNChoKAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAAaAAAACwAAACUAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAQAQAAAAAAABAAAAAAAAAAEAAAAAAAAAAsAAAAAAAAAAIAKACmAQIAAwMAABAAAAAAAAAAAAB/AAAAAAAAAAAA/////xEAAABLVFhvcmllbnRhdGlvbgByAAAAACcAAABLVFh3cml0ZXIAdG9rdHggdjQuNC4yIC8gbGlia3R4IHY0LjQuMgAAMwAAAEtUWHdyaXRlclNjUGFyYW1zAC0tZW5jb2RlIHVhc3RjIC0tdWFzdGNfcXVhbGl0eSAwAAD3////H3zvAQAAAAAAAAAA", "base64");
|
|
223225
|
+
var ensurePlaceholders = (outDir) => {
|
|
223226
|
+
const bake2 = join30(outDir, "lightmap.bake");
|
|
223227
|
+
const ktx2 = join30(outDir, "lightmap.ktx2");
|
|
223228
|
+
if (!existsSync28(bake2))
|
|
223229
|
+
writeFileSync22(bake2, JSON.stringify({ version: 1, placeholder: true, size: 1, texel: 0, instances: [] }) + `
|
|
223230
|
+
`);
|
|
223231
|
+
if (!existsSync28(ktx2))
|
|
223232
|
+
writeFileSync22(ktx2, PLACEHOLDER_KTX2);
|
|
223233
|
+
};
|
|
223234
|
+
var lightmap = async (args) => {
|
|
223235
|
+
const sub = args._[0];
|
|
223236
|
+
if (sub === "bake")
|
|
223237
|
+
return bake({ ...args, _: args._.slice(1) });
|
|
223238
|
+
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)");
|
|
223239
|
+
};
|
|
223240
|
+
|
|
222147
223241
|
// src/commands/login.ts
|
|
222148
223242
|
init_api();
|
|
222149
223243
|
init_config();
|
|
@@ -222161,8 +223255,9 @@ var login = async (args) => {
|
|
|
222161
223255
|
|
|
222162
223256
|
// src/commands/shaders.ts
|
|
222163
223257
|
init_manifest();
|
|
222164
|
-
import { readFileSync as
|
|
223258
|
+
import { readFileSync as readFileSync25 } from "node:fs";
|
|
222165
223259
|
init_util();
|
|
223260
|
+
init_compile();
|
|
222166
223261
|
var collectShaders = () => {
|
|
222167
223262
|
let root;
|
|
222168
223263
|
let manifest;
|
|
@@ -222188,8 +223283,27 @@ var status2 = async () => {
|
|
|
222188
223283
|
log(`shaders ${c.dim(`(compiled = up-to-date artifacts in ${shadersCacheDir(root)})`)}`);
|
|
222189
223284
|
const width = Math.max(...shaders.map((s2) => s2.path.length));
|
|
222190
223285
|
for (const shader of shaders) {
|
|
222191
|
-
|
|
223286
|
+
let state;
|
|
223287
|
+
if (matcId === null)
|
|
223288
|
+
state = c.dim("unknown (matc not downloaded)");
|
|
223289
|
+
else {
|
|
223290
|
+
const text = readFileSync25(shader.absPath, "utf8");
|
|
223291
|
+
if (shaderIsCompiled(root, text, matcId))
|
|
223292
|
+
state = "compiled";
|
|
223293
|
+
else {
|
|
223294
|
+
const present = shaderCompiledPlatforms(root, text, matcId);
|
|
223295
|
+
state = present.length === 0 ? c.yellow("not compiled — run `lecodes shaders compile`") : c.yellow(`partial (${present.join(", ")}) — \`lecodes shaders compile\` for the rest`);
|
|
223296
|
+
}
|
|
223297
|
+
}
|
|
222192
223298
|
log(` ${shader.path.padEnd(width)} ${state}`);
|
|
223299
|
+
const schema = parseShaderSchema(readFileSync25(shader.absPath, "utf8"));
|
|
223300
|
+
for (const p2 of schema.params) {
|
|
223301
|
+
const range = p2.min !== undefined ? ` [${p2.min}..${p2.max}]` : "";
|
|
223302
|
+
const def = p2.default !== null && p2.default !== undefined ? ` = ${JSON.stringify(p2.default)}` : "";
|
|
223303
|
+
log(` ${c.dim(p2.type.padEnd(10))} ${p2.name}${c.dim(` ${p2.editor}${range}${def}`)}${p2.tooltip ? c.dim(` — ${p2.tooltip}`) : ""}`);
|
|
223304
|
+
}
|
|
223305
|
+
for (const e2 of schema.errors)
|
|
223306
|
+
log(` ${c.yellow(e2)}`);
|
|
222193
223307
|
}
|
|
222194
223308
|
}
|
|
222195
223309
|
try {
|
|
@@ -222201,7 +223315,23 @@ var status2 = async () => {
|
|
|
222201
223315
|
log(`published ${c.dim(e2 instanceof CliError ? e2.message : "unknown (couldn't reach the release manifest)")}`);
|
|
222202
223316
|
}
|
|
222203
223317
|
};
|
|
222204
|
-
var
|
|
223318
|
+
var parsePlatforms = (args) => {
|
|
223319
|
+
const raw = flagStr(args, "platform", "platforms");
|
|
223320
|
+
if (raw === undefined)
|
|
223321
|
+
return;
|
|
223322
|
+
const known = allShaderPlatforms();
|
|
223323
|
+
const asked = raw.split(",").map((p2) => p2.trim()).filter(Boolean);
|
|
223324
|
+
if (asked.length === 0)
|
|
223325
|
+
throw new CliError(`--platform needs at least one target. Known: ${known.join(", ")}`);
|
|
223326
|
+
const bad = asked.filter((p2) => !known.includes(p2));
|
|
223327
|
+
if (bad.length > 0) {
|
|
223328
|
+
throw new CliError(`Unknown shader platform${bad.length > 1 ? "s" : ""}: ${bad.join(", ")}
|
|
223329
|
+
Known: ${known.join(", ")}`);
|
|
223330
|
+
}
|
|
223331
|
+
return asked;
|
|
223332
|
+
};
|
|
223333
|
+
var compile3 = async (args) => {
|
|
223334
|
+
const platforms = parsePlatforms(args);
|
|
222205
223335
|
const { root, entries, shaders } = collectShaders();
|
|
222206
223336
|
if (shaders.length === 0) {
|
|
222207
223337
|
note("No .mat shaders in this project.");
|
|
@@ -222209,11 +223339,11 @@ var compile3 = async () => {
|
|
|
222209
223339
|
}
|
|
222210
223340
|
const matcExe = await resolveMatcExe();
|
|
222211
223341
|
const matcId = matcIdentity(matcExe);
|
|
222212
|
-
const wasCompiled = new Map(shaders.map((s2) => [s2.path, shaderIsCompiled(root,
|
|
223342
|
+
const wasCompiled = new Map(shaders.map((s2) => [s2.path, shaderIsCompiled(root, readFileSync25(s2.absPath, "utf8"), matcId)]));
|
|
222213
223343
|
const errors2 = [];
|
|
222214
223344
|
for (const shader of shaders) {
|
|
222215
223345
|
try {
|
|
222216
|
-
const { warnings } = await applyLocalShaders([shader], { root, local: true });
|
|
223346
|
+
const { warnings } = await applyLocalShaders([shader], { root, local: true, platforms });
|
|
222217
223347
|
for (const w of warnings)
|
|
222218
223348
|
warnErr(w);
|
|
222219
223349
|
log(` ${shader.path} ${wasCompiled.get(shader.path) ? c.dim("compiled (cached)") : "compiled"}`);
|
|
@@ -222228,22 +223358,23 @@ var compile3 = async () => {
|
|
|
222228
223358
|
${c.red("✗")} ${e2}`);
|
|
222229
223359
|
if (errors2.length > 0)
|
|
222230
223360
|
throw new CliError(`${errors2.length} of ${shaders.length} shader${shaders.length === 1 ? "" : "s"} failed to compile.`);
|
|
222231
|
-
|
|
223361
|
+
const scope = platforms ? ` [${platforms.join(", ")}]` : "";
|
|
223362
|
+
note(`${shaders.length} shader${shaders.length === 1 ? "" : "s"} compiled${scope} → ${shadersCacheDir(root)}`);
|
|
222232
223363
|
};
|
|
222233
223364
|
var shaders = async (args) => {
|
|
222234
223365
|
const sub = args._[0];
|
|
222235
223366
|
if (sub === undefined)
|
|
222236
223367
|
return status2();
|
|
222237
223368
|
if (sub === "compile")
|
|
222238
|
-
return compile3();
|
|
223369
|
+
return compile3(args);
|
|
222239
223370
|
if (sub === "update")
|
|
222240
223371
|
return updateMatc();
|
|
222241
|
-
throw new CliError(`Unknown subcommand "shaders ${sub}". Use \`lecodes shaders\` (status), \`lecodes shaders compile\` or \`lecodes shaders update\`.`);
|
|
223372
|
+
throw new CliError(`Unknown subcommand "shaders ${sub}". Use \`lecodes shaders\` (status), \`lecodes shaders compile [--platform a,b]\` or \`lecodes shaders update\`.`);
|
|
222242
223373
|
};
|
|
222243
223374
|
|
|
222244
223375
|
// src/commands/render.ts
|
|
222245
|
-
import { mkdirSync as
|
|
222246
|
-
import { dirname as dirname15, resolve as
|
|
223376
|
+
import { mkdirSync as mkdirSync22, readFileSync as readFileSync27, writeFileSync as writeFileSync23 } from "node:fs";
|
|
223377
|
+
import { dirname as dirname15, resolve as resolve16 } from "node:path";
|
|
222247
223378
|
|
|
222248
223379
|
// src/compile/headlessBundle.ts
|
|
222249
223380
|
init_api();
|
|
@@ -222254,7 +223385,7 @@ init_fonts();
|
|
|
222254
223385
|
init_assetIcons();
|
|
222255
223386
|
init_util();
|
|
222256
223387
|
var LOCAL_ASSET_SCHEME2 = "lecodesfile://";
|
|
222257
|
-
var compileHeadlessBundle = async (args) => {
|
|
223388
|
+
var compileHeadlessBundle = async (args, opts = {}) => {
|
|
222258
223389
|
let root;
|
|
222259
223390
|
let manifest;
|
|
222260
223391
|
try {
|
|
@@ -222289,7 +223420,9 @@ var compileHeadlessBundle = async (args) => {
|
|
|
222289
223420
|
warnings.push("This is a local project (no server) — assets always load from the working tree.");
|
|
222290
223421
|
}
|
|
222291
223422
|
}
|
|
222292
|
-
const compileEntries = useLocalAssets ? entries.map((e2) => e2.type === "resource" && e2.absPath ? { ...e2, fileSrc: encodeURIComponent(e2.absPath) } : e2) : entries;
|
|
223423
|
+
const compileEntries = useLocalAssets ? entries.map((e2) => e2.type === "resource" && e2.absPath ? { ...e2, fileSrc: encodeURIComponent(e2.absPath) } : e2) : [...entries];
|
|
223424
|
+
if (opts.screen)
|
|
223425
|
+
compileEntries.push(opts.screen.entry);
|
|
222293
223426
|
await applyProjectFonts(compileEntries, useLocalAssets ? {
|
|
222294
223427
|
mode: "vendor",
|
|
222295
223428
|
root,
|
|
@@ -222299,7 +223432,7 @@ var compileHeadlessBundle = async (args) => {
|
|
|
222299
223432
|
} : { mode: "cdn", root });
|
|
222300
223433
|
await applyAppAssetIcons(compileEntries);
|
|
222301
223434
|
note("Compiling…");
|
|
222302
|
-
const entryFlag = flagStr(args, "entry");
|
|
223435
|
+
const entryFlag = opts.screen?.entryPath ?? flagStr(args, "entry");
|
|
222303
223436
|
if (!entryFlag) {
|
|
222304
223437
|
const ambiguous = ambiguousEntryWarning(compileEntries);
|
|
222305
223438
|
if (ambiguous)
|
|
@@ -222322,12 +223455,84 @@ var compileHeadlessBundle = async (args) => {
|
|
|
222322
223455
|
return { js, root, warnings, useLocalAssets };
|
|
222323
223456
|
};
|
|
222324
223457
|
|
|
223458
|
+
// src/compile/screenEntry.ts
|
|
223459
|
+
init_designMeta();
|
|
223460
|
+
init_util();
|
|
223461
|
+
import { existsSync as existsSync29, readFileSync as readFileSync26, statSync as statSync11 } from "node:fs";
|
|
223462
|
+
import { isAbsolute, join as join31, relative as relative5, resolve as resolve14 } from "node:path";
|
|
223463
|
+
var SCREEN_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs"];
|
|
223464
|
+
var SCREEN_ENTRY_PATH = "/.screen-entry.ts";
|
|
223465
|
+
var DEFAULT_DESIGN_DIR = "design";
|
|
223466
|
+
var screenArg = (args) => args._[0];
|
|
223467
|
+
var hasSeparator = (file) => file.includes("/") || file.includes("\\");
|
|
223468
|
+
var findScreenFile = (root, file, dirFlag) => {
|
|
223469
|
+
const candidates = [resolve14(process.cwd(), file)];
|
|
223470
|
+
if (!hasSeparator(file)) {
|
|
223471
|
+
candidates.push(join31(root, (dirFlag ?? DEFAULT_DESIGN_DIR).replace(/[/\\]+$/, ""), "screens", file));
|
|
223472
|
+
}
|
|
223473
|
+
for (const base of candidates) {
|
|
223474
|
+
if (existsSync29(base) && statSync11(base).isFile())
|
|
223475
|
+
return base;
|
|
223476
|
+
for (const ext2 of SCREEN_EXTS)
|
|
223477
|
+
if (existsSync29(base + ext2))
|
|
223478
|
+
return base + ext2;
|
|
223479
|
+
}
|
|
223480
|
+
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/.`));
|
|
223481
|
+
};
|
|
223482
|
+
var designDirOf = (root, key) => {
|
|
223483
|
+
const m = /^\/(.*)screens\/([^/]+)\.[^./]+$/.exec(key);
|
|
223484
|
+
if (!m)
|
|
223485
|
+
return null;
|
|
223486
|
+
const dir = join31(root, m[1]);
|
|
223487
|
+
return existsSync29(join31(dir, "meta.json")) ? { dir, id: m[2] } : null;
|
|
223488
|
+
};
|
|
223489
|
+
var resolveScreenTarget = (root, file, opts = {}) => {
|
|
223490
|
+
const abs = findScreenFile(root, file, opts.dir);
|
|
223491
|
+
const rel = relative5(root, abs);
|
|
223492
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
223493
|
+
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.`);
|
|
223494
|
+
}
|
|
223495
|
+
const key = toKey(rel);
|
|
223496
|
+
if (!/^\s*export\s+default\b/m.test(readFileSync26(abs, "utf8"))) {
|
|
223497
|
+
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.`);
|
|
223498
|
+
}
|
|
223499
|
+
const invoke = opts.state !== undefined ? `screen(${JSON.stringify(opts.state)})` : "screen()";
|
|
223500
|
+
const spec = "." + key.replace(/\.[^./]+$/, "");
|
|
223501
|
+
const design2 = designDirOf(root, key);
|
|
223502
|
+
return {
|
|
223503
|
+
entryPath: SCREEN_ENTRY_PATH,
|
|
223504
|
+
entry: {
|
|
223505
|
+
path: SCREEN_ENTRY_PATH,
|
|
223506
|
+
type: "text",
|
|
223507
|
+
text: `import screen from ${JSON.stringify(spec)}
|
|
223508
|
+
` + `const __screen = typeof screen === "function" ? ${invoke} : screen
|
|
223509
|
+
` + `__screen.open()
|
|
223510
|
+
`
|
|
223511
|
+
},
|
|
223512
|
+
frame: design2 ? screenFrame(design2.dir, design2.id) : undefined,
|
|
223513
|
+
label: (design2 ? design2.id : key.slice(1)) + (opts.state !== undefined ? `@${opts.state}` : "")
|
|
223514
|
+
};
|
|
223515
|
+
};
|
|
223516
|
+
var screenTargetFromArgs = (root, args) => {
|
|
223517
|
+
const file = screenArg(args);
|
|
223518
|
+
if (file === undefined) {
|
|
223519
|
+
if (flagStr(args, "state") !== undefined) {
|
|
223520
|
+
throw new CliError("--state picks a screen's state — name the screen file too: lecodes render <file> --state <name>");
|
|
223521
|
+
}
|
|
223522
|
+
return;
|
|
223523
|
+
}
|
|
223524
|
+
if (flagStr(args, "entry") !== undefined) {
|
|
223525
|
+
throw new CliError("Use either a screen file or --entry, not both — each names the module to compile from.");
|
|
223526
|
+
}
|
|
223527
|
+
return resolveScreenTarget(root, file, { state: flagStr(args, "state"), dir: flagStr(args, "dir") });
|
|
223528
|
+
};
|
|
223529
|
+
|
|
222325
223530
|
// src/commands/render.ts
|
|
222326
223531
|
init_manifest();
|
|
222327
223532
|
|
|
222328
223533
|
// src/desktopScript.ts
|
|
222329
223534
|
init_util();
|
|
222330
|
-
import { resolve as
|
|
223535
|
+
import { resolve as resolve15 } from "node:path";
|
|
222331
223536
|
var isPoint = (v) => typeof v === "object" && v !== null && typeof v.x === "number" && typeof v.y === "number";
|
|
222332
223537
|
var FRAME_MS = 16;
|
|
222333
223538
|
var ACTIONS = ["tap", "type", "fill", "scroll", "wait", "frames", "drag", "hold", "key", "keyDown", "keyUp", "look", "mouseTo", "wheel", "stick", "gamepad", "camera", "view"];
|
|
@@ -222355,7 +223560,7 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
222355
223560
|
}
|
|
222356
223561
|
throw new CliError(`${at}: "${key}" must be { "x", "y" }.`);
|
|
222357
223562
|
};
|
|
222358
|
-
const
|
|
223563
|
+
const num2 = (at, key, v, def) => {
|
|
222359
223564
|
if (v === undefined && def !== undefined)
|
|
222360
223565
|
return def;
|
|
222361
223566
|
if (typeof v !== "number")
|
|
@@ -222382,7 +223587,7 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
222382
223587
|
}
|
|
222383
223588
|
if (action === "view") {
|
|
222384
223589
|
const v = s2.view;
|
|
222385
|
-
const fov = s2.fov !== undefined ?
|
|
223590
|
+
const fov = s2.fov !== undefined ? num2(at, "fov", s2.fov) : undefined;
|
|
222386
223591
|
if (v === "off") {
|
|
222387
223592
|
plan.lines.push(emit("view off"));
|
|
222388
223593
|
inspecting = false;
|
|
@@ -222402,7 +223607,7 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
222402
223607
|
parts.push("camera off");
|
|
222403
223608
|
} else if (typeof cam === "object" && cam !== null && isVec3(cam.at) && isVec3(cam.lookAt)) {
|
|
222404
223609
|
const { at: eye, lookAt, fov } = cam;
|
|
222405
|
-
const fovN = fov !== undefined ?
|
|
223610
|
+
const fovN = fov !== undefined ? num2(at, "camera.fov", fov) : undefined;
|
|
222406
223611
|
plan.lines.push(emit(`camera ${eye.join(" ")} ${lookAt.join(" ")}${fovN !== undefined ? ` ${fovN}` : ""}`));
|
|
222407
223612
|
inspecting = true;
|
|
222408
223613
|
parts.push(`camera at (${eye.join(",")}) → (${lookAt.join(",")})`);
|
|
@@ -222419,20 +223624,20 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
222419
223624
|
parts.push(`tap ${pt(p2)}`);
|
|
222420
223625
|
} else if (action === "drag") {
|
|
222421
223626
|
const a2 = coord(at, "drag", s2.drag), b = coord(at, "to", s2.to);
|
|
222422
|
-
const ms =
|
|
223627
|
+
const ms = num2(at, "ms", s2.ms, 300);
|
|
222423
223628
|
plan.lines.push(emit(`drag ${a2.x} ${a2.y} ${b.x} ${b.y} ${Math.round(ms)}`));
|
|
222424
223629
|
budgetMs += ms + 4 * FRAME_MS;
|
|
222425
223630
|
parts.push(`drag ${pt(a2)} → ${pt(b)} over ${ms}ms`);
|
|
222426
223631
|
} else if (action === "hold") {
|
|
222427
223632
|
const p2 = coord(at, "hold", s2.hold);
|
|
222428
|
-
const ms =
|
|
223633
|
+
const ms = num2(at, "ms", s2.ms);
|
|
222429
223634
|
plan.lines.push(emit(`hold ${p2.x} ${p2.y} ${Math.round(ms)}`));
|
|
222430
223635
|
budgetMs += ms + 4 * FRAME_MS;
|
|
222431
223636
|
parts.push(`hold ${pt(p2)} ${ms}ms`);
|
|
222432
223637
|
} else if (action === "key") {
|
|
222433
223638
|
if (typeof s2.key !== "string")
|
|
222434
223639
|
throw new CliError(`${at}: "key" must be a KeyboardEvent.code string.`);
|
|
222435
|
-
const ms =
|
|
223640
|
+
const ms = num2(at, "ms", s2.ms);
|
|
222436
223641
|
plan.lines.push(emit(`key ${s2.key} ${Math.round(ms)}`));
|
|
222437
223642
|
budgetMs += ms + 2 * FRAME_MS;
|
|
222438
223643
|
parts.push(`key ${s2.key} ${ms}ms`);
|
|
@@ -222447,7 +223652,7 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
222447
223652
|
const v = s2.look;
|
|
222448
223653
|
if (!Array.isArray(v) || v.length !== 2 || !v.every((n2) => typeof n2 === "number"))
|
|
222449
223654
|
throw new CliError(`${at}: "look" must be [dx, dy] (logical px of mouse motion).`);
|
|
222450
|
-
const ms =
|
|
223655
|
+
const ms = num2(at, "ms", s2.ms, FRAME_MS);
|
|
222451
223656
|
plan.lines.push(emit(`look ${v[0]} ${v[1]} ${Math.round(ms)}`));
|
|
222452
223657
|
budgetMs += ms + 2 * FRAME_MS;
|
|
222453
223658
|
parts.push(`look ${v[0]},${v[1]} over ${ms}ms`);
|
|
@@ -222493,12 +223698,12 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
222493
223698
|
budgetMs += 2 * FRAME_MS;
|
|
222494
223699
|
parts.push(`gamepad ${s2.gamepad} #${pad}`);
|
|
222495
223700
|
} else if (action === "wait") {
|
|
222496
|
-
const ms =
|
|
223701
|
+
const ms = num2(at, "wait", s2.wait);
|
|
222497
223702
|
plan.lines.push(emit(`wait ${Math.round(ms)}`));
|
|
222498
223703
|
budgetMs += ms + FRAME_MS;
|
|
222499
223704
|
parts.push(`wait ${ms}ms`);
|
|
222500
223705
|
} else if (action === "frames") {
|
|
222501
|
-
const n2 =
|
|
223706
|
+
const n2 = num2(at, "frames", s2.frames);
|
|
222502
223707
|
plan.lines.push(emit(`frames ${Math.round(n2)}`));
|
|
222503
223708
|
budgetMs += n2 * FRAME_MS + FRAME_MS;
|
|
222504
223709
|
parts.push(`advance ${n2} frames`);
|
|
@@ -222528,7 +223733,7 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
222528
223733
|
if (s2.screenshot !== undefined) {
|
|
222529
223734
|
if (typeof s2.screenshot !== "string")
|
|
222530
223735
|
throw new CliError(`${at}: "screenshot" must be a path.`);
|
|
222531
|
-
plan.screenshot =
|
|
223736
|
+
plan.screenshot = resolve15(opts.outDir, s2.screenshot);
|
|
222532
223737
|
plan.lines.push(emit(`frames 2`));
|
|
222533
223738
|
plan.lines.push(emit(`shot ${plan.screenshot}`));
|
|
222534
223739
|
budgetMs += 3 * FRAME_MS;
|
|
@@ -222637,9 +223842,9 @@ var requireRenderer = async () => {
|
|
|
222637
223842
|
};
|
|
222638
223843
|
var fileScenarioIO = (dir) => ({
|
|
222639
223844
|
write: (relPath, data) => {
|
|
222640
|
-
const abs =
|
|
222641
|
-
|
|
222642
|
-
|
|
223845
|
+
const abs = resolve16(dir, relPath);
|
|
223846
|
+
mkdirSync22(dirname15(abs), { recursive: true });
|
|
223847
|
+
writeFileSync23(abs, data);
|
|
222643
223848
|
}
|
|
222644
223849
|
});
|
|
222645
223850
|
var resolveFixtures = (value, baseDir) => {
|
|
@@ -222647,9 +223852,9 @@ var resolveFixtures = (value, baseDir) => {
|
|
|
222647
223852
|
return;
|
|
222648
223853
|
let map = value;
|
|
222649
223854
|
if (typeof value === "string") {
|
|
222650
|
-
const abs =
|
|
223855
|
+
const abs = resolve16(baseDir, value);
|
|
222651
223856
|
try {
|
|
222652
|
-
map = JSON.parse(
|
|
223857
|
+
map = JSON.parse(readFileSync27(abs, "utf8"));
|
|
222653
223858
|
} catch (e2) {
|
|
222654
223859
|
throw new CliError(`Can't read fixtures "${value}": ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
222655
223860
|
}
|
|
@@ -222662,7 +223867,7 @@ var resolveFixtures = (value, baseDir) => {
|
|
|
222662
223867
|
var readScenarioFile = (path2) => {
|
|
222663
223868
|
let raw;
|
|
222664
223869
|
try {
|
|
222665
|
-
raw = JSON.parse(
|
|
223870
|
+
raw = JSON.parse(readFileSync27(resolve16(path2), "utf8"));
|
|
222666
223871
|
} catch (e2) {
|
|
222667
223872
|
throw new CliError(`Can't read scenario "${path2}": ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
222668
223873
|
}
|
|
@@ -222697,10 +223902,11 @@ var renderDesktop = async (args) => {
|
|
|
222697
223902
|
root = process.cwd();
|
|
222698
223903
|
}
|
|
222699
223904
|
const win = readAppConfigAt(root)?.desktop?.window;
|
|
223905
|
+
const screen2 = screenTargetFromArgs(root, args);
|
|
222700
223906
|
const scriptFlag = flagStr(args, "script");
|
|
222701
223907
|
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;
|
|
223908
|
+
const width = Number(flagStr(args, "width")) || scenario?.width || screen2?.frame?.[0] || win?.width || 390;
|
|
223909
|
+
const height = Number(flagStr(args, "height")) || scenario?.height || screen2?.frame?.[1] || win?.height || 844;
|
|
222704
223910
|
const frames = Number(flagStr(args, "frames")) || 60;
|
|
222705
223911
|
const pngFlag = flagStr(args, "png") ?? "screenshot.png";
|
|
222706
223912
|
const logs = flagBool(args, "logs");
|
|
@@ -222711,17 +223917,24 @@ var renderDesktop = async (args) => {
|
|
|
222711
223917
|
const { camera, view } = resolveInspectionCamera(args);
|
|
222712
223918
|
if ((camera || view) && scenario)
|
|
222713
223919
|
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
|
-
|
|
223920
|
+
const desktopRenderer = resolveDesktopRenderer(root, args);
|
|
223921
|
+
const { js, resources } = await buildBundle(args, undefined, {
|
|
223922
|
+
localAssets: true,
|
|
223923
|
+
desktopRenderer,
|
|
223924
|
+
extraEntries: screen2 ? [screen2.entry] : undefined,
|
|
223925
|
+
entryOverride: screen2?.entryPath
|
|
223926
|
+
});
|
|
223927
|
+
mkdirSync22(dirname15(resolve16(pngFlag)), { recursive: true });
|
|
222716
223928
|
if (scenario) {
|
|
222717
223929
|
const outDir = flagStr(args, "out-dir") ?? ".";
|
|
222718
223930
|
const plan = translateScenario(scenario.steps, { outDir, headroomFrames: frames });
|
|
222719
223931
|
for (const st of plan.steps)
|
|
222720
223932
|
if (st.screenshot)
|
|
222721
|
-
|
|
223933
|
+
mkdirSync22(dirname15(st.screenshot), { recursive: true });
|
|
222722
223934
|
const timeoutMs2 = Number(flagStr(args, "timeout")) || 60000 + plan.steps.length * 5000;
|
|
222723
223935
|
note(`Running ${plan.steps.length} step${plan.steps.length === 1 ? "" : "s"} (native)…`);
|
|
222724
223936
|
const { output } = await runDesktopRender(js, resources, {
|
|
223937
|
+
renderer: desktopRenderer,
|
|
222725
223938
|
width,
|
|
222726
223939
|
height,
|
|
222727
223940
|
frames: plan.frameCap,
|
|
@@ -222751,8 +223964,9 @@ var renderDesktop = async (args) => {
|
|
|
222751
223964
|
process.exit(result.ok ? 0 : 1);
|
|
222752
223965
|
}
|
|
222753
223966
|
const timeoutMs = Number(flagStr(args, "timeout")) || 60000;
|
|
222754
|
-
note("Rendering (native)…");
|
|
223967
|
+
note(screen2 ? `Rendering ${screen2.label} (native)…` : "Rendering (native)…");
|
|
222755
223968
|
await runDesktopRender(js, resources, {
|
|
223969
|
+
renderer: desktopRenderer,
|
|
222756
223970
|
width,
|
|
222757
223971
|
height,
|
|
222758
223972
|
frames,
|
|
@@ -222798,6 +224012,13 @@ var render = async (args) => {
|
|
|
222798
224012
|
return renderDesktop(args);
|
|
222799
224013
|
const scriptFlag = flagStr(args, "script");
|
|
222800
224014
|
const scenario = scriptFlag !== undefined ? readScenarioFile(scriptFlag) : null;
|
|
224015
|
+
let projectRoot2;
|
|
224016
|
+
try {
|
|
224017
|
+
projectRoot2 = findProjectRoot(process.cwd());
|
|
224018
|
+
} catch {
|
|
224019
|
+
projectRoot2 = process.cwd();
|
|
224020
|
+
}
|
|
224021
|
+
const screen2 = screenTargetFromArgs(projectRoot2, args);
|
|
222801
224022
|
const stepCount = scenario && Array.isArray(scenario.steps) ? scenario.steps.length : 0;
|
|
222802
224023
|
const timeoutMs = Number(flagStr(args, "timeout")) || 15000 + stepCount * 3000;
|
|
222803
224024
|
const killer = setTimeout(() => {
|
|
@@ -222805,15 +224026,15 @@ var render = async (args) => {
|
|
|
222805
224026
|
process.exit(1);
|
|
222806
224027
|
}, timeoutMs);
|
|
222807
224028
|
killer.unref?.();
|
|
222808
|
-
const { js, warnings, useLocalAssets, root } = await compileHeadlessBundle(args);
|
|
224029
|
+
const { js, warnings, useLocalAssets, root } = await compileHeadlessBundle(args, { screen: screen2 });
|
|
222809
224030
|
const renderer = await requireRenderer();
|
|
222810
224031
|
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;
|
|
224032
|
+
const width = Number(flagStr(args, "width")) || scenario?.width || screen2?.frame?.[0] || projectViewport?.width || 390;
|
|
224033
|
+
const height = Number(flagStr(args, "height")) || scenario?.height || screen2?.frame?.[1] || projectViewport?.height || 844;
|
|
222813
224034
|
const settleStr = flagStr(args, "settle") ?? (scenario?.settle !== undefined ? String(scenario.settle) : undefined);
|
|
222814
224035
|
const timeStr = flagStr(args, "time");
|
|
222815
224036
|
const onConsole = flagBool(args, "logs") ? (level, text2) => logErr(`${c.dim(`[${level}]`)} ${text2}`) : undefined;
|
|
222816
|
-
const envelopeFixtures = resolveFixtures(scenario?.fixtures, scriptFlag !== undefined ? dirname15(
|
|
224037
|
+
const envelopeFixtures = resolveFixtures(scenario?.fixtures, scriptFlag !== undefined ? dirname15(resolve16(scriptFlag)) : ".");
|
|
222817
224038
|
const flagFixtures = resolveFixtures(flagStr(args, "fixtures"), ".");
|
|
222818
224039
|
const fixtures = envelopeFixtures || flagFixtures ? { ...envelopeFixtures, ...flagFixtures } : undefined;
|
|
222819
224040
|
if (fixtures) {
|
|
@@ -222852,7 +224073,7 @@ var render = async (args) => {
|
|
|
222852
224073
|
${c.red(st.error ?? "failed")}`);
|
|
222853
224074
|
}
|
|
222854
224075
|
if (!result2.ok && result2.failureArtifacts.length > 0) {
|
|
222855
|
-
logErr(c.dim(` failing frame: ${result2.failureArtifacts.map((p2) =>
|
|
224076
|
+
logErr(c.dim(` failing frame: ${result2.failureArtifacts.map((p2) => resolve16(outDir, p2)).join(", ")}`));
|
|
222856
224077
|
}
|
|
222857
224078
|
logErr(result2.ok ? c.green(`✓ ${result2.steps.length}/${steps.length} steps passed`) : c.red(`✗ failed at step ${result2.steps.length} of ${steps.length}`));
|
|
222858
224079
|
return done((cb) => cb(), result2.ok ? 0 : 1);
|
|
@@ -222862,7 +224083,7 @@ var render = async (args) => {
|
|
|
222862
224083
|
}
|
|
222863
224084
|
const pngFlag = flagStr(args, "png") ?? (flagBool(args, "png") ? "screenshot.png" : undefined);
|
|
222864
224085
|
if (pngFlag !== undefined) {
|
|
222865
|
-
note("Rendering PNG…");
|
|
224086
|
+
note(screen2 ? `Rendering ${screen2.label} (PNG)…` : "Rendering PNG…");
|
|
222866
224087
|
const clip = parseClip(flagStr(args, "clip"));
|
|
222867
224088
|
const { png: png2, width: outW, height: outH, warnings: pngWarnings } = await renderer.renderToPng(js, {
|
|
222868
224089
|
width,
|
|
@@ -222876,12 +224097,13 @@ var render = async (args) => {
|
|
|
222876
224097
|
if (isNullSink(pngFlag)) {
|
|
222877
224098
|
note(`Discarded PNG (${pngFlag})`);
|
|
222878
224099
|
} else {
|
|
222879
|
-
|
|
224100
|
+
mkdirSync22(dirname15(resolve16(pngFlag)), { recursive: true });
|
|
224101
|
+
writeFileSync23(resolve16(pngFlag), png2);
|
|
222880
224102
|
note(`Wrote ${pngFlag} (${png2.length} bytes, ${outW}x${outH}${clip !== undefined ? ", clipped" : ""})`);
|
|
222881
224103
|
}
|
|
222882
224104
|
return done((cb) => cb());
|
|
222883
224105
|
}
|
|
222884
|
-
note("Rendering…");
|
|
224106
|
+
note(screen2 ? `Rendering ${screen2.label}…` : "Rendering…");
|
|
222885
224107
|
const result = await renderer.renderToJson(js, { width, height, ...timing });
|
|
222886
224108
|
for (const w of [...warnings, ...result.warnings])
|
|
222887
224109
|
warnErr(w);
|
|
@@ -222891,7 +224113,7 @@ var render = async (args) => {
|
|
|
222891
224113
|
return done((cb) => cb());
|
|
222892
224114
|
}
|
|
222893
224115
|
if (out) {
|
|
222894
|
-
|
|
224116
|
+
writeFileSync23(resolve16(out), result.text);
|
|
222895
224117
|
note(`Wrote ${out} (${result.text.length} bytes)`);
|
|
222896
224118
|
return done((cb) => cb());
|
|
222897
224119
|
}
|
|
@@ -222906,8 +224128,8 @@ init_api();
|
|
|
222906
224128
|
init_config();
|
|
222907
224129
|
init_manifest();
|
|
222908
224130
|
init_util();
|
|
222909
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
222910
|
-
import { join as
|
|
224131
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync30, readFileSync as readFileSync28, writeFileSync as writeFileSync24 } from "node:fs";
|
|
224132
|
+
import { join as join32 } from "node:path";
|
|
222911
224133
|
var ENV_VAR = "LECODES_PUSH_KEY";
|
|
222912
224134
|
var context = () => {
|
|
222913
224135
|
const root = findProjectRoot(process.cwd());
|
|
@@ -223051,11 +224273,11 @@ var pnKeysList = async (ctx) => {
|
|
|
223051
224273
|
};
|
|
223052
224274
|
var writeEnv = (root, key) => {
|
|
223053
224275
|
const touched = [];
|
|
223054
|
-
const envPath =
|
|
224276
|
+
const envPath = join32(root, ".env");
|
|
223055
224277
|
const line = `${ENV_VAR}=${key}`;
|
|
223056
|
-
const existing =
|
|
224278
|
+
const existing = existsSync30(envPath) ? readFileSync28(envPath, "utf8") : "";
|
|
223057
224279
|
if (new RegExp(`^${ENV_VAR}=`, "m").test(existing)) {
|
|
223058
|
-
|
|
224280
|
+
writeFileSync24(envPath, existing.replace(new RegExp(`^${ENV_VAR}=.*$`, "m"), line));
|
|
223059
224281
|
} else {
|
|
223060
224282
|
const separator = !existing || existing.endsWith(`
|
|
223061
224283
|
`) ? "" : `
|
|
@@ -223064,8 +224286,8 @@ var writeEnv = (root, key) => {
|
|
|
223064
224286
|
`);
|
|
223065
224287
|
}
|
|
223066
224288
|
touched.push(".env");
|
|
223067
|
-
const ignorePath =
|
|
223068
|
-
const ignore =
|
|
224289
|
+
const ignorePath = join32(root, ".gitignore");
|
|
224290
|
+
const ignore = existsSync30(ignorePath) ? readFileSync28(ignorePath, "utf8") : "";
|
|
223069
224291
|
if (!ignore.split(`
|
|
223070
224292
|
`).some((l2) => l2.trim() === ".env")) {
|
|
223071
224293
|
const separator = !ignore || ignore.endsWith(`
|
|
@@ -223164,8 +224386,8 @@ var pn = async (args) => {
|
|
|
223164
224386
|
init_api();
|
|
223165
224387
|
init_config();
|
|
223166
224388
|
init_manifest();
|
|
223167
|
-
import { existsSync as
|
|
223168
|
-
import { dirname as dirname16, join as
|
|
224389
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync23, rmSync as rmSync10, writeFileSync as writeFileSync25 } from "node:fs";
|
|
224390
|
+
import { dirname as dirname16, join as join33 } from "node:path";
|
|
223169
224391
|
init_types4();
|
|
223170
224392
|
init_localFiles();
|
|
223171
224393
|
init_util();
|
|
@@ -223188,9 +224410,9 @@ var pull = async (args) => {
|
|
|
223188
224410
|
const remotePaths = new Set([...buildPaths(project.assets).entries()].filter(([id]) => project.assets.find((a2) => a2.id === id)?.type !== "folder").map(([, path2]) => path2));
|
|
223189
224411
|
for (const path2 of Object.keys(manifest.files)) {
|
|
223190
224412
|
if (!remotePaths.has(path2)) {
|
|
223191
|
-
const abs =
|
|
223192
|
-
if (
|
|
223193
|
-
|
|
224413
|
+
const abs = join33(root, ...path2.replace(/^\//, "").split("/"));
|
|
224414
|
+
if (existsSync31(abs))
|
|
224415
|
+
rmSync10(abs);
|
|
223194
224416
|
}
|
|
223195
224417
|
}
|
|
223196
224418
|
const files = await materializeAssets(root, project, apiUrl, token, progress);
|
|
@@ -223227,18 +224449,18 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
|
|
|
223227
224449
|
const files = { ...manifest.files };
|
|
223228
224450
|
for (const asset of targets) {
|
|
223229
224451
|
const key = pathById.get(asset.id);
|
|
223230
|
-
const abs =
|
|
223231
|
-
|
|
224452
|
+
const abs = join33(root, ...key.replace(/^\//, "").split("/"));
|
|
224453
|
+
mkdirSync23(dirname16(abs), { recursive: true });
|
|
223232
224454
|
let sha;
|
|
223233
224455
|
if (asset.type === "file") {
|
|
223234
224456
|
if (!asset.file?.src)
|
|
223235
224457
|
continue;
|
|
223236
224458
|
const bytes = await downloadBlob(apiUrl, asset.file.src);
|
|
223237
|
-
|
|
224459
|
+
writeFileSync25(abs, bytes);
|
|
223238
224460
|
sha = sha256(bytes);
|
|
223239
224461
|
} else {
|
|
223240
224462
|
const text = asset.text ?? "";
|
|
223241
|
-
|
|
224463
|
+
writeFileSync25(abs, text);
|
|
223242
224464
|
sha = sha256(text);
|
|
223243
224465
|
}
|
|
223244
224466
|
files[key] = { id: asset.id, type: kindOf3(asset.type), sha };
|
|
@@ -223251,8 +224473,8 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
|
|
|
223251
224473
|
|
|
223252
224474
|
// src/commands/scene.ts
|
|
223253
224475
|
init_manifest();
|
|
223254
|
-
import { existsSync as
|
|
223255
|
-
import { basename as
|
|
224476
|
+
import { existsSync as existsSync33, readFileSync as readFileSync30, readdirSync as readdirSync14, statSync as statSync12, writeFileSync as writeFileSync27 } from "node:fs";
|
|
224477
|
+
import { basename as basename16, dirname as dirname17, join as join35, resolve as resolve17 } from "node:path";
|
|
223256
224478
|
|
|
223257
224479
|
// src/compile/sceneCompile.ts
|
|
223258
224480
|
init_localFiles();
|
|
@@ -223260,18 +224482,19 @@ init_ignore();
|
|
|
223260
224482
|
init_distRoot();
|
|
223261
224483
|
init_util();
|
|
223262
224484
|
init_projectCompile();
|
|
223263
|
-
import {
|
|
223264
|
-
import {
|
|
224485
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
224486
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync24, readFileSync as readFileSync29, writeFileSync as writeFileSync26 } from "node:fs";
|
|
224487
|
+
import { join as join34 } from "node:path";
|
|
223265
224488
|
var TEXT_EXT = /\.(ts|js|json|svg|txt|md)$/i;
|
|
223266
224489
|
var rel = (key) => key.replace(/^\//, "");
|
|
223267
224490
|
var resolveHarness = (loadSceneHarness2) => {
|
|
223268
|
-
const inRepo =
|
|
224491
|
+
const inRepo = join34(distRoot, "..", "projects", "src", "scene-editor");
|
|
223269
224492
|
try {
|
|
223270
|
-
return loadSceneHarness2(
|
|
224493
|
+
return loadSceneHarness2(existsSync32(inRepo) ? inRepo : undefined);
|
|
223271
224494
|
} catch {
|
|
223272
|
-
const vendored =
|
|
223273
|
-
if (
|
|
223274
|
-
return JSON.parse(
|
|
224495
|
+
const vendored = join34(distRoot, "runtime", "scene-harness.json");
|
|
224496
|
+
if (existsSync32(vendored))
|
|
224497
|
+
return JSON.parse(readFileSync29(vendored, "utf8"));
|
|
223275
224498
|
throw new CliError("scene-editor harness sources not found — reinstall the CLI or set LECODES_SCENE_HARNESS_DIR.");
|
|
223276
224499
|
}
|
|
223277
224500
|
};
|
|
@@ -223283,11 +224506,32 @@ var compileSceneEditor = async (opts) => {
|
|
|
223283
224506
|
for (const file of scan2.files) {
|
|
223284
224507
|
const path2 = "./" + rel(file.path);
|
|
223285
224508
|
if (TEXT_EXT.test(path2)) {
|
|
223286
|
-
entries.push({ path: path2, type: "text", text:
|
|
224509
|
+
entries.push({ path: path2, type: "text", text: readFileSync29(file.absPath, "utf8") });
|
|
224510
|
+
} else if (/\.mat$/i.test(path2)) {
|
|
224511
|
+
entries.push({ path: path2, type: "shader", text: readFileSync29(file.absPath, "utf8"), absPath: file.absPath });
|
|
223287
224512
|
} else {
|
|
223288
224513
|
entries.push({ path: path2, type: "resource", fileSrc: "/project/" + rel(file.path) });
|
|
223289
224514
|
}
|
|
223290
224515
|
}
|
|
224516
|
+
const shaderEntries = entries.filter((e2) => e2.type === "shader");
|
|
224517
|
+
if (shaderEntries.length > 0) {
|
|
224518
|
+
const { warnings } = await applyLocalShaders(shaderEntries, { root, srcPrefix: "/project/.lecodes/shaders", platforms: ["webgl", "webgl-lite"] });
|
|
224519
|
+
for (const w of warnings)
|
|
224520
|
+
warnErr(w);
|
|
224521
|
+
const { buildLiteMaterialArtifact: buildLiteMaterialArtifact2 } = await Promise.resolve().then(() => (init_compile(), exports_compile));
|
|
224522
|
+
for (const e2 of shaderEntries) {
|
|
224523
|
+
if (e2.shaders?.length)
|
|
224524
|
+
continue;
|
|
224525
|
+
const hash = createHash8("sha256").update(e2.text ?? "").digest("hex").slice(0, 16);
|
|
224526
|
+
const dir = shadersCacheDir(root);
|
|
224527
|
+
mkdirSync24(dir, { recursive: true });
|
|
224528
|
+
const liteName = `${hash}_webgl-lite.filamat`;
|
|
224529
|
+
if (!existsSync32(join34(dir, liteName)))
|
|
224530
|
+
writeFileSync26(join34(dir, liteName), JSON.stringify(buildLiteMaterialArtifact2(e2.text ?? "")));
|
|
224531
|
+
const src = `/project/.lecodes/shaders/${hash}_opengl.filamat`;
|
|
224532
|
+
e2.shaders = ["opengl", "webgl", "webgl-lite"].map((platform) => ({ platform, src }));
|
|
224533
|
+
}
|
|
224534
|
+
}
|
|
223291
224535
|
const scene = scenePath.startsWith("./") ? scenePath : "./" + rel(scenePath);
|
|
223292
224536
|
if (!entries.some((e2) => e2.path === scene && e2.type === "text")) {
|
|
223293
224537
|
return { js: null, error: `Scene file not found: ${scene}` };
|
|
@@ -223317,7 +224561,7 @@ var DEFAULT_PORT3 = 4488;
|
|
|
223317
224561
|
var findAppRoot = (start) => {
|
|
223318
224562
|
let dir = start;
|
|
223319
224563
|
while (true) {
|
|
223320
|
-
if (
|
|
224564
|
+
if (existsSync33(join35(dir, "app.json")))
|
|
223321
224565
|
return dir;
|
|
223322
224566
|
const parent = dirname17(dir);
|
|
223323
224567
|
if (parent === dir)
|
|
@@ -223336,10 +224580,10 @@ var getContext2 = (args) => {
|
|
|
223336
224580
|
manifest = null;
|
|
223337
224581
|
const pathArg = args._[0];
|
|
223338
224582
|
if (pathArg) {
|
|
223339
|
-
const abs =
|
|
223340
|
-
if (
|
|
224583
|
+
const abs = resolve17(process.cwd(), pathArg);
|
|
224584
|
+
if (existsSync33(abs) && statSync12(abs).isDirectory())
|
|
223341
224585
|
root = abs;
|
|
223342
|
-
else if (abs.endsWith(".scene.ts") &&
|
|
224586
|
+
else if (abs.endsWith(".scene.ts") && existsSync33(abs))
|
|
223343
224587
|
root = findAppRoot(dirname17(abs)) ?? dirname17(abs);
|
|
223344
224588
|
else
|
|
223345
224589
|
throw new CliError(`No such folder or .scene.ts: ${pathArg}`);
|
|
@@ -223347,15 +224591,15 @@ var getContext2 = (args) => {
|
|
|
223347
224591
|
root = findAppRoot(root) ?? root;
|
|
223348
224592
|
}
|
|
223349
224593
|
}
|
|
223350
|
-
return { root, manifest, name: manifest?.name ??
|
|
224594
|
+
return { root, manifest, name: manifest?.name ?? basename16(root) };
|
|
223351
224595
|
};
|
|
223352
224596
|
var writeMcpConfig2 = (root, port) => {
|
|
223353
|
-
const path2 =
|
|
224597
|
+
const path2 = join35(root, ".mcp.json");
|
|
223354
224598
|
const entry = { type: "http", url: `http://localhost:${port}/mcp` };
|
|
223355
224599
|
let config = {};
|
|
223356
|
-
if (
|
|
224600
|
+
if (existsSync33(path2)) {
|
|
223357
224601
|
try {
|
|
223358
|
-
config = JSON.parse(
|
|
224602
|
+
config = JSON.parse(readFileSync30(path2, "utf8"));
|
|
223359
224603
|
} catch {
|
|
223360
224604
|
warnErr(".mcp.json exists but isn't valid JSON — left it untouched. Add the lecodes-scene server by hand.");
|
|
223361
224605
|
return;
|
|
@@ -223368,17 +224612,17 @@ var writeMcpConfig2 = (root, port) => {
|
|
|
223368
224612
|
return;
|
|
223369
224613
|
}
|
|
223370
224614
|
config.mcpServers["lecodes-scene"] = entry;
|
|
223371
|
-
|
|
224615
|
+
writeFileSync27(path2, JSON.stringify(config, null, 2) + `
|
|
223372
224616
|
`);
|
|
223373
224617
|
note(`${existing ? "updated" : "created"} .mcp.json (lecodes-scene MCP → ${entry.url})`);
|
|
223374
224618
|
};
|
|
223375
224619
|
var countScenes = (dir) => {
|
|
223376
224620
|
let n2 = 0;
|
|
223377
|
-
for (const name of
|
|
224621
|
+
for (const name of readdirSync14(dir)) {
|
|
223378
224622
|
if (name === "node_modules" || name.startsWith("."))
|
|
223379
224623
|
continue;
|
|
223380
|
-
const full =
|
|
223381
|
-
if (
|
|
224624
|
+
const full = join35(dir, name);
|
|
224625
|
+
if (statSync12(full).isDirectory())
|
|
223382
224626
|
n2 += countScenes(full);
|
|
223383
224627
|
else if (name.endsWith(".scene.ts"))
|
|
223384
224628
|
n2++;
|
|
@@ -223406,7 +224650,8 @@ var scene = async (args) => {
|
|
|
223406
224650
|
manifest: ctx.manifest,
|
|
223407
224651
|
name: ctx.name,
|
|
223408
224652
|
scenePath
|
|
223409
|
-
})
|
|
224653
|
+
}),
|
|
224654
|
+
shaderArtifact: (shaderText, platform) => localShaderArtifact(ctx.root, shaderText, platform)
|
|
223410
224655
|
});
|
|
223411
224656
|
if (!flagBool(args, "no-mcp"))
|
|
223412
224657
|
writeMcpConfig2(ctx.root, port);
|
|
@@ -223420,6 +224665,79 @@ var scene = async (args) => {
|
|
|
223420
224665
|
|
|
223421
224666
|
// src/commands/assets.ts
|
|
223422
224667
|
init_util();
|
|
224668
|
+
|
|
224669
|
+
// src/cmgenTool.ts
|
|
224670
|
+
import { existsSync as existsSync34, readdirSync as readdirSync15 } from "node:fs";
|
|
224671
|
+
import { homedir as homedir8 } from "node:os";
|
|
224672
|
+
import { join as join36, resolve as resolve18 } from "node:path";
|
|
224673
|
+
init_util();
|
|
224674
|
+
var exeName2 = () => process.platform === "win32" ? "cmgen.exe" : "cmgen";
|
|
224675
|
+
var platformKey3 = () => {
|
|
224676
|
+
if (process.platform === "win32")
|
|
224677
|
+
return "windows-x64";
|
|
224678
|
+
if (process.platform === "linux")
|
|
224679
|
+
return "linux-x64";
|
|
224680
|
+
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}.`);
|
|
224681
|
+
};
|
|
224682
|
+
var cacheRoot3 = () => join36(homedir8(), ".lecodes", "cmgen");
|
|
224683
|
+
var findCmgenIn = (dir) => {
|
|
224684
|
+
for (const entry of readdirSync15(dir, { withFileTypes: true })) {
|
|
224685
|
+
const p2 = join36(dir, entry.name);
|
|
224686
|
+
if (entry.isFile() && entry.name === exeName2())
|
|
224687
|
+
return p2;
|
|
224688
|
+
if (entry.isDirectory()) {
|
|
224689
|
+
const found = findCmgenIn(p2);
|
|
224690
|
+
if (found)
|
|
224691
|
+
return found;
|
|
224692
|
+
}
|
|
224693
|
+
}
|
|
224694
|
+
return null;
|
|
224695
|
+
};
|
|
224696
|
+
var installedCmgenVersions = () => {
|
|
224697
|
+
try {
|
|
224698
|
+
return readdirSync15(cacheRoot3(), { withFileTypes: true }).filter((e2) => e2.isDirectory() && e2.name.startsWith("cmgen-")).map((e2) => e2.name.slice("cmgen-".length)).filter((v) => findCmgenIn(join36(cacheRoot3(), `cmgen-${v}`)) !== null).sort(semverNewer);
|
|
224699
|
+
} catch {
|
|
224700
|
+
return [];
|
|
224701
|
+
}
|
|
224702
|
+
};
|
|
224703
|
+
var fetchCmgenManifest = () => fetchReleaseProduct("cmgen", "cmgen (environment tool)");
|
|
224704
|
+
var downloadCmgen = async (version, versions, dest) => {
|
|
224705
|
+
const key = platformKey3();
|
|
224706
|
+
const file = versions.find((v) => v.version === version)?.files[key];
|
|
224707
|
+
if (!file) {
|
|
224708
|
+
throw new CliError(`cmgen ${version} [${key}] isn't published — build filament's host tools locally and point ` + `LECODES_CMGEN at the binary.`);
|
|
224709
|
+
}
|
|
224710
|
+
note(`Downloading the environment tool (cmgen ${version}, ${(file.size / 1024 / 1024).toFixed(1)} MB)…`);
|
|
224711
|
+
await downloadReleaseArchive(file, {
|
|
224712
|
+
cacheRoot: cacheRoot3(),
|
|
224713
|
+
dest,
|
|
224714
|
+
validate: (dir) => findCmgenIn(dir) ? null : "The downloaded archive doesn't contain the cmgen binary."
|
|
224715
|
+
});
|
|
224716
|
+
};
|
|
224717
|
+
var resolveCmgenExeCached = () => {
|
|
224718
|
+
const override = process.env.LECODES_CMGEN;
|
|
224719
|
+
if (override) {
|
|
224720
|
+
if (!existsSync34(override))
|
|
224721
|
+
throw new CliError(`LECODES_CMGEN points at "${override}" which doesn't exist.`);
|
|
224722
|
+
return resolve18(override);
|
|
224723
|
+
}
|
|
224724
|
+
const cached = installedCmgenVersions()[0];
|
|
224725
|
+
return cached ? findCmgenIn(join36(cacheRoot3(), `cmgen-${cached}`)) : null;
|
|
224726
|
+
};
|
|
224727
|
+
var resolveCmgenExe = async () => {
|
|
224728
|
+
const cached = resolveCmgenExeCached();
|
|
224729
|
+
if (cached)
|
|
224730
|
+
return cached;
|
|
224731
|
+
const { latest, versions } = await fetchCmgenManifest();
|
|
224732
|
+
const dir = join36(cacheRoot3(), `cmgen-${latest}`);
|
|
224733
|
+
await downloadCmgen(latest, versions, dir);
|
|
224734
|
+
const exe = findCmgenIn(dir);
|
|
224735
|
+
if (!exe)
|
|
224736
|
+
throw new CliError(`cmgen cache at ${dir} is broken — delete the folder and retry.`);
|
|
224737
|
+
return exe;
|
|
224738
|
+
};
|
|
224739
|
+
|
|
224740
|
+
// src/commands/assets.ts
|
|
223423
224741
|
var assets = async (args) => {
|
|
223424
224742
|
let mod;
|
|
223425
224743
|
try {
|
|
@@ -223428,6 +224746,9 @@ var assets = async (args) => {
|
|
|
223428
224746
|
throw new CliError("The asset pipeline isn't installed. Run: npm install -g lecodes-assets");
|
|
223429
224747
|
}
|
|
223430
224748
|
const raw = args._raw ?? args._;
|
|
224749
|
+
if (raw[0] === "sky" && !process.env.LECODES_CMGEN) {
|
|
224750
|
+
process.env.LECODES_CMGEN = await resolveCmgenExe();
|
|
224751
|
+
}
|
|
223431
224752
|
const code = await mod.runAssetsCli(raw);
|
|
223432
224753
|
if (code !== 0)
|
|
223433
224754
|
process.exitCode = code;
|
|
@@ -223466,8 +224787,8 @@ var status3 = async (_args) => {
|
|
|
223466
224787
|
};
|
|
223467
224788
|
|
|
223468
224789
|
// src/commands/test.ts
|
|
223469
|
-
import { existsSync as
|
|
223470
|
-
import { dirname as dirname18, resolve as
|
|
224790
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync25, readFileSync as readFileSync31, readdirSync as readdirSync16 } from "node:fs";
|
|
224791
|
+
import { dirname as dirname18, resolve as resolve19 } from "node:path";
|
|
223471
224792
|
init_manifest();
|
|
223472
224793
|
init_util();
|
|
223473
224794
|
var deriveDesignFlows = (meta, entryScreen) => {
|
|
@@ -223530,7 +224851,7 @@ var slug3 = (s2) => s2.replace(/[^a-zA-Z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").
|
|
|
223530
224851
|
var readFlowFile = (abs, displayName) => {
|
|
223531
224852
|
let raw;
|
|
223532
224853
|
try {
|
|
223533
|
-
raw = JSON.parse(
|
|
224854
|
+
raw = JSON.parse(readFileSync31(abs, "utf8"));
|
|
223534
224855
|
} catch (e2) {
|
|
223535
224856
|
throw new CliError(`Can't read "${displayName}": ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
223536
224857
|
}
|
|
@@ -223556,7 +224877,14 @@ var test = async (args) => {
|
|
|
223556
224877
|
const desktop2 = flagBool(args, "desktop");
|
|
223557
224878
|
if (desktop2 && flagBool(args, "flows"))
|
|
223558
224879
|
throw new CliError("--flows needs the headless renderer (design flows tap by name) — drop --desktop.");
|
|
223559
|
-
|
|
224880
|
+
let desktopRoot;
|
|
224881
|
+
try {
|
|
224882
|
+
desktopRoot = findProjectRoot(process.cwd());
|
|
224883
|
+
} catch {
|
|
224884
|
+
desktopRoot = process.cwd();
|
|
224885
|
+
}
|
|
224886
|
+
const desktopRenderer = desktop2 ? resolveDesktopRenderer(desktopRoot, args) : undefined;
|
|
224887
|
+
const native = desktop2 ? await buildBundle(args, undefined, { localAssets: true, desktopRenderer }) : null;
|
|
223560
224888
|
const headless = desktop2 ? null : await compileHeadlessBundle(args);
|
|
223561
224889
|
const warnings = headless?.warnings ?? [];
|
|
223562
224890
|
const useLocalAssets = headless?.useLocalAssets ?? false;
|
|
@@ -223578,40 +224906,40 @@ var test = async (args) => {
|
|
|
223578
224906
|
if (!Number.isFinite(nativeFixedDt) || nativeFixedDt < 0 || nativeFixedDt > 1000)
|
|
223579
224907
|
throw new CliError(`--fixed-dt wants a number of ms (0 = wall clock, max 1000), got "${fixedDtFlag}".`);
|
|
223580
224908
|
const projectViewport = desktopDefaultViewport(root);
|
|
223581
|
-
const testsDir =
|
|
223582
|
-
const designMetaPath =
|
|
224909
|
+
const testsDir = resolve19(root, flagStr(args, "dir") ?? "tests");
|
|
224910
|
+
const designMetaPath = resolve19(root, flagStr(args, "design-dir") ?? "design", "meta.json");
|
|
223583
224911
|
const asJson = flagBool(args, "json");
|
|
223584
224912
|
const out = asJson ? logErr : log;
|
|
223585
224913
|
const cases = [];
|
|
223586
224914
|
if (args._.length > 0) {
|
|
223587
224915
|
for (const given of args._) {
|
|
223588
|
-
const candidates = [
|
|
223589
|
-
const abs = candidates.find((p2) =>
|
|
224916
|
+
const candidates = [resolve19(given), resolve19(testsDir, given), resolve19(testsDir, `${given}.flow.json`)];
|
|
224917
|
+
const abs = candidates.find((p2) => existsSync35(p2));
|
|
223590
224918
|
if (!abs)
|
|
223591
224919
|
throw new CliError(`No such test: "${given}" (looked in ${testsDir}).`);
|
|
223592
224920
|
cases.push(readFlowFile(abs, given));
|
|
223593
224921
|
}
|
|
223594
|
-
} else if (
|
|
223595
|
-
for (const f3 of
|
|
223596
|
-
cases.push(readFlowFile(
|
|
224922
|
+
} else if (existsSync35(testsDir)) {
|
|
224923
|
+
for (const f3 of readdirSync16(testsDir).filter((f4) => f4.endsWith(".flow.json")).sort()) {
|
|
224924
|
+
cases.push(readFlowFile(resolve19(testsDir, f3), f3));
|
|
223597
224925
|
}
|
|
223598
224926
|
}
|
|
223599
224927
|
const fixturesFlag = flagStr(args, "fixtures");
|
|
223600
|
-
const defaultPack =
|
|
223601
|
-
const flowFixtures = fixturesFlag !== undefined ? resolveFixtures(fixturesFlag, ".") :
|
|
224928
|
+
const defaultPack = resolve19(testsDir, "fixtures.json");
|
|
224929
|
+
const flowFixtures = fixturesFlag !== undefined ? resolveFixtures(fixturesFlag, ".") : existsSync35(defaultPack) ? resolveFixtures("fixtures.json", testsDir) : undefined;
|
|
223602
224930
|
const flowsOnly = flagBool(args, "flows");
|
|
223603
224931
|
const wantFlows = !flagBool(args, "no-flows") && args._.length === 0;
|
|
223604
224932
|
if (flowsOnly)
|
|
223605
224933
|
cases.length = 0;
|
|
223606
224934
|
let skipped = [];
|
|
223607
224935
|
let entryScreen = null;
|
|
223608
|
-
if (desktop2 && wantFlows &&
|
|
224936
|
+
if (desktop2 && wantFlows && existsSync35(designMetaPath)) {
|
|
223609
224937
|
note("Design-derived flows skipped with --desktop (they tap by name; run `lecodes test` headless for them).");
|
|
223610
224938
|
}
|
|
223611
|
-
if (!desktop2 && (wantFlows || flowsOnly) &&
|
|
224939
|
+
if (!desktop2 && (wantFlows || flowsOnly) && existsSync35(designMetaPath)) {
|
|
223612
224940
|
let meta;
|
|
223613
224941
|
try {
|
|
223614
|
-
meta = JSON.parse(
|
|
224942
|
+
meta = JSON.parse(readFileSync31(designMetaPath, "utf8"));
|
|
223615
224943
|
} catch (e2) {
|
|
223616
224944
|
throw new CliError(`Can't read ${designMetaPath}: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
223617
224945
|
}
|
|
@@ -223643,17 +224971,18 @@ var test = async (args) => {
|
|
|
223643
224971
|
}, timeoutMs);
|
|
223644
224972
|
killer.unref?.();
|
|
223645
224973
|
const onConsole = flagBool(args, "logs") ? (level, text) => logErr(`${c.dim(`[${level}]`)} ${text}`) : undefined;
|
|
223646
|
-
const artifactsRoot =
|
|
224974
|
+
const artifactsRoot = resolve19(root, flagStr(args, "artifacts") ?? resolve19(testsDir, ".artifacts"));
|
|
223647
224975
|
const results = [];
|
|
223648
224976
|
const runNativeCase = async (kase) => {
|
|
223649
|
-
const caseDir =
|
|
224977
|
+
const caseDir = resolve19(artifactsRoot, slug3(kase.name));
|
|
223650
224978
|
const plan = translateScenario(kase.steps, { outDir: caseDir, headroomFrames: nativeFrames });
|
|
223651
224979
|
for (const st of plan.steps)
|
|
223652
224980
|
if (st.screenshot)
|
|
223653
|
-
|
|
223654
|
-
|
|
223655
|
-
const finalPng =
|
|
224981
|
+
mkdirSync25(dirname18(st.screenshot), { recursive: true });
|
|
224982
|
+
mkdirSync25(caseDir, { recursive: true });
|
|
224983
|
+
const finalPng = resolve19(caseDir, "final.png");
|
|
223656
224984
|
const { output } = await runDesktopRender(native.js, native.resources, {
|
|
224985
|
+
renderer: desktopRenderer,
|
|
223657
224986
|
width: Number(flagStr(args, "width")) || kase.width || projectViewport?.width || 390,
|
|
223658
224987
|
height: Number(flagStr(args, "height")) || kase.height || projectViewport?.height || 844,
|
|
223659
224988
|
frames: plan.frameCap,
|
|
@@ -223703,14 +225032,14 @@ var test = async (args) => {
|
|
|
223703
225032
|
onConsole
|
|
223704
225033
|
});
|
|
223705
225034
|
try {
|
|
223706
|
-
const io = fileScenarioIO(
|
|
225035
|
+
const io = fileScenarioIO(resolve19(artifactsRoot, slug3(kase.name)));
|
|
223707
225036
|
const outcome = await renderer.runScenario(session, steps, io);
|
|
223708
225037
|
result = {
|
|
223709
225038
|
name: kase.name,
|
|
223710
225039
|
kind: kase.kind,
|
|
223711
225040
|
ok: outcome.ok,
|
|
223712
225041
|
steps: outcome.steps.map((s2) => ({ desc: s2.desc, ok: s2.ok, error: s2.error })),
|
|
223713
|
-
artifacts: outcome.failureArtifacts.map((p2) =>
|
|
225042
|
+
artifacts: outcome.failureArtifacts.map((p2) => resolve19(artifactsRoot, slug3(kase.name), p2))
|
|
223714
225043
|
};
|
|
223715
225044
|
} finally {
|
|
223716
225045
|
session.dispose();
|
|
@@ -223778,11 +225107,11 @@ init_config();
|
|
|
223778
225107
|
init_manifest();
|
|
223779
225108
|
init_types4();
|
|
223780
225109
|
init_util();
|
|
223781
|
-
import { resolve as
|
|
225110
|
+
import { resolve as resolve20 } from "node:path";
|
|
223782
225111
|
var label = (bundle) => `${Object.keys(bundle).length} file(s)`;
|
|
223783
225112
|
var types4 = async (args) => {
|
|
223784
225113
|
const dirArg = args._[0];
|
|
223785
|
-
const root = dirArg ?
|
|
225114
|
+
const root = dirArg ? resolve20(dirArg) : (() => {
|
|
223786
225115
|
try {
|
|
223787
225116
|
return findProjectRoot(process.cwd());
|
|
223788
225117
|
} catch {
|
|
@@ -223841,23 +225170,23 @@ var types4 = async (args) => {
|
|
|
223841
225170
|
};
|
|
223842
225171
|
|
|
223843
225172
|
// src/index.ts
|
|
223844
|
-
import { existsSync as
|
|
225173
|
+
import { existsSync as existsSync38, readFileSync as readFileSync34, realpathSync as realpathSync2 } from "node:fs";
|
|
223845
225174
|
import { createRequire as createRequire4 } from "node:module";
|
|
223846
|
-
import { delimiter as delimiter2, dirname as
|
|
225175
|
+
import { delimiter as delimiter2, dirname as dirname21, join as join39 } from "node:path";
|
|
223847
225176
|
|
|
223848
225177
|
// src/commands/install.ts
|
|
223849
225178
|
init_util();
|
|
223850
|
-
import { execFileSync as
|
|
223851
|
-
import { appendFileSync as appendFileSync3, chmodSync as chmodSync2, cpSync as
|
|
223852
|
-
import { homedir as
|
|
223853
|
-
import { basename as
|
|
225179
|
+
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
225180
|
+
import { appendFileSync as appendFileSync3, chmodSync as chmodSync2, cpSync as cpSync5, existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as readFileSync32, realpathSync, rmSync as rmSync11, symlinkSync } from "node:fs";
|
|
225181
|
+
import { homedir as homedir9 } from "node:os";
|
|
225182
|
+
import { basename as basename17, delimiter, dirname as dirname19, join as join37, resolve as resolve21, sep as sep2 } from "node:path";
|
|
223854
225183
|
var isWin = process.platform === "win32";
|
|
223855
|
-
var HOME =
|
|
225184
|
+
var HOME = homedir9();
|
|
223856
225185
|
var safeRealpath = (p2) => {
|
|
223857
225186
|
try {
|
|
223858
225187
|
return realpathSync(p2);
|
|
223859
225188
|
} catch {
|
|
223860
|
-
return
|
|
225189
|
+
return resolve21(p2);
|
|
223861
225190
|
}
|
|
223862
225191
|
};
|
|
223863
225192
|
var onPath = (dir) => {
|
|
@@ -223874,24 +225203,24 @@ var onPath = (dir) => {
|
|
|
223874
225203
|
};
|
|
223875
225204
|
var install = async (args) => {
|
|
223876
225205
|
const bundle = dirname19(process.execPath);
|
|
223877
|
-
const
|
|
225206
|
+
const exeName3 = basename17(process.execPath);
|
|
223878
225207
|
const chiselName = isWin ? "chisel.exe" : "chisel";
|
|
223879
|
-
if (!
|
|
225208
|
+
if (!existsSync36(join37(bundle, "runtime")) || !existsSync36(join37(bundle, chiselName))) {
|
|
223880
225209
|
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
225210
|
}
|
|
223882
|
-
const companions = [
|
|
225211
|
+
const companions = [exeName3, chiselName, "runtime", "README.txt"].filter((f3) => existsSync36(join37(bundle, f3)));
|
|
223883
225212
|
if (isWin)
|
|
223884
225213
|
return installWindows(args, bundle, companions);
|
|
223885
225214
|
return installUnix(args, bundle, companions);
|
|
223886
225215
|
};
|
|
223887
225216
|
var copyBundle = (bundle, target, companions) => {
|
|
223888
|
-
|
|
225217
|
+
mkdirSync26(target, { recursive: true });
|
|
223889
225218
|
if (safeRealpath(target) === safeRealpath(bundle))
|
|
223890
225219
|
return;
|
|
223891
225220
|
for (const f3 of companions) {
|
|
223892
|
-
const dest =
|
|
223893
|
-
|
|
223894
|
-
|
|
225221
|
+
const dest = join37(target, f3);
|
|
225222
|
+
rmSync11(dest, { recursive: true, force: true });
|
|
225223
|
+
cpSync5(join37(bundle, f3), dest, { recursive: true });
|
|
223895
225224
|
}
|
|
223896
225225
|
};
|
|
223897
225226
|
var permError = (e2, dir, system) => {
|
|
@@ -223903,41 +225232,41 @@ var permError = (e2, dir, system) => {
|
|
|
223903
225232
|
};
|
|
223904
225233
|
var installUnix = (args, bundle, companions) => {
|
|
223905
225234
|
const system = flagBool(args, "system");
|
|
223906
|
-
const libDir =
|
|
223907
|
-
const binDir =
|
|
223908
|
-
const link2 =
|
|
225235
|
+
const libDir = resolve21(flagStr(args, "dir") ?? (system ? "/usr/local/lib/lecodes-cli" : join37(HOME, ".local", "lib", "lecodes-cli")));
|
|
225236
|
+
const binDir = resolve21(flagStr(args, "bin") ?? (system ? "/usr/local/bin" : join37(HOME, ".local", "bin")));
|
|
225237
|
+
const link2 = join37(binDir, "lecodes");
|
|
223909
225238
|
try {
|
|
223910
225239
|
copyBundle(bundle, libDir, companions);
|
|
223911
|
-
chmodSync2(
|
|
223912
|
-
chmodSync2(
|
|
225240
|
+
chmodSync2(join37(libDir, "lecodes"), 493);
|
|
225241
|
+
chmodSync2(join37(libDir, "chisel"), 493);
|
|
223913
225242
|
} catch (e2) {
|
|
223914
225243
|
throw permError(e2, libDir, system);
|
|
223915
225244
|
}
|
|
223916
225245
|
try {
|
|
223917
|
-
|
|
223918
|
-
|
|
223919
|
-
symlinkSync(
|
|
225246
|
+
mkdirSync26(binDir, { recursive: true });
|
|
225247
|
+
rmSync11(link2, { force: true });
|
|
225248
|
+
symlinkSync(join37(libDir, "lecodes"), link2);
|
|
223920
225249
|
} catch (e2) {
|
|
223921
225250
|
throw permError(e2, binDir, system);
|
|
223922
225251
|
}
|
|
223923
225252
|
success(`Installed to ${c.bold(libDir)}`);
|
|
223924
|
-
log(` ${c.dim(link2 + " → " +
|
|
225253
|
+
log(` ${c.dim(link2 + " → " + join37(libDir, "lecodes"))}`);
|
|
223925
225254
|
finishPathUnix(args, binDir);
|
|
223926
225255
|
};
|
|
223927
225256
|
var shellRc = () => {
|
|
223928
|
-
const shell =
|
|
225257
|
+
const shell = basename17(process.env.SHELL ?? "");
|
|
223929
225258
|
if (shell === "zsh")
|
|
223930
|
-
return { file:
|
|
225259
|
+
return { file: join37(HOME, ".zshrc"), name: "~/.zshrc" };
|
|
223931
225260
|
if (shell === "bash")
|
|
223932
|
-
return { file:
|
|
223933
|
-
return { file:
|
|
225261
|
+
return { file: join37(HOME, ".bashrc"), name: "~/.bashrc" };
|
|
225262
|
+
return { file: join37(HOME, ".profile"), name: "~/.profile" };
|
|
223934
225263
|
};
|
|
223935
225264
|
var PATH_MARKER = "# Added by `lecodes install`";
|
|
223936
225265
|
var appendPathToShellRc = (binDir) => {
|
|
223937
225266
|
const { file, name } = shellRc();
|
|
223938
225267
|
const line = `export PATH="${binDir}:$PATH"`;
|
|
223939
225268
|
try {
|
|
223940
|
-
const existing =
|
|
225269
|
+
const existing = existsSync36(file) ? readFileSync32(file, "utf8") : "";
|
|
223941
225270
|
if (existing.includes(line))
|
|
223942
225271
|
return name;
|
|
223943
225272
|
appendFileSync3(file, `${existing && !existing.endsWith(`
|
|
@@ -223975,7 +225304,7 @@ var finishPathUnix = (args, binDir) => {
|
|
|
223975
225304
|
log(` • or install system-wide (no PATH edit): ${c.bold("sudo lecodes install --system")}`);
|
|
223976
225305
|
};
|
|
223977
225306
|
var installWindows = (args, bundle, companions) => {
|
|
223978
|
-
const target =
|
|
225307
|
+
const target = resolve21(flagStr(args, "dir") ?? join37(process.env.LOCALAPPDATA ?? join37(HOME, "AppData", "Local"), "Programs", "lecodes-cli"));
|
|
223979
225308
|
try {
|
|
223980
225309
|
copyBundle(bundle, target, companions);
|
|
223981
225310
|
} catch (e2) {
|
|
@@ -224003,7 +225332,7 @@ var installWindows = (args, bundle, companions) => {
|
|
|
224003
225332
|
var addToUserPathWindows = (dir) => {
|
|
224004
225333
|
const script = "$d = $env:LECODES_INSTALL_DIR;" + "$p = [Environment]::GetEnvironmentVariable('Path','User'); if (-not $p) { $p = '' };" + "$parts = @($p.Split(';') | Where-Object { $_ -ne '' });" + "if ($parts -notcontains $d) { [Environment]::SetEnvironmentVariable('Path', (@($parts + $d) -join ';'), 'User') }";
|
|
224005
225334
|
try {
|
|
224006
|
-
|
|
225335
|
+
execFileSync7("powershell", ["-NoProfile", "-NonInteractive", "-Command", script], { stdio: "ignore", env: { ...process.env, LECODES_INSTALL_DIR: dir } });
|
|
224007
225336
|
return true;
|
|
224008
225337
|
} catch {
|
|
224009
225338
|
return false;
|
|
@@ -224012,10 +225341,85 @@ var addToUserPathWindows = (dir) => {
|
|
|
224012
225341
|
|
|
224013
225342
|
// src/index.ts
|
|
224014
225343
|
init_util();
|
|
225344
|
+
|
|
225345
|
+
// src/projectEnv.ts
|
|
225346
|
+
import { existsSync as existsSync37, readFileSync as readFileSync33 } from "node:fs";
|
|
225347
|
+
import { homedir as homedir10 } from "node:os";
|
|
225348
|
+
import { dirname as dirname20, join as join38, resolve as resolve22 } from "node:path";
|
|
225349
|
+
var KEY_RE = /^(LECODES|CREATOR)_[A-Z0-9_]+$/;
|
|
225350
|
+
var parseDotEnv = (text) => {
|
|
225351
|
+
const out = {};
|
|
225352
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
225353
|
+
const line = raw.trim();
|
|
225354
|
+
if (!line || line.startsWith("#"))
|
|
225355
|
+
continue;
|
|
225356
|
+
const m = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line);
|
|
225357
|
+
if (!m)
|
|
225358
|
+
continue;
|
|
225359
|
+
const key = m[1];
|
|
225360
|
+
let value = m[2];
|
|
225361
|
+
if (value.startsWith('"')) {
|
|
225362
|
+
const end = value.indexOf('"', 1);
|
|
225363
|
+
value = (end < 0 ? value.slice(1) : value.slice(1, end)).replace(/\\n/g, `
|
|
225364
|
+
`).replace(/\\t/g, "\t").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
225365
|
+
} else if (value.startsWith("'")) {
|
|
225366
|
+
const end = value.indexOf("'", 1);
|
|
225367
|
+
value = end < 0 ? value.slice(1) : value.slice(1, end);
|
|
225368
|
+
} else {
|
|
225369
|
+
const hash = value.search(/\s#/);
|
|
225370
|
+
value = (hash >= 0 ? value.slice(0, hash) : value).trim();
|
|
225371
|
+
}
|
|
225372
|
+
out[key] = value;
|
|
225373
|
+
}
|
|
225374
|
+
return out;
|
|
225375
|
+
};
|
|
225376
|
+
var applyDotEnv = (parsed, dir, env) => {
|
|
225377
|
+
const out = {};
|
|
225378
|
+
for (const [key, raw] of Object.entries(parsed)) {
|
|
225379
|
+
if (!KEY_RE.test(key) || env[key] !== undefined)
|
|
225380
|
+
continue;
|
|
225381
|
+
let value = raw;
|
|
225382
|
+
if (value.startsWith("./") || value.startsWith("../"))
|
|
225383
|
+
value = resolve22(dir, value);
|
|
225384
|
+
else if (value === "~" || value.startsWith("~/"))
|
|
225385
|
+
value = join38(homedir10(), value.slice(1));
|
|
225386
|
+
out[key] = value;
|
|
225387
|
+
}
|
|
225388
|
+
return out;
|
|
225389
|
+
};
|
|
225390
|
+
var findEnvRoot = (start) => {
|
|
225391
|
+
let dir = resolve22(start);
|
|
225392
|
+
while (true) {
|
|
225393
|
+
if (existsSync37(join38(dir, "app.json")) || existsSync37(join38(dir, ".lecodes")))
|
|
225394
|
+
return dir;
|
|
225395
|
+
const parent = dirname20(dir);
|
|
225396
|
+
if (parent === dir)
|
|
225397
|
+
return null;
|
|
225398
|
+
dir = parent;
|
|
225399
|
+
}
|
|
225400
|
+
};
|
|
225401
|
+
var loadProjectEnv = (cwd = process.cwd()) => {
|
|
225402
|
+
try {
|
|
225403
|
+
const root = findEnvRoot(cwd);
|
|
225404
|
+
if (!root)
|
|
225405
|
+
return [];
|
|
225406
|
+
const file = join38(root, ".env");
|
|
225407
|
+
if (!existsSync37(file))
|
|
225408
|
+
return [];
|
|
225409
|
+
const added = applyDotEnv(parseDotEnv(readFileSync33(file, "utf8")), root, process.env);
|
|
225410
|
+
for (const [k, v] of Object.entries(added))
|
|
225411
|
+
process.env[k] = v;
|
|
225412
|
+
return Object.keys(added);
|
|
225413
|
+
} catch {
|
|
225414
|
+
return [];
|
|
225415
|
+
}
|
|
225416
|
+
};
|
|
225417
|
+
|
|
225418
|
+
// src/index.ts
|
|
224015
225419
|
var STANDALONE = (() => {
|
|
224016
|
-
const dir =
|
|
225420
|
+
const dir = dirname21(process.execPath);
|
|
224017
225421
|
const chisel = process.platform === "win32" ? "chisel.exe" : "chisel";
|
|
224018
|
-
return
|
|
225422
|
+
return existsSync38(join39(dir, "runtime")) && existsSync38(join39(dir, chisel));
|
|
224019
225423
|
})();
|
|
224020
225424
|
var INSTALLED = STANDALONE && (() => {
|
|
224021
225425
|
const exe = process.platform === "win32" ? "lecodes.exe" : "lecodes";
|
|
@@ -224030,8 +225434,8 @@ var INSTALLED = STANDALONE && (() => {
|
|
|
224030
225434
|
if (!d)
|
|
224031
225435
|
return false;
|
|
224032
225436
|
try {
|
|
224033
|
-
const cand =
|
|
224034
|
-
return
|
|
225437
|
+
const cand = join39(d, exe);
|
|
225438
|
+
return existsSync38(cand) && realpathSync2(cand) === self2;
|
|
224035
225439
|
} catch {
|
|
224036
225440
|
return false;
|
|
224037
225441
|
}
|
|
@@ -224102,6 +225506,7 @@ ${c.bold("Commands:")}
|
|
|
224102
225506
|
--desktop also open the app in the native desktop host
|
|
224103
225507
|
(same hot reload; window from app.json's
|
|
224104
225508
|
desktop block; closing it keeps the server)
|
|
225509
|
+
--fps (--desktop) draw the host's frame counter
|
|
224105
225510
|
compile Compile the local project to JS (prints to stdout)
|
|
224106
225511
|
(was 'lecodes preview' — the old name still works)
|
|
224107
225512
|
-o, --out <file> write to a file instead of stdout
|
|
@@ -224110,8 +225515,16 @@ ${c.bold("Commands:")}
|
|
|
224110
225515
|
--no-minify keep the bundle readable (no compress/mangle)
|
|
224111
225516
|
--no-sourcemap don't generate/inline the source map
|
|
224112
225517
|
--public-url <url> asset URL prefix (default: the API origin)
|
|
224113
|
-
render
|
|
225518
|
+
render [file] Render the local UI project to a semantic JSON description
|
|
224114
225519
|
(needs the optional 'lecodes-renderer' package; UI projects only)
|
|
225520
|
+
[file] render ONE screen module instead of the app:
|
|
225521
|
+
a file that default-exports a UIScreen (or a
|
|
225522
|
+
(state) => UIScreen) — every 'lecodes design'
|
|
225523
|
+
screen is written that way. A bare id
|
|
225524
|
+
("login") resolves in design/screens/, and a
|
|
225525
|
+
design screen renders at its meta.json frame
|
|
225526
|
+
--state <name> ([file]) the state to pass a
|
|
225527
|
+
(state) => UIScreen screen
|
|
224115
225528
|
-o, --out <file> write JSON to a file instead of stdout
|
|
224116
225529
|
--png [file] rasterize a PNG (default screenshot.png;
|
|
224117
225530
|
needs @napi-rs/canvas)
|
|
@@ -224204,7 +225617,7 @@ ${c.bold("Commands:")}
|
|
|
224204
225617
|
add "<text>" [--screen <id>] [--state <s>] [--element <name>]
|
|
224205
225618
|
reply <number> "<text>" · resolve <number> [--reply "<text>"] [--reopen]
|
|
224206
225619
|
(--ai signs a write as Claude; sends with the next "lecodes push")
|
|
224207
|
-
desktop [run|update]
|
|
225620
|
+
desktop [run|build|update] The desktop target — the host 'lecodes render --desktop' uses too:
|
|
224208
225621
|
no args shows the version status (selected / downloaded /
|
|
224209
225622
|
newest published)
|
|
224210
225623
|
run compile the current project and open it in a real
|
|
@@ -224214,11 +225627,45 @@ ${c.bold("Commands:")}
|
|
|
224214
225627
|
app.json fullscreen; --scale <0.5..4|fit> overrides
|
|
224215
225628
|
the display scale — "fit" sizes a 1080p-designed app
|
|
224216
225629
|
to the monitor with the density raised to match;
|
|
224217
|
-
--entry overrides the entrypoint;
|
|
225630
|
+
--entry overrides the entrypoint; --fps draws the
|
|
225631
|
+
host's frame counter in the corner — composited fps,
|
|
225632
|
+
plus the 3D fps when they diverge; asset() resources
|
|
224218
225633
|
load from the working tree — --remote-assets or
|
|
224219
225634
|
--public-url <url> uses the server's copies instead)
|
|
225635
|
+
build stage a portable app folder (default dist/<name>/):
|
|
225636
|
+
the cached host renamed to <Name>.exe + app.js +
|
|
225637
|
+
assets/ (the project's files) + runtime/ (the engine's)
|
|
225638
|
+
+ app.json (the host reads title/size/fullscreen from
|
|
225639
|
+
it) + app.ico from app.json "icon". Duplicate asset
|
|
225640
|
+
basenames are a build error (they silently alias in
|
|
225641
|
+
the bundle). Also spelled 'lecodes app build desktop'.
|
|
225642
|
+
On macOS: a signed .app instead (the downloaded LeCodes
|
|
225643
|
+
Desktop.app + the project inside it); app.json
|
|
225644
|
+
desktop.macos = bundleId / buildNumber / category /
|
|
225645
|
+
signing.identity / notarize.profile (a notarytool
|
|
225646
|
+
keychain profile — notarized + stapled, so it opens
|
|
225647
|
+
on any Mac without "Open Anyway").
|
|
225648
|
+
-o <dir> output folder (default dist/<name>)
|
|
225649
|
+
--slug <name> folder/zip name override (Latin-only)
|
|
225650
|
+
--renderer <gl|vulkan> host + shader variant
|
|
225651
|
+
--probe headless-render the staged folder to PNG
|
|
225652
|
+
(macOS: a brief windowed run + snapshot)
|
|
225653
|
+
--zip also pack the result for sending
|
|
225654
|
+
--sign <identity|adhoc> macOS codesign identity
|
|
225655
|
+
--notarize <profile> / --no-notarize macOS
|
|
225656
|
+
--zip <out>.zip, UTF-8/forward-slash entries
|
|
224220
225657
|
update move to the newest published release
|
|
224221
225658
|
(cached in ~/.lecodes/renderer)
|
|
225659
|
+
lightmap bake Bake static shadows + ambient occlusion into a lightmap atlas
|
|
225660
|
+
(assets/lightmap/lightmap.{png,bake}) by running the app in
|
|
225661
|
+
the desktop host: the statics are what the app registers
|
|
225662
|
+
with Lightmap.add, the bake fires at its Lightmap.load call.
|
|
225663
|
+
Props need UVs first: lecodes assets doctor … --lightmap-uv
|
|
225664
|
+
--out <dir> where the atlas lands (assets/lightmap)
|
|
225665
|
+
--size <px> atlas edge (4096) --texel <m> density (0.02)
|
|
225666
|
+
--sun-rays/--ao-rays <n> quality (32 / 64)
|
|
225667
|
+
--ao-distance <m> AO reach (2) --sun-angle <deg> (0.5)
|
|
225668
|
+
--frames <n> load headroom (900) --logs host output
|
|
224222
225669
|
shaders [compile|update] The project's custom shaders (.mat) and their local compile:
|
|
224223
225670
|
no args lists the shaders + their compile state; compile
|
|
224224
225671
|
compiles them all into .lecodes/shaders (the same pass
|
|
@@ -224252,6 +225699,11 @@ ${c.bold("Commands:")}
|
|
|
224252
225699
|
Skeleton, clips (+ root motion / first-frame-vs-rest per clip), meshes,
|
|
224253
225700
|
materials, textures, bind sanity, duplicate names; --filmstrip renders
|
|
224254
225701
|
a CPU-skinned PNG strip per clip (no GPU)
|
|
225702
|
+
assets sky <env.exr|.hdr> [-o dir] [--size N] [--ibl-size N] [--ibl-only]
|
|
225703
|
+
Equirectangular HDR → the two cubemaps a scene needs: sky.ktx (sharp,
|
|
225704
|
+
for 'skybox: { texture }') and ibl.ktx (prefiltered + SH, lights the
|
|
225705
|
+
scene) — they are NOT interchangeable. Reports where the sun is in
|
|
225706
|
+
the environment so the scene's light can be aimed to match
|
|
224255
225707
|
assets doctor <in.glb|dir> [--fix] [--ktx2] [--dump]
|
|
224256
225708
|
Analyze / fix a GLB against engine limits (bone cap 256, texture
|
|
224257
225709
|
formats/size, KTX2 via toktx)
|
|
@@ -224300,15 +225752,8 @@ ${c.bold("Commands:")}
|
|
|
224300
225752
|
--configuration <c> Release (default) or Debug
|
|
224301
225753
|
--no-aab skip the .aab (APK only)
|
|
224302
225754
|
--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
|
|
225755
|
+
app build desktop Same as 'lecodes desktop build' (kept for symmetry with the
|
|
225756
|
+
ios/android shells) — see the desktop group above.
|
|
224312
225757
|
app upload ios Upload the archive to App Store Connect (then bumps buildNumber)
|
|
224313
225758
|
--key-path <p8> --key-id <id> --issuer-id <id>
|
|
224314
225759
|
ASC API key; stored in ~/.lecodes/asc.json
|
|
@@ -224322,8 +225767,11 @@ ${c.bold("Commands:")}
|
|
|
224322
225767
|
pn keys [new|revoke] Push keys for YOUR server: list, mint (owner only, shown once
|
|
224323
225768
|
— 'new --env' banks it in .env), revoke <id>
|
|
224324
225769
|
|
|
224325
|
-
Config lives in ~/.lecodes/config.json (override with LECODES_API / LECODES_TOKEN)
|
|
224326
|
-
|
|
225770
|
+
Config lives in ~/.lecodes/config.json (override with LECODES_API / LECODES_TOKEN).
|
|
225771
|
+
A .env next to app.json is read at startup for LECODES_* / CREATOR_* keys (the shell's own
|
|
225772
|
+
environment wins; ./ paths resolve against the project; the file is never pushed) — e.g.
|
|
225773
|
+
LECODES_DESKTOP_EXE, LECODES_MATC, LECODES_NOTARY_PROFILE, LECODES_MACOS_SIGN_ID, LECODES_TOKEN.`;
|
|
225774
|
+
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
225775
|
if (STANDALONE)
|
|
224328
225776
|
commands.install = install;
|
|
224329
225777
|
var readVersion = () => package_default.version ?? "unknown";
|
|
@@ -224336,15 +225784,15 @@ var OPTIONAL_PEERS = [
|
|
|
224336
225784
|
var peerVersion = (name, probe) => {
|
|
224337
225785
|
try {
|
|
224338
225786
|
const require2 = createRequire4(import.meta.url);
|
|
224339
|
-
let dir =
|
|
225787
|
+
let dir = dirname21(require2.resolve(probe));
|
|
224340
225788
|
for (let depth = 0;depth < 16; depth++) {
|
|
224341
|
-
const manifest =
|
|
224342
|
-
if (
|
|
224343
|
-
const parsed = JSON.parse(
|
|
225789
|
+
const manifest = join39(dir, "package.json");
|
|
225790
|
+
if (existsSync38(manifest)) {
|
|
225791
|
+
const parsed = JSON.parse(readFileSync34(manifest, "utf8"));
|
|
224344
225792
|
if (parsed.name === name)
|
|
224345
225793
|
return parsed.version ?? "unknown";
|
|
224346
225794
|
}
|
|
224347
|
-
const parent =
|
|
225795
|
+
const parent = dirname21(dir);
|
|
224348
225796
|
if (parent === dir)
|
|
224349
225797
|
break;
|
|
224350
225798
|
dir = parent;
|
|
@@ -224369,6 +225817,7 @@ var printVersion = () => {
|
|
|
224369
225817
|
var main = async () => {
|
|
224370
225818
|
const argv = process.argv.slice(2);
|
|
224371
225819
|
const command = argv[0];
|
|
225820
|
+
loadProjectEnv();
|
|
224372
225821
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
224373
225822
|
log(HELP);
|
|
224374
225823
|
return;
|