lecodes-cli 0.14.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -864,6 +864,18 @@ var buildHeader = (name, code, opts = {}) => {
|
|
|
864
864
|
header += `// icon: ${opts.icon}
|
|
865
865
|
`;
|
|
866
866
|
header += `// last-updated: ${new Date().toISOString()}
|
|
867
|
+
`;
|
|
868
|
+
if (!code.includes("_creator.createScene") && !code.includes("_creator.createEntity"))
|
|
869
|
+
header += `// gl: disable
|
|
870
|
+
`;
|
|
871
|
+
else if (code.includes("_creator.createEngine"))
|
|
872
|
+
header += `// gl: defer
|
|
873
|
+
`;
|
|
874
|
+
if (code.includes("_creator2d."))
|
|
875
|
+
header += `// 2d: enable
|
|
876
|
+
`;
|
|
877
|
+
if (code.includes("_creator.createARController"))
|
|
878
|
+
header += `// ar: enable
|
|
867
879
|
`;
|
|
868
880
|
const localAssets = new Set;
|
|
869
881
|
for (const m of code.matchAll(/_creatorUtils\.fetchLocal\((.+?)\)/g)) {
|
|
@@ -882,17 +894,6 @@ var buildHeader = (name, code, opts = {}) => {
|
|
|
882
894
|
if (opts.icons)
|
|
883
895
|
header += `// icons: ${opts.icons}
|
|
884
896
|
`;
|
|
885
|
-
if (!code.includes("_creator.createScene") && !code.includes("_creator.createEntity"))
|
|
886
|
-
header += `// gl: disable
|
|
887
|
-
`;
|
|
888
|
-
else if (code.includes("_creator.createEngine"))
|
|
889
|
-
header += `// gl: defer
|
|
890
|
-
`;
|
|
891
|
-
if (code.includes("_creator2d."))
|
|
892
|
-
header += `// 2d: enable
|
|
893
|
-
`;
|
|
894
|
-
if (code.includes("_creator.createARController"))
|
|
895
|
-
header += "// ar: enable";
|
|
896
897
|
header += `
|
|
897
898
|
`;
|
|
898
899
|
return header;
|
|
@@ -42820,65 +42821,65 @@ var init_esm30 = __esm(() => {
|
|
|
42820
42821
|
});
|
|
42821
42822
|
|
|
42822
42823
|
// src/types.ts
|
|
42823
|
-
import { dirname as
|
|
42824
|
-
import { existsSync as
|
|
42824
|
+
import { dirname as dirname13, join as join18, relative as relative3, sep } from "node:path";
|
|
42825
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync12, readdirSync as readdirSync8, readFileSync as readFileSync16, rmSync as rmSync5, statSync as statSync6, writeFileSync as writeFileSync14 } from "node:fs";
|
|
42825
42826
|
var TSCONFIG, listBundleFiles = (dir, base = dir) => {
|
|
42826
|
-
if (!
|
|
42827
|
+
if (!existsSync17(dir))
|
|
42827
42828
|
return [];
|
|
42828
42829
|
const out = [];
|
|
42829
|
-
for (const name of
|
|
42830
|
-
const path2 =
|
|
42831
|
-
if (
|
|
42830
|
+
for (const name of readdirSync8(dir)) {
|
|
42831
|
+
const path2 = join18(dir, name);
|
|
42832
|
+
if (statSync6(path2).isDirectory())
|
|
42832
42833
|
out.push(...listBundleFiles(path2, base));
|
|
42833
42834
|
else
|
|
42834
42835
|
out.push(relative3(base, path2).split(sep).join("/"));
|
|
42835
42836
|
}
|
|
42836
42837
|
return out;
|
|
42837
42838
|
}, pruneEmptyDirs = (dir, root) => {
|
|
42838
|
-
if (!
|
|
42839
|
+
if (!existsSync17(dir) || !statSync6(dir).isDirectory())
|
|
42839
42840
|
return;
|
|
42840
|
-
for (const name of
|
|
42841
|
-
pruneEmptyDirs(
|
|
42842
|
-
if (dir !== root &&
|
|
42843
|
-
|
|
42841
|
+
for (const name of readdirSync8(dir))
|
|
42842
|
+
pruneEmptyDirs(join18(dir, name), root);
|
|
42843
|
+
if (dir !== root && readdirSync8(dir).length === 0)
|
|
42844
|
+
rmSync5(dir, { recursive: true });
|
|
42844
42845
|
}, writeTypeBundle = (root, bundle) => {
|
|
42845
|
-
const typesDir =
|
|
42846
|
+
const typesDir = join18(root, LECODES_DIR, "types");
|
|
42846
42847
|
const before = new Set(listBundleFiles(typesDir));
|
|
42847
42848
|
const stats = { added: 0, updated: 0, removed: 0, total: Object.keys(bundle).length };
|
|
42848
42849
|
for (const [path2, text] of Object.entries(bundle)) {
|
|
42849
|
-
const dest =
|
|
42850
|
+
const dest = join18(typesDir, path2);
|
|
42850
42851
|
if (!before.has(path2))
|
|
42851
42852
|
stats.added++;
|
|
42852
|
-
else if (
|
|
42853
|
+
else if (readFileSync16(dest, "utf8") !== text)
|
|
42853
42854
|
stats.updated++;
|
|
42854
42855
|
else
|
|
42855
42856
|
continue;
|
|
42856
|
-
|
|
42857
|
-
|
|
42857
|
+
mkdirSync12(dirname13(dest), { recursive: true });
|
|
42858
|
+
writeFileSync14(dest, text);
|
|
42858
42859
|
}
|
|
42859
42860
|
for (const path2 of before) {
|
|
42860
42861
|
if (path2 in bundle)
|
|
42861
42862
|
continue;
|
|
42862
|
-
|
|
42863
|
+
rmSync5(join18(typesDir, path2));
|
|
42863
42864
|
stats.removed++;
|
|
42864
42865
|
}
|
|
42865
42866
|
pruneEmptyDirs(typesDir, typesDir);
|
|
42866
42867
|
return stats;
|
|
42867
42868
|
}, bundleHash = (bundle) => sha256(Object.keys(bundle).sort().map((k) => `${k}\x00${bundle[k]}`).join("\x00")), readProjectTypeBundle = (root) => {
|
|
42868
|
-
const typesDir =
|
|
42869
|
+
const typesDir = join18(root, LECODES_DIR, "types");
|
|
42869
42870
|
const bundle = {};
|
|
42870
42871
|
for (const path2 of listBundleFiles(typesDir))
|
|
42871
|
-
bundle[path2] =
|
|
42872
|
+
bundle[path2] = readFileSync16(join18(typesDir, path2), "utf8");
|
|
42872
42873
|
return bundle;
|
|
42873
42874
|
}, materializeTypes = async (root, apiUrl) => writeTypeBundle(root, await getTypes(apiUrl)), readLocalTypeBundle = () => {
|
|
42874
42875
|
const candidates = [
|
|
42875
|
-
|
|
42876
|
-
|
|
42876
|
+
join18(distRoot, "runtime", "sdk-types.json"),
|
|
42877
|
+
join18(distRoot, "..", "sdk", "dist", "types.json")
|
|
42877
42878
|
];
|
|
42878
42879
|
for (const path2 of candidates) {
|
|
42879
|
-
if (
|
|
42880
|
+
if (existsSync17(path2)) {
|
|
42880
42881
|
try {
|
|
42881
|
-
return JSON.parse(
|
|
42882
|
+
return JSON.parse(readFileSync16(path2, "utf8"));
|
|
42882
42883
|
} catch {}
|
|
42883
42884
|
}
|
|
42884
42885
|
}
|
|
@@ -42890,7 +42891,7 @@ var TSCONFIG, listBundleFiles = (dir, base = dir) => {
|
|
|
42890
42891
|
writeTypeBundle(root, bundle);
|
|
42891
42892
|
return true;
|
|
42892
42893
|
}, writeTsconfig = (root) => {
|
|
42893
|
-
|
|
42894
|
+
writeFileSync14(join18(root, "tsconfig.json"), JSON.stringify(TSCONFIG, null, 2));
|
|
42894
42895
|
};
|
|
42895
42896
|
var init_types4 = __esm(() => {
|
|
42896
42897
|
init_api();
|
|
@@ -42917,13 +42918,13 @@ var init_types4 = __esm(() => {
|
|
|
42917
42918
|
|
|
42918
42919
|
// src/compile/designCompile.ts
|
|
42919
42920
|
import { readFileSync as readFileSync17 } from "node:fs";
|
|
42920
|
-
import { basename as
|
|
42921
|
+
import { basename as basename7 } from "node:path";
|
|
42921
42922
|
var LOCAL_ASSET_SCHEME = "lecodesfile://", compileDesignScreen = async (opts) => {
|
|
42922
42923
|
const { root, manifest, designDir, designKey, screenId, state } = opts;
|
|
42923
42924
|
const entries = [];
|
|
42924
42925
|
const scan2 = scanLocal(root, { ignore: loadIgnore(root), tracked: new Set(Object.keys(manifest?.files ?? {})) });
|
|
42925
42926
|
for (const file of scan2.files) {
|
|
42926
|
-
const type = manifest?.files[file.path]?.type ?? classifyKind(
|
|
42927
|
+
const type = manifest?.files[file.path]?.type ?? classifyKind(basename7(file.path));
|
|
42927
42928
|
if (type === "resource") {
|
|
42928
42929
|
entries.push({
|
|
42929
42930
|
path: file.path,
|
|
@@ -42979,7 +42980,7 @@ var init_designCompile = __esm(() => {
|
|
|
42979
42980
|
|
|
42980
42981
|
// src/browserAuth.ts
|
|
42981
42982
|
import { createServer } from "node:http";
|
|
42982
|
-
import { spawn as
|
|
42983
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
42983
42984
|
import { randomBytes } from "node:crypto";
|
|
42984
42985
|
import { hostname } from "node:os";
|
|
42985
42986
|
var SUCCESS_HTML = `<!doctype html><meta charset="utf-8"><title>lecodes</title>
|
|
@@ -43004,16 +43005,16 @@ var SUCCESS_HTML = `<!doctype html><meta charset="utf-8"><title>lecodes</title>
|
|
|
43004
43005
|
args = [url];
|
|
43005
43006
|
}
|
|
43006
43007
|
try {
|
|
43007
|
-
const child =
|
|
43008
|
+
const child = spawn4(cmd, args, { stdio: "ignore", detached: true });
|
|
43008
43009
|
child.on("error", () => {});
|
|
43009
43010
|
child.unref();
|
|
43010
43011
|
} catch {}
|
|
43011
43012
|
}, browserLogin = async (webBase, timeoutMs = 5 * 60 * 1000, open = openBrowser) => {
|
|
43012
43013
|
const state = randomBytes(16).toString("hex");
|
|
43013
|
-
let
|
|
43014
|
+
let resolve9;
|
|
43014
43015
|
let reject;
|
|
43015
43016
|
const tokenPromise = new Promise((res, rej) => {
|
|
43016
|
-
|
|
43017
|
+
resolve9 = res;
|
|
43017
43018
|
reject = rej;
|
|
43018
43019
|
});
|
|
43019
43020
|
const server = createServer((req, res) => {
|
|
@@ -43031,7 +43032,7 @@ var SUCCESS_HTML = `<!doctype html><meta charset="utf-8"><title>lecodes</title>
|
|
|
43031
43032
|
}
|
|
43032
43033
|
res.writeHead(200, { "content-type": "text/html" });
|
|
43033
43034
|
res.end(SUCCESS_HTML);
|
|
43034
|
-
|
|
43035
|
+
resolve9(token);
|
|
43035
43036
|
});
|
|
43036
43037
|
await new Promise((res, rej) => {
|
|
43037
43038
|
server.once("error", rej);
|
|
@@ -43532,8 +43533,8 @@ __export(exports_design, {
|
|
|
43532
43533
|
flushDesignComments: () => flushDesignComments,
|
|
43533
43534
|
design: () => design
|
|
43534
43535
|
});
|
|
43535
|
-
import { existsSync as
|
|
43536
|
-
import { basename as
|
|
43536
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync15, readFileSync as readFileSync18, readdirSync as readdirSync11, writeFileSync as writeFileSync16, appendFileSync } from "node:fs";
|
|
43537
|
+
import { basename as basename8, join as join20 } from "node:path";
|
|
43537
43538
|
var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
43538
43539
|
let root;
|
|
43539
43540
|
let manifest;
|
|
@@ -43548,13 +43549,13 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
43548
43549
|
return {
|
|
43549
43550
|
root,
|
|
43550
43551
|
manifest,
|
|
43551
|
-
name: manifest?.name ??
|
|
43552
|
+
name: manifest?.name ?? basename8(root),
|
|
43552
43553
|
dirName,
|
|
43553
|
-
designDir:
|
|
43554
|
+
designDir: join20(root, dirName),
|
|
43554
43555
|
designKey: "/" + dirName.replace(/\\/g, "/")
|
|
43555
43556
|
};
|
|
43556
43557
|
}, loadDesignServer = async (ctx, needs = []) => {
|
|
43557
|
-
if (!
|
|
43558
|
+
if (!existsSync20(ctx.designDir)) {
|
|
43558
43559
|
throw new CliError(`No ${ctx.dirName}/ folder here. Run "lecodes design init" to scaffold it.`);
|
|
43559
43560
|
}
|
|
43560
43561
|
let mod;
|
|
@@ -43586,13 +43587,13 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
43586
43587
|
`).find((l2) => l2.trim())?.trim() ?? "";
|
|
43587
43588
|
return line.length > max ? line.slice(0, max - 1) + "…" : line;
|
|
43588
43589
|
}, listScreens = (designDir) => {
|
|
43589
|
-
const dir =
|
|
43590
|
-
if (!
|
|
43590
|
+
const dir = join20(designDir, "screens");
|
|
43591
|
+
if (!existsSync20(dir))
|
|
43591
43592
|
return [];
|
|
43592
|
-
return
|
|
43593
|
+
return readdirSync11(dir).filter((f3) => f3.endsWith(".ts") && !f3.startsWith(".")).map((f3) => f3.slice(0, -3)).sort();
|
|
43593
43594
|
}, PHONE_SIZE, readMetaFrames = (designDir) => {
|
|
43594
43595
|
try {
|
|
43595
|
-
const meta = JSON.parse(readFileSync18(
|
|
43596
|
+
const meta = JSON.parse(readFileSync18(join20(designDir, "meta.json"), "utf8"));
|
|
43596
43597
|
const d = meta?.defaultSize;
|
|
43597
43598
|
const valid = Array.isArray(d) && d.length === 2 && d.every((n2) => typeof n2 === "number" && Number.isFinite(n2) && n2 >= 64 && n2 <= 8192);
|
|
43598
43599
|
return {
|
|
@@ -43603,10 +43604,10 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
43603
43604
|
return { screens: {}, defaultSize: PHONE_SIZE };
|
|
43604
43605
|
}
|
|
43605
43606
|
}, writeMcpConfig = (ctx, port) => {
|
|
43606
|
-
const path2 =
|
|
43607
|
+
const path2 = join20(ctx.root, ".mcp.json");
|
|
43607
43608
|
const entry = { type: "http", url: `http://localhost:${port}/mcp` };
|
|
43608
43609
|
let config = {};
|
|
43609
|
-
if (
|
|
43610
|
+
if (existsSync20(path2)) {
|
|
43610
43611
|
try {
|
|
43611
43612
|
config = JSON.parse(readFileSync18(path2, "utf8"));
|
|
43612
43613
|
} catch {
|
|
@@ -43621,7 +43622,7 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
43621
43622
|
return;
|
|
43622
43623
|
}
|
|
43623
43624
|
config.mcpServers["lecodes-design"] = entry;
|
|
43624
|
-
|
|
43625
|
+
writeFileSync16(path2, JSON.stringify(config, null, 2) + `
|
|
43625
43626
|
`);
|
|
43626
43627
|
log(`${c.green(existing ? "updated" : "created")} .mcp.json (lecodes-design MCP → ${entry.url})`);
|
|
43627
43628
|
}, init = async (ctx, args) => {
|
|
@@ -43630,26 +43631,26 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
43630
43631
|
["spec.md", SPEC_MD],
|
|
43631
43632
|
["README.md", README_HINT],
|
|
43632
43633
|
["CLAUDE.md", CLAUDE_MD],
|
|
43633
|
-
[
|
|
43634
|
-
[
|
|
43635
|
-
[
|
|
43634
|
+
[join20("shared", "tokens.ts"), TOKENS_TS],
|
|
43635
|
+
[join20("shared", "tabs.ts"), TABS_TS],
|
|
43636
|
+
[join20("screens", "home.ts"), HOME_SCREEN_TS]
|
|
43636
43637
|
];
|
|
43637
|
-
|
|
43638
|
-
|
|
43638
|
+
mkdirSync15(join20(ctx.designDir, "screens"), { recursive: true });
|
|
43639
|
+
mkdirSync15(join20(ctx.designDir, "shared"), { recursive: true });
|
|
43639
43640
|
let created = 0;
|
|
43640
43641
|
for (const [rel, content] of files) {
|
|
43641
|
-
const abs =
|
|
43642
|
-
if (
|
|
43642
|
+
const abs = join20(ctx.designDir, rel);
|
|
43643
|
+
if (existsSync20(abs)) {
|
|
43643
43644
|
note(`kept ${ctx.dirName}/${rel.replace(/\\/g, "/")}`);
|
|
43644
43645
|
continue;
|
|
43645
43646
|
}
|
|
43646
|
-
|
|
43647
|
+
writeFileSync16(abs, content);
|
|
43647
43648
|
log(`${c.green("created")} ${ctx.dirName}/${rel.replace(/\\/g, "/")}`);
|
|
43648
43649
|
created++;
|
|
43649
43650
|
}
|
|
43650
|
-
const ignorePath =
|
|
43651
|
+
const ignorePath = join20(ctx.root, ".lecodesignore");
|
|
43651
43652
|
const ignoreLines = [`${ctx.dirName}/.snapshots/`, `${ctx.dirName}/comments/`];
|
|
43652
|
-
const current =
|
|
43653
|
+
const current = existsSync20(ignorePath) ? readFileSync18(ignorePath, "utf8") : "";
|
|
43653
43654
|
const present = new Set(current.split(/\r?\n/));
|
|
43654
43655
|
const missing = ignoreLines.filter((line) => !present.has(line));
|
|
43655
43656
|
if (missing.length) {
|
|
@@ -43664,8 +43665,8 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
43664
43665
|
writeMcpConfig(ctx, Number(flagStr(args, "port")) || DEFAULT_PORT);
|
|
43665
43666
|
const wroteTypes = materializeTypesLocal(ctx.designDir);
|
|
43666
43667
|
writeTsconfig(ctx.designDir);
|
|
43667
|
-
|
|
43668
|
-
|
|
43668
|
+
mkdirSync15(join20(ctx.designDir, ".lecodes", "types"), { recursive: true });
|
|
43669
|
+
writeFileSync16(join20(ctx.designDir, ".lecodes", "types", "design-macros.d.ts"), DESIGN_MACROS_DTS);
|
|
43669
43670
|
log(`${c.green("created")} ${ctx.dirName}/tsconfig.json${wroteTypes ? ` + ${ctx.dirName}/.lecodes/types/` : ""} (IDE types)`);
|
|
43670
43671
|
if (!wroteTypes)
|
|
43671
43672
|
warnErr("Bundled SDK types not found — tsconfig written, but globals may show unresolved until the CLI is rebuilt with vendored types.");
|
|
@@ -43713,16 +43714,16 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
43713
43714
|
killer.unref?.();
|
|
43714
43715
|
const png2 = flagBool(args, "png") || flagStr(args, "png") !== undefined;
|
|
43715
43716
|
const frames = readMetaFrames(ctx.designDir);
|
|
43716
|
-
const outDir = flagStr(args, "out-dir") ??
|
|
43717
|
+
const outDir = flagStr(args, "out-dir") ?? join20(ctx.designDir, ".snapshots");
|
|
43717
43718
|
const single = targets.length === 1 && requested.length === 1 && !png2 && !allStates && !flagStr(args, "out-dir");
|
|
43718
43719
|
const onConsole = flagBool(args, "logs") ? (level, text) => logErr(`${c.dim(`[${level}]`)} ${text}`) : undefined;
|
|
43719
43720
|
if (!single)
|
|
43720
|
-
|
|
43721
|
+
mkdirSync15(outDir, { recursive: true });
|
|
43721
43722
|
for (const id of targets) {
|
|
43722
43723
|
const [width, height] = frames.screens[id]?.size ?? frames.defaultSize;
|
|
43723
43724
|
let plan = [{ state: undefined, bases: [id] }];
|
|
43724
43725
|
if (discover) {
|
|
43725
|
-
const { states, canonical } = discover(readFileSync18(
|
|
43726
|
+
const { states, canonical } = discover(readFileSync18(join20(ctx.designDir, "screens", `${id}.ts`), "utf8"));
|
|
43726
43727
|
if (stateFlag !== undefined) {
|
|
43727
43728
|
if (!states.includes(stateFlag)) {
|
|
43728
43729
|
if (requested.length === 1)
|
|
@@ -43763,11 +43764,11 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
43763
43764
|
for (const w of pngResult.warnings)
|
|
43764
43765
|
warnErr(`${tag}: ${w}`);
|
|
43765
43766
|
for (const base of bases) {
|
|
43766
|
-
|
|
43767
|
-
log(`${c.green("✓")} ${tag} → ${
|
|
43767
|
+
writeFileSync16(join20(outDir, `${base}.json`), result.text);
|
|
43768
|
+
log(`${c.green("✓")} ${tag} → ${join20(outDir, `${base}.json`)}`);
|
|
43768
43769
|
if (pngResult) {
|
|
43769
|
-
|
|
43770
|
-
log(`${c.green("✓")} ${tag} → ${
|
|
43770
|
+
writeFileSync16(join20(outDir, `${base}.png`), pngResult.png);
|
|
43771
|
+
log(`${c.green("✓")} ${tag} → ${join20(outDir, `${base}.png`)}`);
|
|
43771
43772
|
}
|
|
43772
43773
|
}
|
|
43773
43774
|
}
|
|
@@ -44069,7 +44070,7 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
44069
44070
|
note("Read them with: lecodes design comments");
|
|
44070
44071
|
}, flushDesignComments = async (args) => {
|
|
44071
44072
|
const ctx = getContext(args);
|
|
44072
|
-
if (!
|
|
44073
|
+
if (!existsSync20(ctx.designDir))
|
|
44073
44074
|
return null;
|
|
44074
44075
|
const platform = optionalPlatform(ctx);
|
|
44075
44076
|
if (!platform)
|
|
@@ -45211,7 +45212,7 @@ var require_main = __commonJS((exports, module) => {
|
|
|
45211
45212
|
// package.json
|
|
45212
45213
|
var package_default = {
|
|
45213
45214
|
name: "lecodes-cli",
|
|
45214
|
-
version: "0.
|
|
45215
|
+
version: "0.15.0",
|
|
45215
45216
|
dependencies: {
|
|
45216
45217
|
"@letary/chisel": "^0.6.0",
|
|
45217
45218
|
jimp: "^1.6.1"
|
|
@@ -45224,7 +45225,7 @@ var package_default = {
|
|
|
45224
45225
|
sdk: "workspace:*"
|
|
45225
45226
|
},
|
|
45226
45227
|
peerDependencies: {
|
|
45227
|
-
"lecodes-design": "^0.
|
|
45228
|
+
"lecodes-design": "^0.8.0",
|
|
45228
45229
|
"lecodes-renderer": "^0.8.2",
|
|
45229
45230
|
"lecodes-3d-editor": "^0.2.0"
|
|
45230
45231
|
},
|
|
@@ -45272,10 +45273,10 @@ var package_default = {
|
|
|
45272
45273
|
// src/commands/app.ts
|
|
45273
45274
|
init_manifest();
|
|
45274
45275
|
init_config();
|
|
45275
|
-
import { execFileSync as
|
|
45276
|
-
import { cpSync as
|
|
45277
|
-
import { homedir as
|
|
45278
|
-
import { basename as
|
|
45276
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
45277
|
+
import { cpSync as cpSync3, existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync15, readdirSync as readdirSync7, rmSync as rmSync4, statSync as statSync5, writeFileSync as writeFileSync13 } from "node:fs";
|
|
45278
|
+
import { homedir as homedir6 } from "node:os";
|
|
45279
|
+
import { basename as basename6, dirname as dirname12, join as join17, resolve as resolve6 } from "node:path";
|
|
45279
45280
|
|
|
45280
45281
|
// src/commands/compile.ts
|
|
45281
45282
|
init_api();
|
|
@@ -46359,7 +46360,7 @@ var readJson = (path) => {
|
|
|
46359
46360
|
if (!existsSync10(path))
|
|
46360
46361
|
return null;
|
|
46361
46362
|
try {
|
|
46362
|
-
return JSON.parse(readFileSync11(path, "utf8"));
|
|
46363
|
+
return JSON.parse(readFileSync11(path, "utf8").replace(/^\uFEFF/, ""));
|
|
46363
46364
|
} catch {
|
|
46364
46365
|
throw new CliError(`Invalid JSON in ${path}`);
|
|
46365
46366
|
}
|
|
@@ -47984,16 +47985,492 @@ var doctorAndroidChecks = async (root, config, check) => {
|
|
|
47984
47985
|
}
|
|
47985
47986
|
};
|
|
47986
47987
|
|
|
47988
|
+
// src/commands/appDesktop.ts
|
|
47989
|
+
import { execFileSync as execFileSync3, spawn as spawn3 } from "node:child_process";
|
|
47990
|
+
import { cpSync as cpSync2, existsSync as existsSync15, mkdirSync as mkdirSync10, readdirSync as readdirSync6, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync12 } from "node:fs";
|
|
47991
|
+
import { basename as basename5, dirname as dirname11, join as join16, resolve as resolve5 } from "node:path";
|
|
47992
|
+
|
|
47993
|
+
// src/desktopRenderer.ts
|
|
47994
|
+
init_util();
|
|
47995
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
47996
|
+
import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
|
|
47997
|
+
import { copyFileSync, existsSync as existsSync14, mkdirSync as mkdirSync9, mkdtempSync, readdirSync as readdirSync5, readFileSync as readFileSync14, renameSync as renameSync2, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync11 } from "node:fs";
|
|
47998
|
+
import { homedir as homedir5, tmpdir } from "node:os";
|
|
47999
|
+
import { join as join15, resolve as resolve4 } from "node:path";
|
|
48000
|
+
var MANIFEST_URL = "https://storage.yandexcloud.net/le-codes/releases/versions.json";
|
|
48001
|
+
var DESKTOP_RENDERER_VERSION = "1.1.0";
|
|
48002
|
+
var semverNewer = (a2, b) => {
|
|
48003
|
+
const pa = a2.split(/[.-]/).map(Number), pb = b.split(/[.-]/).map(Number);
|
|
48004
|
+
for (let i2 = 0;i2 < 3; i2++)
|
|
48005
|
+
if ((pb[i2] ?? 0) !== (pa[i2] ?? 0))
|
|
48006
|
+
return (pb[i2] ?? 0) - (pa[i2] ?? 0);
|
|
48007
|
+
return 0;
|
|
48008
|
+
};
|
|
48009
|
+
var platformKey = () => {
|
|
48010
|
+
if (process.platform === "win32")
|
|
48011
|
+
return "windows-x64";
|
|
48012
|
+
if (process.platform === "linux")
|
|
48013
|
+
return "ubuntu-linux-x64";
|
|
48014
|
+
throw new CliError(process.platform === "darwin" ? "`lecodes render --desktop` isn't available on macOS yet (the macOS host ships separately)." : `\`lecodes render --desktop\` isn't available on ${process.platform}.`);
|
|
48015
|
+
};
|
|
48016
|
+
var exeName = () => process.platform === "win32" ? "lecodes-desktop.exe" : "lecodes-desktop";
|
|
48017
|
+
var findExe = (dir) => {
|
|
48018
|
+
const name = exeName();
|
|
48019
|
+
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
48020
|
+
const p2 = join15(dir, entry.name);
|
|
48021
|
+
if (entry.isFile() && entry.name === name)
|
|
48022
|
+
return p2;
|
|
48023
|
+
if (entry.isDirectory()) {
|
|
48024
|
+
const found = findExe(p2);
|
|
48025
|
+
if (found)
|
|
48026
|
+
return found;
|
|
48027
|
+
}
|
|
48028
|
+
}
|
|
48029
|
+
return null;
|
|
48030
|
+
};
|
|
48031
|
+
var cacheRoot = () => join15(homedir5(), ".lecodes", "renderer");
|
|
48032
|
+
var currentFile = () => join15(cacheRoot(), "current.json");
|
|
48033
|
+
var installedDesktopVersions = () => {
|
|
48034
|
+
try {
|
|
48035
|
+
return readdirSync5(cacheRoot(), { withFileTypes: true }).filter((e2) => e2.isDirectory() && e2.name.startsWith("desktop-")).map((e2) => e2.name.slice("desktop-".length)).filter((v) => findExe(join15(cacheRoot(), `desktop-${v}`)) !== null).sort(semverNewer);
|
|
48036
|
+
} catch {
|
|
48037
|
+
return [];
|
|
48038
|
+
}
|
|
48039
|
+
};
|
|
48040
|
+
var selectedDesktopVersion = () => {
|
|
48041
|
+
try {
|
|
48042
|
+
const saved = JSON.parse(readFileSync14(currentFile(), "utf8")).version;
|
|
48043
|
+
if (saved && /^\d+\.\d+\.\d+/.test(saved) && semverNewer(DESKTOP_RENDERER_VERSION, saved) > 0)
|
|
48044
|
+
return saved;
|
|
48045
|
+
} catch {}
|
|
48046
|
+
return DESKTOP_RENDERER_VERSION;
|
|
48047
|
+
};
|
|
48048
|
+
var fetchDesktopManifest = async () => {
|
|
48049
|
+
let manifest;
|
|
48050
|
+
try {
|
|
48051
|
+
const res = await fetch(MANIFEST_URL);
|
|
48052
|
+
if (!res.ok)
|
|
48053
|
+
throw new Error(`HTTP ${res.status}`);
|
|
48054
|
+
manifest = await res.json();
|
|
48055
|
+
} catch (e2) {
|
|
48056
|
+
throw new CliError(`Can't fetch the release manifest (${MANIFEST_URL}): ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
48057
|
+
}
|
|
48058
|
+
const desktop = manifest.products?.desktop;
|
|
48059
|
+
if (!desktop)
|
|
48060
|
+
throw new CliError("The release manifest has no desktop product.");
|
|
48061
|
+
return desktop;
|
|
48062
|
+
};
|
|
48063
|
+
var downloadDesktop = async (version, dest) => {
|
|
48064
|
+
const key = platformKey();
|
|
48065
|
+
const entry = (await fetchDesktopManifest()).versions.find((v) => v.version === version);
|
|
48066
|
+
const file = entry?.files[key];
|
|
48067
|
+
if (!file) {
|
|
48068
|
+
throw new CliError(`LeCodes Desktop ${version} [${key}] isn't published yet — \`--desktop\` needs it for the ` + `headless render contract.
|
|
48069
|
+
Set LECODES_DESKTOP_EXE to a locally built host, or update lecodes-cli.`);
|
|
48070
|
+
}
|
|
48071
|
+
note(`Downloading the native renderer (LeCodes Desktop ${version}, ${(file.size / 1024 / 1024).toFixed(1)} MB)…`);
|
|
48072
|
+
let bytes;
|
|
48073
|
+
try {
|
|
48074
|
+
const res = await fetch(file.url);
|
|
48075
|
+
if (!res.ok)
|
|
48076
|
+
throw new Error(`HTTP ${res.status}`);
|
|
48077
|
+
bytes = Buffer.from(await res.arrayBuffer());
|
|
48078
|
+
} catch (e2) {
|
|
48079
|
+
throw new CliError(`Download failed (${file.url}): ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
48080
|
+
}
|
|
48081
|
+
const sha = createHash5("sha256").update(bytes).digest("hex");
|
|
48082
|
+
if (sha !== file.sha256)
|
|
48083
|
+
throw new CliError(`Downloaded archive is corrupt (sha256 mismatch) — try again.`);
|
|
48084
|
+
mkdirSync9(cacheRoot(), { recursive: true });
|
|
48085
|
+
const stage = mkdtempSync(join15(cacheRoot(), "dl-"));
|
|
48086
|
+
try {
|
|
48087
|
+
const archive = join15(stage, file.url.split("/").pop() ?? "archive");
|
|
48088
|
+
writeFileSync11(archive, bytes);
|
|
48089
|
+
const tar = spawnSync3("tar", ["-xf", archive, "-C", stage], { stdio: ["ignore", "ignore", "pipe"] });
|
|
48090
|
+
if (tar.status !== 0)
|
|
48091
|
+
throw new CliError(`Couldn't extract the renderer archive: ${tar.stderr?.toString() ?? "tar failed"}`);
|
|
48092
|
+
rmSync2(archive);
|
|
48093
|
+
if (!findExe(stage))
|
|
48094
|
+
throw new CliError("The downloaded archive doesn't contain the desktop host binary.");
|
|
48095
|
+
try {
|
|
48096
|
+
renameSync2(stage, dest);
|
|
48097
|
+
} catch {
|
|
48098
|
+
if (!findExe(dest))
|
|
48099
|
+
throw new CliError(`Couldn't move the renderer into ${dest}.`);
|
|
48100
|
+
}
|
|
48101
|
+
} finally {
|
|
48102
|
+
rmSync2(stage, { recursive: true, force: true });
|
|
48103
|
+
}
|
|
48104
|
+
};
|
|
48105
|
+
var resolveDesktopExe = async () => {
|
|
48106
|
+
const override = process.env.LECODES_DESKTOP_EXE;
|
|
48107
|
+
if (override) {
|
|
48108
|
+
if (!existsSync14(override))
|
|
48109
|
+
throw new CliError(`LECODES_DESKTOP_EXE points at "${override}" which doesn't exist.`);
|
|
48110
|
+
return resolve4(override);
|
|
48111
|
+
}
|
|
48112
|
+
const version = selectedDesktopVersion();
|
|
48113
|
+
const dir = join15(cacheRoot(), `desktop-${version}`);
|
|
48114
|
+
let exe = existsSync14(dir) ? findExe(dir) : null;
|
|
48115
|
+
if (!exe) {
|
|
48116
|
+
await downloadDesktop(version, dir);
|
|
48117
|
+
exe = findExe(dir);
|
|
48118
|
+
if (!exe)
|
|
48119
|
+
throw new CliError(`Renderer cache at ${dir} is broken — delete the folder and retry.`);
|
|
48120
|
+
}
|
|
48121
|
+
return exe;
|
|
48122
|
+
};
|
|
48123
|
+
var updateDesktop = async () => {
|
|
48124
|
+
const { latest } = await fetchDesktopManifest();
|
|
48125
|
+
if (semverNewer(latest, DESKTOP_RENDERER_VERSION) > 0) {
|
|
48126
|
+
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.`);
|
|
48127
|
+
}
|
|
48128
|
+
const before = selectedDesktopVersion();
|
|
48129
|
+
const dir = join15(cacheRoot(), `desktop-${latest}`);
|
|
48130
|
+
const hadIt = existsSync14(dir) && findExe(dir) !== null;
|
|
48131
|
+
if (!hadIt)
|
|
48132
|
+
await downloadDesktop(latest, dir);
|
|
48133
|
+
mkdirSync9(cacheRoot(), { recursive: true });
|
|
48134
|
+
writeFileSync11(currentFile(), JSON.stringify({ version: latest }, null, 2));
|
|
48135
|
+
if (latest === before && hadIt)
|
|
48136
|
+
note(`Already up to date: LeCodes Desktop ${latest}.`);
|
|
48137
|
+
else if (latest === before)
|
|
48138
|
+
note(`Re-downloaded LeCodes Desktop ${latest}.`);
|
|
48139
|
+
else
|
|
48140
|
+
note(`Desktop renderer set to ${latest} (was ${before}).`);
|
|
48141
|
+
};
|
|
48142
|
+
var windowEnv = (opts) => {
|
|
48143
|
+
const env = {};
|
|
48144
|
+
const width = opts.width ?? (opts.height !== undefined ? 1280 : undefined);
|
|
48145
|
+
const height = opts.height ?? (opts.width !== undefined ? 720 : undefined);
|
|
48146
|
+
if (width && height)
|
|
48147
|
+
env.CREATOR_WINDOW_SIZE = `${width}x${height}`;
|
|
48148
|
+
if (opts.minWidth && opts.minHeight)
|
|
48149
|
+
env.CREATOR_MIN_WINDOW_SIZE = `${opts.minWidth}x${opts.minHeight}`;
|
|
48150
|
+
if (opts.title)
|
|
48151
|
+
env.CREATOR_WINDOW_TITLE = opts.title;
|
|
48152
|
+
if (opts.resizable === false)
|
|
48153
|
+
env.CREATOR_WINDOW_FIXED = "1";
|
|
48154
|
+
if (opts.fullscreen !== undefined)
|
|
48155
|
+
env.CREATOR_WINDOW_FULLSCREEN = opts.fullscreen ? "1" : "0";
|
|
48156
|
+
if (opts.scale === "fit")
|
|
48157
|
+
env.CREATOR_UI_SCALE = "fit";
|
|
48158
|
+
else if (typeof opts.scale === "number")
|
|
48159
|
+
env.CREATOR_UI_SCALE = String(opts.scale);
|
|
48160
|
+
return env;
|
|
48161
|
+
};
|
|
48162
|
+
var stageBundle = (js, resources) => {
|
|
48163
|
+
const runDir = mkdtempSync(join15(tmpdir(), "lecodes-render-"));
|
|
48164
|
+
writeFileSync11(join15(runDir, "app.js"), js);
|
|
48165
|
+
for (const r2 of resources) {
|
|
48166
|
+
if (r2.absPath && existsSync14(r2.absPath) && statSync3(r2.absPath).isFile()) {
|
|
48167
|
+
copyFileSync(r2.absPath, join15(runDir, r2.name));
|
|
48168
|
+
}
|
|
48169
|
+
}
|
|
48170
|
+
return runDir;
|
|
48171
|
+
};
|
|
48172
|
+
var runDesktopApp = async (js, resources, opts = {}) => {
|
|
48173
|
+
const exe = await resolveDesktopExe();
|
|
48174
|
+
const runDir = stageBundle(js, resources);
|
|
48175
|
+
try {
|
|
48176
|
+
const env = { ...process.env, ...windowEnv(opts) };
|
|
48177
|
+
await new Promise((done, fail) => {
|
|
48178
|
+
const child = spawn2(exe, [join15(runDir, "app.js")], { cwd: runDir, env, stdio: ["ignore", "inherit", "inherit"] });
|
|
48179
|
+
child.on("error", (e2) => fail(new CliError(`Can't run the desktop host: ${e2.message}`)));
|
|
48180
|
+
child.on("exit", (code) => {
|
|
48181
|
+
if (code === 0 || code === null)
|
|
48182
|
+
done();
|
|
48183
|
+
else
|
|
48184
|
+
fail(new CliError(`The desktop host exited with code ${code}.`));
|
|
48185
|
+
});
|
|
48186
|
+
});
|
|
48187
|
+
} finally {
|
|
48188
|
+
rmSync2(runDir, { recursive: true, force: true });
|
|
48189
|
+
}
|
|
48190
|
+
};
|
|
48191
|
+
var runDesktopDev = async (url, opts = {}) => {
|
|
48192
|
+
const exe = await resolveDesktopExe();
|
|
48193
|
+
const env = { ...process.env, ...windowEnv(opts) };
|
|
48194
|
+
await new Promise((done, fail) => {
|
|
48195
|
+
const child = spawn2(exe, [url], { env, stdio: ["ignore", "ignore", "inherit"] });
|
|
48196
|
+
child.on("error", (e2) => fail(new CliError(`Can't run the desktop host: ${e2.message}`)));
|
|
48197
|
+
child.on("exit", () => done());
|
|
48198
|
+
});
|
|
48199
|
+
};
|
|
48200
|
+
var runDesktopRender = async (js, resources, opts) => {
|
|
48201
|
+
const exe = await resolveDesktopExe();
|
|
48202
|
+
const runDir = stageBundle(js, resources);
|
|
48203
|
+
try {
|
|
48204
|
+
const appJs = join15(runDir, "app.js");
|
|
48205
|
+
const outAbs = resolve4(opts.outPng);
|
|
48206
|
+
await new Promise((done, fail) => {
|
|
48207
|
+
const child = spawn2(exe, [appJs], {
|
|
48208
|
+
cwd: runDir,
|
|
48209
|
+
env: {
|
|
48210
|
+
...process.env,
|
|
48211
|
+
CREATOR_DUMP_PNG: outAbs,
|
|
48212
|
+
CREATOR_MAX_FRAMES: String(opts.frames),
|
|
48213
|
+
CREATOR_WINDOW_SIZE: `${opts.width}x${opts.height}`,
|
|
48214
|
+
CREATOR_HIDDEN: "1"
|
|
48215
|
+
},
|
|
48216
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
48217
|
+
});
|
|
48218
|
+
const tail = [];
|
|
48219
|
+
const sink = (d) => {
|
|
48220
|
+
const text = String(d);
|
|
48221
|
+
tail.push(text);
|
|
48222
|
+
while (tail.length > 80)
|
|
48223
|
+
tail.shift();
|
|
48224
|
+
if (opts.logs)
|
|
48225
|
+
process.stderr.write(text);
|
|
48226
|
+
};
|
|
48227
|
+
child.stdout.on("data", sink);
|
|
48228
|
+
child.stderr.on("data", sink);
|
|
48229
|
+
const killer = setTimeout(() => {
|
|
48230
|
+
child.kill();
|
|
48231
|
+
fail(new CliError(`The native render timed out after ${opts.timeoutMs}ms (raise --timeout, or lower --frames).`));
|
|
48232
|
+
}, opts.timeoutMs);
|
|
48233
|
+
child.on("error", (e2) => {
|
|
48234
|
+
clearTimeout(killer);
|
|
48235
|
+
fail(new CliError(`Can't run the desktop renderer: ${e2.message}`));
|
|
48236
|
+
});
|
|
48237
|
+
child.on("exit", (code) => {
|
|
48238
|
+
clearTimeout(killer);
|
|
48239
|
+
if (code === 0 && existsSync14(outAbs))
|
|
48240
|
+
return done();
|
|
48241
|
+
const detail = tail.length > 0 ? `
|
|
48242
|
+
${tail.join("").trimEnd()}` : "";
|
|
48243
|
+
fail(new CliError(code === 0 ? `The renderer exited cleanly but produced no PNG — did the app open a screen?${detail}` : `The renderer exited with code ${code}.${detail}`));
|
|
48244
|
+
});
|
|
48245
|
+
});
|
|
48246
|
+
} finally {
|
|
48247
|
+
rmSync2(runDir, { recursive: true, force: true });
|
|
48248
|
+
}
|
|
48249
|
+
};
|
|
48250
|
+
|
|
48251
|
+
// src/commands/appDesktop.ts
|
|
48252
|
+
init_util();
|
|
48253
|
+
var mb = (bytes) => `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
48254
|
+
var dirSize = (dir) => readdirSync6(dir, { withFileTypes: true }).reduce((sum, e2) => {
|
|
48255
|
+
const p2 = join16(dir, e2.name);
|
|
48256
|
+
return sum + (e2.isDirectory() ? dirSize(p2) : statSync4(p2).size);
|
|
48257
|
+
}, 0);
|
|
48258
|
+
var initDesktop = async (args) => {
|
|
48259
|
+
const root = projectRoot();
|
|
48260
|
+
const existing = readJson(join16(root, "app.json")) ?? {};
|
|
48261
|
+
const name = flagStr(args, "name") ?? existing.name ?? basename5(root);
|
|
48262
|
+
const window2 = {
|
|
48263
|
+
width: 1280,
|
|
48264
|
+
height: 800,
|
|
48265
|
+
minWidth: 640,
|
|
48266
|
+
minHeight: 480,
|
|
48267
|
+
...existing.desktop?.window,
|
|
48268
|
+
...flagBool(args, "fullscreen") ? { fullscreen: true } : {}
|
|
48269
|
+
};
|
|
48270
|
+
const config = { ...existing, name, desktop: { ...existing.desktop, window: window2 } };
|
|
48271
|
+
writeJson(join16(root, "app.json"), config);
|
|
48272
|
+
log(`${c.green("written")} app.json (desktop.window ${window2.width}×${window2.height}${window2.fullscreen ? ", fullscreen" : ""})`);
|
|
48273
|
+
log(`Next: ${c.bold("lecodes dev --desktop")} to develop, ${c.bold("lecodes app build desktop")} to package.`);
|
|
48274
|
+
};
|
|
48275
|
+
var cleanFileName = (name) => name.replace(/[<>:"/\\|?*\u0000-\u001f]/g, "").replace(/[. ]+$/, "").trim() || "app";
|
|
48276
|
+
var buildDesktop = async (args) => {
|
|
48277
|
+
const root = projectRoot();
|
|
48278
|
+
const config = readJson(join16(root, "app.json")) ?? {};
|
|
48279
|
+
const name = (config.name ?? basename5(root)).trim() || "app";
|
|
48280
|
+
const slug = flagStr(args, "slug") ?? cleanFileName(name);
|
|
48281
|
+
const out = resolve5(root, flagStr(args, "o", "out") ?? join16(root, "dist", slug));
|
|
48282
|
+
if (existsSync15(out)) {
|
|
48283
|
+
const entries = readdirSync6(out);
|
|
48284
|
+
const looksLikeBuild = entries.length === 0 || entries.includes("app.js");
|
|
48285
|
+
if (!looksLikeBuild) {
|
|
48286
|
+
throw new CliError(`${out} exists and doesn't look like a previous desktop build — pick another -o or clear it yourself.`);
|
|
48287
|
+
}
|
|
48288
|
+
}
|
|
48289
|
+
if (config.desktop?.entry && flagStr(args, "entry") === undefined)
|
|
48290
|
+
args.flags.entry = config.desktop.entry;
|
|
48291
|
+
note(`Building "${name}" for desktop…`);
|
|
48292
|
+
const { js, resources } = await buildBundle(args, { remotePatterns: config.resources?.remote ?? [] });
|
|
48293
|
+
const wanted = new Set([...js.matchAll(/fetchLocal\("([^"]+)"\)/g)].map((m) => m[1]));
|
|
48294
|
+
const shipped = resources.filter((r2) => !r2.remote && wanted.has(r2.name));
|
|
48295
|
+
const byName = new Map;
|
|
48296
|
+
for (const r2 of resources) {
|
|
48297
|
+
if (r2.remote || !wanted.has(r2.name))
|
|
48298
|
+
continue;
|
|
48299
|
+
const set = byName.get(r2.name) ?? new Set;
|
|
48300
|
+
set.add(r2.path);
|
|
48301
|
+
byName.set(r2.name, set);
|
|
48302
|
+
}
|
|
48303
|
+
const collisions = [...byName.entries()].filter(([, paths]) => paths.size > 1);
|
|
48304
|
+
if (collisions.length > 0) {
|
|
48305
|
+
throw new CliError("Asset file names must be unique across the whole project — the bundle refers to assets " + `by BASENAME, so same-named files silently replace each other in the built app:
|
|
48306
|
+
` + collisions.map(([n2, paths]) => ` ${n2}
|
|
48307
|
+
` + [...paths].map((p2) => ` ${p2}`).join(`
|
|
48308
|
+
`)).join(`
|
|
48309
|
+
`));
|
|
48310
|
+
}
|
|
48311
|
+
const shippedNames = new Set(shipped.map((r2) => r2.name));
|
|
48312
|
+
const missing = [...wanted].filter((n2) => !shippedNames.has(n2));
|
|
48313
|
+
if (missing.length > 0) {
|
|
48314
|
+
throw new CliError(`The bundle references assets with no local file: ${missing.join(", ")}`);
|
|
48315
|
+
}
|
|
48316
|
+
const exe = await resolveDesktopExe();
|
|
48317
|
+
const hostDir = dirname11(exe);
|
|
48318
|
+
const version = process.env.LECODES_DESKTOP_EXE ? "local build" : selectedDesktopVersion();
|
|
48319
|
+
rmSync3(out, { recursive: true, force: true });
|
|
48320
|
+
mkdirSync10(out, { recursive: true });
|
|
48321
|
+
for (const entry of readdirSync6(hostDir, { withFileTypes: true })) {
|
|
48322
|
+
const keep = entry.isFile() && entry.name.toLowerCase().endsWith(".dll") || entry.isDirectory() && (entry.name === "assets" || entry.name === "fonts");
|
|
48323
|
+
if (keep)
|
|
48324
|
+
cpSync2(join16(hostDir, entry.name), join16(out, entry.name), { recursive: true });
|
|
48325
|
+
}
|
|
48326
|
+
if (!existsSync15(join16(out, "assets", "initial.js"))) {
|
|
48327
|
+
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.)`);
|
|
48328
|
+
}
|
|
48329
|
+
const exeName2 = process.platform === "win32" ? `${cleanFileName(name)}.exe` : cleanFileName(name);
|
|
48330
|
+
cpSync2(exe, join16(out, exeName2));
|
|
48331
|
+
note(` host: LeCodes Desktop ${version}`);
|
|
48332
|
+
writeFileSync12(join16(out, "app.js"), js);
|
|
48333
|
+
let assetBytes = 0, assetCount = 0;
|
|
48334
|
+
for (const r2 of shipped) {
|
|
48335
|
+
if (!r2.absPath || !existsSync15(r2.absPath) || !statSync4(r2.absPath).isFile()) {
|
|
48336
|
+
warn(`asset "${r2.name}" (${r2.path}) has no local file — skipped`);
|
|
48337
|
+
continue;
|
|
48338
|
+
}
|
|
48339
|
+
cpSync2(r2.absPath, join16(out, r2.name));
|
|
48340
|
+
assetBytes += statSync4(r2.absPath).size;
|
|
48341
|
+
assetCount++;
|
|
48342
|
+
}
|
|
48343
|
+
note(` assets: ${assetCount} files, ${mb(assetBytes)}`);
|
|
48344
|
+
writeJson(join16(out, "app.json"), { name, ...config.desktop ? { desktop: { window: config.desktop.window } } : {} });
|
|
48345
|
+
if (config.icon) {
|
|
48346
|
+
await writeIco(root, config, join16(out, "app.ico"));
|
|
48347
|
+
note(" app.ico rendered from " + config.icon);
|
|
48348
|
+
}
|
|
48349
|
+
writeFileSync12(join16(out, "README.txt"), "\uFEFF" + readmeText(name, exeName2, version));
|
|
48350
|
+
success(`Portable build: ${out} (${mb(dirSize(out))})`);
|
|
48351
|
+
if (flagBool(args, "probe"))
|
|
48352
|
+
await probeBuild(out, exeName2, config, args);
|
|
48353
|
+
if (flagBool(args, "zip"))
|
|
48354
|
+
zipBuild(out);
|
|
48355
|
+
if (!flagBool(args, "probe"))
|
|
48356
|
+
note(`Tip: --probe renders the staged folder headless to verify exactly what ships.`);
|
|
48357
|
+
};
|
|
48358
|
+
var writeIco = async (root, config, dest) => {
|
|
48359
|
+
const { image: image2 } = await loadIconMaster(root, config);
|
|
48360
|
+
const sizes = [16, 24, 32, 48, 64, 256];
|
|
48361
|
+
const pngs = [];
|
|
48362
|
+
for (const s2 of sizes) {
|
|
48363
|
+
const frame = image2.clone();
|
|
48364
|
+
frame.resize({ w: s2, h: s2 });
|
|
48365
|
+
pngs.push(Buffer.from(await frame.getBuffer("image/png")));
|
|
48366
|
+
}
|
|
48367
|
+
const header = Buffer.alloc(6 + sizes.length * 16);
|
|
48368
|
+
header.writeUInt16LE(0, 0);
|
|
48369
|
+
header.writeUInt16LE(1, 2);
|
|
48370
|
+
header.writeUInt16LE(sizes.length, 4);
|
|
48371
|
+
let offset = header.length;
|
|
48372
|
+
sizes.forEach((s2, i2) => {
|
|
48373
|
+
const e2 = 6 + i2 * 16;
|
|
48374
|
+
header.writeUInt8(s2 === 256 ? 0 : s2, e2);
|
|
48375
|
+
header.writeUInt8(s2 === 256 ? 0 : s2, e2 + 1);
|
|
48376
|
+
header.writeUInt16LE(1, e2 + 4);
|
|
48377
|
+
header.writeUInt16LE(32, e2 + 6);
|
|
48378
|
+
header.writeUInt32LE(pngs[i2].length, e2 + 8);
|
|
48379
|
+
header.writeUInt32LE(offset, e2 + 12);
|
|
48380
|
+
offset += pngs[i2].length;
|
|
48381
|
+
});
|
|
48382
|
+
writeFileSync12(dest, Buffer.concat([header, ...pngs]));
|
|
48383
|
+
};
|
|
48384
|
+
var probeBuild = async (out, exeName2, config, args) => {
|
|
48385
|
+
const win = config.desktop?.window;
|
|
48386
|
+
const frames = Number(flagStr(args, "frames")) || 90;
|
|
48387
|
+
const shot = join16(dirname11(out), `${basename5(out)}-probe.png`);
|
|
48388
|
+
note(`Probe: hidden render of the staged folder (${frames} frames)…`);
|
|
48389
|
+
await new Promise((done, fail) => {
|
|
48390
|
+
const child = spawn3(join16(out, exeName2), [], {
|
|
48391
|
+
cwd: out,
|
|
48392
|
+
env: {
|
|
48393
|
+
...process.env,
|
|
48394
|
+
CREATOR_HIDDEN: "1",
|
|
48395
|
+
CREATOR_MAX_FRAMES: String(frames),
|
|
48396
|
+
CREATOR_DUMP_PNG: shot,
|
|
48397
|
+
CREATOR_WINDOW_SIZE: `${win?.width ?? 1280}x${win?.height ?? 720}`
|
|
48398
|
+
},
|
|
48399
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
48400
|
+
});
|
|
48401
|
+
const tail = [];
|
|
48402
|
+
const sink = (d) => {
|
|
48403
|
+
tail.push(String(d));
|
|
48404
|
+
while (tail.length > 60)
|
|
48405
|
+
tail.shift();
|
|
48406
|
+
};
|
|
48407
|
+
child.stdout.on("data", sink);
|
|
48408
|
+
child.stderr.on("data", sink);
|
|
48409
|
+
const killer = setTimeout(() => {
|
|
48410
|
+
child.kill();
|
|
48411
|
+
fail(new CliError("The probe timed out after 120s — raise --frames headroom or check the app."));
|
|
48412
|
+
}, 120000);
|
|
48413
|
+
child.on("error", (e2) => {
|
|
48414
|
+
clearTimeout(killer);
|
|
48415
|
+
fail(new CliError(`Probe couldn't run: ${e2.message}`));
|
|
48416
|
+
});
|
|
48417
|
+
child.on("exit", (code) => {
|
|
48418
|
+
clearTimeout(killer);
|
|
48419
|
+
if (code === 0 && existsSync15(shot)) {
|
|
48420
|
+
success(`Probe: ${shot}`);
|
|
48421
|
+
done();
|
|
48422
|
+
} else {
|
|
48423
|
+
const detail = tail.length > 0 ? `
|
|
48424
|
+
${tail.join("").trimEnd()}` : "";
|
|
48425
|
+
fail(new CliError(`Probe failed (exit ${code}).${detail}`));
|
|
48426
|
+
}
|
|
48427
|
+
});
|
|
48428
|
+
});
|
|
48429
|
+
};
|
|
48430
|
+
var zipBuild = (out) => {
|
|
48431
|
+
const zip = `${out}.zip`;
|
|
48432
|
+
rmSync3(zip, { force: true });
|
|
48433
|
+
note("Zipping…");
|
|
48434
|
+
if (process.platform === "win32") {
|
|
48435
|
+
const q = (s2) => s2.replaceAll("'", "''");
|
|
48436
|
+
execFileSync3("powershell", ["-NoProfile", "-Command", [
|
|
48437
|
+
"Add-Type -AssemblyName System.IO.Compression.FileSystem;",
|
|
48438
|
+
`$src='${q(out)}'; $base=Split-Path $src -Parent;`,
|
|
48439
|
+
`$zip=[System.IO.Compression.ZipFile]::Open('${q(zip)}','Create');`,
|
|
48440
|
+
"Get-ChildItem $src -Recurse -File | ForEach-Object {",
|
|
48441
|
+
" $rel=$_.FullName.Substring($base.Length+1).Replace('\\','/');",
|
|
48442
|
+
" [void][System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip,$_.FullName,$rel,'Optimal') };",
|
|
48443
|
+
"$zip.Dispose()"
|
|
48444
|
+
].join(" ")], { stdio: ["ignore", "ignore", "pipe"] });
|
|
48445
|
+
} else {
|
|
48446
|
+
execFileSync3("zip", ["-rq", zip, basename5(out)], { cwd: dirname11(out) });
|
|
48447
|
+
}
|
|
48448
|
+
success(`Zip: ${zip} (${mb(statSync4(zip).size)})`);
|
|
48449
|
+
};
|
|
48450
|
+
var readmeText = (name, exeName2, version) => `${name}
|
|
48451
|
+
${"=".repeat(Math.max(name.length, 4))}
|
|
48452
|
+
|
|
48453
|
+
A LeCodes desktop app. Nothing to install: unpack the WHOLE folder anywhere and run ${exeName2}.
|
|
48454
|
+
Keep the files together — the app reads its assets from this folder.
|
|
48455
|
+
|
|
48456
|
+
On first run Windows may show a SmartScreen prompt (any unsigned app gets one):
|
|
48457
|
+
choose "More info", then "Run anyway".
|
|
48458
|
+
|
|
48459
|
+
Requires 64-bit Windows 10/11.
|
|
48460
|
+
|
|
48461
|
+
Built with LeCodes Desktop ${version}.
|
|
48462
|
+
`;
|
|
48463
|
+
|
|
47987
48464
|
// src/commands/app.ts
|
|
47988
48465
|
var SDK_REPO = "letary/lecodes-ios-sdk";
|
|
47989
48466
|
var SDK_MIN_VERSION = "1.2.0";
|
|
47990
48467
|
var SDK_VARIANTS = ["core", "2d", "3d", "full"];
|
|
47991
48468
|
var SDK_IDENTITY_VERSION = "1.6.0";
|
|
47992
48469
|
var shellSdkVersion = (iosDir) => {
|
|
47993
|
-
const path2 =
|
|
47994
|
-
if (!
|
|
48470
|
+
const path2 = join17(iosDir, "LeCodesRuntime", "Package.swift");
|
|
48471
|
+
if (!existsSync16(path2))
|
|
47995
48472
|
return null;
|
|
47996
|
-
const m = /lecodes-ios-sdk\.git",\s*(?:exact|from):\s*"([^"]+)"/.exec(
|
|
48473
|
+
const m = /lecodes-ios-sdk\.git",\s*(?:exact|from):\s*"([^"]+)"/.exec(readFileSync15(path2, "utf8"));
|
|
47997
48474
|
return m && VERSION_RE.test(m[1]) ? m[1] : null;
|
|
47998
48475
|
};
|
|
47999
48476
|
var IOS_SDK_OPTS = { repo: SDK_REPO, minVersion: SDK_MIN_VERSION, configKey: "ios.sdk" };
|
|
@@ -48001,15 +48478,17 @@ var appInit = async (args) => {
|
|
|
48001
48478
|
const platform = args._[1];
|
|
48002
48479
|
if (platform === "android")
|
|
48003
48480
|
return initAndroid(args);
|
|
48481
|
+
if (platform === "desktop")
|
|
48482
|
+
return initDesktop(args);
|
|
48004
48483
|
if (platform !== "ios") {
|
|
48005
|
-
throw new CliError(platform ? `Unsupported platform "${platform}" — use ios or
|
|
48484
|
+
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>]");
|
|
48006
48485
|
}
|
|
48007
48486
|
const root = projectRoot();
|
|
48008
|
-
const iosDir =
|
|
48009
|
-
if (
|
|
48487
|
+
const iosDir = join17(root, "ios");
|
|
48488
|
+
if (existsSync16(iosDir)) {
|
|
48010
48489
|
throw new CliError("ios/ already exists — run `lecodes app sync` to update it.");
|
|
48011
48490
|
}
|
|
48012
|
-
const existing = readJson(
|
|
48491
|
+
const existing = readJson(join17(root, "app.json")) ?? {};
|
|
48013
48492
|
const variant = flagStr(args, "variant") ?? existing.ios?.variant ?? "auto";
|
|
48014
48493
|
if (variant !== "auto" && !SDK_VARIANTS.includes(variant)) {
|
|
48015
48494
|
throw new CliError(`--variant must be auto, ${SDK_VARIANTS.join(", ")} (got "${variant}")`);
|
|
@@ -48017,7 +48496,7 @@ var appInit = async (args) => {
|
|
|
48017
48496
|
const sdkFlag = flagStr(args, "sdk") ?? existing.ios?.sdk;
|
|
48018
48497
|
const sdkVersion = await resolveSdkVersion(process.env.LECODES_IOS_SDK ?? sdkFlag ?? "latest", null, IOS_SDK_OPTS);
|
|
48019
48498
|
const sdk = sdkFlag === "latest" ? "latest" : sdkVersion;
|
|
48020
|
-
const name = flagStr(args, "name") ?? existing.name ??
|
|
48499
|
+
const name = flagStr(args, "name") ?? existing.name ?? basename6(root);
|
|
48021
48500
|
const config = {
|
|
48022
48501
|
...existing,
|
|
48023
48502
|
name,
|
|
@@ -48031,7 +48510,7 @@ var appInit = async (args) => {
|
|
|
48031
48510
|
buildNumber: existing.ios?.buildNumber ?? 1
|
|
48032
48511
|
}
|
|
48033
48512
|
};
|
|
48034
|
-
writeJson(
|
|
48513
|
+
writeJson(join17(root, "app.json"), config);
|
|
48035
48514
|
log(`${c.green("written")} app.json`);
|
|
48036
48515
|
ensureShellIgnored(root, "ios");
|
|
48037
48516
|
const identity = {
|
|
@@ -48042,9 +48521,9 @@ var appInit = async (args) => {
|
|
|
48042
48521
|
buildNumber: config.ios.buildNumber
|
|
48043
48522
|
};
|
|
48044
48523
|
const write = (rel, content) => {
|
|
48045
|
-
const abs =
|
|
48046
|
-
|
|
48047
|
-
|
|
48524
|
+
const abs = join17(iosDir, rel);
|
|
48525
|
+
mkdirSync11(dirname12(abs), { recursive: true });
|
|
48526
|
+
writeFileSync13(abs, content);
|
|
48048
48527
|
log(`${c.green("created")} ios/${rel}`);
|
|
48049
48528
|
};
|
|
48050
48529
|
write("README.md", IOS_README);
|
|
@@ -48099,23 +48578,23 @@ var ensureSdkResourcesBundle = async (sdkVersion) => {
|
|
|
48099
48578
|
const override = process.env.LECODES_SDK_RESOURCES;
|
|
48100
48579
|
if (override) {
|
|
48101
48580
|
note(`Using local resources bundle ${override} (LECODES_SDK_RESOURCES)`);
|
|
48102
|
-
return
|
|
48581
|
+
return resolve6(override);
|
|
48103
48582
|
}
|
|
48104
|
-
const dir =
|
|
48105
|
-
const bundle =
|
|
48106
|
-
if (
|
|
48583
|
+
const dir = join17(homedir6(), ".lecodes", "sdk-resources", sdkVersion);
|
|
48584
|
+
const bundle = join17(dir, "LeCodesSDKResources.bundle");
|
|
48585
|
+
if (existsSync16(bundle))
|
|
48107
48586
|
return bundle;
|
|
48108
48587
|
const url = `https://github.com/${SDK_REPO}/releases/download/${sdkVersion}/LeCodesSDKResources.bundle.zip`;
|
|
48109
48588
|
note(`Downloading LeCodesSDKResources.bundle (SDK ${sdkVersion})…`);
|
|
48110
48589
|
const res = await fetch(url);
|
|
48111
48590
|
if (!res.ok)
|
|
48112
48591
|
throw new CliError(`Resources bundle download failed (HTTP ${res.status}) — ${url}`);
|
|
48113
|
-
|
|
48114
|
-
const zip =
|
|
48115
|
-
|
|
48116
|
-
|
|
48117
|
-
|
|
48118
|
-
if (!
|
|
48592
|
+
mkdirSync11(dir, { recursive: true });
|
|
48593
|
+
const zip = join17(dir, "LeCodesSDKResources.bundle.zip");
|
|
48594
|
+
writeFileSync13(zip, new Uint8Array(await res.arrayBuffer()));
|
|
48595
|
+
execFileSync4("ditto", ["-x", "-k", zip, dir]);
|
|
48596
|
+
rmSync4(zip);
|
|
48597
|
+
if (!existsSync16(bundle))
|
|
48119
48598
|
throw new CliError(`No LeCodesSDKResources.bundle inside the release asset ${url}`);
|
|
48120
48599
|
return bundle;
|
|
48121
48600
|
};
|
|
@@ -48140,13 +48619,13 @@ var resolveUpdateUrl = (update, root) => {
|
|
|
48140
48619
|
};
|
|
48141
48620
|
var appSync = async (args) => {
|
|
48142
48621
|
const root = projectRoot();
|
|
48143
|
-
const config = readJson(
|
|
48622
|
+
const config = readJson(join17(root, "app.json"));
|
|
48144
48623
|
const platform = args._[1];
|
|
48145
48624
|
if (platform !== undefined && platform !== "ios" && platform !== "android") {
|
|
48146
48625
|
throw new CliError(`Unknown platform "${platform}" — use ios or android.`);
|
|
48147
48626
|
}
|
|
48148
|
-
const hasIos = !!config?.ios &&
|
|
48149
|
-
const hasAndroid = !!config?.android &&
|
|
48627
|
+
const hasIos = !!config?.ios && existsSync16(join17(root, "ios"));
|
|
48628
|
+
const hasAndroid = !!config?.android && existsSync16(join17(root, "android"));
|
|
48150
48629
|
if (platform === "ios" && !hasIos)
|
|
48151
48630
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
48152
48631
|
if (platform === "android" && !hasAndroid)
|
|
@@ -48161,7 +48640,7 @@ var appSync = async (args) => {
|
|
|
48161
48640
|
await syncAndroid(root, config, iosArtifacts);
|
|
48162
48641
|
};
|
|
48163
48642
|
var syncIos = async (root, config) => {
|
|
48164
|
-
const iosDir =
|
|
48643
|
+
const iosDir = join17(root, "ios");
|
|
48165
48644
|
if (!config.ios)
|
|
48166
48645
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
48167
48646
|
const requestedVariant = config.ios.variant ?? "auto";
|
|
@@ -48171,14 +48650,14 @@ var syncIos = async (root, config) => {
|
|
|
48171
48650
|
const linkedSdk = shellSdkVersion(iosDir);
|
|
48172
48651
|
const sdkPin = process.env.LECODES_IOS_SDK ?? config.ios.sdk ?? linkedSdk ?? "latest";
|
|
48173
48652
|
const sdkVersion = await resolveSdkVersion(sdkPin, linkedSdk, IOS_SDK_OPTS);
|
|
48174
|
-
const lockPath =
|
|
48653
|
+
const lockPath = join17(root, "plugins.lock.json");
|
|
48175
48654
|
const oldLock = readJson(lockPath)?.plugins ?? {};
|
|
48176
48655
|
const pluginSpecs = Object.entries(config.plugins ?? {});
|
|
48177
48656
|
const runtimePlugins = [];
|
|
48178
48657
|
const infoValues = {};
|
|
48179
48658
|
const entitlementValues = {};
|
|
48180
48659
|
const newLock = {};
|
|
48181
|
-
const sourcesDir =
|
|
48660
|
+
const sourcesDir = join17(iosDir, "LeCodesRuntime", "Sources");
|
|
48182
48661
|
for (const [id, spec] of pluginSpecs) {
|
|
48183
48662
|
const plugin = resolvePlugin(id, spec, oldLock[id], "ios");
|
|
48184
48663
|
const ios = plugin.manifest.ios;
|
|
@@ -48186,12 +48665,12 @@ var syncIos = async (root, config) => {
|
|
|
48186
48665
|
const register = ios.register;
|
|
48187
48666
|
if (!module || !register)
|
|
48188
48667
|
throw new CliError(`Plugin "${id}": manifest ios.module / ios.register missing`);
|
|
48189
|
-
const src =
|
|
48190
|
-
if (!
|
|
48668
|
+
const src = join17(plugin.dir, ios.sources ?? "ios");
|
|
48669
|
+
if (!existsSync16(src))
|
|
48191
48670
|
throw new CliError(`Plugin "${id}": sources dir ${src} missing`);
|
|
48192
|
-
const dest =
|
|
48193
|
-
|
|
48194
|
-
|
|
48671
|
+
const dest = join17(sourcesDir, module);
|
|
48672
|
+
rmSync4(dest, { recursive: true, force: true });
|
|
48673
|
+
cpSync3(src, dest, { recursive: true });
|
|
48195
48674
|
if (ios.minSdk && VERSION_RE.test(ios.minSdk) && cmpVersion(sdkVersion, ios.minSdk) < 0) {
|
|
48196
48675
|
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).`);
|
|
48197
48676
|
}
|
|
@@ -48206,9 +48685,9 @@ var syncIos = async (root, config) => {
|
|
|
48206
48685
|
log(`${c.green("plugin")} ${id}@${plugin.lock.version} (${plugin.lock.tag} → Sources/${module})`);
|
|
48207
48686
|
}
|
|
48208
48687
|
const keep = new Set(["LeCodesRuntime", ...runtimePlugins.map((p2) => p2.module)]);
|
|
48209
|
-
for (const entry of
|
|
48210
|
-
if (!keep.has(entry) &&
|
|
48211
|
-
|
|
48688
|
+
for (const entry of existsSync16(sourcesDir) ? readdirSync7(sourcesDir) : []) {
|
|
48689
|
+
if (!keep.has(entry) && statSync5(join17(sourcesDir, entry)).isDirectory()) {
|
|
48690
|
+
rmSync4(join17(sourcesDir, entry), { recursive: true });
|
|
48212
48691
|
log(`${c.yellow("removed")} ios/LeCodesRuntime/Sources/${entry} (plugin uninstalled)`);
|
|
48213
48692
|
}
|
|
48214
48693
|
}
|
|
@@ -48216,11 +48695,11 @@ var syncIos = async (root, config) => {
|
|
|
48216
48695
|
if (!Array.isArray(remotePatterns) || remotePatterns.some((p2) => typeof p2 !== "string")) {
|
|
48217
48696
|
throw new CliError('app.json "resources.remote" must be an array of glob strings (e.g. ["videos/**"]).');
|
|
48218
48697
|
}
|
|
48219
|
-
const resourcesDir =
|
|
48220
|
-
|
|
48221
|
-
|
|
48222
|
-
const resources = await compileShell({ _: [], flags: { o:
|
|
48223
|
-
const bundleJs =
|
|
48698
|
+
const resourcesDir = join17(iosDir, "App", "Resources");
|
|
48699
|
+
rmSync4(resourcesDir, { recursive: true, force: true });
|
|
48700
|
+
mkdirSync11(resourcesDir, { recursive: true });
|
|
48701
|
+
const resources = await compileShell({ _: [], flags: { o: join17(resourcesDir, "app.js") } }, { remotePatterns });
|
|
48702
|
+
const bundleJs = readFileSync15(join17(resourcesDir, "app.js"), "utf8");
|
|
48224
48703
|
const { variant, detected } = resolveVariant(requestedVariant, bundleJs);
|
|
48225
48704
|
const WHY = {
|
|
48226
48705
|
core: "UI only",
|
|
@@ -48234,8 +48713,8 @@ var syncIos = async (root, config) => {
|
|
|
48234
48713
|
const sdkProvided = new Set;
|
|
48235
48714
|
if (variant === "3d" || variant === "full") {
|
|
48236
48715
|
const sdkResources = await ensureSdkResourcesBundle(sdkVersion);
|
|
48237
|
-
|
|
48238
|
-
for (const f3 of
|
|
48716
|
+
cpSync3(sdkResources, join17(resourcesDir, "LeCodesSDKResources.bundle"), { recursive: true });
|
|
48717
|
+
for (const f3 of readdirSync7(sdkResources))
|
|
48239
48718
|
if (f3 !== "Info.plist")
|
|
48240
48719
|
sdkProvided.add(f3);
|
|
48241
48720
|
log(`${c.green("resources")} LeCodesSDKResources.bundle (filament) → ios/App/Resources/`);
|
|
@@ -48262,7 +48741,7 @@ var syncIos = async (root, config) => {
|
|
|
48262
48741
|
}
|
|
48263
48742
|
if (candidates.length > 1)
|
|
48264
48743
|
warn(`asset name collision: ${name} (${candidates.map((r2) => r2.path).join(", ")}) — embedding ${candidates[0].path}`);
|
|
48265
|
-
|
|
48744
|
+
cpSync3(candidates[0].absPath, join17(resourcesDir, name));
|
|
48266
48745
|
embedded++;
|
|
48267
48746
|
}
|
|
48268
48747
|
if (embedded > 0)
|
|
@@ -48282,20 +48761,20 @@ var syncIos = async (root, config) => {
|
|
|
48282
48761
|
}
|
|
48283
48762
|
if (shellUuid)
|
|
48284
48763
|
log(`${c.green("identity")} project ${shellUuid} baked into the shell (world identity)`);
|
|
48285
|
-
|
|
48286
|
-
|
|
48287
|
-
|
|
48764
|
+
writeFileSync13(join17(iosDir, "README.md"), IOS_README);
|
|
48765
|
+
writeFileSync13(join17(iosDir, "LeCodesRuntime", "Package.swift"), runtimePackageTemplate(sdkVersion, variant, runtimePlugins));
|
|
48766
|
+
writeFileSync13(join17(sourcesDir, "LeCodesRuntime", "LeCodesRuntime.swift"), runtimeSwiftTemplate(runtimePlugins, shellUuid));
|
|
48288
48767
|
const mergedKeys = {};
|
|
48289
48768
|
for (const [key, values] of Object.entries(infoValues))
|
|
48290
48769
|
mergedKeys[key] = [...new Set(values)].join(" ");
|
|
48291
48770
|
const updateUrl = resolveUpdateUrl(config.update, root);
|
|
48292
|
-
|
|
48771
|
+
writeFileSync13(join17(iosDir, "App", "Info.plist"), infoPlistTemplate(mergedKeys, updateUrl));
|
|
48293
48772
|
if (updateUrl)
|
|
48294
48773
|
log(`${c.green("update")} OTA source ${updateUrl} — checked natively at launch, new code applies next launch`);
|
|
48295
|
-
if (pluginSpecs.length > 0 ||
|
|
48774
|
+
if (pluginSpecs.length > 0 || existsSync16(lockPath))
|
|
48296
48775
|
writeJson(lockPath, { plugins: newLock });
|
|
48297
|
-
const pbxPath =
|
|
48298
|
-
let pbx =
|
|
48776
|
+
const pbxPath = join17(iosDir, "App.xcodeproj", "project.pbxproj");
|
|
48777
|
+
let pbx = readFileSync15(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};`);
|
|
48299
48778
|
const mergedEntitlements = {};
|
|
48300
48779
|
for (const [key, values] of Object.entries(entitlementValues)) {
|
|
48301
48780
|
if (values.every((v) => Array.isArray(v))) {
|
|
@@ -48316,7 +48795,7 @@ var syncIos = async (root, config) => {
|
|
|
48316
48795
|
}
|
|
48317
48796
|
const hasEntitlements = Object.keys(mergedEntitlements).length > 0;
|
|
48318
48797
|
if (pbx.includes("CODE_SIGN_ENTITLEMENTS") || hasEntitlements) {
|
|
48319
|
-
|
|
48798
|
+
writeFileSync13(join17(iosDir, "App", "App.entitlements"), entitlementsTemplate(mergedEntitlements));
|
|
48320
48799
|
if (hasEntitlements)
|
|
48321
48800
|
log(`${c.green("caps")} App/App.entitlements: ${Object.keys(mergedEntitlements).sort().join(", ")}`);
|
|
48322
48801
|
if (!pbx.includes("CODE_SIGN_ENTITLEMENTS")) {
|
|
@@ -48332,7 +48811,7 @@ $2Info.plist,`);
|
|
|
48332
48811
|
}
|
|
48333
48812
|
}
|
|
48334
48813
|
}
|
|
48335
|
-
|
|
48814
|
+
writeFileSync13(pbxPath, pbx);
|
|
48336
48815
|
if (config.icon) {
|
|
48337
48816
|
await writeIosAppIcon(iosDir, await loadIconMaster(root, config));
|
|
48338
48817
|
}
|
|
@@ -48344,7 +48823,7 @@ $2Info.plist,`);
|
|
|
48344
48823
|
var appPlugin = async (args) => {
|
|
48345
48824
|
const sub = args._[1];
|
|
48346
48825
|
const root = projectRoot();
|
|
48347
|
-
const configPath =
|
|
48826
|
+
const configPath = join17(root, "app.json");
|
|
48348
48827
|
const config = readJson(configPath);
|
|
48349
48828
|
if (!config)
|
|
48350
48829
|
throw new CliError("No app.json here — run `lecodes app init <ios|android>` first.");
|
|
@@ -48355,7 +48834,7 @@ var appPlugin = async (args) => {
|
|
|
48355
48834
|
log("No plugins installed.");
|
|
48356
48835
|
return;
|
|
48357
48836
|
}
|
|
48358
|
-
const lock = readJson(
|
|
48837
|
+
const lock = readJson(join17(root, "plugins.lock.json"))?.plugins ?? {};
|
|
48359
48838
|
for (const [id2, spec] of entries) {
|
|
48360
48839
|
const pin = lock[id2] ? c.dim(` ${lock[id2].repo} ${lock[id2].tag} (${lock[id2].commit.slice(0, 7)})`) : c.dim(" (not synced)");
|
|
48361
48840
|
log(`${c.bold(id2)} ${spec}${pin}`);
|
|
@@ -48408,14 +48887,14 @@ var exportOptionsPlist = (method, teamId, destination) => `<?xml version="1.0" e
|
|
|
48408
48887
|
`;
|
|
48409
48888
|
var requireShell = () => {
|
|
48410
48889
|
const root = projectRoot();
|
|
48411
|
-
const config = readJson(
|
|
48412
|
-
if (!config?.ios || !
|
|
48890
|
+
const config = readJson(join17(root, "app.json"));
|
|
48891
|
+
if (!config?.ios || !existsSync16(join17(root, "ios"))) {
|
|
48413
48892
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
48414
48893
|
}
|
|
48415
48894
|
return { root, config };
|
|
48416
48895
|
};
|
|
48417
48896
|
var updateSdkPin = async (root, config, args, checkOnly, platform) => {
|
|
48418
|
-
const [label, repo, minVersion, shellVersion] = platform === "ios" ? ["lecodes-ios-sdk", SDK_REPO, SDK_MIN_VERSION, () => shellSdkVersion(
|
|
48897
|
+
const [label, repo, minVersion, shellVersion] = platform === "ios" ? ["lecodes-ios-sdk", SDK_REPO, SDK_MIN_VERSION, () => shellSdkVersion(join17(root, "ios"))] : ["lecodes-android-sdk", ANDROID_SDK_REPO, ANDROID_SDK_MIN_VERSION, () => shellSdkVersionAndroid(join17(root, "android"))];
|
|
48419
48898
|
const section = config[platform];
|
|
48420
48899
|
const wanted = flagStr(args, "sdk");
|
|
48421
48900
|
let changed = false;
|
|
@@ -48446,19 +48925,19 @@ var updateSdkPin = async (root, config, args, checkOnly, platform) => {
|
|
|
48446
48925
|
}
|
|
48447
48926
|
if (!checkOnly && nextPin !== pin) {
|
|
48448
48927
|
section.sdk = nextPin;
|
|
48449
|
-
writeJson(
|
|
48928
|
+
writeJson(join17(root, "app.json"), config);
|
|
48450
48929
|
}
|
|
48451
48930
|
return changed;
|
|
48452
48931
|
};
|
|
48453
48932
|
var appUpdate = async (args) => {
|
|
48454
48933
|
const root = projectRoot();
|
|
48455
|
-
const config = readJson(
|
|
48934
|
+
const config = readJson(join17(root, "app.json"));
|
|
48456
48935
|
const platform = args._[1];
|
|
48457
48936
|
if (platform !== undefined && platform !== "ios" && platform !== "android") {
|
|
48458
48937
|
throw new CliError(`Unknown platform "${platform}" — use ios or android.`);
|
|
48459
48938
|
}
|
|
48460
|
-
const hasIos = !!config?.ios &&
|
|
48461
|
-
const hasAndroid = !!config?.android &&
|
|
48939
|
+
const hasIos = !!config?.ios && existsSync16(join17(root, "ios"));
|
|
48940
|
+
const hasAndroid = !!config?.android && existsSync16(join17(root, "android"));
|
|
48462
48941
|
if (platform === "ios" && !hasIos)
|
|
48463
48942
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
48464
48943
|
if (platform === "android" && !hasAndroid)
|
|
@@ -48476,7 +48955,7 @@ var appUpdate = async (args) => {
|
|
|
48476
48955
|
changed = await updateSdkPin(root, config, args, checkOnly, "ios") || changed;
|
|
48477
48956
|
if (doAndroid)
|
|
48478
48957
|
changed = await updateSdkPin(root, config, args, checkOnly, "android") || changed;
|
|
48479
|
-
const lockPath =
|
|
48958
|
+
const lockPath = join17(root, "plugins.lock.json");
|
|
48480
48959
|
const lock = readJson(lockPath)?.plugins ?? {};
|
|
48481
48960
|
const specs = Object.entries(config.plugins ?? {});
|
|
48482
48961
|
if (specs.length > 0 && !flagBool(args, "no-plugins")) {
|
|
@@ -48505,7 +48984,7 @@ var appUpdate = async (args) => {
|
|
|
48505
48984
|
log(`${c.green("plugin")} ${id} ${entry?.tag ?? "—"} → ${c.bold(tag)}`);
|
|
48506
48985
|
changed = true;
|
|
48507
48986
|
}
|
|
48508
|
-
if (!checkOnly &&
|
|
48987
|
+
if (!checkOnly && existsSync16(lockPath))
|
|
48509
48988
|
writeJson(lockPath, { plugins: kept });
|
|
48510
48989
|
}
|
|
48511
48990
|
log("");
|
|
@@ -48525,9 +49004,9 @@ var appUpdate = async (args) => {
|
|
|
48525
49004
|
};
|
|
48526
49005
|
var appDoctor = async (_args) => {
|
|
48527
49006
|
const root = projectRoot();
|
|
48528
|
-
const config = readJson(
|
|
48529
|
-
const hasIos = !!config?.ios &&
|
|
48530
|
-
const hasAndroid = !!config?.android &&
|
|
49007
|
+
const config = readJson(join17(root, "app.json"));
|
|
49008
|
+
const hasIos = !!config?.ios && existsSync16(join17(root, "ios"));
|
|
49009
|
+
const hasAndroid = !!config?.android && existsSync16(join17(root, "android"));
|
|
48531
49010
|
if (!hasIos && !hasAndroid) {
|
|
48532
49011
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
48533
49012
|
}
|
|
@@ -48538,14 +49017,14 @@ var appDoctor = async (_args) => {
|
|
|
48538
49017
|
failed++;
|
|
48539
49018
|
};
|
|
48540
49019
|
if (config?.icon) {
|
|
48541
|
-
check(
|
|
49020
|
+
check(existsSync16(join17(root, config.icon)), `Icon master ${config.icon}`, `app.json "icon" points at a missing file — sync will fail`);
|
|
48542
49021
|
}
|
|
48543
49022
|
if (hasIos)
|
|
48544
49023
|
await doctorIosChecks(root, config, check);
|
|
48545
49024
|
if (hasAndroid) {
|
|
48546
49025
|
await doctorAndroidChecks(root, config, check);
|
|
48547
49026
|
if (!hasIos) {
|
|
48548
|
-
const lock = readJson(
|
|
49027
|
+
const lock = readJson(join17(root, "plugins.lock.json"))?.plugins ?? {};
|
|
48549
49028
|
const wanted = Object.keys(config.plugins ?? {});
|
|
48550
49029
|
const inSync = wanted.every((id) => lock[id]) && Object.keys(lock).every((id) => wanted.includes(id));
|
|
48551
49030
|
check(wanted.length === 0 || inSync, "Plugins synced", "run `lecodes app sync`");
|
|
@@ -48564,22 +49043,22 @@ var doctorIosChecks = async (root, config, check) => {
|
|
|
48564
49043
|
} catch {}
|
|
48565
49044
|
check(xcodeVersion !== "", `Xcode CLI (${xcodeVersion || "not found"})`, "install Xcode + command line tools");
|
|
48566
49045
|
check(!!config.ios.teamId, "Development team set", "lecodes app init wrote app.json — fill ios.teamId", false);
|
|
48567
|
-
const iconDir =
|
|
48568
|
-
const hasIcon =
|
|
49046
|
+
const iconDir = join17(root, "ios", "App", "Assets.xcassets", "AppIcon.appiconset");
|
|
49047
|
+
const hasIcon = existsSync16(iconDir) && readdirSync7(iconDir).some((f3) => f3.endsWith(".png"));
|
|
48569
49048
|
check(hasIcon, "App icon present", "drop a 1024×1024 PNG into ios/App/Assets.xcassets/AppIcon.appiconset/ (App Store requires it)", false);
|
|
48570
|
-
const bundle =
|
|
48571
|
-
check(
|
|
49049
|
+
const bundle = join17(root, "ios", "App", "Resources", "app.js");
|
|
49050
|
+
check(existsSync16(bundle), "Bundled app.js", "run `lecodes app sync`");
|
|
48572
49051
|
const requestedVariant = config.ios.variant ?? "auto";
|
|
48573
49052
|
const validVariant = requestedVariant === "auto" || SDK_VARIANTS.includes(requestedVariant);
|
|
48574
49053
|
check(validVariant, `ios.variant "${requestedVariant}"`, `use "auto" or one of ${SDK_VARIANTS.join(", ")}`);
|
|
48575
|
-
if (
|
|
48576
|
-
const { variant } = resolveVariant(requestedVariant,
|
|
48577
|
-
const pkg =
|
|
49054
|
+
if (existsSync16(bundle) && validVariant) {
|
|
49055
|
+
const { variant } = resolveVariant(requestedVariant, readFileSync15(bundle, "utf8"));
|
|
49056
|
+
const pkg = readFileSync15(join17(root, "ios", "LeCodesRuntime", "Package.swift"), "utf8");
|
|
48578
49057
|
const needsResources = variant === "3d" || variant === "full";
|
|
48579
|
-
const hasResources =
|
|
49058
|
+
const hasResources = existsSync16(join17(root, "ios", "App", "Resources", "LeCodesSDKResources.bundle"));
|
|
48580
49059
|
check(pkg.includes(`"LeCodesSDK-${variant}"`) && (!needsResources || hasResources), `SDK variant ${variant}${needsResources ? " (+ filament resources)" : ""}`, "run `lecodes app sync`");
|
|
48581
49060
|
}
|
|
48582
|
-
const linked = shellSdkVersion(
|
|
49061
|
+
const linked = shellSdkVersion(join17(root, "ios"));
|
|
48583
49062
|
const pin = config.ios.sdk ?? "latest";
|
|
48584
49063
|
check(!!linked, `SDK ${linked ?? "version unreadable"}`, "run `lecodes app sync`");
|
|
48585
49064
|
if (linked && pin !== "latest") {
|
|
@@ -48591,24 +49070,24 @@ var doctorIosChecks = async (root, config, check) => {
|
|
|
48591
49070
|
check(cmpVersion(linked, latest) >= 0, `SDK is the latest release (${latest})`, "run `lecodes app update`", false);
|
|
48592
49071
|
} catch {}
|
|
48593
49072
|
}
|
|
48594
|
-
const lock = readJson(
|
|
49073
|
+
const lock = readJson(join17(root, "plugins.lock.json"))?.plugins ?? {};
|
|
48595
49074
|
const wanted = Object.keys(config.plugins ?? {});
|
|
48596
49075
|
const inSync = wanted.every((id) => lock[id]) && Object.keys(lock).every((id) => wanted.includes(id));
|
|
48597
49076
|
check(wanted.length === 0 || inSync, "Plugins synced", "run `lecodes app sync`");
|
|
48598
|
-
const pbxFile =
|
|
48599
|
-
if (
|
|
48600
|
-
check(
|
|
49077
|
+
const pbxFile = join17(root, "ios", "App.xcodeproj", "project.pbxproj");
|
|
49078
|
+
if (existsSync16(pbxFile) && readFileSync15(pbxFile, "utf8").includes("CODE_SIGN_ENTITLEMENTS")) {
|
|
49079
|
+
check(existsSync16(join17(root, "ios", "App", "App.entitlements")), "App/App.entitlements present", "run `lecodes app sync`");
|
|
48601
49080
|
}
|
|
48602
49081
|
if (wanted.includes("push")) {
|
|
48603
|
-
const runtimeSwift =
|
|
48604
|
-
const baked =
|
|
49082
|
+
const runtimeSwift = join17(root, "ios", "LeCodesRuntime", "Sources", "LeCodesRuntime", "LeCodesRuntime.swift");
|
|
49083
|
+
const baked = existsSync16(runtimeSwift) && readFileSync15(runtimeSwift, "utf8").includes("bootProjectUuid");
|
|
48605
49084
|
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);
|
|
48606
49085
|
}
|
|
48607
49086
|
if (config.update) {
|
|
48608
|
-
const plist =
|
|
49087
|
+
const plist = readFileSync15(join17(root, "ios", "App", "Info.plist"), "utf8");
|
|
48609
49088
|
check(plist.includes("<key>LeCodesUpdateURL</key>"), "OTA update URL in Info.plist", "run `lecodes app sync`", false);
|
|
48610
49089
|
}
|
|
48611
|
-
const asc = readJson(
|
|
49090
|
+
const asc = readJson(join17(homedir6(), ".lecodes", "asc.json"));
|
|
48612
49091
|
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);
|
|
48613
49092
|
};
|
|
48614
49093
|
var ARCHIVE_PATH = "ios/build/App.xcarchive";
|
|
@@ -48616,8 +49095,10 @@ var appBuild = async (args) => {
|
|
|
48616
49095
|
const platform = args._[1];
|
|
48617
49096
|
if (platform === "android")
|
|
48618
49097
|
return buildAndroid(args);
|
|
49098
|
+
if (platform === "desktop")
|
|
49099
|
+
return buildDesktop(args);
|
|
48619
49100
|
if (platform !== undefined && platform !== "ios") {
|
|
48620
|
-
throw new CliError(`Unknown platform "${platform}" — use ios or
|
|
49101
|
+
throw new CliError(`Unknown platform "${platform}" — use ios, android or desktop.`);
|
|
48621
49102
|
}
|
|
48622
49103
|
const { root, config } = requireShell();
|
|
48623
49104
|
const configuration = flagStr(args, "configuration") ?? "Release";
|
|
@@ -48642,8 +49123,8 @@ var appBuild = async (args) => {
|
|
|
48642
49123
|
success(`Archive: ios/build/App.xcarchive`);
|
|
48643
49124
|
if (flagBool(args, "no-export"))
|
|
48644
49125
|
return;
|
|
48645
|
-
const optionsPath =
|
|
48646
|
-
|
|
49126
|
+
const optionsPath = join17(root, "ios", "build", "ExportOptions.plist");
|
|
49127
|
+
writeFileSync13(optionsPath, exportOptionsPlist(method, config.ios.teamId ?? ""));
|
|
48647
49128
|
note(`Exporting (.ipa, method ${method})…`);
|
|
48648
49129
|
runTool(root, "xcodebuild", [
|
|
48649
49130
|
"-exportArchive",
|
|
@@ -48662,10 +49143,10 @@ var appUpload = async (args) => {
|
|
|
48662
49143
|
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).");
|
|
48663
49144
|
}
|
|
48664
49145
|
const { root, config } = requireShell();
|
|
48665
|
-
if (!
|
|
49146
|
+
if (!existsSync16(join17(root, ARCHIVE_PATH))) {
|
|
48666
49147
|
throw new CliError("No archive at ios/build/App.xcarchive — run `lecodes app build ios` first.");
|
|
48667
49148
|
}
|
|
48668
|
-
const ascPath =
|
|
49149
|
+
const ascPath = join17(homedir6(), ".lecodes", "asc.json");
|
|
48669
49150
|
const stored = readJson(ascPath) ?? {};
|
|
48670
49151
|
const keyPath = flagStr(args, "key-path") ?? stored.keyPath;
|
|
48671
49152
|
const keyId = flagStr(args, "key-id") ?? stored.keyId;
|
|
@@ -48674,15 +49155,15 @@ var appUpload = async (args) => {
|
|
|
48674
49155
|
throw new CliError(`App Store Connect API key needed: --key-path <AuthKey.p8> --key-id <id> --issuer-id <id>
|
|
48675
49156
|
(create one at appstoreconnect.apple.com → Users and Access → Integrations; stored in ~/.lecodes/asc.json after first use)`);
|
|
48676
49157
|
}
|
|
48677
|
-
if (!
|
|
49158
|
+
if (!existsSync16(keyPath))
|
|
48678
49159
|
throw new CliError(`API key file not found: ${keyPath}`);
|
|
48679
49160
|
if (flagStr(args, "key-path")) {
|
|
48680
|
-
|
|
49161
|
+
mkdirSync11(dirname12(ascPath), { recursive: true });
|
|
48681
49162
|
writeJson(ascPath, { keyPath, keyId, issuerId });
|
|
48682
49163
|
note("Stored the key reference in ~/.lecodes/asc.json");
|
|
48683
49164
|
}
|
|
48684
|
-
const optionsPath =
|
|
48685
|
-
|
|
49165
|
+
const optionsPath = join17(root, "ios", "build", "UploadOptions.plist");
|
|
49166
|
+
writeFileSync13(optionsPath, exportOptionsPlist("app-store-connect", config.ios.teamId ?? "", "upload"));
|
|
48686
49167
|
note("Uploading to App Store Connect… (processing continues on Apple's side)");
|
|
48687
49168
|
runTool(root, "xcodebuild", [
|
|
48688
49169
|
"-exportArchive",
|
|
@@ -48693,14 +49174,14 @@ var appUpload = async (args) => {
|
|
|
48693
49174
|
"-exportPath",
|
|
48694
49175
|
"ios/build/upload",
|
|
48695
49176
|
"-authenticationKeyPath",
|
|
48696
|
-
|
|
49177
|
+
resolve6(keyPath),
|
|
48697
49178
|
"-authenticationKeyID",
|
|
48698
49179
|
keyId,
|
|
48699
49180
|
"-authenticationKeyIssuerID",
|
|
48700
49181
|
issuerId,
|
|
48701
49182
|
"-allowProvisioningUpdates"
|
|
48702
49183
|
]);
|
|
48703
|
-
const configPath =
|
|
49184
|
+
const configPath = join17(root, "app.json");
|
|
48704
49185
|
const fresh = readJson(configPath);
|
|
48705
49186
|
fresh.ios.buildNumber = (fresh.ios.buildNumber ?? 1) + 1;
|
|
48706
49187
|
writeJson(configPath, fresh);
|
|
@@ -48711,19 +49192,19 @@ var appOpen = async (args) => {
|
|
|
48711
49192
|
const root = projectRoot();
|
|
48712
49193
|
let platform = args._[1];
|
|
48713
49194
|
if (!platform) {
|
|
48714
|
-
const hasIos =
|
|
48715
|
-
const hasAndroid =
|
|
49195
|
+
const hasIos = existsSync16(join17(root, "ios"));
|
|
49196
|
+
const hasAndroid = existsSync16(join17(root, "android"));
|
|
48716
49197
|
if (hasIos && hasAndroid)
|
|
48717
49198
|
throw new CliError("Both shells exist — `lecodes app open ios` or `lecodes app open android`.");
|
|
48718
49199
|
platform = hasAndroid ? "android" : "ios";
|
|
48719
49200
|
}
|
|
48720
49201
|
if (platform === "ios") {
|
|
48721
|
-
const proj =
|
|
48722
|
-
if (!
|
|
49202
|
+
const proj = join17(root, "ios", "App.xcodeproj");
|
|
49203
|
+
if (!existsSync16(proj))
|
|
48723
49204
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
48724
49205
|
if (process.platform !== "darwin")
|
|
48725
49206
|
throw new CliError("Opening Xcode requires macOS.");
|
|
48726
|
-
|
|
49207
|
+
execFileSync4("open", [proj], { stdio: "ignore" });
|
|
48727
49208
|
success("Opened ios/App.xcodeproj in Xcode.");
|
|
48728
49209
|
return;
|
|
48729
49210
|
}
|
|
@@ -48749,15 +49230,15 @@ var app = async (args) => {
|
|
|
48749
49230
|
return appUpload(args);
|
|
48750
49231
|
if (sub === "open")
|
|
48751
49232
|
return appOpen(args);
|
|
48752
|
-
throw new CliError("Usage: lecodes app <init ios|android | sync | update | open | plugin list|add|remove | doctor | build ios|android | upload ios> — see `lecodes help`.");
|
|
49233
|
+
throw new CliError("Usage: lecodes app <init ios|android|desktop | sync | update | open | plugin list|add|remove | doctor | build ios|android|desktop | upload ios> — see `lecodes help`.");
|
|
48753
49234
|
};
|
|
48754
49235
|
|
|
48755
49236
|
// src/commands/clone.ts
|
|
48756
49237
|
init_api();
|
|
48757
49238
|
init_config();
|
|
48758
49239
|
init_manifest();
|
|
48759
|
-
import { existsSync as
|
|
48760
|
-
import { resolve as
|
|
49240
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync13, readdirSync as readdirSync9 } from "node:fs";
|
|
49241
|
+
import { resolve as resolve7 } from "node:path";
|
|
48761
49242
|
init_types4();
|
|
48762
49243
|
init_ignore();
|
|
48763
49244
|
init_util();
|
|
@@ -48778,11 +49259,11 @@ var clone3 = async (args) => {
|
|
|
48778
49259
|
const uuid = parseUuid(args._[0]);
|
|
48779
49260
|
const project = await getProject(apiUrl, token, uuid);
|
|
48780
49261
|
const dir = args._[1] ?? slug(project.name);
|
|
48781
|
-
const root =
|
|
48782
|
-
if (
|
|
49262
|
+
const root = resolve7(dir);
|
|
49263
|
+
if (existsSync18(root) && readdirSync9(root).length > 0) {
|
|
48783
49264
|
throw new CliError(`Directory "${dir}" already exists and is not empty.`);
|
|
48784
49265
|
}
|
|
48785
|
-
|
|
49266
|
+
mkdirSync13(root, { recursive: true });
|
|
48786
49267
|
info(c.dim(`Fetching project ${c.bold(project.name)}…`));
|
|
48787
49268
|
const files = await materializeAssets(root, project, apiUrl, token, progress);
|
|
48788
49269
|
endProgress();
|
|
@@ -48804,8 +49285,8 @@ init_config();
|
|
|
48804
49285
|
init_manifest();
|
|
48805
49286
|
init_types4();
|
|
48806
49287
|
init_ignore();
|
|
48807
|
-
import { existsSync as
|
|
48808
|
-
import { join as
|
|
49288
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync14, readdirSync as readdirSync10, writeFileSync as writeFileSync15 } from "node:fs";
|
|
49289
|
+
import { join as join19, resolve as resolve8 } from "node:path";
|
|
48809
49290
|
|
|
48810
49291
|
// src/commands/projectTemplates.ts
|
|
48811
49292
|
var appClaudeMd = (name) => `# ${name} — LeCodes app
|
|
@@ -48861,11 +49342,28 @@ optional \`expect\` (\`screen\`, \`node\`/\`notNode\`, \`text\`/\`notText\`,
|
|
|
48861
49342
|
with \`"fixtures": "fixtures.json"\` in its envelope.
|
|
48862
49343
|
- \`fill\` fills every empty input — use it before tapping a disabled-until-filled submit.
|
|
48863
49344
|
|
|
49345
|
+
## SDK documentation
|
|
49346
|
+
|
|
49347
|
+
\`.lecodes/types/\` is the exact API surface (signatures only). For semantics and examples,
|
|
49348
|
+
fetch the docs — they are served as raw markdown for AI tools:
|
|
49349
|
+
|
|
49350
|
+
- https://le.codes/llms.txt — the docs map; fetch it first to find the right page
|
|
49351
|
+
- https://le.codes/en/docs/api/<area>/<page>.md — one reference page as raw markdown
|
|
49352
|
+
(e.g. \`ui/styling.md\`, \`2d/scene2d.md\`, \`3d/scene.md\`, \`runtime/storage.md\`)
|
|
49353
|
+
- https://le.codes/en/docs/guide/<page>.md — guide pages (project anatomy, CLI, tracks)
|
|
49354
|
+
- https://le.codes/llms-full.txt — everything in one file (large; prefer per-page)
|
|
49355
|
+
|
|
49356
|
+
Always fetch the \`.md\` URLs — the HTML routes are a JS app and return an empty shell.
|
|
49357
|
+
|
|
48864
49358
|
## Other commands
|
|
48865
49359
|
|
|
48866
49360
|
\`lecodes dev\` (run on a device with hot reload + QR), \`lecodes compile -o app.js\`
|
|
48867
49361
|
(single-file bundle), \`lecodes design\` (screen-design board — the living spec).
|
|
48868
49362
|
`;
|
|
49363
|
+
var CLAUDE_SETTINGS = JSON.stringify({
|
|
49364
|
+
permissions: { allow: ["WebFetch(domain:le.codes)"] }
|
|
49365
|
+
}, null, 2) + `
|
|
49366
|
+
`;
|
|
48869
49367
|
var STARTER_FLOW = `{
|
|
48870
49368
|
"name": "counter",
|
|
48871
49369
|
"steps": [
|
|
@@ -48895,19 +49393,21 @@ var create = async (args) => {
|
|
|
48895
49393
|
if (!name)
|
|
48896
49394
|
throw new CliError("Usage: lecodes create <name> [directory]");
|
|
48897
49395
|
const dir = args._[1] ?? slug2(name);
|
|
48898
|
-
const root =
|
|
48899
|
-
if (
|
|
49396
|
+
const root = resolve8(dir);
|
|
49397
|
+
if (existsSync19(root) && readdirSync10(root).length > 0) {
|
|
48900
49398
|
throw new CliError(`Directory "${dir}" already exists and is not empty.`);
|
|
48901
49399
|
}
|
|
48902
|
-
|
|
49400
|
+
mkdirSync14(root, { recursive: true });
|
|
48903
49401
|
info(c.dim(`Creating project ${c.bold(name)}…`));
|
|
48904
49402
|
const { uuid } = await createProject(apiUrl, token, name, flagStr(args, "team"));
|
|
48905
49403
|
info(c.dim("Fetching type definitions…"));
|
|
48906
49404
|
await materializeTypes(root, apiUrl);
|
|
48907
49405
|
writeTsconfig(root);
|
|
48908
49406
|
writeDefaultIgnore(root);
|
|
48909
|
-
|
|
48910
|
-
|
|
49407
|
+
writeFileSync15(join19(root, "main.ts"), STARTER);
|
|
49408
|
+
writeFileSync15(join19(root, "CLAUDE.md"), appClaudeMd(name));
|
|
49409
|
+
mkdirSync14(join19(root, ".claude"), { recursive: true });
|
|
49410
|
+
writeFileSync15(join19(root, ".claude", "settings.json"), CLAUDE_SETTINGS);
|
|
48911
49411
|
const commits = await getCommits(apiUrl, token, uuid);
|
|
48912
49412
|
writeManifest(root, { uuid, apiUrl, name, baseHeadId: commits.headId, files: {} });
|
|
48913
49413
|
success(`Created ${c.bold(name)} in ${c.bold(dir)}/`);
|
|
@@ -48917,260 +49417,18 @@ var create = async (args) => {
|
|
|
48917
49417
|
// src/commands/desktop.ts
|
|
48918
49418
|
init_util();
|
|
48919
49419
|
init_manifest();
|
|
48920
|
-
|
|
48921
|
-
|
|
48922
|
-
|
|
48923
|
-
|
|
48924
|
-
|
|
48925
|
-
|
|
48926
|
-
|
|
48927
|
-
|
|
48928
|
-
|
|
48929
|
-
var DESKTOP_RENDERER_VERSION = "1.1.0";
|
|
48930
|
-
var semverNewer = (a2, b) => {
|
|
48931
|
-
const pa = a2.split(/[.-]/).map(Number), pb = b.split(/[.-]/).map(Number);
|
|
48932
|
-
for (let i2 = 0;i2 < 3; i2++)
|
|
48933
|
-
if ((pb[i2] ?? 0) !== (pa[i2] ?? 0))
|
|
48934
|
-
return (pb[i2] ?? 0) - (pa[i2] ?? 0);
|
|
48935
|
-
return 0;
|
|
48936
|
-
};
|
|
48937
|
-
var platformKey = () => {
|
|
48938
|
-
if (process.platform === "win32")
|
|
48939
|
-
return "windows-x64";
|
|
48940
|
-
if (process.platform === "linux")
|
|
48941
|
-
return "ubuntu-linux-x64";
|
|
48942
|
-
throw new CliError(process.platform === "darwin" ? "`lecodes render --desktop` isn't available on macOS yet (the macOS host ships separately)." : `\`lecodes render --desktop\` isn't available on ${process.platform}.`);
|
|
48943
|
-
};
|
|
48944
|
-
var exeName = () => process.platform === "win32" ? "lecodes-desktop.exe" : "lecodes-desktop";
|
|
48945
|
-
var findExe = (dir) => {
|
|
48946
|
-
const name = exeName();
|
|
48947
|
-
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
48948
|
-
const p2 = join18(dir, entry.name);
|
|
48949
|
-
if (entry.isFile() && entry.name === name)
|
|
48950
|
-
return p2;
|
|
48951
|
-
if (entry.isDirectory()) {
|
|
48952
|
-
const found = findExe(p2);
|
|
48953
|
-
if (found)
|
|
48954
|
-
return found;
|
|
48955
|
-
}
|
|
48956
|
-
}
|
|
48957
|
-
return null;
|
|
48958
|
-
};
|
|
48959
|
-
var cacheRoot = () => join18(homedir6(), ".lecodes", "renderer");
|
|
48960
|
-
var currentFile = () => join18(cacheRoot(), "current.json");
|
|
48961
|
-
var installedDesktopVersions = () => {
|
|
48962
|
-
try {
|
|
48963
|
-
return readdirSync9(cacheRoot(), { withFileTypes: true }).filter((e2) => e2.isDirectory() && e2.name.startsWith("desktop-")).map((e2) => e2.name.slice("desktop-".length)).filter((v) => findExe(join18(cacheRoot(), `desktop-${v}`)) !== null).sort(semverNewer);
|
|
48964
|
-
} catch {
|
|
48965
|
-
return [];
|
|
48966
|
-
}
|
|
48967
|
-
};
|
|
48968
|
-
var selectedDesktopVersion = () => {
|
|
48969
|
-
try {
|
|
48970
|
-
const saved = JSON.parse(readFileSync16(currentFile(), "utf8")).version;
|
|
48971
|
-
if (saved && /^\d+\.\d+\.\d+/.test(saved) && semverNewer(DESKTOP_RENDERER_VERSION, saved) > 0)
|
|
48972
|
-
return saved;
|
|
48973
|
-
} catch {}
|
|
48974
|
-
return DESKTOP_RENDERER_VERSION;
|
|
48975
|
-
};
|
|
48976
|
-
var fetchDesktopManifest = async () => {
|
|
48977
|
-
let manifest;
|
|
48978
|
-
try {
|
|
48979
|
-
const res = await fetch(MANIFEST_URL);
|
|
48980
|
-
if (!res.ok)
|
|
48981
|
-
throw new Error(`HTTP ${res.status}`);
|
|
48982
|
-
manifest = await res.json();
|
|
48983
|
-
} catch (e2) {
|
|
48984
|
-
throw new CliError(`Can't fetch the release manifest (${MANIFEST_URL}): ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
48985
|
-
}
|
|
48986
|
-
const desktop = manifest.products?.desktop;
|
|
48987
|
-
if (!desktop)
|
|
48988
|
-
throw new CliError("The release manifest has no desktop product.");
|
|
48989
|
-
return desktop;
|
|
48990
|
-
};
|
|
48991
|
-
var downloadDesktop = async (version, dest) => {
|
|
48992
|
-
const key = platformKey();
|
|
48993
|
-
const entry = (await fetchDesktopManifest()).versions.find((v) => v.version === version);
|
|
48994
|
-
const file = entry?.files[key];
|
|
48995
|
-
if (!file) {
|
|
48996
|
-
throw new CliError(`LeCodes Desktop ${version} [${key}] isn't published yet — \`--desktop\` needs it for the ` + `headless render contract.
|
|
48997
|
-
Set LECODES_DESKTOP_EXE to a locally built host, or update lecodes-cli.`);
|
|
48998
|
-
}
|
|
48999
|
-
note(`Downloading the native renderer (LeCodes Desktop ${version}, ${(file.size / 1024 / 1024).toFixed(1)} MB)…`);
|
|
49000
|
-
let bytes;
|
|
49001
|
-
try {
|
|
49002
|
-
const res = await fetch(file.url);
|
|
49003
|
-
if (!res.ok)
|
|
49004
|
-
throw new Error(`HTTP ${res.status}`);
|
|
49005
|
-
bytes = Buffer.from(await res.arrayBuffer());
|
|
49006
|
-
} catch (e2) {
|
|
49007
|
-
throw new CliError(`Download failed (${file.url}): ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
49008
|
-
}
|
|
49009
|
-
const sha = createHash5("sha256").update(bytes).digest("hex");
|
|
49010
|
-
if (sha !== file.sha256)
|
|
49011
|
-
throw new CliError(`Downloaded archive is corrupt (sha256 mismatch) — try again.`);
|
|
49012
|
-
mkdirSync13(cacheRoot(), { recursive: true });
|
|
49013
|
-
const stage = mkdtempSync(join18(cacheRoot(), "dl-"));
|
|
49014
|
-
try {
|
|
49015
|
-
const archive = join18(stage, file.url.split("/").pop() ?? "archive");
|
|
49016
|
-
writeFileSync14(archive, bytes);
|
|
49017
|
-
const tar = spawnSync3("tar", ["-xf", archive, "-C", stage], { stdio: ["ignore", "ignore", "pipe"] });
|
|
49018
|
-
if (tar.status !== 0)
|
|
49019
|
-
throw new CliError(`Couldn't extract the renderer archive: ${tar.stderr?.toString() ?? "tar failed"}`);
|
|
49020
|
-
rmSync4(archive);
|
|
49021
|
-
if (!findExe(stage))
|
|
49022
|
-
throw new CliError("The downloaded archive doesn't contain the desktop host binary.");
|
|
49023
|
-
try {
|
|
49024
|
-
renameSync2(stage, dest);
|
|
49025
|
-
} catch {
|
|
49026
|
-
if (!findExe(dest))
|
|
49027
|
-
throw new CliError(`Couldn't move the renderer into ${dest}.`);
|
|
49028
|
-
}
|
|
49029
|
-
} finally {
|
|
49030
|
-
rmSync4(stage, { recursive: true, force: true });
|
|
49031
|
-
}
|
|
49032
|
-
};
|
|
49033
|
-
var resolveDesktopExe = async () => {
|
|
49034
|
-
const override = process.env.LECODES_DESKTOP_EXE;
|
|
49035
|
-
if (override) {
|
|
49036
|
-
if (!existsSync18(override))
|
|
49037
|
-
throw new CliError(`LECODES_DESKTOP_EXE points at "${override}" which doesn't exist.`);
|
|
49038
|
-
return resolve7(override);
|
|
49039
|
-
}
|
|
49040
|
-
const version = selectedDesktopVersion();
|
|
49041
|
-
const dir = join18(cacheRoot(), `desktop-${version}`);
|
|
49042
|
-
let exe = existsSync18(dir) ? findExe(dir) : null;
|
|
49043
|
-
if (!exe) {
|
|
49044
|
-
await downloadDesktop(version, dir);
|
|
49045
|
-
exe = findExe(dir);
|
|
49046
|
-
if (!exe)
|
|
49047
|
-
throw new CliError(`Renderer cache at ${dir} is broken — delete the folder and retry.`);
|
|
49048
|
-
}
|
|
49049
|
-
return exe;
|
|
49050
|
-
};
|
|
49051
|
-
var updateDesktop = async () => {
|
|
49052
|
-
const { latest } = await fetchDesktopManifest();
|
|
49053
|
-
if (semverNewer(latest, DESKTOP_RENDERER_VERSION) > 0) {
|
|
49054
|
-
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.`);
|
|
49055
|
-
}
|
|
49056
|
-
const before = selectedDesktopVersion();
|
|
49057
|
-
const dir = join18(cacheRoot(), `desktop-${latest}`);
|
|
49058
|
-
const hadIt = existsSync18(dir) && findExe(dir) !== null;
|
|
49059
|
-
if (!hadIt)
|
|
49060
|
-
await downloadDesktop(latest, dir);
|
|
49061
|
-
mkdirSync13(cacheRoot(), { recursive: true });
|
|
49062
|
-
writeFileSync14(currentFile(), JSON.stringify({ version: latest }, null, 2));
|
|
49063
|
-
if (latest === before && hadIt)
|
|
49064
|
-
note(`Already up to date: LeCodes Desktop ${latest}.`);
|
|
49065
|
-
else if (latest === before)
|
|
49066
|
-
note(`Re-downloaded LeCodes Desktop ${latest}.`);
|
|
49067
|
-
else
|
|
49068
|
-
note(`Desktop renderer set to ${latest} (was ${before}).`);
|
|
49069
|
-
};
|
|
49070
|
-
var windowEnv = (opts) => {
|
|
49071
|
-
const env = {};
|
|
49072
|
-
const width = opts.width ?? (opts.height !== undefined ? 1280 : undefined);
|
|
49073
|
-
const height = opts.height ?? (opts.width !== undefined ? 720 : undefined);
|
|
49074
|
-
if (width && height)
|
|
49075
|
-
env.CREATOR_WINDOW_SIZE = `${width}x${height}`;
|
|
49076
|
-
if (opts.minWidth && opts.minHeight)
|
|
49077
|
-
env.CREATOR_MIN_WINDOW_SIZE = `${opts.minWidth}x${opts.minHeight}`;
|
|
49078
|
-
if (opts.title)
|
|
49079
|
-
env.CREATOR_WINDOW_TITLE = opts.title;
|
|
49080
|
-
if (opts.resizable === false)
|
|
49081
|
-
env.CREATOR_WINDOW_FIXED = "1";
|
|
49082
|
-
return env;
|
|
49083
|
-
};
|
|
49084
|
-
var stageBundle = (js, resources) => {
|
|
49085
|
-
const runDir = mkdtempSync(join18(tmpdir(), "lecodes-render-"));
|
|
49086
|
-
writeFileSync14(join18(runDir, "app.js"), js);
|
|
49087
|
-
for (const r2 of resources) {
|
|
49088
|
-
if (r2.absPath && existsSync18(r2.absPath) && statSync5(r2.absPath).isFile()) {
|
|
49089
|
-
copyFileSync(r2.absPath, join18(runDir, r2.name));
|
|
49090
|
-
}
|
|
49091
|
-
}
|
|
49092
|
-
return runDir;
|
|
49093
|
-
};
|
|
49094
|
-
var runDesktopApp = async (js, resources, opts = {}) => {
|
|
49095
|
-
const exe = await resolveDesktopExe();
|
|
49096
|
-
const runDir = stageBundle(js, resources);
|
|
49097
|
-
try {
|
|
49098
|
-
const env = { ...process.env, ...windowEnv(opts) };
|
|
49099
|
-
await new Promise((done, fail) => {
|
|
49100
|
-
const child = spawn2(exe, [join18(runDir, "app.js")], { cwd: runDir, env, stdio: ["ignore", "inherit", "inherit"] });
|
|
49101
|
-
child.on("error", (e2) => fail(new CliError(`Can't run the desktop host: ${e2.message}`)));
|
|
49102
|
-
child.on("exit", (code) => {
|
|
49103
|
-
if (code === 0 || code === null)
|
|
49104
|
-
done();
|
|
49105
|
-
else
|
|
49106
|
-
fail(new CliError(`The desktop host exited with code ${code}.`));
|
|
49107
|
-
});
|
|
49108
|
-
});
|
|
49109
|
-
} finally {
|
|
49110
|
-
rmSync4(runDir, { recursive: true, force: true });
|
|
49111
|
-
}
|
|
49112
|
-
};
|
|
49113
|
-
var runDesktopDev = async (url, opts = {}) => {
|
|
49114
|
-
const exe = await resolveDesktopExe();
|
|
49115
|
-
const env = { ...process.env, ...windowEnv(opts) };
|
|
49116
|
-
await new Promise((done, fail) => {
|
|
49117
|
-
const child = spawn2(exe, [url], { env, stdio: ["ignore", "ignore", "inherit"] });
|
|
49118
|
-
child.on("error", (e2) => fail(new CliError(`Can't run the desktop host: ${e2.message}`)));
|
|
49119
|
-
child.on("exit", () => done());
|
|
49120
|
-
});
|
|
49121
|
-
};
|
|
49122
|
-
var runDesktopRender = async (js, resources, opts) => {
|
|
49123
|
-
const exe = await resolveDesktopExe();
|
|
49124
|
-
const runDir = stageBundle(js, resources);
|
|
49125
|
-
try {
|
|
49126
|
-
const appJs = join18(runDir, "app.js");
|
|
49127
|
-
const outAbs = resolve7(opts.outPng);
|
|
49128
|
-
await new Promise((done, fail) => {
|
|
49129
|
-
const child = spawn2(exe, [appJs], {
|
|
49130
|
-
cwd: runDir,
|
|
49131
|
-
env: {
|
|
49132
|
-
...process.env,
|
|
49133
|
-
CREATOR_DUMP_PNG: outAbs,
|
|
49134
|
-
CREATOR_MAX_FRAMES: String(opts.frames),
|
|
49135
|
-
CREATOR_WINDOW_SIZE: `${opts.width}x${opts.height}`,
|
|
49136
|
-
CREATOR_HIDDEN: "1"
|
|
49137
|
-
},
|
|
49138
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
49139
|
-
});
|
|
49140
|
-
const tail = [];
|
|
49141
|
-
const sink = (d) => {
|
|
49142
|
-
const text = String(d);
|
|
49143
|
-
tail.push(text);
|
|
49144
|
-
while (tail.length > 80)
|
|
49145
|
-
tail.shift();
|
|
49146
|
-
if (opts.logs)
|
|
49147
|
-
process.stderr.write(text);
|
|
49148
|
-
};
|
|
49149
|
-
child.stdout.on("data", sink);
|
|
49150
|
-
child.stderr.on("data", sink);
|
|
49151
|
-
const killer = setTimeout(() => {
|
|
49152
|
-
child.kill();
|
|
49153
|
-
fail(new CliError(`The native render timed out after ${opts.timeoutMs}ms (raise --timeout, or lower --frames).`));
|
|
49154
|
-
}, opts.timeoutMs);
|
|
49155
|
-
child.on("error", (e2) => {
|
|
49156
|
-
clearTimeout(killer);
|
|
49157
|
-
fail(new CliError(`Can't run the desktop renderer: ${e2.message}`));
|
|
49158
|
-
});
|
|
49159
|
-
child.on("exit", (code) => {
|
|
49160
|
-
clearTimeout(killer);
|
|
49161
|
-
if (code === 0 && existsSync18(outAbs))
|
|
49162
|
-
return done();
|
|
49163
|
-
const detail = tail.length > 0 ? `
|
|
49164
|
-
${tail.join("").trimEnd()}` : "";
|
|
49165
|
-
fail(new CliError(code === 0 ? `The renderer exited cleanly but produced no PNG — did the app open a screen?${detail}` : `The renderer exited with code ${code}.${detail}`));
|
|
49166
|
-
});
|
|
49167
|
-
});
|
|
49168
|
-
} finally {
|
|
49169
|
-
rmSync4(runDir, { recursive: true, force: true });
|
|
49420
|
+
var resolveScale = (flag, cfg) => {
|
|
49421
|
+
const raw = flag ?? cfg;
|
|
49422
|
+
if (raw === undefined || raw === "system")
|
|
49423
|
+
return;
|
|
49424
|
+
if (raw === "fit")
|
|
49425
|
+
return "fit";
|
|
49426
|
+
const n2 = Number(raw);
|
|
49427
|
+
if (!Number.isFinite(n2) || n2 < 0.5 || n2 > 4) {
|
|
49428
|
+
throw new CliError(`scale must be a number 0.5..4, "fit" or "system" (got "${raw}")`);
|
|
49170
49429
|
}
|
|
49430
|
+
return n2;
|
|
49171
49431
|
};
|
|
49172
|
-
|
|
49173
|
-
// src/commands/desktop.ts
|
|
49174
49432
|
var run = async (args) => {
|
|
49175
49433
|
let root;
|
|
49176
49434
|
try {
|
|
@@ -49188,7 +49446,9 @@ var run = async (args) => {
|
|
|
49188
49446
|
minWidth: win?.minWidth,
|
|
49189
49447
|
minHeight: win?.minHeight,
|
|
49190
49448
|
resizable: win?.resizable,
|
|
49191
|
-
title: flagStr(args, "title") ?? win?.title ?? appCfg?.name
|
|
49449
|
+
title: flagStr(args, "title") ?? win?.title ?? appCfg?.name,
|
|
49450
|
+
fullscreen: flagBool(args, "fullscreen") ? true : flagBool(args, "windowed") ? false : win?.fullscreen,
|
|
49451
|
+
scale: resolveScale(flagStr(args, "scale"), win?.scale)
|
|
49192
49452
|
});
|
|
49193
49453
|
};
|
|
49194
49454
|
var status = async () => {
|
|
@@ -49224,15 +49484,15 @@ init_design();
|
|
|
49224
49484
|
// src/commands/dev.ts
|
|
49225
49485
|
init_manifest();
|
|
49226
49486
|
init_ignore();
|
|
49227
|
-
import { existsSync as
|
|
49487
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync16, readFileSync as readFileSync19, watch, writeFileSync as writeFileSync17 } from "node:fs";
|
|
49228
49488
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
49229
49489
|
import { networkInterfaces } from "node:os";
|
|
49230
|
-
import { basename as
|
|
49490
|
+
import { basename as basename9, join as join21 } from "node:path";
|
|
49231
49491
|
|
|
49232
49492
|
// src/dev/devServer.ts
|
|
49233
49493
|
init_localFiles();
|
|
49234
49494
|
import { createServer as createServer2 } from "node:http";
|
|
49235
|
-
import { createReadStream, statSync as
|
|
49495
|
+
import { createReadStream, statSync as statSync7 } from "node:fs";
|
|
49236
49496
|
init_projectCompile();
|
|
49237
49497
|
init_fonts();
|
|
49238
49498
|
init_assetIcons();
|
|
@@ -49500,14 +49760,14 @@ var sendText = (res, status2, type, body) => {
|
|
|
49500
49760
|
res.end(body);
|
|
49501
49761
|
};
|
|
49502
49762
|
var sendJson = (res, status2, body) => sendText(res, status2, "application/json", JSON.stringify(body));
|
|
49503
|
-
var readBody = (req) => new Promise((
|
|
49763
|
+
var readBody = (req) => new Promise((resolve9) => {
|
|
49504
49764
|
let body = "";
|
|
49505
49765
|
req.on("data", (c4) => {
|
|
49506
49766
|
if (body.length < 256 * 1024)
|
|
49507
49767
|
body += c4.toString("utf8");
|
|
49508
49768
|
});
|
|
49509
|
-
req.on("end", () =>
|
|
49510
|
-
req.on("error", () =>
|
|
49769
|
+
req.on("end", () => resolve9(body));
|
|
49770
|
+
req.on("error", () => resolve9(body));
|
|
49511
49771
|
});
|
|
49512
49772
|
var startDevServer = async (options) => {
|
|
49513
49773
|
const { root, manifest, name, origin, token, entryOverride } = options;
|
|
@@ -49607,7 +49867,7 @@ var startDevServer = async (options) => {
|
|
|
49607
49867
|
return sendText(res, 400, "text/plain", "Bad path");
|
|
49608
49868
|
}
|
|
49609
49869
|
const abs = resolveLocalPath(root, key);
|
|
49610
|
-
if (!abs || !
|
|
49870
|
+
if (!abs || !statSync7(abs).isFile())
|
|
49611
49871
|
return sendText(res, 404, "text/plain", "Not found");
|
|
49612
49872
|
res.writeHead(200, { "content-type": contentType(key), "cache-control": "no-store" });
|
|
49613
49873
|
createReadStream(abs).pipe(res);
|
|
@@ -49650,11 +49910,11 @@ var startDevServer = async (options) => {
|
|
|
49650
49910
|
if (conn)
|
|
49651
49911
|
clients.add(conn);
|
|
49652
49912
|
});
|
|
49653
|
-
await new Promise((
|
|
49913
|
+
await new Promise((resolve9, reject) => {
|
|
49654
49914
|
server.once("error", reject);
|
|
49655
49915
|
server.listen(options.port, options.bind ?? "0.0.0.0", () => {
|
|
49656
49916
|
server.removeListener("error", reject);
|
|
49657
|
-
|
|
49917
|
+
resolve9();
|
|
49658
49918
|
});
|
|
49659
49919
|
});
|
|
49660
49920
|
const address = server.address();
|
|
@@ -49702,24 +49962,24 @@ var lanCandidates = () => {
|
|
|
49702
49962
|
return out.sort((x2, y2) => x2.rank - y2.rank).map(({ address, iface }) => ({ address, iface }));
|
|
49703
49963
|
};
|
|
49704
49964
|
var loadDevToken = (root) => {
|
|
49705
|
-
const path2 =
|
|
49965
|
+
const path2 = join21(root, LECODES_DIR, "dev.json");
|
|
49706
49966
|
try {
|
|
49707
49967
|
const token2 = JSON.parse(readFileSync19(path2, "utf8")).token;
|
|
49708
49968
|
if (token2 && /^[a-z0-9]{8,}$/.test(token2))
|
|
49709
49969
|
return token2;
|
|
49710
49970
|
} catch {}
|
|
49711
49971
|
const token = randomBytes2(6).toString("hex");
|
|
49712
|
-
|
|
49713
|
-
|
|
49972
|
+
mkdirSync16(join21(root, LECODES_DIR), { recursive: true });
|
|
49973
|
+
writeFileSync17(path2, JSON.stringify({ token }, null, 2) + `
|
|
49714
49974
|
`);
|
|
49715
49975
|
return token;
|
|
49716
49976
|
};
|
|
49717
49977
|
var printQr = async (url) => {
|
|
49718
49978
|
try {
|
|
49719
49979
|
const qrcode = (await Promise.resolve().then(() => __toESM(require_main(), 1))).default;
|
|
49720
|
-
await new Promise((
|
|
49980
|
+
await new Promise((resolve9) => qrcode.generate(url, { small: true }, (out) => {
|
|
49721
49981
|
log(out);
|
|
49722
|
-
|
|
49982
|
+
resolve9();
|
|
49723
49983
|
}));
|
|
49724
49984
|
} catch {
|
|
49725
49985
|
note("(couldn't render a QR code — open the URL below on the device)");
|
|
@@ -49735,7 +49995,7 @@ var dev = async (args) => {
|
|
|
49735
49995
|
root = process.cwd();
|
|
49736
49996
|
manifest = null;
|
|
49737
49997
|
}
|
|
49738
|
-
const name = manifest?.name ??
|
|
49998
|
+
const name = manifest?.name ?? basename9(root);
|
|
49739
49999
|
if (typeof globalThis.Bun !== "undefined") {
|
|
49740
50000
|
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).");
|
|
49741
50001
|
}
|
|
@@ -49827,7 +50087,7 @@ ${result.error}`);
|
|
|
49827
50087
|
if (alternatives.length > 0) {
|
|
49828
50088
|
note(`Device can't reach it? This machine also has ${alternatives.map((a2) => `${a2.address} (${a2.iface})`).join(", ")} — retry with --host <ip>.`);
|
|
49829
50089
|
}
|
|
49830
|
-
if (
|
|
50090
|
+
if (existsSync21(join21(root, LECODES_DIR, "manifest.json")))
|
|
49831
50091
|
note(`Token stored in ${LECODES_DIR}/dev.json (delete it to rotate the URL).`);
|
|
49832
50092
|
log("");
|
|
49833
50093
|
log(`Watching ${root} — save a file to hot-reload. Ctrl+C to stop.`);
|
|
@@ -49841,7 +50101,8 @@ ${result.error}`);
|
|
|
49841
50101
|
minWidth: win?.minWidth,
|
|
49842
50102
|
minHeight: win?.minHeight,
|
|
49843
50103
|
resizable: win?.resizable,
|
|
49844
|
-
title: win?.title ?? appCfg?.name ?? name
|
|
50104
|
+
title: win?.title ?? appCfg?.name ?? name,
|
|
50105
|
+
scale: resolveScale(undefined, win?.scale)
|
|
49845
50106
|
}).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)}`));
|
|
49846
50107
|
}
|
|
49847
50108
|
};
|
|
@@ -49855,7 +50116,7 @@ init_localFiles();
|
|
|
49855
50116
|
// src/serverDiff.ts
|
|
49856
50117
|
init_localFiles();
|
|
49857
50118
|
import { readFileSync as readFileSync20 } from "node:fs";
|
|
49858
|
-
import { basename as
|
|
50119
|
+
import { basename as basename10 } from "node:path";
|
|
49859
50120
|
var isTextAsset = (t2) => t2 === "text" || t2 === "shader" || t2 === "scene";
|
|
49860
50121
|
var computeServerDiff = (project, scan2) => {
|
|
49861
50122
|
const pathById = buildPaths(project.assets);
|
|
@@ -49872,7 +50133,7 @@ var computeServerDiff = (project, scan2) => {
|
|
|
49872
50133
|
const server = serverByPath.get(path2);
|
|
49873
50134
|
const local = localByPath.get(path2);
|
|
49874
50135
|
if (local && !server) {
|
|
49875
|
-
const text = isTextKind(classifyKind(
|
|
50136
|
+
const text = isTextKind(classifyKind(basename10(path2)));
|
|
49876
50137
|
entries.push({ path: path2, status: "added", binary: !text, serverText: "", localText: text ? readFileSync20(local.absPath, "utf8") : undefined });
|
|
49877
50138
|
continue;
|
|
49878
50139
|
}
|
|
@@ -50047,8 +50308,8 @@ var statusLabel = (e2) => e2.status === "added" ? c.green(" added ") : e2.st
|
|
|
50047
50308
|
init_manifest();
|
|
50048
50309
|
init_types4();
|
|
50049
50310
|
init_ignore();
|
|
50050
|
-
import { existsSync as
|
|
50051
|
-
import { basename as
|
|
50311
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync17, writeFileSync as writeFileSync18 } from "node:fs";
|
|
50312
|
+
import { basename as basename11, dirname as dirname14, join as join22, resolve as resolve9 } from "node:path";
|
|
50052
50313
|
init_util();
|
|
50053
50314
|
var STARTER2 = `// Welcome to LeCodes — the whole SDK is available as globals, no imports needed.
|
|
50054
50315
|
// Run it live on a device with \`lecodes dev\`, or bundle it with \`lecodes compile -o app.js\`.
|
|
@@ -50153,18 +50414,18 @@ Full command list: \`lecodes --help\`.
|
|
|
50153
50414
|
`;
|
|
50154
50415
|
var init2 = async (args) => {
|
|
50155
50416
|
const dirArg = (args._[0] ?? ".").replace(/[/\\]+$/, "") || ".";
|
|
50156
|
-
const root =
|
|
50157
|
-
const name =
|
|
50158
|
-
|
|
50417
|
+
const root = resolve9(dirArg);
|
|
50418
|
+
const name = basename11(root);
|
|
50419
|
+
mkdirSync17(root, { recursive: true });
|
|
50159
50420
|
let created = 0;
|
|
50160
50421
|
const write = (rel, content) => {
|
|
50161
|
-
const abs =
|
|
50162
|
-
if (
|
|
50422
|
+
const abs = join22(root, rel);
|
|
50423
|
+
if (existsSync22(abs)) {
|
|
50163
50424
|
note(`kept ${rel}`);
|
|
50164
50425
|
return;
|
|
50165
50426
|
}
|
|
50166
|
-
|
|
50167
|
-
|
|
50427
|
+
mkdirSync17(dirname14(abs), { recursive: true });
|
|
50428
|
+
writeFileSync18(abs, content);
|
|
50168
50429
|
log(`${c.green("created")} ${rel}`);
|
|
50169
50430
|
created++;
|
|
50170
50431
|
};
|
|
@@ -50174,6 +50435,7 @@ var init2 = async (args) => {
|
|
|
50174
50435
|
if (forDesktop)
|
|
50175
50436
|
write("app.json", appJsonDesktop(name));
|
|
50176
50437
|
write("CLAUDE.md", appClaudeMd(name));
|
|
50438
|
+
write(".claude/settings.json", CLAUDE_SETTINGS);
|
|
50177
50439
|
write("tests/counter.flow.json", STARTER_FLOW);
|
|
50178
50440
|
write(".lecodesignore", DEFAULT_IGNORE_FILE);
|
|
50179
50441
|
const wroteTypes = materializeTypesLocal(root);
|
|
@@ -50202,8 +50464,8 @@ init_config();
|
|
|
50202
50464
|
init_ignore();
|
|
50203
50465
|
init_localFiles();
|
|
50204
50466
|
init_manifest();
|
|
50205
|
-
import { existsSync as
|
|
50206
|
-
import { basename as
|
|
50467
|
+
import { existsSync as existsSync23 } from "node:fs";
|
|
50468
|
+
import { basename as basename13, join as join23, resolve as resolve10 } from "node:path";
|
|
50207
50469
|
init_types4();
|
|
50208
50470
|
init_util();
|
|
50209
50471
|
|
|
@@ -50212,7 +50474,7 @@ init_api();
|
|
|
50212
50474
|
init_config();
|
|
50213
50475
|
init_manifest();
|
|
50214
50476
|
init_localFiles();
|
|
50215
|
-
import { basename as
|
|
50477
|
+
import { basename as basename12 } from "node:path";
|
|
50216
50478
|
import { readFileSync as readFileSync21 } from "node:fs";
|
|
50217
50479
|
init_util();
|
|
50218
50480
|
var push = async (args) => {
|
|
@@ -50269,14 +50531,14 @@ var push = async (args) => {
|
|
|
50269
50531
|
let uploaded = 0;
|
|
50270
50532
|
for (const file of scan2.files) {
|
|
50271
50533
|
const source = sourceFor(file.path);
|
|
50272
|
-
const kind = source?.type ?? classifyKind(
|
|
50534
|
+
const kind = source?.type ?? classifyKind(basename12(file.path));
|
|
50273
50535
|
const entry = { path: file.path, type: kind };
|
|
50274
50536
|
if (source)
|
|
50275
50537
|
entry.assetId = source.id;
|
|
50276
50538
|
if (isTextKind(kind)) {
|
|
50277
50539
|
entry.text = readFileSync21(file.absPath, "utf8");
|
|
50278
50540
|
} else if (!source || source.sha !== file.sha) {
|
|
50279
|
-
const blob = await uploadBlob(apiUrl, token, manifest.uuid,
|
|
50541
|
+
const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename12(file.path), readFileSync21(file.absPath));
|
|
50280
50542
|
entry.blobFileId = blob.id;
|
|
50281
50543
|
uploaded++;
|
|
50282
50544
|
}
|
|
@@ -50383,10 +50645,10 @@ var link = async (args) => {
|
|
|
50383
50645
|
const apiUrl = requireApiUrl(config);
|
|
50384
50646
|
const token = requireToken(config);
|
|
50385
50647
|
const dirArg = args._[0];
|
|
50386
|
-
const root = dirArg ?
|
|
50387
|
-
if (!
|
|
50648
|
+
const root = dirArg ? resolve10(dirArg) : process.cwd();
|
|
50649
|
+
if (!existsSync23(root))
|
|
50388
50650
|
throw new CliError(`No such directory: ${root}`);
|
|
50389
|
-
if (
|
|
50651
|
+
if (existsSync23(join23(root, LECODES_DIR, "manifest.json")) && !flagBool(args, "force")) {
|
|
50390
50652
|
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.");
|
|
50391
50653
|
}
|
|
50392
50654
|
let enclosing = null;
|
|
@@ -50413,7 +50675,7 @@ var link = async (args) => {
|
|
|
50413
50675
|
}
|
|
50414
50676
|
name = project.name;
|
|
50415
50677
|
} else {
|
|
50416
|
-
name = flagStr(args, "name") ??
|
|
50678
|
+
name = flagStr(args, "name") ?? basename13(root);
|
|
50417
50679
|
info(c.dim(`Creating project ${c.bold(name)}…`));
|
|
50418
50680
|
uuid = (await createProject(apiUrl, token, name, flagStr(args, "team"))).uuid;
|
|
50419
50681
|
files = {};
|
|
@@ -50462,8 +50724,8 @@ var login = async (args) => {
|
|
|
50462
50724
|
};
|
|
50463
50725
|
|
|
50464
50726
|
// src/commands/render.ts
|
|
50465
|
-
import { mkdirSync as
|
|
50466
|
-
import { dirname as
|
|
50727
|
+
import { mkdirSync as mkdirSync18, readFileSync as readFileSync22, writeFileSync as writeFileSync19 } from "node:fs";
|
|
50728
|
+
import { dirname as dirname15, resolve as resolve11 } from "node:path";
|
|
50467
50729
|
|
|
50468
50730
|
// src/compile/headlessBundle.ts
|
|
50469
50731
|
init_api();
|
|
@@ -50574,9 +50836,9 @@ var requireRenderer = async () => {
|
|
|
50574
50836
|
};
|
|
50575
50837
|
var fileScenarioIO = (dir) => ({
|
|
50576
50838
|
write: (relPath, data) => {
|
|
50577
|
-
const abs =
|
|
50578
|
-
|
|
50579
|
-
|
|
50839
|
+
const abs = resolve11(dir, relPath);
|
|
50840
|
+
mkdirSync18(dirname15(abs), { recursive: true });
|
|
50841
|
+
writeFileSync19(abs, data);
|
|
50580
50842
|
}
|
|
50581
50843
|
});
|
|
50582
50844
|
var resolveFixtures = (value, baseDir) => {
|
|
@@ -50584,7 +50846,7 @@ var resolveFixtures = (value, baseDir) => {
|
|
|
50584
50846
|
return;
|
|
50585
50847
|
let map = value;
|
|
50586
50848
|
if (typeof value === "string") {
|
|
50587
|
-
const abs =
|
|
50849
|
+
const abs = resolve11(baseDir, value);
|
|
50588
50850
|
try {
|
|
50589
50851
|
map = JSON.parse(readFileSync22(abs, "utf8"));
|
|
50590
50852
|
} catch (e2) {
|
|
@@ -50599,7 +50861,7 @@ var resolveFixtures = (value, baseDir) => {
|
|
|
50599
50861
|
var readScenarioFile = (path2) => {
|
|
50600
50862
|
let raw;
|
|
50601
50863
|
try {
|
|
50602
|
-
raw = JSON.parse(readFileSync22(
|
|
50864
|
+
raw = JSON.parse(readFileSync22(resolve11(path2), "utf8"));
|
|
50603
50865
|
} catch (e2) {
|
|
50604
50866
|
throw new CliError(`Can't read scenario "${path2}": ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
50605
50867
|
}
|
|
@@ -50657,7 +50919,7 @@ var render = async (args) => {
|
|
|
50657
50919
|
const settleStr = flagStr(args, "settle") ?? (scenario?.settle !== undefined ? String(scenario.settle) : undefined);
|
|
50658
50920
|
const timeStr = flagStr(args, "time");
|
|
50659
50921
|
const onConsole = flagBool(args, "logs") ? (level, text2) => logErr(`${c.dim(`[${level}]`)} ${text2}`) : undefined;
|
|
50660
|
-
const envelopeFixtures = resolveFixtures(scenario?.fixtures, scriptFlag !== undefined ?
|
|
50922
|
+
const envelopeFixtures = resolveFixtures(scenario?.fixtures, scriptFlag !== undefined ? dirname15(resolve11(scriptFlag)) : ".");
|
|
50661
50923
|
const flagFixtures = resolveFixtures(flagStr(args, "fixtures"), ".");
|
|
50662
50924
|
const fixtures = envelopeFixtures || flagFixtures ? { ...envelopeFixtures, ...flagFixtures } : undefined;
|
|
50663
50925
|
if (fixtures) {
|
|
@@ -50696,7 +50958,7 @@ var render = async (args) => {
|
|
|
50696
50958
|
${c.red(st.error ?? "failed")}`);
|
|
50697
50959
|
}
|
|
50698
50960
|
if (!result2.ok && result2.failureArtifacts.length > 0) {
|
|
50699
|
-
logErr(c.dim(` failing frame: ${result2.failureArtifacts.map((p2) =>
|
|
50961
|
+
logErr(c.dim(` failing frame: ${result2.failureArtifacts.map((p2) => resolve11(outDir, p2)).join(", ")}`));
|
|
50700
50962
|
}
|
|
50701
50963
|
logErr(result2.ok ? c.green(`✓ ${result2.steps.length}/${steps.length} steps passed`) : c.red(`✗ failed at step ${result2.steps.length} of ${steps.length}`));
|
|
50702
50964
|
return done((cb) => cb(), result2.ok ? 0 : 1);
|
|
@@ -50718,7 +50980,7 @@ var render = async (args) => {
|
|
|
50718
50980
|
if (isNullSink(pngFlag)) {
|
|
50719
50981
|
note(`Discarded PNG (${pngFlag})`);
|
|
50720
50982
|
} else {
|
|
50721
|
-
|
|
50983
|
+
writeFileSync19(resolve11(pngFlag), png2);
|
|
50722
50984
|
note(`Wrote ${pngFlag} (${png2.length} bytes, ${width}x${height})`);
|
|
50723
50985
|
}
|
|
50724
50986
|
return done((cb) => cb());
|
|
@@ -50733,7 +50995,7 @@ var render = async (args) => {
|
|
|
50733
50995
|
return done((cb) => cb());
|
|
50734
50996
|
}
|
|
50735
50997
|
if (out) {
|
|
50736
|
-
|
|
50998
|
+
writeFileSync19(resolve11(out), result.text);
|
|
50737
50999
|
note(`Wrote ${out} (${result.text.length} bytes)`);
|
|
50738
51000
|
return done((cb) => cb());
|
|
50739
51001
|
}
|
|
@@ -50748,8 +51010,8 @@ init_api();
|
|
|
50748
51010
|
init_config();
|
|
50749
51011
|
init_manifest();
|
|
50750
51012
|
init_util();
|
|
50751
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
50752
|
-
import { join as
|
|
51013
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync24, readFileSync as readFileSync23, writeFileSync as writeFileSync20 } from "node:fs";
|
|
51014
|
+
import { join as join24 } from "node:path";
|
|
50753
51015
|
var ENV_VAR = "LECODES_PUSH_KEY";
|
|
50754
51016
|
var context = () => {
|
|
50755
51017
|
const root = findProjectRoot(process.cwd());
|
|
@@ -50893,11 +51155,11 @@ var pnKeysList = async (ctx) => {
|
|
|
50893
51155
|
};
|
|
50894
51156
|
var writeEnv = (root, key) => {
|
|
50895
51157
|
const touched = [];
|
|
50896
|
-
const envPath =
|
|
51158
|
+
const envPath = join24(root, ".env");
|
|
50897
51159
|
const line = `${ENV_VAR}=${key}`;
|
|
50898
|
-
const existing =
|
|
51160
|
+
const existing = existsSync24(envPath) ? readFileSync23(envPath, "utf8") : "";
|
|
50899
51161
|
if (new RegExp(`^${ENV_VAR}=`, "m").test(existing)) {
|
|
50900
|
-
|
|
51162
|
+
writeFileSync20(envPath, existing.replace(new RegExp(`^${ENV_VAR}=.*$`, "m"), line));
|
|
50901
51163
|
} else {
|
|
50902
51164
|
const separator = !existing || existing.endsWith(`
|
|
50903
51165
|
`) ? "" : `
|
|
@@ -50906,8 +51168,8 @@ var writeEnv = (root, key) => {
|
|
|
50906
51168
|
`);
|
|
50907
51169
|
}
|
|
50908
51170
|
touched.push(".env");
|
|
50909
|
-
const ignorePath =
|
|
50910
|
-
const ignore =
|
|
51171
|
+
const ignorePath = join24(root, ".gitignore");
|
|
51172
|
+
const ignore = existsSync24(ignorePath) ? readFileSync23(ignorePath, "utf8") : "";
|
|
50911
51173
|
if (!ignore.split(`
|
|
50912
51174
|
`).some((l2) => l2.trim() === ".env")) {
|
|
50913
51175
|
const separator = !ignore || ignore.endsWith(`
|
|
@@ -51006,8 +51268,8 @@ var pn = async (args) => {
|
|
|
51006
51268
|
init_api();
|
|
51007
51269
|
init_config();
|
|
51008
51270
|
init_manifest();
|
|
51009
|
-
import { existsSync as
|
|
51010
|
-
import { dirname as
|
|
51271
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync19, rmSync as rmSync6, writeFileSync as writeFileSync21 } from "node:fs";
|
|
51272
|
+
import { dirname as dirname16, join as join25 } from "node:path";
|
|
51011
51273
|
init_types4();
|
|
51012
51274
|
init_localFiles();
|
|
51013
51275
|
init_util();
|
|
@@ -51030,9 +51292,9 @@ var pull = async (args) => {
|
|
|
51030
51292
|
const remotePaths = new Set([...buildPaths(project.assets).entries()].filter(([id]) => project.assets.find((a2) => a2.id === id)?.type !== "folder").map(([, path2]) => path2));
|
|
51031
51293
|
for (const path2 of Object.keys(manifest.files)) {
|
|
51032
51294
|
if (!remotePaths.has(path2)) {
|
|
51033
|
-
const abs =
|
|
51034
|
-
if (
|
|
51035
|
-
|
|
51295
|
+
const abs = join25(root, ...path2.replace(/^\//, "").split("/"));
|
|
51296
|
+
if (existsSync25(abs))
|
|
51297
|
+
rmSync6(abs);
|
|
51036
51298
|
}
|
|
51037
51299
|
}
|
|
51038
51300
|
const files = await materializeAssets(root, project, apiUrl, token, progress);
|
|
@@ -51069,18 +51331,18 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
|
|
|
51069
51331
|
const files = { ...manifest.files };
|
|
51070
51332
|
for (const asset of targets) {
|
|
51071
51333
|
const key = pathById.get(asset.id);
|
|
51072
|
-
const abs =
|
|
51073
|
-
|
|
51334
|
+
const abs = join25(root, ...key.replace(/^\//, "").split("/"));
|
|
51335
|
+
mkdirSync19(dirname16(abs), { recursive: true });
|
|
51074
51336
|
let sha;
|
|
51075
51337
|
if (asset.type === "file") {
|
|
51076
51338
|
if (!asset.file?.src)
|
|
51077
51339
|
continue;
|
|
51078
51340
|
const bytes = await downloadBlob(apiUrl, asset.file.src);
|
|
51079
|
-
|
|
51341
|
+
writeFileSync21(abs, bytes);
|
|
51080
51342
|
sha = sha256(bytes);
|
|
51081
51343
|
} else {
|
|
51082
51344
|
const text = asset.text ?? "";
|
|
51083
|
-
|
|
51345
|
+
writeFileSync21(abs, text);
|
|
51084
51346
|
sha = sha256(text);
|
|
51085
51347
|
}
|
|
51086
51348
|
files[key] = { id: asset.id, type: kindOf3(asset.type), sha };
|
|
@@ -51093,8 +51355,8 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
|
|
|
51093
51355
|
|
|
51094
51356
|
// src/commands/scene.ts
|
|
51095
51357
|
init_manifest();
|
|
51096
|
-
import { existsSync as
|
|
51097
|
-
import { basename as
|
|
51358
|
+
import { existsSync as existsSync27, readFileSync as readFileSync25, readdirSync as readdirSync12, statSync as statSync8, writeFileSync as writeFileSync22 } from "node:fs";
|
|
51359
|
+
import { basename as basename14, dirname as dirname17, join as join27, resolve as resolve12 } from "node:path";
|
|
51098
51360
|
|
|
51099
51361
|
// src/compile/sceneCompile.ts
|
|
51100
51362
|
init_localFiles();
|
|
@@ -51102,17 +51364,17 @@ init_ignore();
|
|
|
51102
51364
|
init_distRoot();
|
|
51103
51365
|
init_util();
|
|
51104
51366
|
init_projectCompile();
|
|
51105
|
-
import { existsSync as
|
|
51106
|
-
import { join as
|
|
51367
|
+
import { existsSync as existsSync26, readFileSync as readFileSync24 } from "node:fs";
|
|
51368
|
+
import { join as join26 } from "node:path";
|
|
51107
51369
|
var TEXT_EXT = /\.(ts|js|json|svg|txt|md)$/i;
|
|
51108
51370
|
var rel = (key) => key.replace(/^\//, "");
|
|
51109
51371
|
var resolveHarness = (loadSceneHarness2) => {
|
|
51110
|
-
const inRepo =
|
|
51372
|
+
const inRepo = join26(distRoot, "..", "projects", "src", "scene-editor");
|
|
51111
51373
|
try {
|
|
51112
|
-
return loadSceneHarness2(
|
|
51374
|
+
return loadSceneHarness2(existsSync26(inRepo) ? inRepo : undefined);
|
|
51113
51375
|
} catch {
|
|
51114
|
-
const vendored =
|
|
51115
|
-
if (
|
|
51376
|
+
const vendored = join26(distRoot, "runtime", "scene-harness.json");
|
|
51377
|
+
if (existsSync26(vendored))
|
|
51116
51378
|
return JSON.parse(readFileSync24(vendored, "utf8"));
|
|
51117
51379
|
throw new CliError("scene-editor harness sources not found — reinstall the CLI or set LECODES_SCENE_HARNESS_DIR.");
|
|
51118
51380
|
}
|
|
@@ -51167,22 +51429,22 @@ var getContext2 = (args) => {
|
|
|
51167
51429
|
manifest = null;
|
|
51168
51430
|
const pathArg = args._[0];
|
|
51169
51431
|
if (pathArg) {
|
|
51170
|
-
const abs =
|
|
51171
|
-
if (
|
|
51432
|
+
const abs = resolve12(process.cwd(), pathArg);
|
|
51433
|
+
if (existsSync27(abs) && statSync8(abs).isDirectory())
|
|
51172
51434
|
root = abs;
|
|
51173
|
-
else if (abs.endsWith(".scene.ts") &&
|
|
51174
|
-
root =
|
|
51435
|
+
else if (abs.endsWith(".scene.ts") && existsSync27(abs))
|
|
51436
|
+
root = dirname17(abs);
|
|
51175
51437
|
else
|
|
51176
51438
|
throw new CliError(`No such folder or .scene.ts: ${pathArg}`);
|
|
51177
51439
|
}
|
|
51178
51440
|
}
|
|
51179
|
-
return { root, manifest, name: manifest?.name ??
|
|
51441
|
+
return { root, manifest, name: manifest?.name ?? basename14(root) };
|
|
51180
51442
|
};
|
|
51181
51443
|
var writeMcpConfig2 = (root, port) => {
|
|
51182
|
-
const path2 =
|
|
51444
|
+
const path2 = join27(root, ".mcp.json");
|
|
51183
51445
|
const entry = { type: "http", url: `http://localhost:${port}/mcp` };
|
|
51184
51446
|
let config = {};
|
|
51185
|
-
if (
|
|
51447
|
+
if (existsSync27(path2)) {
|
|
51186
51448
|
try {
|
|
51187
51449
|
config = JSON.parse(readFileSync25(path2, "utf8"));
|
|
51188
51450
|
} catch {
|
|
@@ -51197,17 +51459,17 @@ var writeMcpConfig2 = (root, port) => {
|
|
|
51197
51459
|
return;
|
|
51198
51460
|
}
|
|
51199
51461
|
config.mcpServers["lecodes-scene"] = entry;
|
|
51200
|
-
|
|
51462
|
+
writeFileSync22(path2, JSON.stringify(config, null, 2) + `
|
|
51201
51463
|
`);
|
|
51202
51464
|
note(`${existing ? "updated" : "created"} .mcp.json (lecodes-scene MCP → ${entry.url})`);
|
|
51203
51465
|
};
|
|
51204
51466
|
var countScenes = (dir) => {
|
|
51205
51467
|
let n2 = 0;
|
|
51206
|
-
for (const name of
|
|
51468
|
+
for (const name of readdirSync12(dir)) {
|
|
51207
51469
|
if (name === "node_modules" || name.startsWith("."))
|
|
51208
51470
|
continue;
|
|
51209
|
-
const full =
|
|
51210
|
-
if (
|
|
51471
|
+
const full = join27(dir, name);
|
|
51472
|
+
if (statSync8(full).isDirectory())
|
|
51211
51473
|
n2 += countScenes(full);
|
|
51212
51474
|
else if (name.endsWith(".scene.ts"))
|
|
51213
51475
|
n2++;
|
|
@@ -51280,8 +51542,8 @@ var status2 = async (_args) => {
|
|
|
51280
51542
|
};
|
|
51281
51543
|
|
|
51282
51544
|
// src/commands/test.ts
|
|
51283
|
-
import { existsSync as
|
|
51284
|
-
import { dirname as
|
|
51545
|
+
import { existsSync as existsSync28, readFileSync as readFileSync26, readdirSync as readdirSync13 } from "node:fs";
|
|
51546
|
+
import { dirname as dirname18, resolve as resolve13 } from "node:path";
|
|
51285
51547
|
init_util();
|
|
51286
51548
|
var deriveDesignFlows = (meta, entryScreen) => {
|
|
51287
51549
|
const edges = meta.edges ?? [];
|
|
@@ -51357,7 +51619,7 @@ var readFlowFile = (abs, displayName) => {
|
|
|
51357
51619
|
name: file.name ?? displayName,
|
|
51358
51620
|
kind: "file",
|
|
51359
51621
|
steps: file.steps,
|
|
51360
|
-
fixtures: resolveFixtures(file.fixtures,
|
|
51622
|
+
fixtures: resolveFixtures(file.fixtures, dirname18(abs)),
|
|
51361
51623
|
width: file.width,
|
|
51362
51624
|
height: file.height,
|
|
51363
51625
|
settle: file.settle,
|
|
@@ -51369,34 +51631,34 @@ var test = async (args) => {
|
|
|
51369
51631
|
const { js, warnings, useLocalAssets, root } = await compileHeadlessBundle(args);
|
|
51370
51632
|
const renderer = await requireRenderer();
|
|
51371
51633
|
const projectViewport = desktopDefaultViewport(root);
|
|
51372
|
-
const testsDir =
|
|
51373
|
-
const designMetaPath =
|
|
51634
|
+
const testsDir = resolve13(root, flagStr(args, "dir") ?? "tests");
|
|
51635
|
+
const designMetaPath = resolve13(root, flagStr(args, "design-dir") ?? "design", "meta.json");
|
|
51374
51636
|
const asJson = flagBool(args, "json");
|
|
51375
51637
|
const out = asJson ? logErr : log;
|
|
51376
51638
|
const cases = [];
|
|
51377
51639
|
if (args._.length > 0) {
|
|
51378
51640
|
for (const given of args._) {
|
|
51379
|
-
const candidates = [
|
|
51380
|
-
const abs = candidates.find((p2) =>
|
|
51641
|
+
const candidates = [resolve13(given), resolve13(testsDir, given), resolve13(testsDir, `${given}.flow.json`)];
|
|
51642
|
+
const abs = candidates.find((p2) => existsSync28(p2));
|
|
51381
51643
|
if (!abs)
|
|
51382
51644
|
throw new CliError(`No such test: "${given}" (looked in ${testsDir}).`);
|
|
51383
51645
|
cases.push(readFlowFile(abs, given));
|
|
51384
51646
|
}
|
|
51385
|
-
} else if (
|
|
51386
|
-
for (const f3 of
|
|
51387
|
-
cases.push(readFlowFile(
|
|
51647
|
+
} else if (existsSync28(testsDir)) {
|
|
51648
|
+
for (const f3 of readdirSync13(testsDir).filter((f4) => f4.endsWith(".flow.json")).sort()) {
|
|
51649
|
+
cases.push(readFlowFile(resolve13(testsDir, f3), f3));
|
|
51388
51650
|
}
|
|
51389
51651
|
}
|
|
51390
51652
|
const fixturesFlag = flagStr(args, "fixtures");
|
|
51391
|
-
const defaultPack =
|
|
51392
|
-
const flowFixtures = fixturesFlag !== undefined ? resolveFixtures(fixturesFlag, ".") :
|
|
51653
|
+
const defaultPack = resolve13(testsDir, "fixtures.json");
|
|
51654
|
+
const flowFixtures = fixturesFlag !== undefined ? resolveFixtures(fixturesFlag, ".") : existsSync28(defaultPack) ? resolveFixtures("fixtures.json", testsDir) : undefined;
|
|
51393
51655
|
const flowsOnly = flagBool(args, "flows");
|
|
51394
51656
|
const wantFlows = !flagBool(args, "no-flows") && args._.length === 0;
|
|
51395
51657
|
if (flowsOnly)
|
|
51396
51658
|
cases.length = 0;
|
|
51397
51659
|
let skipped = [];
|
|
51398
51660
|
let entryScreen = null;
|
|
51399
|
-
if ((wantFlows || flowsOnly) &&
|
|
51661
|
+
if ((wantFlows || flowsOnly) && existsSync28(designMetaPath)) {
|
|
51400
51662
|
let meta;
|
|
51401
51663
|
try {
|
|
51402
51664
|
meta = JSON.parse(readFileSync26(designMetaPath, "utf8"));
|
|
@@ -51431,7 +51693,7 @@ var test = async (args) => {
|
|
|
51431
51693
|
}, timeoutMs);
|
|
51432
51694
|
killer.unref?.();
|
|
51433
51695
|
const onConsole = flagBool(args, "logs") ? (level, text) => logErr(`${c.dim(`[${level}]`)} ${text}`) : undefined;
|
|
51434
|
-
const artifactsRoot =
|
|
51696
|
+
const artifactsRoot = resolve13(root, flagStr(args, "artifacts") ?? resolve13(testsDir, ".artifacts"));
|
|
51435
51697
|
const results = [];
|
|
51436
51698
|
for (const kase of cases) {
|
|
51437
51699
|
const flagSettle = flagStr(args, "settle");
|
|
@@ -51449,14 +51711,14 @@ var test = async (args) => {
|
|
|
51449
51711
|
onConsole
|
|
51450
51712
|
});
|
|
51451
51713
|
try {
|
|
51452
|
-
const io = fileScenarioIO(
|
|
51714
|
+
const io = fileScenarioIO(resolve13(artifactsRoot, slug3(kase.name)));
|
|
51453
51715
|
const outcome = await renderer.runScenario(session, steps, io);
|
|
51454
51716
|
result = {
|
|
51455
51717
|
name: kase.name,
|
|
51456
51718
|
kind: kase.kind,
|
|
51457
51719
|
ok: outcome.ok,
|
|
51458
51720
|
steps: outcome.steps.map((s2) => ({ desc: s2.desc, ok: s2.ok, error: s2.error })),
|
|
51459
|
-
artifacts: outcome.failureArtifacts.map((p2) =>
|
|
51721
|
+
artifacts: outcome.failureArtifacts.map((p2) => resolve13(artifactsRoot, slug3(kase.name), p2))
|
|
51460
51722
|
};
|
|
51461
51723
|
} finally {
|
|
51462
51724
|
session.dispose();
|
|
@@ -51518,11 +51780,11 @@ init_config();
|
|
|
51518
51780
|
init_manifest();
|
|
51519
51781
|
init_types4();
|
|
51520
51782
|
init_util();
|
|
51521
|
-
import { resolve as
|
|
51783
|
+
import { resolve as resolve14 } from "node:path";
|
|
51522
51784
|
var label = (bundle) => `${Object.keys(bundle).length} file(s)`;
|
|
51523
51785
|
var types4 = async (args) => {
|
|
51524
51786
|
const dirArg = args._[0];
|
|
51525
|
-
const root = dirArg ?
|
|
51787
|
+
const root = dirArg ? resolve14(dirArg) : (() => {
|
|
51526
51788
|
try {
|
|
51527
51789
|
return findProjectRoot(process.cwd());
|
|
51528
51790
|
} catch {
|
|
@@ -51581,23 +51843,23 @@ var types4 = async (args) => {
|
|
|
51581
51843
|
};
|
|
51582
51844
|
|
|
51583
51845
|
// src/index.ts
|
|
51584
|
-
import { existsSync as
|
|
51846
|
+
import { existsSync as existsSync30, readFileSync as readFileSync28, realpathSync as realpathSync2 } from "node:fs";
|
|
51585
51847
|
import { createRequire as createRequire4 } from "node:module";
|
|
51586
|
-
import { delimiter as delimiter2, dirname as
|
|
51848
|
+
import { delimiter as delimiter2, dirname as dirname20, join as join29 } from "node:path";
|
|
51587
51849
|
|
|
51588
51850
|
// src/commands/install.ts
|
|
51589
51851
|
init_util();
|
|
51590
|
-
import { execFileSync as
|
|
51591
|
-
import { appendFileSync as appendFileSync3, chmodSync as chmodSync2, cpSync as
|
|
51852
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
51853
|
+
import { appendFileSync as appendFileSync3, chmodSync as chmodSync2, cpSync as cpSync4, existsSync as existsSync29, mkdirSync as mkdirSync20, readFileSync as readFileSync27, realpathSync, rmSync as rmSync7, symlinkSync } from "node:fs";
|
|
51592
51854
|
import { homedir as homedir7 } from "node:os";
|
|
51593
|
-
import { basename as
|
|
51855
|
+
import { basename as basename15, delimiter, dirname as dirname19, join as join28, resolve as resolve15, sep as sep2 } from "node:path";
|
|
51594
51856
|
var isWin = process.platform === "win32";
|
|
51595
51857
|
var HOME = homedir7();
|
|
51596
51858
|
var safeRealpath = (p2) => {
|
|
51597
51859
|
try {
|
|
51598
51860
|
return realpathSync(p2);
|
|
51599
51861
|
} catch {
|
|
51600
|
-
return
|
|
51862
|
+
return resolve15(p2);
|
|
51601
51863
|
}
|
|
51602
51864
|
};
|
|
51603
51865
|
var onPath = (dir) => {
|
|
@@ -51613,25 +51875,25 @@ var onPath = (dir) => {
|
|
|
51613
51875
|
});
|
|
51614
51876
|
};
|
|
51615
51877
|
var install = async (args) => {
|
|
51616
|
-
const bundle =
|
|
51617
|
-
const exeName2 =
|
|
51878
|
+
const bundle = dirname19(process.execPath);
|
|
51879
|
+
const exeName2 = basename15(process.execPath);
|
|
51618
51880
|
const chiselName = isWin ? "chisel.exe" : "chisel";
|
|
51619
|
-
if (!
|
|
51881
|
+
if (!existsSync29(join28(bundle, "runtime")) || !existsSync29(join28(bundle, chiselName))) {
|
|
51620
51882
|
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.");
|
|
51621
51883
|
}
|
|
51622
|
-
const companions = [exeName2, chiselName, "runtime", "README.txt"].filter((f3) =>
|
|
51884
|
+
const companions = [exeName2, chiselName, "runtime", "README.txt"].filter((f3) => existsSync29(join28(bundle, f3)));
|
|
51623
51885
|
if (isWin)
|
|
51624
51886
|
return installWindows(args, bundle, companions);
|
|
51625
51887
|
return installUnix(args, bundle, companions);
|
|
51626
51888
|
};
|
|
51627
51889
|
var copyBundle = (bundle, target, companions) => {
|
|
51628
|
-
|
|
51890
|
+
mkdirSync20(target, { recursive: true });
|
|
51629
51891
|
if (safeRealpath(target) === safeRealpath(bundle))
|
|
51630
51892
|
return;
|
|
51631
51893
|
for (const f3 of companions) {
|
|
51632
|
-
const dest =
|
|
51633
|
-
|
|
51634
|
-
|
|
51894
|
+
const dest = join28(target, f3);
|
|
51895
|
+
rmSync7(dest, { recursive: true, force: true });
|
|
51896
|
+
cpSync4(join28(bundle, f3), dest, { recursive: true });
|
|
51635
51897
|
}
|
|
51636
51898
|
};
|
|
51637
51899
|
var permError = (e2, dir, system) => {
|
|
@@ -51643,41 +51905,41 @@ var permError = (e2, dir, system) => {
|
|
|
51643
51905
|
};
|
|
51644
51906
|
var installUnix = (args, bundle, companions) => {
|
|
51645
51907
|
const system = flagBool(args, "system");
|
|
51646
|
-
const libDir =
|
|
51647
|
-
const binDir =
|
|
51648
|
-
const link2 =
|
|
51908
|
+
const libDir = resolve15(flagStr(args, "dir") ?? (system ? "/usr/local/lib/lecodes-cli" : join28(HOME, ".local", "lib", "lecodes-cli")));
|
|
51909
|
+
const binDir = resolve15(flagStr(args, "bin") ?? (system ? "/usr/local/bin" : join28(HOME, ".local", "bin")));
|
|
51910
|
+
const link2 = join28(binDir, "lecodes");
|
|
51649
51911
|
try {
|
|
51650
51912
|
copyBundle(bundle, libDir, companions);
|
|
51651
|
-
chmodSync2(
|
|
51652
|
-
chmodSync2(
|
|
51913
|
+
chmodSync2(join28(libDir, "lecodes"), 493);
|
|
51914
|
+
chmodSync2(join28(libDir, "chisel"), 493);
|
|
51653
51915
|
} catch (e2) {
|
|
51654
51916
|
throw permError(e2, libDir, system);
|
|
51655
51917
|
}
|
|
51656
51918
|
try {
|
|
51657
|
-
|
|
51658
|
-
|
|
51659
|
-
symlinkSync(
|
|
51919
|
+
mkdirSync20(binDir, { recursive: true });
|
|
51920
|
+
rmSync7(link2, { force: true });
|
|
51921
|
+
symlinkSync(join28(libDir, "lecodes"), link2);
|
|
51660
51922
|
} catch (e2) {
|
|
51661
51923
|
throw permError(e2, binDir, system);
|
|
51662
51924
|
}
|
|
51663
51925
|
success(`Installed to ${c.bold(libDir)}`);
|
|
51664
|
-
log(` ${c.dim(link2 + " → " +
|
|
51926
|
+
log(` ${c.dim(link2 + " → " + join28(libDir, "lecodes"))}`);
|
|
51665
51927
|
finishPathUnix(args, binDir);
|
|
51666
51928
|
};
|
|
51667
51929
|
var shellRc = () => {
|
|
51668
|
-
const shell =
|
|
51930
|
+
const shell = basename15(process.env.SHELL ?? "");
|
|
51669
51931
|
if (shell === "zsh")
|
|
51670
|
-
return { file:
|
|
51932
|
+
return { file: join28(HOME, ".zshrc"), name: "~/.zshrc" };
|
|
51671
51933
|
if (shell === "bash")
|
|
51672
|
-
return { file:
|
|
51673
|
-
return { file:
|
|
51934
|
+
return { file: join28(HOME, ".bashrc"), name: "~/.bashrc" };
|
|
51935
|
+
return { file: join28(HOME, ".profile"), name: "~/.profile" };
|
|
51674
51936
|
};
|
|
51675
51937
|
var PATH_MARKER = "# Added by `lecodes install`";
|
|
51676
51938
|
var appendPathToShellRc = (binDir) => {
|
|
51677
51939
|
const { file, name } = shellRc();
|
|
51678
51940
|
const line = `export PATH="${binDir}:$PATH"`;
|
|
51679
51941
|
try {
|
|
51680
|
-
const existing =
|
|
51942
|
+
const existing = existsSync29(file) ? readFileSync27(file, "utf8") : "";
|
|
51681
51943
|
if (existing.includes(line))
|
|
51682
51944
|
return name;
|
|
51683
51945
|
appendFileSync3(file, `${existing && !existing.endsWith(`
|
|
@@ -51715,7 +51977,7 @@ var finishPathUnix = (args, binDir) => {
|
|
|
51715
51977
|
log(` • or install system-wide (no PATH edit): ${c.bold("sudo lecodes install --system")}`);
|
|
51716
51978
|
};
|
|
51717
51979
|
var installWindows = (args, bundle, companions) => {
|
|
51718
|
-
const target =
|
|
51980
|
+
const target = resolve15(flagStr(args, "dir") ?? join28(process.env.LOCALAPPDATA ?? join28(HOME, "AppData", "Local"), "Programs", "lecodes-cli"));
|
|
51719
51981
|
try {
|
|
51720
51982
|
copyBundle(bundle, target, companions);
|
|
51721
51983
|
} catch (e2) {
|
|
@@ -51743,7 +52005,7 @@ var installWindows = (args, bundle, companions) => {
|
|
|
51743
52005
|
var addToUserPathWindows = (dir) => {
|
|
51744
52006
|
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') }";
|
|
51745
52007
|
try {
|
|
51746
|
-
|
|
52008
|
+
execFileSync5("powershell", ["-NoProfile", "-NonInteractive", "-Command", script], { stdio: "ignore", env: { ...process.env, LECODES_INSTALL_DIR: dir } });
|
|
51747
52009
|
return true;
|
|
51748
52010
|
} catch {
|
|
51749
52011
|
return false;
|
|
@@ -51753,9 +52015,9 @@ var addToUserPathWindows = (dir) => {
|
|
|
51753
52015
|
// src/index.ts
|
|
51754
52016
|
init_util();
|
|
51755
52017
|
var STANDALONE = (() => {
|
|
51756
|
-
const dir =
|
|
52018
|
+
const dir = dirname20(process.execPath);
|
|
51757
52019
|
const chisel = process.platform === "win32" ? "chisel.exe" : "chisel";
|
|
51758
|
-
return
|
|
52020
|
+
return existsSync30(join29(dir, "runtime")) && existsSync30(join29(dir, chisel));
|
|
51759
52021
|
})();
|
|
51760
52022
|
var INSTALLED = STANDALONE && (() => {
|
|
51761
52023
|
const exe = process.platform === "win32" ? "lecodes.exe" : "lecodes";
|
|
@@ -51770,8 +52032,8 @@ var INSTALLED = STANDALONE && (() => {
|
|
|
51770
52032
|
if (!d)
|
|
51771
52033
|
return false;
|
|
51772
52034
|
try {
|
|
51773
|
-
const cand =
|
|
51774
|
-
return
|
|
52035
|
+
const cand = join29(d, exe);
|
|
52036
|
+
return existsSync30(cand) && realpathSync2(cand) === self2;
|
|
51775
52037
|
} catch {
|
|
51776
52038
|
return false;
|
|
51777
52039
|
}
|
|
@@ -51925,7 +52187,11 @@ ${c.bold("Commands:")}
|
|
|
51925
52187
|
run compile the current project and open it in a real
|
|
51926
52188
|
desktop window (size/min-size/title default from
|
|
51927
52189
|
app.json's desktop block; --width/--height/--title
|
|
51928
|
-
override; --
|
|
52190
|
+
override; --fullscreen/--windowed override the
|
|
52191
|
+
app.json fullscreen; --scale <0.5..4|fit> overrides
|
|
52192
|
+
the display scale — "fit" sizes a 1080p-designed app
|
|
52193
|
+
to the monitor with the density raised to match;
|
|
52194
|
+
--entry overrides the entrypoint)
|
|
51929
52195
|
update move to the newest published release
|
|
51930
52196
|
(cached in ~/.lecodes/renderer)
|
|
51931
52197
|
scene [path] LeCodes Scene Editor — visually edit a .scene.ts (tree, inspector,
|
|
@@ -51934,8 +52200,10 @@ ${c.bold("Commands:")}
|
|
|
51934
52200
|
--port <n> editor-server port (default 4488)
|
|
51935
52201
|
--no-open don't open the browser
|
|
51936
52202
|
--no-mcp don't register the MCP server in .mcp.json
|
|
51937
|
-
app init <ios|android>
|
|
51938
|
-
(offline-first: runs the bundled app.js; never pushed to LeCodes)
|
|
52203
|
+
app init <ios|android|desktop> Generate a native shell in <project>/ios/ or <project>/android/
|
|
52204
|
+
(offline-first: runs the bundled app.js; never pushed to LeCodes).
|
|
52205
|
+
desktop: no shell needed — writes app.json's desktop block
|
|
52206
|
+
(window size; --fullscreen for kiosks)
|
|
51939
52207
|
--name <s> display name (default: app.json / folder name)
|
|
51940
52208
|
--bundle-id <id> iOS bundle identifier
|
|
51941
52209
|
--app-id <id> Android applicationId (default com.example.<slug>)
|
|
@@ -51977,6 +52245,15 @@ ${c.bold("Commands:")}
|
|
|
51977
52245
|
--configuration <c> Release (default) or Debug
|
|
51978
52246
|
--no-aab skip the .aab (APK only)
|
|
51979
52247
|
--no-sync skip the lecodes app sync step
|
|
52248
|
+
app build desktop Stage a portable desktop app folder (default dist/<name>/):
|
|
52249
|
+
the cached host renamed to <Name>.exe + app.js + every asset +
|
|
52250
|
+
app.json (the host ≥1.3.0 reads title/size/fullscreen from it)
|
|
52251
|
+
+ app.ico from app.json "icon". Duplicate asset basenames are
|
|
52252
|
+
a build error (they silently alias in the bundle).
|
|
52253
|
+
-o <dir> output folder (default dist/<name>)
|
|
52254
|
+
--slug <name> folder/zip name override (e.g. Latin-only)
|
|
52255
|
+
--probe headless-render the staged folder to a PNG
|
|
52256
|
+
--zip <out>.zip, UTF-8/forward-slash entries
|
|
51980
52257
|
app upload ios Upload the archive to App Store Connect (then bumps buildNumber)
|
|
51981
52258
|
--key-path <p8> --key-id <id> --issuer-id <id>
|
|
51982
52259
|
ASC API key; stored in ~/.lecodes/asc.json
|
|
@@ -52003,15 +52280,15 @@ var OPTIONAL_PEERS = [
|
|
|
52003
52280
|
var peerVersion = (name, probe) => {
|
|
52004
52281
|
try {
|
|
52005
52282
|
const require2 = createRequire4(import.meta.url);
|
|
52006
|
-
let dir =
|
|
52283
|
+
let dir = dirname20(require2.resolve(probe));
|
|
52007
52284
|
for (let depth = 0;depth < 16; depth++) {
|
|
52008
|
-
const manifest =
|
|
52009
|
-
if (
|
|
52285
|
+
const manifest = join29(dir, "package.json");
|
|
52286
|
+
if (existsSync30(manifest)) {
|
|
52010
52287
|
const parsed = JSON.parse(readFileSync28(manifest, "utf8"));
|
|
52011
52288
|
if (parsed.name === name)
|
|
52012
52289
|
return parsed.version ?? "unknown";
|
|
52013
52290
|
}
|
|
52014
|
-
const parent =
|
|
52291
|
+
const parent = dirname20(dir);
|
|
52015
52292
|
if (parent === dir)
|
|
52016
52293
|
break;
|
|
52017
52294
|
dir = parent;
|