lecodes-cli 0.14.2 → 0.16.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.16.0",
|
|
45215
45216
|
dependencies: {
|
|
45216
45217
|
"@letary/chisel": "^0.6.0",
|
|
45217
45218
|
jimp: "^1.6.1"
|
|
@@ -45224,9 +45225,9 @@ var package_default = {
|
|
|
45224
45225
|
sdk: "workspace:*"
|
|
45225
45226
|
},
|
|
45226
45227
|
peerDependencies: {
|
|
45227
|
-
"lecodes-design": "^0.
|
|
45228
|
-
"lecodes-renderer": "^0.8.
|
|
45229
|
-
"lecodes-3d-editor": "^0.
|
|
45228
|
+
"lecodes-design": "^0.8.0",
|
|
45229
|
+
"lecodes-renderer": "^0.8.3",
|
|
45230
|
+
"lecodes-3d-editor": "^0.3.0"
|
|
45230
45231
|
},
|
|
45231
45232
|
bin: {
|
|
45232
45233
|
lecodes: "dist/index.js"
|
|
@@ -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();
|
|
@@ -46323,6 +46324,23 @@ NEXT launch. Offline it's a silent no-op — the app always launches from a loca
|
|
|
46323
46324
|
An App Store update always beats a cached OTA bundle. JS only: adding/removing native
|
|
46324
46325
|
plugins still needs a store release.
|
|
46325
46326
|
|
|
46327
|
+
Shipping an update is just \`lecodes push --publish\` — no rebuild, no store review:
|
|
46328
|
+
running apps download the new code on their next launch and run it on the launch after.
|
|
46329
|
+
|
|
46330
|
+
Shells generated before OTA support have a \`ViewController.swift\` that reads app.js from
|
|
46331
|
+
\`Bundle.main\` directly, so downloaded updates would never run — and since \`App/*.swift\`
|
|
46332
|
+
is yours, sync won't rewrite it (\`lecodes app doctor\` flags this). Replace the
|
|
46333
|
+
\`Bundle.main\` load with:
|
|
46334
|
+
|
|
46335
|
+
\`\`\`swift
|
|
46336
|
+
LeCodesUpdater.checkForUpdate()
|
|
46337
|
+
guard let code = LeCodesUpdater.activeBundle() else {
|
|
46338
|
+
print("LeCodes: bundled app.js is missing — run \`lecodes app sync\`")
|
|
46339
|
+
return
|
|
46340
|
+
}
|
|
46341
|
+
engine.run(code)
|
|
46342
|
+
\`\`\`
|
|
46343
|
+
|
|
46326
46344
|
## Config
|
|
46327
46345
|
|
|
46328
46346
|
Identity lives in \`../app.json\` (name, version, plugins, ios.bundleId / teamId / variant /
|
|
@@ -46359,7 +46377,7 @@ var readJson = (path) => {
|
|
|
46359
46377
|
if (!existsSync10(path))
|
|
46360
46378
|
return null;
|
|
46361
46379
|
try {
|
|
46362
|
-
return JSON.parse(readFileSync11(path, "utf8"));
|
|
46380
|
+
return JSON.parse(readFileSync11(path, "utf8").replace(/^\uFEFF/, ""));
|
|
46363
46381
|
} catch {
|
|
46364
46382
|
throw new CliError(`Invalid JSON in ${path}`);
|
|
46365
46383
|
}
|
|
@@ -46400,6 +46418,34 @@ var ensureShellIgnored = (root, dir) => {
|
|
|
46400
46418
|
`);
|
|
46401
46419
|
log(`${c.green("updated")} .lecodesignore (+ ${dir}/)`);
|
|
46402
46420
|
};
|
|
46421
|
+
var SDK_VARIANTS = ["core", "2d", "3d", "full"];
|
|
46422
|
+
var detectEngines = (bundleJs) => {
|
|
46423
|
+
const lines = [];
|
|
46424
|
+
for (const line of bundleJs.split(`
|
|
46425
|
+
`)) {
|
|
46426
|
+
if (!line.startsWith("//"))
|
|
46427
|
+
break;
|
|
46428
|
+
lines.push(line);
|
|
46429
|
+
}
|
|
46430
|
+
const header = lines.join(`
|
|
46431
|
+
`);
|
|
46432
|
+
return { gl: !/^\/\/ gl: disable$/m.test(header), g2: /^\/\/ 2d: enable$/m.test(header) };
|
|
46433
|
+
};
|
|
46434
|
+
var resolveVariant = (requested, bundleJs, configKey = "ios.variant") => {
|
|
46435
|
+
const uses = detectEngines(bundleJs);
|
|
46436
|
+
const detected = uses.gl && uses.g2 ? "full" : uses.gl ? "3d" : uses.g2 ? "2d" : "core";
|
|
46437
|
+
if (requested === "auto")
|
|
46438
|
+
return { variant: detected, detected };
|
|
46439
|
+
const variant = requested;
|
|
46440
|
+
const missing = [
|
|
46441
|
+
uses.gl && variant !== "3d" && variant !== "full" ? "3D" : null,
|
|
46442
|
+
uses.g2 && variant !== "2d" && variant !== "full" ? "2D" : null
|
|
46443
|
+
].filter(Boolean);
|
|
46444
|
+
if (missing.length > 0) {
|
|
46445
|
+
warn(`project uses the ${missing.join(" + ")} engine but ${configKey} forces "${variant}" — those scenes won't run on device; remove ${configKey} to auto-select "${detected}".`);
|
|
46446
|
+
}
|
|
46447
|
+
return { variant, detected };
|
|
46448
|
+
};
|
|
46403
46449
|
var VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
46404
46450
|
var cmpVersion = (a, b) => {
|
|
46405
46451
|
const pa = a.split(".").map(Number);
|
|
@@ -46747,7 +46793,7 @@ validateDistributionUrl=true
|
|
|
46747
46793
|
zipStoreBase=GRADLE_USER_HOME
|
|
46748
46794
|
zipStorePath=wrapper/dists
|
|
46749
46795
|
`;
|
|
46750
|
-
var runtimeBuildGradleTemplate = (sdkVersion, pluginModules) => {
|
|
46796
|
+
var runtimeBuildGradleTemplate = (sdkVersion, pluginModules, sdkArtifact = "lecodes-sdk") => {
|
|
46751
46797
|
const deps = SDK_RUNTIME_DEPS.map((d) => `
|
|
46752
46798
|
api("${d}")`).join("");
|
|
46753
46799
|
const plugins = pluginModules.map((m) => `
|
|
@@ -46779,8 +46825,10 @@ kotlin {
|
|
|
46779
46825
|
}
|
|
46780
46826
|
|
|
46781
46827
|
dependencies {
|
|
46782
|
-
// Exact pin — the release asset URL is derived from this version (see
|
|
46783
|
-
|
|
46828
|
+
// Exact pin — the release asset URL is derived from this artifact + version (see
|
|
46829
|
+
// settings.gradle.kts). The artifact is the SDK variant sync picked (lecodes-sdk = full,
|
|
46830
|
+
// -3d / -2d / -core = smaller engine builds); every variant vends the same Kotlin API.
|
|
46831
|
+
api("io.letary:${sdkArtifact}:${sdkVersion}@aar")
|
|
46784
46832
|
// The fat AAR carries no POM, so its own dependencies ride here explicitly.${deps}${plugins}
|
|
46785
46833
|
}
|
|
46786
46834
|
`;
|
|
@@ -46830,7 +46878,7 @@ ${push ? ` ${pushNs}.PushPlugin.setForeground(foreground)
|
|
|
46830
46878
|
}
|
|
46831
46879
|
`;
|
|
46832
46880
|
};
|
|
46833
|
-
var pluginBuildGradleTemplate = (id, module, namespace, sdkVersion, dependencies, aarNames, hasConsumerRules) => {
|
|
46881
|
+
var pluginBuildGradleTemplate = (id, module, namespace, sdkVersion, dependencies, aarNames, hasConsumerRules, sdkArtifact = "lecodes-sdk") => {
|
|
46834
46882
|
const consumer = hasConsumerRules ? `
|
|
46835
46883
|
consumerProguardFiles("consumer-rules.pro")` : "";
|
|
46836
46884
|
const aars = aarNames.map((n2) => `
|
|
@@ -46865,7 +46913,9 @@ kotlin {
|
|
|
46865
46913
|
}
|
|
46866
46914
|
|
|
46867
46915
|
dependencies {
|
|
46868
|
-
|
|
46916
|
+
// Same artifact (SDK variant) as lecodes-runtime — two different variant coordinates in one
|
|
46917
|
+
// shell would put two copies of the SDK classes on the runtime classpath.
|
|
46918
|
+
implementation("io.letary:${sdkArtifact}:${sdkVersion}@aar")${sdkDeps}${aars}${manifestDeps}
|
|
46869
46919
|
}
|
|
46870
46920
|
`;
|
|
46871
46921
|
};
|
|
@@ -46897,6 +46947,14 @@ in \`settings.gradle.kts\`. \`lecodes app update\` moves the pin (or set \`app.j
|
|
|
46897
46947
|
\`android.sdk\`). The SDK AAR ships no POM, so its own dependencies are declared explicitly in
|
|
46898
46948
|
the generated runtime build file.
|
|
46899
46949
|
|
|
46950
|
+
## SDK variant
|
|
46951
|
+
|
|
46952
|
+
Sync links the smallest SDK artifact covering the engines the compiled bundle actually uses —
|
|
46953
|
+
\`lecodes-sdk\` (full: 3D+2D), \`lecodes-sdk-3d\`, \`lecodes-sdk-2d\` or \`lecodes-sdk-core\`
|
|
46954
|
+
(UI only) — so an app without 3D scenes doesn't ship Filament. If the project starts using an
|
|
46955
|
+
engine, sync switches automatically on the next run; set \`android.variant\` in \`../app.json\`
|
|
46956
|
+
to force one (needs lecodes-android-sdk >= 1.3.0; older releases ship only the full AAR).
|
|
46957
|
+
|
|
46900
46958
|
## App-local native code
|
|
46901
46959
|
|
|
46902
46960
|
Drop Kotlin files into \`app/src/main/java/\` and register them in \`MainActivity\` after the
|
|
@@ -47560,6 +47618,8 @@ if "%OS%"=="Windows_NT" endlocal
|
|
|
47560
47618
|
// src/commands/appAndroid.ts
|
|
47561
47619
|
var ANDROID_SDK_REPO = "letary/lecodes-android-sdk";
|
|
47562
47620
|
var ANDROID_SDK_MIN_VERSION = "1.2.0";
|
|
47621
|
+
var ANDROID_SDK_VARIANTS_MIN = "1.3.0";
|
|
47622
|
+
var variantArtifact = (variant) => variant === "full" ? "lecodes-sdk" : `lecodes-sdk-${variant}`;
|
|
47563
47623
|
var ANDROID_SDK_URL_DEFAULT = `https://github.com/${ANDROID_SDK_REPO}/releases/download`;
|
|
47564
47624
|
var sdkRepoUrl = () => process.env.LECODES_ANDROID_SDK_URL ?? ANDROID_SDK_URL_DEFAULT;
|
|
47565
47625
|
var ANDROID_SDK_OPTS = { repo: ANDROID_SDK_REPO, minVersion: ANDROID_SDK_MIN_VERSION, configKey: "android.sdk" };
|
|
@@ -47567,7 +47627,7 @@ var shellSdkVersionAndroid = (androidDir) => {
|
|
|
47567
47627
|
const path2 = join14(androidDir, "lecodes-runtime", "build.gradle.kts");
|
|
47568
47628
|
if (!existsSync13(path2))
|
|
47569
47629
|
return null;
|
|
47570
|
-
const m = /io\.letary:lecodes-sdk
|
|
47630
|
+
const m = /io\.letary:lecodes-sdk(?:-(?:core|2d|3d))?:(\d+\.\d+\.\d+)@aar/.exec(readFileSync13(path2, "utf8"));
|
|
47571
47631
|
return m ? m[1] : null;
|
|
47572
47632
|
};
|
|
47573
47633
|
var GRADLE_WRAPPER_JAR_URL = "https://raw.githubusercontent.com/gradle/gradle/v8.13.0/gradle/wrapper/gradle-wrapper.jar";
|
|
@@ -47600,6 +47660,10 @@ var initAndroid = async (args) => {
|
|
|
47600
47660
|
throw new CliError("android/ already exists — run `lecodes app sync` to update it.");
|
|
47601
47661
|
}
|
|
47602
47662
|
const existing = readJson(join14(root, "app.json")) ?? {};
|
|
47663
|
+
const variant = flagStr(args, "variant") ?? existing.android?.variant ?? "auto";
|
|
47664
|
+
if (variant !== "auto" && !SDK_VARIANTS.includes(variant)) {
|
|
47665
|
+
throw new CliError(`--variant must be auto, ${SDK_VARIANTS.join(", ")} (got "${variant}")`);
|
|
47666
|
+
}
|
|
47603
47667
|
const sdkFlag = flagStr(args, "sdk") ?? existing.android?.sdk;
|
|
47604
47668
|
const sdkVersion = await resolveSdkVersion(process.env.LECODES_ANDROID_SDK ?? sdkFlag ?? "latest", null, ANDROID_SDK_OPTS);
|
|
47605
47669
|
const sdk = sdkFlag === "latest" ? "latest" : sdkVersion;
|
|
@@ -47614,6 +47678,7 @@ var initAndroid = async (args) => {
|
|
|
47614
47678
|
applicationId: flagStr(args, "app-id") ?? existing.android?.applicationId ?? defaultAppId,
|
|
47615
47679
|
versionCode: existing.android?.versionCode ?? 1,
|
|
47616
47680
|
sdk,
|
|
47681
|
+
variant,
|
|
47617
47682
|
...existing.android?.signing ? { signing: existing.android.signing } : {}
|
|
47618
47683
|
}
|
|
47619
47684
|
};
|
|
@@ -47655,7 +47720,9 @@ var initAndroid = async (args) => {
|
|
|
47655
47720
|
write("app/src/main/res/values/colors.xml", COLORS_XML);
|
|
47656
47721
|
write("app/src/main/res/values/strings.xml", stringsXmlTemplate(name));
|
|
47657
47722
|
write("app/src/main/res/drawable/ic_launcher.xml", IC_LAUNCHER_XML);
|
|
47658
|
-
|
|
47723
|
+
const initVariant = variant === "auto" ? "core" : variant;
|
|
47724
|
+
const initArtifact = cmpVersion(sdkVersion, ANDROID_SDK_VARIANTS_MIN) >= 0 ? variantArtifact(initVariant) : "lecodes-sdk";
|
|
47725
|
+
write("lecodes-runtime/build.gradle.kts", runtimeBuildGradleTemplate(sdkVersion, [], initArtifact));
|
|
47659
47726
|
write("lecodes-runtime/src/main/kotlin/io/letary/lecodes/runtime/LeCodesRuntime.kt", runtimeKotlinTemplate([]));
|
|
47660
47727
|
log("");
|
|
47661
47728
|
log(`${c.green("sdk")} lecodes-android-sdk ${sdkVersion}${sdk === "latest" ? c.dim(" (android.sdk: latest — re-resolved on every sync)") : c.dim(" (pinned in app.json android.sdk — `lecodes app update` moves it)")}`);
|
|
@@ -47680,6 +47747,40 @@ var syncAndroid = async (root, config, prior) => {
|
|
|
47680
47747
|
const linkedSdk = shellSdkVersionAndroid(androidDir);
|
|
47681
47748
|
const sdkPin = process.env.LECODES_ANDROID_SDK ?? config.android?.sdk ?? linkedSdk ?? "latest";
|
|
47682
47749
|
const sdkVersion = await resolveSdkVersion(sdkPin, linkedSdk, ANDROID_SDK_OPTS);
|
|
47750
|
+
const remotePatterns = config.resources?.remote ?? [];
|
|
47751
|
+
if (!Array.isArray(remotePatterns) || remotePatterns.some((p2) => typeof p2 !== "string")) {
|
|
47752
|
+
throw new CliError('app.json "resources.remote" must be an array of glob strings (e.g. ["videos/**"]).');
|
|
47753
|
+
}
|
|
47754
|
+
const assetsDir = join14(androidDir, "app", "src", "main", "assets");
|
|
47755
|
+
rmrf(assetsDir);
|
|
47756
|
+
mkdirSync8(assetsDir, { recursive: true });
|
|
47757
|
+
let bundleJs;
|
|
47758
|
+
let resources;
|
|
47759
|
+
if (prior) {
|
|
47760
|
+
writeFileSync10(join14(assetsDir, "app.js"), prior.bundleJs);
|
|
47761
|
+
bundleJs = prior.bundleJs;
|
|
47762
|
+
resources = prior.resources;
|
|
47763
|
+
} else {
|
|
47764
|
+
resources = await compileShell({ _: [], flags: { o: join14(assetsDir, "app.js") } }, { remotePatterns });
|
|
47765
|
+
bundleJs = readFileSync13(join14(assetsDir, "app.js"), "utf8");
|
|
47766
|
+
}
|
|
47767
|
+
const requestedVariant = config.android?.variant ?? "auto";
|
|
47768
|
+
if (requestedVariant !== "auto" && !SDK_VARIANTS.includes(requestedVariant)) {
|
|
47769
|
+
throw new CliError(`app.json android.variant must be "auto" or one of ${SDK_VARIANTS.join(", ")} (got "${requestedVariant}").`);
|
|
47770
|
+
}
|
|
47771
|
+
const { variant, detected } = resolveVariant(requestedVariant, bundleJs, "android.variant");
|
|
47772
|
+
let sdkArtifact = variantArtifact(variant);
|
|
47773
|
+
if (sdkArtifact !== "lecodes-sdk" && cmpVersion(sdkVersion, ANDROID_SDK_VARIANTS_MIN) < 0) {
|
|
47774
|
+
warn(`lecodes-android-sdk ${sdkVersion} predates the variant matrix (${ANDROID_SDK_VARIANTS_MIN}) — linking the full lecodes-sdk AAR; run \`lecodes app update\` to pick up variant builds.`);
|
|
47775
|
+
sdkArtifact = "lecodes-sdk";
|
|
47776
|
+
}
|
|
47777
|
+
const WHY = {
|
|
47778
|
+
full: "3D + 2D engines in use",
|
|
47779
|
+
"3d": "3D in use, no 2D",
|
|
47780
|
+
"2d": "2D in use, no 3D",
|
|
47781
|
+
core: "no 3D/2D engine use detected"
|
|
47782
|
+
};
|
|
47783
|
+
log(`${c.green("variant")} ${variant} → ${sdkArtifact}${requestedVariant === "auto" ? c.dim(` (auto: ${WHY[detected]})`) : c.dim(" (forced by android.variant)")}`);
|
|
47683
47784
|
const lockPath = join14(root, "plugins.lock.json");
|
|
47684
47785
|
const oldLock = readJson(lockPath)?.plugins ?? {};
|
|
47685
47786
|
const pluginSpecs = Object.entries(config.plugins ?? {});
|
|
@@ -47720,7 +47821,7 @@ var syncAndroid = async (root, config, prior) => {
|
|
|
47720
47821
|
aarWrapperPaths.push(`:plugins:${module}:libs:${base}`);
|
|
47721
47822
|
}
|
|
47722
47823
|
const hasConsumerRules = existsSync13(join14(dest, "consumer-rules.pro"));
|
|
47723
|
-
writeFileSync10(join14(dest, "build.gradle.kts"), pluginBuildGradleTemplate(id, module, namespace, sdkVersion, android.dependencies ?? [], aarNames, hasConsumerRules));
|
|
47824
|
+
writeFileSync10(join14(dest, "build.gradle.kts"), pluginBuildGradleTemplate(id, module, namespace, sdkVersion, android.dependencies ?? [], aarNames, hasConsumerRules, sdkArtifact));
|
|
47724
47825
|
runtimePlugins.push({ id, module, register });
|
|
47725
47826
|
newLock[id] = plugin.lock;
|
|
47726
47827
|
log(`${c.green("plugin")} ${id}@${plugin.lock.version} (${plugin.lock.tag} → plugins/${module})`);
|
|
@@ -47732,23 +47833,6 @@ var syncAndroid = async (root, config, prior) => {
|
|
|
47732
47833
|
log(`${c.yellow("removed")} android/plugins/${entry} (plugin uninstalled)`);
|
|
47733
47834
|
}
|
|
47734
47835
|
}
|
|
47735
|
-
const remotePatterns = config.resources?.remote ?? [];
|
|
47736
|
-
if (!Array.isArray(remotePatterns) || remotePatterns.some((p2) => typeof p2 !== "string")) {
|
|
47737
|
-
throw new CliError('app.json "resources.remote" must be an array of glob strings (e.g. ["videos/**"]).');
|
|
47738
|
-
}
|
|
47739
|
-
const assetsDir = join14(androidDir, "app", "src", "main", "assets");
|
|
47740
|
-
rmrf(assetsDir);
|
|
47741
|
-
mkdirSync8(assetsDir, { recursive: true });
|
|
47742
|
-
let bundleJs;
|
|
47743
|
-
let resources;
|
|
47744
|
-
if (prior) {
|
|
47745
|
-
writeFileSync10(join14(assetsDir, "app.js"), prior.bundleJs);
|
|
47746
|
-
bundleJs = prior.bundleJs;
|
|
47747
|
-
resources = prior.resources;
|
|
47748
|
-
} else {
|
|
47749
|
-
resources = await compileShell({ _: [], flags: { o: join14(assetsDir, "app.js") } }, { remotePatterns });
|
|
47750
|
-
bundleJs = readFileSync13(join14(assetsDir, "app.js"), "utf8");
|
|
47751
|
-
}
|
|
47752
47836
|
const sdkWhy = sdkPin === "latest" ? "latest" : config.android?.sdk ? "pinned by android.sdk" : "from the shell — set android.sdk or run `lecodes app update`";
|
|
47753
47837
|
log(`${c.green("sdk")} lecodes-android-sdk ${sdkVersion}${c.dim(` (${sdkWhy})`)}`);
|
|
47754
47838
|
const preload = /^\/\/ preload: (.*)$/m.exec(bundleJs)?.[1].split(";").map((s2) => s2.trim()).filter(Boolean) ?? [];
|
|
@@ -47791,7 +47875,7 @@ var syncAndroid = async (root, config, prior) => {
|
|
|
47791
47875
|
writeFileSync10(join14(androidDir, "settings.gradle.kts"), settingsGradleTemplate(slugify(config.name ?? basename4(root)), modules, aarWrapperPaths, sdkRepoUrl()));
|
|
47792
47876
|
writeFileSync10(join14(androidDir, "build.gradle.kts"), rootBuildGradleTemplate([...gradlePlugins].map(([id, version]) => ({ id, version }))));
|
|
47793
47877
|
writeFileSync10(join14(androidDir, "gradle.properties"), GRADLE_PROPERTIES);
|
|
47794
|
-
writeFileSync10(join14(androidDir, "lecodes-runtime", "build.gradle.kts"), runtimeBuildGradleTemplate(sdkVersion, modules));
|
|
47878
|
+
writeFileSync10(join14(androidDir, "lecodes-runtime", "build.gradle.kts"), runtimeBuildGradleTemplate(sdkVersion, modules, sdkArtifact));
|
|
47795
47879
|
const runtimeKt = join14(androidDir, "lecodes-runtime", "src", "main", "kotlin", "io", "letary", "lecodes", "runtime", "LeCodesRuntime.kt");
|
|
47796
47880
|
mkdirSync8(dirname10(runtimeKt), { recursive: true });
|
|
47797
47881
|
writeFileSync10(runtimeKt, runtimeKotlinTemplate(runtimePlugins, shellUuid));
|
|
@@ -47962,6 +48046,9 @@ var doctorAndroidChecks = async (root, config, check) => {
|
|
|
47962
48046
|
check(javaMajor >= 17, `JDK ${javaMajor || "not found"}`, "install JDK 17+ (Android Studio ships one)");
|
|
47963
48047
|
const hasSdkHome = !!(process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT) || existsSync13(join14(androidDir, "local.properties"));
|
|
47964
48048
|
check(hasSdkHome, "Android SDK location", "set ANDROID_HOME or create android/local.properties");
|
|
48049
|
+
const requestedVariant = config.android?.variant ?? "auto";
|
|
48050
|
+
const validVariant = requestedVariant === "auto" || SDK_VARIANTS.includes(requestedVariant);
|
|
48051
|
+
check(validVariant, `android.variant "${requestedVariant}"`, `use "auto" or one of ${SDK_VARIANTS.join(", ")}`);
|
|
47965
48052
|
const wrapperJar = join14(androidDir, "gradle", "wrapper", "gradle-wrapper.jar");
|
|
47966
48053
|
check(existsSync13(wrapperJar) && statSync2(wrapperJar).size > 0, "Gradle wrapper jar", `run \`gradle wrapper --gradle-version ${GRADLE_VERSION}\` in android/ (or let Android Studio repair it)`);
|
|
47967
48054
|
check(existsSync13(join14(androidDir, "app", "src", "main", "assets", "app.js")), "Bundled app.js", "run `lecodes app sync`");
|
|
@@ -47984,16 +48071,491 @@ var doctorAndroidChecks = async (root, config, check) => {
|
|
|
47984
48071
|
}
|
|
47985
48072
|
};
|
|
47986
48073
|
|
|
48074
|
+
// src/commands/appDesktop.ts
|
|
48075
|
+
import { execFileSync as execFileSync3, spawn as spawn3 } from "node:child_process";
|
|
48076
|
+
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";
|
|
48077
|
+
import { basename as basename5, dirname as dirname11, join as join16, resolve as resolve5 } from "node:path";
|
|
48078
|
+
|
|
48079
|
+
// src/desktopRenderer.ts
|
|
48080
|
+
init_util();
|
|
48081
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
48082
|
+
import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
|
|
48083
|
+
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";
|
|
48084
|
+
import { homedir as homedir5, tmpdir } from "node:os";
|
|
48085
|
+
import { join as join15, resolve as resolve4 } from "node:path";
|
|
48086
|
+
var MANIFEST_URL = "https://storage.yandexcloud.net/le-codes/releases/versions.json";
|
|
48087
|
+
var DESKTOP_RENDERER_VERSION = "1.1.0";
|
|
48088
|
+
var semverNewer = (a2, b) => {
|
|
48089
|
+
const pa = a2.split(/[.-]/).map(Number), pb = b.split(/[.-]/).map(Number);
|
|
48090
|
+
for (let i2 = 0;i2 < 3; i2++)
|
|
48091
|
+
if ((pb[i2] ?? 0) !== (pa[i2] ?? 0))
|
|
48092
|
+
return (pb[i2] ?? 0) - (pa[i2] ?? 0);
|
|
48093
|
+
return 0;
|
|
48094
|
+
};
|
|
48095
|
+
var platformKey = () => {
|
|
48096
|
+
if (process.platform === "win32")
|
|
48097
|
+
return "windows-x64";
|
|
48098
|
+
if (process.platform === "linux")
|
|
48099
|
+
return "ubuntu-linux-x64";
|
|
48100
|
+
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}.`);
|
|
48101
|
+
};
|
|
48102
|
+
var exeName = () => process.platform === "win32" ? "lecodes-desktop.exe" : "lecodes-desktop";
|
|
48103
|
+
var findExe = (dir) => {
|
|
48104
|
+
const name = exeName();
|
|
48105
|
+
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
48106
|
+
const p2 = join15(dir, entry.name);
|
|
48107
|
+
if (entry.isFile() && entry.name === name)
|
|
48108
|
+
return p2;
|
|
48109
|
+
if (entry.isDirectory()) {
|
|
48110
|
+
const found = findExe(p2);
|
|
48111
|
+
if (found)
|
|
48112
|
+
return found;
|
|
48113
|
+
}
|
|
48114
|
+
}
|
|
48115
|
+
return null;
|
|
48116
|
+
};
|
|
48117
|
+
var cacheRoot = () => join15(homedir5(), ".lecodes", "renderer");
|
|
48118
|
+
var currentFile = () => join15(cacheRoot(), "current.json");
|
|
48119
|
+
var installedDesktopVersions = () => {
|
|
48120
|
+
try {
|
|
48121
|
+
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);
|
|
48122
|
+
} catch {
|
|
48123
|
+
return [];
|
|
48124
|
+
}
|
|
48125
|
+
};
|
|
48126
|
+
var selectedDesktopVersion = () => {
|
|
48127
|
+
try {
|
|
48128
|
+
const saved = JSON.parse(readFileSync14(currentFile(), "utf8")).version;
|
|
48129
|
+
if (saved && /^\d+\.\d+\.\d+/.test(saved) && semverNewer(DESKTOP_RENDERER_VERSION, saved) > 0)
|
|
48130
|
+
return saved;
|
|
48131
|
+
} catch {}
|
|
48132
|
+
return DESKTOP_RENDERER_VERSION;
|
|
48133
|
+
};
|
|
48134
|
+
var fetchDesktopManifest = async () => {
|
|
48135
|
+
let manifest;
|
|
48136
|
+
try {
|
|
48137
|
+
const res = await fetch(MANIFEST_URL);
|
|
48138
|
+
if (!res.ok)
|
|
48139
|
+
throw new Error(`HTTP ${res.status}`);
|
|
48140
|
+
manifest = await res.json();
|
|
48141
|
+
} catch (e2) {
|
|
48142
|
+
throw new CliError(`Can't fetch the release manifest (${MANIFEST_URL}): ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
48143
|
+
}
|
|
48144
|
+
const desktop = manifest.products?.desktop;
|
|
48145
|
+
if (!desktop)
|
|
48146
|
+
throw new CliError("The release manifest has no desktop product.");
|
|
48147
|
+
return desktop;
|
|
48148
|
+
};
|
|
48149
|
+
var downloadDesktop = async (version, dest) => {
|
|
48150
|
+
const key = platformKey();
|
|
48151
|
+
const entry = (await fetchDesktopManifest()).versions.find((v) => v.version === version);
|
|
48152
|
+
const file = entry?.files[key];
|
|
48153
|
+
if (!file) {
|
|
48154
|
+
throw new CliError(`LeCodes Desktop ${version} [${key}] isn't published yet — \`--desktop\` needs it for the ` + `headless render contract.
|
|
48155
|
+
Set LECODES_DESKTOP_EXE to a locally built host, or update lecodes-cli.`);
|
|
48156
|
+
}
|
|
48157
|
+
note(`Downloading the native renderer (LeCodes Desktop ${version}, ${(file.size / 1024 / 1024).toFixed(1)} MB)…`);
|
|
48158
|
+
let bytes;
|
|
48159
|
+
try {
|
|
48160
|
+
const res = await fetch(file.url);
|
|
48161
|
+
if (!res.ok)
|
|
48162
|
+
throw new Error(`HTTP ${res.status}`);
|
|
48163
|
+
bytes = Buffer.from(await res.arrayBuffer());
|
|
48164
|
+
} catch (e2) {
|
|
48165
|
+
throw new CliError(`Download failed (${file.url}): ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
48166
|
+
}
|
|
48167
|
+
const sha = createHash5("sha256").update(bytes).digest("hex");
|
|
48168
|
+
if (sha !== file.sha256)
|
|
48169
|
+
throw new CliError(`Downloaded archive is corrupt (sha256 mismatch) — try again.`);
|
|
48170
|
+
mkdirSync9(cacheRoot(), { recursive: true });
|
|
48171
|
+
const stage = mkdtempSync(join15(cacheRoot(), "dl-"));
|
|
48172
|
+
try {
|
|
48173
|
+
const archive = join15(stage, file.url.split("/").pop() ?? "archive");
|
|
48174
|
+
writeFileSync11(archive, bytes);
|
|
48175
|
+
const tar = spawnSync3("tar", ["-xf", archive, "-C", stage], { stdio: ["ignore", "ignore", "pipe"] });
|
|
48176
|
+
if (tar.status !== 0)
|
|
48177
|
+
throw new CliError(`Couldn't extract the renderer archive: ${tar.stderr?.toString() ?? "tar failed"}`);
|
|
48178
|
+
rmSync2(archive);
|
|
48179
|
+
if (!findExe(stage))
|
|
48180
|
+
throw new CliError("The downloaded archive doesn't contain the desktop host binary.");
|
|
48181
|
+
try {
|
|
48182
|
+
renameSync2(stage, dest);
|
|
48183
|
+
} catch {
|
|
48184
|
+
if (!findExe(dest))
|
|
48185
|
+
throw new CliError(`Couldn't move the renderer into ${dest}.`);
|
|
48186
|
+
}
|
|
48187
|
+
} finally {
|
|
48188
|
+
rmSync2(stage, { recursive: true, force: true });
|
|
48189
|
+
}
|
|
48190
|
+
};
|
|
48191
|
+
var resolveDesktopExe = async () => {
|
|
48192
|
+
const override = process.env.LECODES_DESKTOP_EXE;
|
|
48193
|
+
if (override) {
|
|
48194
|
+
if (!existsSync14(override))
|
|
48195
|
+
throw new CliError(`LECODES_DESKTOP_EXE points at "${override}" which doesn't exist.`);
|
|
48196
|
+
return resolve4(override);
|
|
48197
|
+
}
|
|
48198
|
+
const version = selectedDesktopVersion();
|
|
48199
|
+
const dir = join15(cacheRoot(), `desktop-${version}`);
|
|
48200
|
+
let exe = existsSync14(dir) ? findExe(dir) : null;
|
|
48201
|
+
if (!exe) {
|
|
48202
|
+
await downloadDesktop(version, dir);
|
|
48203
|
+
exe = findExe(dir);
|
|
48204
|
+
if (!exe)
|
|
48205
|
+
throw new CliError(`Renderer cache at ${dir} is broken — delete the folder and retry.`);
|
|
48206
|
+
}
|
|
48207
|
+
return exe;
|
|
48208
|
+
};
|
|
48209
|
+
var updateDesktop = async () => {
|
|
48210
|
+
const { latest } = await fetchDesktopManifest();
|
|
48211
|
+
if (semverNewer(latest, DESKTOP_RENDERER_VERSION) > 0) {
|
|
48212
|
+
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.`);
|
|
48213
|
+
}
|
|
48214
|
+
const before = selectedDesktopVersion();
|
|
48215
|
+
const dir = join15(cacheRoot(), `desktop-${latest}`);
|
|
48216
|
+
const hadIt = existsSync14(dir) && findExe(dir) !== null;
|
|
48217
|
+
if (!hadIt)
|
|
48218
|
+
await downloadDesktop(latest, dir);
|
|
48219
|
+
mkdirSync9(cacheRoot(), { recursive: true });
|
|
48220
|
+
writeFileSync11(currentFile(), JSON.stringify({ version: latest }, null, 2));
|
|
48221
|
+
if (latest === before && hadIt)
|
|
48222
|
+
note(`Already up to date: LeCodes Desktop ${latest}.`);
|
|
48223
|
+
else if (latest === before)
|
|
48224
|
+
note(`Re-downloaded LeCodes Desktop ${latest}.`);
|
|
48225
|
+
else
|
|
48226
|
+
note(`Desktop renderer set to ${latest} (was ${before}).`);
|
|
48227
|
+
};
|
|
48228
|
+
var windowEnv = (opts) => {
|
|
48229
|
+
const env = {};
|
|
48230
|
+
const width = opts.width ?? (opts.height !== undefined ? 1280 : undefined);
|
|
48231
|
+
const height = opts.height ?? (opts.width !== undefined ? 720 : undefined);
|
|
48232
|
+
if (width && height)
|
|
48233
|
+
env.CREATOR_WINDOW_SIZE = `${width}x${height}`;
|
|
48234
|
+
if (opts.minWidth && opts.minHeight)
|
|
48235
|
+
env.CREATOR_MIN_WINDOW_SIZE = `${opts.minWidth}x${opts.minHeight}`;
|
|
48236
|
+
if (opts.title)
|
|
48237
|
+
env.CREATOR_WINDOW_TITLE = opts.title;
|
|
48238
|
+
if (opts.resizable === false)
|
|
48239
|
+
env.CREATOR_WINDOW_FIXED = "1";
|
|
48240
|
+
if (opts.fullscreen !== undefined)
|
|
48241
|
+
env.CREATOR_WINDOW_FULLSCREEN = opts.fullscreen ? "1" : "0";
|
|
48242
|
+
if (opts.scale === "fit")
|
|
48243
|
+
env.CREATOR_UI_SCALE = "fit";
|
|
48244
|
+
else if (typeof opts.scale === "number")
|
|
48245
|
+
env.CREATOR_UI_SCALE = String(opts.scale);
|
|
48246
|
+
return env;
|
|
48247
|
+
};
|
|
48248
|
+
var stageBundle = (js, resources) => {
|
|
48249
|
+
const runDir = mkdtempSync(join15(tmpdir(), "lecodes-render-"));
|
|
48250
|
+
writeFileSync11(join15(runDir, "app.js"), js);
|
|
48251
|
+
for (const r2 of resources) {
|
|
48252
|
+
if (r2.absPath && existsSync14(r2.absPath) && statSync3(r2.absPath).isFile()) {
|
|
48253
|
+
copyFileSync(r2.absPath, join15(runDir, r2.name));
|
|
48254
|
+
}
|
|
48255
|
+
}
|
|
48256
|
+
return runDir;
|
|
48257
|
+
};
|
|
48258
|
+
var runDesktopApp = async (js, resources, opts = {}) => {
|
|
48259
|
+
const exe = await resolveDesktopExe();
|
|
48260
|
+
const runDir = stageBundle(js, resources);
|
|
48261
|
+
try {
|
|
48262
|
+
const env = { ...process.env, ...windowEnv(opts) };
|
|
48263
|
+
await new Promise((done, fail) => {
|
|
48264
|
+
const child = spawn2(exe, [join15(runDir, "app.js")], { cwd: runDir, env, stdio: ["ignore", "inherit", "inherit"] });
|
|
48265
|
+
child.on("error", (e2) => fail(new CliError(`Can't run the desktop host: ${e2.message}`)));
|
|
48266
|
+
child.on("exit", (code) => {
|
|
48267
|
+
if (code === 0 || code === null)
|
|
48268
|
+
done();
|
|
48269
|
+
else
|
|
48270
|
+
fail(new CliError(`The desktop host exited with code ${code}.`));
|
|
48271
|
+
});
|
|
48272
|
+
});
|
|
48273
|
+
} finally {
|
|
48274
|
+
rmSync2(runDir, { recursive: true, force: true });
|
|
48275
|
+
}
|
|
48276
|
+
};
|
|
48277
|
+
var runDesktopDev = async (url, opts = {}) => {
|
|
48278
|
+
const exe = await resolveDesktopExe();
|
|
48279
|
+
const env = { ...process.env, ...windowEnv(opts) };
|
|
48280
|
+
await new Promise((done, fail) => {
|
|
48281
|
+
const child = spawn2(exe, [url], { env, stdio: ["ignore", "ignore", "inherit"] });
|
|
48282
|
+
child.on("error", (e2) => fail(new CliError(`Can't run the desktop host: ${e2.message}`)));
|
|
48283
|
+
child.on("exit", () => done());
|
|
48284
|
+
});
|
|
48285
|
+
};
|
|
48286
|
+
var runDesktopRender = async (js, resources, opts) => {
|
|
48287
|
+
const exe = await resolveDesktopExe();
|
|
48288
|
+
const runDir = stageBundle(js, resources);
|
|
48289
|
+
try {
|
|
48290
|
+
const appJs = join15(runDir, "app.js");
|
|
48291
|
+
const outAbs = resolve4(opts.outPng);
|
|
48292
|
+
await new Promise((done, fail) => {
|
|
48293
|
+
const child = spawn2(exe, [appJs], {
|
|
48294
|
+
cwd: runDir,
|
|
48295
|
+
env: {
|
|
48296
|
+
...process.env,
|
|
48297
|
+
CREATOR_DUMP_PNG: outAbs,
|
|
48298
|
+
CREATOR_MAX_FRAMES: String(opts.frames),
|
|
48299
|
+
CREATOR_WINDOW_SIZE: `${opts.width}x${opts.height}`,
|
|
48300
|
+
CREATOR_HIDDEN: "1"
|
|
48301
|
+
},
|
|
48302
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
48303
|
+
});
|
|
48304
|
+
const tail = [];
|
|
48305
|
+
const sink = (d) => {
|
|
48306
|
+
const text = String(d);
|
|
48307
|
+
tail.push(text);
|
|
48308
|
+
while (tail.length > 80)
|
|
48309
|
+
tail.shift();
|
|
48310
|
+
if (opts.logs)
|
|
48311
|
+
process.stderr.write(text);
|
|
48312
|
+
};
|
|
48313
|
+
child.stdout.on("data", sink);
|
|
48314
|
+
child.stderr.on("data", sink);
|
|
48315
|
+
const killer = setTimeout(() => {
|
|
48316
|
+
child.kill();
|
|
48317
|
+
fail(new CliError(`The native render timed out after ${opts.timeoutMs}ms (raise --timeout, or lower --frames).`));
|
|
48318
|
+
}, opts.timeoutMs);
|
|
48319
|
+
child.on("error", (e2) => {
|
|
48320
|
+
clearTimeout(killer);
|
|
48321
|
+
fail(new CliError(`Can't run the desktop renderer: ${e2.message}`));
|
|
48322
|
+
});
|
|
48323
|
+
child.on("exit", (code) => {
|
|
48324
|
+
clearTimeout(killer);
|
|
48325
|
+
if (code === 0 && existsSync14(outAbs))
|
|
48326
|
+
return done();
|
|
48327
|
+
const detail = tail.length > 0 ? `
|
|
48328
|
+
${tail.join("").trimEnd()}` : "";
|
|
48329
|
+
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}`));
|
|
48330
|
+
});
|
|
48331
|
+
});
|
|
48332
|
+
} finally {
|
|
48333
|
+
rmSync2(runDir, { recursive: true, force: true });
|
|
48334
|
+
}
|
|
48335
|
+
};
|
|
48336
|
+
|
|
48337
|
+
// src/commands/appDesktop.ts
|
|
48338
|
+
init_util();
|
|
48339
|
+
var mb = (bytes) => `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
48340
|
+
var dirSize = (dir) => readdirSync6(dir, { withFileTypes: true }).reduce((sum, e2) => {
|
|
48341
|
+
const p2 = join16(dir, e2.name);
|
|
48342
|
+
return sum + (e2.isDirectory() ? dirSize(p2) : statSync4(p2).size);
|
|
48343
|
+
}, 0);
|
|
48344
|
+
var initDesktop = async (args) => {
|
|
48345
|
+
const root = projectRoot();
|
|
48346
|
+
const existing = readJson(join16(root, "app.json")) ?? {};
|
|
48347
|
+
const name = flagStr(args, "name") ?? existing.name ?? basename5(root);
|
|
48348
|
+
const window2 = {
|
|
48349
|
+
width: 1280,
|
|
48350
|
+
height: 800,
|
|
48351
|
+
minWidth: 640,
|
|
48352
|
+
minHeight: 480,
|
|
48353
|
+
...existing.desktop?.window,
|
|
48354
|
+
...flagBool(args, "fullscreen") ? { fullscreen: true } : {}
|
|
48355
|
+
};
|
|
48356
|
+
const config = { ...existing, name, desktop: { ...existing.desktop, window: window2 } };
|
|
48357
|
+
writeJson(join16(root, "app.json"), config);
|
|
48358
|
+
log(`${c.green("written")} app.json (desktop.window ${window2.width}×${window2.height}${window2.fullscreen ? ", fullscreen" : ""})`);
|
|
48359
|
+
log(`Next: ${c.bold("lecodes dev --desktop")} to develop, ${c.bold("lecodes app build desktop")} to package.`);
|
|
48360
|
+
};
|
|
48361
|
+
var cleanFileName = (name) => name.replace(/[<>:"/\\|?*\u0000-\u001f]/g, "").replace(/[. ]+$/, "").trim() || "app";
|
|
48362
|
+
var buildDesktop = async (args) => {
|
|
48363
|
+
const root = projectRoot();
|
|
48364
|
+
const config = readJson(join16(root, "app.json")) ?? {};
|
|
48365
|
+
const name = (config.name ?? basename5(root)).trim() || "app";
|
|
48366
|
+
const slug = flagStr(args, "slug") ?? cleanFileName(name);
|
|
48367
|
+
const out = resolve5(root, flagStr(args, "o", "out") ?? join16(root, "dist", slug));
|
|
48368
|
+
if (existsSync15(out)) {
|
|
48369
|
+
const entries = readdirSync6(out);
|
|
48370
|
+
const looksLikeBuild = entries.length === 0 || entries.includes("app.js");
|
|
48371
|
+
if (!looksLikeBuild) {
|
|
48372
|
+
throw new CliError(`${out} exists and doesn't look like a previous desktop build — pick another -o or clear it yourself.`);
|
|
48373
|
+
}
|
|
48374
|
+
}
|
|
48375
|
+
if (config.desktop?.entry && flagStr(args, "entry") === undefined)
|
|
48376
|
+
args.flags.entry = config.desktop.entry;
|
|
48377
|
+
note(`Building "${name}" for desktop…`);
|
|
48378
|
+
const { js, resources } = await buildBundle(args, { remotePatterns: config.resources?.remote ?? [] });
|
|
48379
|
+
const wanted = new Set([...js.matchAll(/fetchLocal\("([^"]+)"\)/g)].map((m) => m[1]));
|
|
48380
|
+
const shipped = resources.filter((r2) => !r2.remote && wanted.has(r2.name));
|
|
48381
|
+
const byName = new Map;
|
|
48382
|
+
for (const r2 of resources) {
|
|
48383
|
+
if (r2.remote || !wanted.has(r2.name))
|
|
48384
|
+
continue;
|
|
48385
|
+
const set = byName.get(r2.name) ?? new Set;
|
|
48386
|
+
set.add(r2.path);
|
|
48387
|
+
byName.set(r2.name, set);
|
|
48388
|
+
}
|
|
48389
|
+
const collisions = [...byName.entries()].filter(([, paths]) => paths.size > 1);
|
|
48390
|
+
if (collisions.length > 0) {
|
|
48391
|
+
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:
|
|
48392
|
+
` + collisions.map(([n2, paths]) => ` ${n2}
|
|
48393
|
+
` + [...paths].map((p2) => ` ${p2}`).join(`
|
|
48394
|
+
`)).join(`
|
|
48395
|
+
`));
|
|
48396
|
+
}
|
|
48397
|
+
const shippedNames = new Set(shipped.map((r2) => r2.name));
|
|
48398
|
+
const missing = [...wanted].filter((n2) => !shippedNames.has(n2));
|
|
48399
|
+
if (missing.length > 0) {
|
|
48400
|
+
throw new CliError(`The bundle references assets with no local file: ${missing.join(", ")}`);
|
|
48401
|
+
}
|
|
48402
|
+
const exe = await resolveDesktopExe();
|
|
48403
|
+
const hostDir = dirname11(exe);
|
|
48404
|
+
const version = process.env.LECODES_DESKTOP_EXE ? "local build" : selectedDesktopVersion();
|
|
48405
|
+
rmSync3(out, { recursive: true, force: true });
|
|
48406
|
+
mkdirSync10(out, { recursive: true });
|
|
48407
|
+
for (const entry of readdirSync6(hostDir, { withFileTypes: true })) {
|
|
48408
|
+
const keep = entry.isFile() && entry.name.toLowerCase().endsWith(".dll") || entry.isDirectory() && (entry.name === "assets" || entry.name === "fonts");
|
|
48409
|
+
if (keep)
|
|
48410
|
+
cpSync2(join16(hostDir, entry.name), join16(out, entry.name), { recursive: true });
|
|
48411
|
+
}
|
|
48412
|
+
if (!existsSync15(join16(out, "assets", "initial.js"))) {
|
|
48413
|
+
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.)`);
|
|
48414
|
+
}
|
|
48415
|
+
const exeName2 = process.platform === "win32" ? `${cleanFileName(name)}.exe` : cleanFileName(name);
|
|
48416
|
+
cpSync2(exe, join16(out, exeName2));
|
|
48417
|
+
note(` host: LeCodes Desktop ${version}`);
|
|
48418
|
+
writeFileSync12(join16(out, "app.js"), js);
|
|
48419
|
+
let assetBytes = 0, assetCount = 0;
|
|
48420
|
+
for (const r2 of shipped) {
|
|
48421
|
+
if (!r2.absPath || !existsSync15(r2.absPath) || !statSync4(r2.absPath).isFile()) {
|
|
48422
|
+
warn(`asset "${r2.name}" (${r2.path}) has no local file — skipped`);
|
|
48423
|
+
continue;
|
|
48424
|
+
}
|
|
48425
|
+
cpSync2(r2.absPath, join16(out, r2.name));
|
|
48426
|
+
assetBytes += statSync4(r2.absPath).size;
|
|
48427
|
+
assetCount++;
|
|
48428
|
+
}
|
|
48429
|
+
note(` assets: ${assetCount} files, ${mb(assetBytes)}`);
|
|
48430
|
+
writeJson(join16(out, "app.json"), { name, ...config.desktop ? { desktop: { window: config.desktop.window } } : {} });
|
|
48431
|
+
if (config.icon) {
|
|
48432
|
+
await writeIco(root, config, join16(out, "app.ico"));
|
|
48433
|
+
note(" app.ico rendered from " + config.icon);
|
|
48434
|
+
}
|
|
48435
|
+
writeFileSync12(join16(out, "README.txt"), "\uFEFF" + readmeText(name, exeName2, version));
|
|
48436
|
+
success(`Portable build: ${out} (${mb(dirSize(out))})`);
|
|
48437
|
+
if (flagBool(args, "probe"))
|
|
48438
|
+
await probeBuild(out, exeName2, config, args);
|
|
48439
|
+
if (flagBool(args, "zip"))
|
|
48440
|
+
zipBuild(out);
|
|
48441
|
+
if (!flagBool(args, "probe"))
|
|
48442
|
+
note(`Tip: --probe renders the staged folder headless to verify exactly what ships.`);
|
|
48443
|
+
};
|
|
48444
|
+
var writeIco = async (root, config, dest) => {
|
|
48445
|
+
const { image: image2 } = await loadIconMaster(root, config);
|
|
48446
|
+
const sizes = [16, 24, 32, 48, 64, 256];
|
|
48447
|
+
const pngs = [];
|
|
48448
|
+
for (const s2 of sizes) {
|
|
48449
|
+
const frame = image2.clone();
|
|
48450
|
+
frame.resize({ w: s2, h: s2 });
|
|
48451
|
+
pngs.push(Buffer.from(await frame.getBuffer("image/png")));
|
|
48452
|
+
}
|
|
48453
|
+
const header = Buffer.alloc(6 + sizes.length * 16);
|
|
48454
|
+
header.writeUInt16LE(0, 0);
|
|
48455
|
+
header.writeUInt16LE(1, 2);
|
|
48456
|
+
header.writeUInt16LE(sizes.length, 4);
|
|
48457
|
+
let offset = header.length;
|
|
48458
|
+
sizes.forEach((s2, i2) => {
|
|
48459
|
+
const e2 = 6 + i2 * 16;
|
|
48460
|
+
header.writeUInt8(s2 === 256 ? 0 : s2, e2);
|
|
48461
|
+
header.writeUInt8(s2 === 256 ? 0 : s2, e2 + 1);
|
|
48462
|
+
header.writeUInt16LE(1, e2 + 4);
|
|
48463
|
+
header.writeUInt16LE(32, e2 + 6);
|
|
48464
|
+
header.writeUInt32LE(pngs[i2].length, e2 + 8);
|
|
48465
|
+
header.writeUInt32LE(offset, e2 + 12);
|
|
48466
|
+
offset += pngs[i2].length;
|
|
48467
|
+
});
|
|
48468
|
+
writeFileSync12(dest, Buffer.concat([header, ...pngs]));
|
|
48469
|
+
};
|
|
48470
|
+
var probeBuild = async (out, exeName2, config, args) => {
|
|
48471
|
+
const win = config.desktop?.window;
|
|
48472
|
+
const frames = Number(flagStr(args, "frames")) || 90;
|
|
48473
|
+
const shot = join16(dirname11(out), `${basename5(out)}-probe.png`);
|
|
48474
|
+
note(`Probe: hidden render of the staged folder (${frames} frames)…`);
|
|
48475
|
+
await new Promise((done, fail) => {
|
|
48476
|
+
const child = spawn3(join16(out, exeName2), [], {
|
|
48477
|
+
cwd: out,
|
|
48478
|
+
env: {
|
|
48479
|
+
...process.env,
|
|
48480
|
+
CREATOR_HIDDEN: "1",
|
|
48481
|
+
CREATOR_MAX_FRAMES: String(frames),
|
|
48482
|
+
CREATOR_DUMP_PNG: shot,
|
|
48483
|
+
CREATOR_WINDOW_SIZE: `${win?.width ?? 1280}x${win?.height ?? 720}`
|
|
48484
|
+
},
|
|
48485
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
48486
|
+
});
|
|
48487
|
+
const tail = [];
|
|
48488
|
+
const sink = (d) => {
|
|
48489
|
+
tail.push(String(d));
|
|
48490
|
+
while (tail.length > 60)
|
|
48491
|
+
tail.shift();
|
|
48492
|
+
};
|
|
48493
|
+
child.stdout.on("data", sink);
|
|
48494
|
+
child.stderr.on("data", sink);
|
|
48495
|
+
const killer = setTimeout(() => {
|
|
48496
|
+
child.kill();
|
|
48497
|
+
fail(new CliError("The probe timed out after 120s — raise --frames headroom or check the app."));
|
|
48498
|
+
}, 120000);
|
|
48499
|
+
child.on("error", (e2) => {
|
|
48500
|
+
clearTimeout(killer);
|
|
48501
|
+
fail(new CliError(`Probe couldn't run: ${e2.message}`));
|
|
48502
|
+
});
|
|
48503
|
+
child.on("exit", (code) => {
|
|
48504
|
+
clearTimeout(killer);
|
|
48505
|
+
if (code === 0 && existsSync15(shot)) {
|
|
48506
|
+
success(`Probe: ${shot}`);
|
|
48507
|
+
done();
|
|
48508
|
+
} else {
|
|
48509
|
+
const detail = tail.length > 0 ? `
|
|
48510
|
+
${tail.join("").trimEnd()}` : "";
|
|
48511
|
+
fail(new CliError(`Probe failed (exit ${code}).${detail}`));
|
|
48512
|
+
}
|
|
48513
|
+
});
|
|
48514
|
+
});
|
|
48515
|
+
};
|
|
48516
|
+
var zipBuild = (out) => {
|
|
48517
|
+
const zip = `${out}.zip`;
|
|
48518
|
+
rmSync3(zip, { force: true });
|
|
48519
|
+
note("Zipping…");
|
|
48520
|
+
if (process.platform === "win32") {
|
|
48521
|
+
const q = (s2) => s2.replaceAll("'", "''");
|
|
48522
|
+
execFileSync3("powershell", ["-NoProfile", "-Command", [
|
|
48523
|
+
"Add-Type -AssemblyName System.IO.Compression.FileSystem;",
|
|
48524
|
+
`$src='${q(out)}'; $base=Split-Path $src -Parent;`,
|
|
48525
|
+
`$zip=[System.IO.Compression.ZipFile]::Open('${q(zip)}','Create');`,
|
|
48526
|
+
"Get-ChildItem $src -Recurse -File | ForEach-Object {",
|
|
48527
|
+
" $rel=$_.FullName.Substring($base.Length+1).Replace('\\','/');",
|
|
48528
|
+
" [void][System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip,$_.FullName,$rel,'Optimal') };",
|
|
48529
|
+
"$zip.Dispose()"
|
|
48530
|
+
].join(" ")], { stdio: ["ignore", "ignore", "pipe"] });
|
|
48531
|
+
} else {
|
|
48532
|
+
execFileSync3("zip", ["-rq", zip, basename5(out)], { cwd: dirname11(out) });
|
|
48533
|
+
}
|
|
48534
|
+
success(`Zip: ${zip} (${mb(statSync4(zip).size)})`);
|
|
48535
|
+
};
|
|
48536
|
+
var readmeText = (name, exeName2, version) => `${name}
|
|
48537
|
+
${"=".repeat(Math.max(name.length, 4))}
|
|
48538
|
+
|
|
48539
|
+
A LeCodes desktop app. Nothing to install: unpack the WHOLE folder anywhere and run ${exeName2}.
|
|
48540
|
+
Keep the files together — the app reads its assets from this folder.
|
|
48541
|
+
|
|
48542
|
+
On first run Windows may show a SmartScreen prompt (any unsigned app gets one):
|
|
48543
|
+
choose "More info", then "Run anyway".
|
|
48544
|
+
|
|
48545
|
+
Requires 64-bit Windows 10/11.
|
|
48546
|
+
|
|
48547
|
+
Built with LeCodes Desktop ${version}.
|
|
48548
|
+
`;
|
|
48549
|
+
|
|
47987
48550
|
// src/commands/app.ts
|
|
47988
48551
|
var SDK_REPO = "letary/lecodes-ios-sdk";
|
|
47989
48552
|
var SDK_MIN_VERSION = "1.2.0";
|
|
47990
|
-
var SDK_VARIANTS = ["core", "2d", "3d", "full"];
|
|
47991
48553
|
var SDK_IDENTITY_VERSION = "1.6.0";
|
|
47992
48554
|
var shellSdkVersion = (iosDir) => {
|
|
47993
|
-
const path2 =
|
|
47994
|
-
if (!
|
|
48555
|
+
const path2 = join17(iosDir, "LeCodesRuntime", "Package.swift");
|
|
48556
|
+
if (!existsSync16(path2))
|
|
47995
48557
|
return null;
|
|
47996
|
-
const m = /lecodes-ios-sdk\.git",\s*(?:exact|from):\s*"([^"]+)"/.exec(
|
|
48558
|
+
const m = /lecodes-ios-sdk\.git",\s*(?:exact|from):\s*"([^"]+)"/.exec(readFileSync15(path2, "utf8"));
|
|
47997
48559
|
return m && VERSION_RE.test(m[1]) ? m[1] : null;
|
|
47998
48560
|
};
|
|
47999
48561
|
var IOS_SDK_OPTS = { repo: SDK_REPO, minVersion: SDK_MIN_VERSION, configKey: "ios.sdk" };
|
|
@@ -48001,15 +48563,17 @@ var appInit = async (args) => {
|
|
|
48001
48563
|
const platform = args._[1];
|
|
48002
48564
|
if (platform === "android")
|
|
48003
48565
|
return initAndroid(args);
|
|
48566
|
+
if (platform === "desktop")
|
|
48567
|
+
return initDesktop(args);
|
|
48004
48568
|
if (platform !== "ios") {
|
|
48005
|
-
throw new CliError(platform ? `Unsupported platform "${platform}" — use ios or
|
|
48569
|
+
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
48570
|
}
|
|
48007
48571
|
const root = projectRoot();
|
|
48008
|
-
const iosDir =
|
|
48009
|
-
if (
|
|
48572
|
+
const iosDir = join17(root, "ios");
|
|
48573
|
+
if (existsSync16(iosDir)) {
|
|
48010
48574
|
throw new CliError("ios/ already exists — run `lecodes app sync` to update it.");
|
|
48011
48575
|
}
|
|
48012
|
-
const existing = readJson(
|
|
48576
|
+
const existing = readJson(join17(root, "app.json")) ?? {};
|
|
48013
48577
|
const variant = flagStr(args, "variant") ?? existing.ios?.variant ?? "auto";
|
|
48014
48578
|
if (variant !== "auto" && !SDK_VARIANTS.includes(variant)) {
|
|
48015
48579
|
throw new CliError(`--variant must be auto, ${SDK_VARIANTS.join(", ")} (got "${variant}")`);
|
|
@@ -48017,7 +48581,7 @@ var appInit = async (args) => {
|
|
|
48017
48581
|
const sdkFlag = flagStr(args, "sdk") ?? existing.ios?.sdk;
|
|
48018
48582
|
const sdkVersion = await resolveSdkVersion(process.env.LECODES_IOS_SDK ?? sdkFlag ?? "latest", null, IOS_SDK_OPTS);
|
|
48019
48583
|
const sdk = sdkFlag === "latest" ? "latest" : sdkVersion;
|
|
48020
|
-
const name = flagStr(args, "name") ?? existing.name ??
|
|
48584
|
+
const name = flagStr(args, "name") ?? existing.name ?? basename6(root);
|
|
48021
48585
|
const config = {
|
|
48022
48586
|
...existing,
|
|
48023
48587
|
name,
|
|
@@ -48031,7 +48595,7 @@ var appInit = async (args) => {
|
|
|
48031
48595
|
buildNumber: existing.ios?.buildNumber ?? 1
|
|
48032
48596
|
}
|
|
48033
48597
|
};
|
|
48034
|
-
writeJson(
|
|
48598
|
+
writeJson(join17(root, "app.json"), config);
|
|
48035
48599
|
log(`${c.green("written")} app.json`);
|
|
48036
48600
|
ensureShellIgnored(root, "ios");
|
|
48037
48601
|
const identity = {
|
|
@@ -48042,9 +48606,9 @@ var appInit = async (args) => {
|
|
|
48042
48606
|
buildNumber: config.ios.buildNumber
|
|
48043
48607
|
};
|
|
48044
48608
|
const write = (rel, content) => {
|
|
48045
|
-
const abs =
|
|
48046
|
-
|
|
48047
|
-
|
|
48609
|
+
const abs = join17(iosDir, rel);
|
|
48610
|
+
mkdirSync11(dirname12(abs), { recursive: true });
|
|
48611
|
+
writeFileSync13(abs, content);
|
|
48048
48612
|
log(`${c.green("created")} ios/${rel}`);
|
|
48049
48613
|
};
|
|
48050
48614
|
write("README.md", IOS_README);
|
|
@@ -48068,54 +48632,27 @@ var appInit = async (args) => {
|
|
|
48068
48632
|
log(` ${c.bold("open ios/App.xcodeproj")} then run on your device from Xcode`);
|
|
48069
48633
|
log(` App icon: set app.json "icon" to a square ≥1024px image — sync renders every size (or drop a PNG into App/Assets.xcassets/AppIcon.appiconset/)`);
|
|
48070
48634
|
};
|
|
48071
|
-
var detectEngines = (bundleJs) => {
|
|
48072
|
-
const lines = [];
|
|
48073
|
-
for (const line of bundleJs.split(`
|
|
48074
|
-
`)) {
|
|
48075
|
-
if (!line.startsWith("//"))
|
|
48076
|
-
break;
|
|
48077
|
-
lines.push(line);
|
|
48078
|
-
}
|
|
48079
|
-
const header = lines.join(`
|
|
48080
|
-
`);
|
|
48081
|
-
return { gl: !/^\/\/ gl: disable$/m.test(header), g2: /^\/\/ 2d: enable$/m.test(header) };
|
|
48082
|
-
};
|
|
48083
|
-
var resolveVariant = (requested, bundleJs) => {
|
|
48084
|
-
const uses = detectEngines(bundleJs);
|
|
48085
|
-
const detected = uses.gl && uses.g2 ? "full" : uses.gl ? "3d" : uses.g2 ? "2d" : "core";
|
|
48086
|
-
if (requested === "auto")
|
|
48087
|
-
return { variant: detected, detected };
|
|
48088
|
-
const variant = requested;
|
|
48089
|
-
const missing = [
|
|
48090
|
-
uses.gl && variant !== "3d" && variant !== "full" ? "3D" : null,
|
|
48091
|
-
uses.g2 && variant !== "2d" && variant !== "full" ? "2D" : null
|
|
48092
|
-
].filter(Boolean);
|
|
48093
|
-
if (missing.length > 0) {
|
|
48094
|
-
warn(`project uses the ${missing.join(" + ")} engine but ios.variant forces "${variant}" — those scenes won't run on device; remove ios.variant to auto-select "${detected}".`);
|
|
48095
|
-
}
|
|
48096
|
-
return { variant, detected };
|
|
48097
|
-
};
|
|
48098
48635
|
var ensureSdkResourcesBundle = async (sdkVersion) => {
|
|
48099
48636
|
const override = process.env.LECODES_SDK_RESOURCES;
|
|
48100
48637
|
if (override) {
|
|
48101
48638
|
note(`Using local resources bundle ${override} (LECODES_SDK_RESOURCES)`);
|
|
48102
|
-
return
|
|
48639
|
+
return resolve6(override);
|
|
48103
48640
|
}
|
|
48104
|
-
const dir =
|
|
48105
|
-
const bundle =
|
|
48106
|
-
if (
|
|
48641
|
+
const dir = join17(homedir6(), ".lecodes", "sdk-resources", sdkVersion);
|
|
48642
|
+
const bundle = join17(dir, "LeCodesSDKResources.bundle");
|
|
48643
|
+
if (existsSync16(bundle))
|
|
48107
48644
|
return bundle;
|
|
48108
48645
|
const url = `https://github.com/${SDK_REPO}/releases/download/${sdkVersion}/LeCodesSDKResources.bundle.zip`;
|
|
48109
48646
|
note(`Downloading LeCodesSDKResources.bundle (SDK ${sdkVersion})…`);
|
|
48110
48647
|
const res = await fetch(url);
|
|
48111
48648
|
if (!res.ok)
|
|
48112
48649
|
throw new CliError(`Resources bundle download failed (HTTP ${res.status}) — ${url}`);
|
|
48113
|
-
|
|
48114
|
-
const zip =
|
|
48115
|
-
|
|
48116
|
-
|
|
48117
|
-
|
|
48118
|
-
if (!
|
|
48650
|
+
mkdirSync11(dir, { recursive: true });
|
|
48651
|
+
const zip = join17(dir, "LeCodesSDKResources.bundle.zip");
|
|
48652
|
+
writeFileSync13(zip, new Uint8Array(await res.arrayBuffer()));
|
|
48653
|
+
execFileSync4("ditto", ["-x", "-k", zip, dir]);
|
|
48654
|
+
rmSync4(zip);
|
|
48655
|
+
if (!existsSync16(bundle))
|
|
48119
48656
|
throw new CliError(`No LeCodesSDKResources.bundle inside the release asset ${url}`);
|
|
48120
48657
|
return bundle;
|
|
48121
48658
|
};
|
|
@@ -48140,13 +48677,13 @@ var resolveUpdateUrl = (update, root) => {
|
|
|
48140
48677
|
};
|
|
48141
48678
|
var appSync = async (args) => {
|
|
48142
48679
|
const root = projectRoot();
|
|
48143
|
-
const config = readJson(
|
|
48680
|
+
const config = readJson(join17(root, "app.json"));
|
|
48144
48681
|
const platform = args._[1];
|
|
48145
48682
|
if (platform !== undefined && platform !== "ios" && platform !== "android") {
|
|
48146
48683
|
throw new CliError(`Unknown platform "${platform}" — use ios or android.`);
|
|
48147
48684
|
}
|
|
48148
|
-
const hasIos = !!config?.ios &&
|
|
48149
|
-
const hasAndroid = !!config?.android &&
|
|
48685
|
+
const hasIos = !!config?.ios && existsSync16(join17(root, "ios"));
|
|
48686
|
+
const hasAndroid = !!config?.android && existsSync16(join17(root, "android"));
|
|
48150
48687
|
if (platform === "ios" && !hasIos)
|
|
48151
48688
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
48152
48689
|
if (platform === "android" && !hasAndroid)
|
|
@@ -48161,7 +48698,7 @@ var appSync = async (args) => {
|
|
|
48161
48698
|
await syncAndroid(root, config, iosArtifacts);
|
|
48162
48699
|
};
|
|
48163
48700
|
var syncIos = async (root, config) => {
|
|
48164
|
-
const iosDir =
|
|
48701
|
+
const iosDir = join17(root, "ios");
|
|
48165
48702
|
if (!config.ios)
|
|
48166
48703
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
48167
48704
|
const requestedVariant = config.ios.variant ?? "auto";
|
|
@@ -48171,14 +48708,14 @@ var syncIos = async (root, config) => {
|
|
|
48171
48708
|
const linkedSdk = shellSdkVersion(iosDir);
|
|
48172
48709
|
const sdkPin = process.env.LECODES_IOS_SDK ?? config.ios.sdk ?? linkedSdk ?? "latest";
|
|
48173
48710
|
const sdkVersion = await resolveSdkVersion(sdkPin, linkedSdk, IOS_SDK_OPTS);
|
|
48174
|
-
const lockPath =
|
|
48711
|
+
const lockPath = join17(root, "plugins.lock.json");
|
|
48175
48712
|
const oldLock = readJson(lockPath)?.plugins ?? {};
|
|
48176
48713
|
const pluginSpecs = Object.entries(config.plugins ?? {});
|
|
48177
48714
|
const runtimePlugins = [];
|
|
48178
48715
|
const infoValues = {};
|
|
48179
48716
|
const entitlementValues = {};
|
|
48180
48717
|
const newLock = {};
|
|
48181
|
-
const sourcesDir =
|
|
48718
|
+
const sourcesDir = join17(iosDir, "LeCodesRuntime", "Sources");
|
|
48182
48719
|
for (const [id, spec] of pluginSpecs) {
|
|
48183
48720
|
const plugin = resolvePlugin(id, spec, oldLock[id], "ios");
|
|
48184
48721
|
const ios = plugin.manifest.ios;
|
|
@@ -48186,12 +48723,12 @@ var syncIos = async (root, config) => {
|
|
|
48186
48723
|
const register = ios.register;
|
|
48187
48724
|
if (!module || !register)
|
|
48188
48725
|
throw new CliError(`Plugin "${id}": manifest ios.module / ios.register missing`);
|
|
48189
|
-
const src =
|
|
48190
|
-
if (!
|
|
48726
|
+
const src = join17(plugin.dir, ios.sources ?? "ios");
|
|
48727
|
+
if (!existsSync16(src))
|
|
48191
48728
|
throw new CliError(`Plugin "${id}": sources dir ${src} missing`);
|
|
48192
|
-
const dest =
|
|
48193
|
-
|
|
48194
|
-
|
|
48729
|
+
const dest = join17(sourcesDir, module);
|
|
48730
|
+
rmSync4(dest, { recursive: true, force: true });
|
|
48731
|
+
cpSync3(src, dest, { recursive: true });
|
|
48195
48732
|
if (ios.minSdk && VERSION_RE.test(ios.minSdk) && cmpVersion(sdkVersion, ios.minSdk) < 0) {
|
|
48196
48733
|
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
48734
|
}
|
|
@@ -48206,9 +48743,9 @@ var syncIos = async (root, config) => {
|
|
|
48206
48743
|
log(`${c.green("plugin")} ${id}@${plugin.lock.version} (${plugin.lock.tag} → Sources/${module})`);
|
|
48207
48744
|
}
|
|
48208
48745
|
const keep = new Set(["LeCodesRuntime", ...runtimePlugins.map((p2) => p2.module)]);
|
|
48209
|
-
for (const entry of
|
|
48210
|
-
if (!keep.has(entry) &&
|
|
48211
|
-
|
|
48746
|
+
for (const entry of existsSync16(sourcesDir) ? readdirSync7(sourcesDir) : []) {
|
|
48747
|
+
if (!keep.has(entry) && statSync5(join17(sourcesDir, entry)).isDirectory()) {
|
|
48748
|
+
rmSync4(join17(sourcesDir, entry), { recursive: true });
|
|
48212
48749
|
log(`${c.yellow("removed")} ios/LeCodesRuntime/Sources/${entry} (plugin uninstalled)`);
|
|
48213
48750
|
}
|
|
48214
48751
|
}
|
|
@@ -48216,11 +48753,11 @@ var syncIos = async (root, config) => {
|
|
|
48216
48753
|
if (!Array.isArray(remotePatterns) || remotePatterns.some((p2) => typeof p2 !== "string")) {
|
|
48217
48754
|
throw new CliError('app.json "resources.remote" must be an array of glob strings (e.g. ["videos/**"]).');
|
|
48218
48755
|
}
|
|
48219
|
-
const resourcesDir =
|
|
48220
|
-
|
|
48221
|
-
|
|
48222
|
-
const resources = await compileShell({ _: [], flags: { o:
|
|
48223
|
-
const bundleJs =
|
|
48756
|
+
const resourcesDir = join17(iosDir, "App", "Resources");
|
|
48757
|
+
rmSync4(resourcesDir, { recursive: true, force: true });
|
|
48758
|
+
mkdirSync11(resourcesDir, { recursive: true });
|
|
48759
|
+
const resources = await compileShell({ _: [], flags: { o: join17(resourcesDir, "app.js") } }, { remotePatterns });
|
|
48760
|
+
const bundleJs = readFileSync15(join17(resourcesDir, "app.js"), "utf8");
|
|
48224
48761
|
const { variant, detected } = resolveVariant(requestedVariant, bundleJs);
|
|
48225
48762
|
const WHY = {
|
|
48226
48763
|
core: "UI only",
|
|
@@ -48234,8 +48771,8 @@ var syncIos = async (root, config) => {
|
|
|
48234
48771
|
const sdkProvided = new Set;
|
|
48235
48772
|
if (variant === "3d" || variant === "full") {
|
|
48236
48773
|
const sdkResources = await ensureSdkResourcesBundle(sdkVersion);
|
|
48237
|
-
|
|
48238
|
-
for (const f3 of
|
|
48774
|
+
cpSync3(sdkResources, join17(resourcesDir, "LeCodesSDKResources.bundle"), { recursive: true });
|
|
48775
|
+
for (const f3 of readdirSync7(sdkResources))
|
|
48239
48776
|
if (f3 !== "Info.plist")
|
|
48240
48777
|
sdkProvided.add(f3);
|
|
48241
48778
|
log(`${c.green("resources")} LeCodesSDKResources.bundle (filament) → ios/App/Resources/`);
|
|
@@ -48262,7 +48799,7 @@ var syncIos = async (root, config) => {
|
|
|
48262
48799
|
}
|
|
48263
48800
|
if (candidates.length > 1)
|
|
48264
48801
|
warn(`asset name collision: ${name} (${candidates.map((r2) => r2.path).join(", ")}) — embedding ${candidates[0].path}`);
|
|
48265
|
-
|
|
48802
|
+
cpSync3(candidates[0].absPath, join17(resourcesDir, name));
|
|
48266
48803
|
embedded++;
|
|
48267
48804
|
}
|
|
48268
48805
|
if (embedded > 0)
|
|
@@ -48282,20 +48819,28 @@ var syncIos = async (root, config) => {
|
|
|
48282
48819
|
}
|
|
48283
48820
|
if (shellUuid)
|
|
48284
48821
|
log(`${c.green("identity")} project ${shellUuid} baked into the shell (world identity)`);
|
|
48285
|
-
|
|
48286
|
-
|
|
48287
|
-
|
|
48822
|
+
writeFileSync13(join17(iosDir, "README.md"), IOS_README);
|
|
48823
|
+
writeFileSync13(join17(iosDir, "LeCodesRuntime", "Package.swift"), runtimePackageTemplate(sdkVersion, variant, runtimePlugins));
|
|
48824
|
+
writeFileSync13(join17(sourcesDir, "LeCodesRuntime", "LeCodesRuntime.swift"), runtimeSwiftTemplate(runtimePlugins, shellUuid));
|
|
48288
48825
|
const mergedKeys = {};
|
|
48289
48826
|
for (const [key, values] of Object.entries(infoValues))
|
|
48290
48827
|
mergedKeys[key] = [...new Set(values)].join(" ");
|
|
48291
48828
|
const updateUrl = resolveUpdateUrl(config.update, root);
|
|
48292
|
-
|
|
48829
|
+
writeFileSync13(join17(iosDir, "App", "Info.plist"), infoPlistTemplate(mergedKeys, updateUrl));
|
|
48293
48830
|
if (updateUrl)
|
|
48294
48831
|
log(`${c.green("update")} OTA source ${updateUrl} — checked natively at launch, new code applies next launch`);
|
|
48295
|
-
if (
|
|
48832
|
+
else if (!config.update) {
|
|
48833
|
+
let manifest = null;
|
|
48834
|
+
try {
|
|
48835
|
+
manifest = readManifest(root);
|
|
48836
|
+
} catch {}
|
|
48837
|
+
if (manifest)
|
|
48838
|
+
note(`tip: add "update": true to app.json for over-the-air JS updates — ship with \`lecodes push --publish\`, no store release`);
|
|
48839
|
+
}
|
|
48840
|
+
if (pluginSpecs.length > 0 || existsSync16(lockPath))
|
|
48296
48841
|
writeJson(lockPath, { plugins: newLock });
|
|
48297
|
-
const pbxPath =
|
|
48298
|
-
let pbx =
|
|
48842
|
+
const pbxPath = join17(iosDir, "App.xcodeproj", "project.pbxproj");
|
|
48843
|
+
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
48844
|
const mergedEntitlements = {};
|
|
48300
48845
|
for (const [key, values] of Object.entries(entitlementValues)) {
|
|
48301
48846
|
if (values.every((v) => Array.isArray(v))) {
|
|
@@ -48316,7 +48861,7 @@ var syncIos = async (root, config) => {
|
|
|
48316
48861
|
}
|
|
48317
48862
|
const hasEntitlements = Object.keys(mergedEntitlements).length > 0;
|
|
48318
48863
|
if (pbx.includes("CODE_SIGN_ENTITLEMENTS") || hasEntitlements) {
|
|
48319
|
-
|
|
48864
|
+
writeFileSync13(join17(iosDir, "App", "App.entitlements"), entitlementsTemplate(mergedEntitlements));
|
|
48320
48865
|
if (hasEntitlements)
|
|
48321
48866
|
log(`${c.green("caps")} App/App.entitlements: ${Object.keys(mergedEntitlements).sort().join(", ")}`);
|
|
48322
48867
|
if (!pbx.includes("CODE_SIGN_ENTITLEMENTS")) {
|
|
@@ -48332,7 +48877,7 @@ $2Info.plist,`);
|
|
|
48332
48877
|
}
|
|
48333
48878
|
}
|
|
48334
48879
|
}
|
|
48335
|
-
|
|
48880
|
+
writeFileSync13(pbxPath, pbx);
|
|
48336
48881
|
if (config.icon) {
|
|
48337
48882
|
await writeIosAppIcon(iosDir, await loadIconMaster(root, config));
|
|
48338
48883
|
}
|
|
@@ -48344,7 +48889,7 @@ $2Info.plist,`);
|
|
|
48344
48889
|
var appPlugin = async (args) => {
|
|
48345
48890
|
const sub = args._[1];
|
|
48346
48891
|
const root = projectRoot();
|
|
48347
|
-
const configPath =
|
|
48892
|
+
const configPath = join17(root, "app.json");
|
|
48348
48893
|
const config = readJson(configPath);
|
|
48349
48894
|
if (!config)
|
|
48350
48895
|
throw new CliError("No app.json here — run `lecodes app init <ios|android>` first.");
|
|
@@ -48355,7 +48900,7 @@ var appPlugin = async (args) => {
|
|
|
48355
48900
|
log("No plugins installed.");
|
|
48356
48901
|
return;
|
|
48357
48902
|
}
|
|
48358
|
-
const lock = readJson(
|
|
48903
|
+
const lock = readJson(join17(root, "plugins.lock.json"))?.plugins ?? {};
|
|
48359
48904
|
for (const [id2, spec] of entries) {
|
|
48360
48905
|
const pin = lock[id2] ? c.dim(` ${lock[id2].repo} ${lock[id2].tag} (${lock[id2].commit.slice(0, 7)})`) : c.dim(" (not synced)");
|
|
48361
48906
|
log(`${c.bold(id2)} ${spec}${pin}`);
|
|
@@ -48408,14 +48953,14 @@ var exportOptionsPlist = (method, teamId, destination) => `<?xml version="1.0" e
|
|
|
48408
48953
|
`;
|
|
48409
48954
|
var requireShell = () => {
|
|
48410
48955
|
const root = projectRoot();
|
|
48411
|
-
const config = readJson(
|
|
48412
|
-
if (!config?.ios || !
|
|
48956
|
+
const config = readJson(join17(root, "app.json"));
|
|
48957
|
+
if (!config?.ios || !existsSync16(join17(root, "ios"))) {
|
|
48413
48958
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
48414
48959
|
}
|
|
48415
48960
|
return { root, config };
|
|
48416
48961
|
};
|
|
48417
48962
|
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(
|
|
48963
|
+
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
48964
|
const section = config[platform];
|
|
48420
48965
|
const wanted = flagStr(args, "sdk");
|
|
48421
48966
|
let changed = false;
|
|
@@ -48446,19 +48991,19 @@ var updateSdkPin = async (root, config, args, checkOnly, platform) => {
|
|
|
48446
48991
|
}
|
|
48447
48992
|
if (!checkOnly && nextPin !== pin) {
|
|
48448
48993
|
section.sdk = nextPin;
|
|
48449
|
-
writeJson(
|
|
48994
|
+
writeJson(join17(root, "app.json"), config);
|
|
48450
48995
|
}
|
|
48451
48996
|
return changed;
|
|
48452
48997
|
};
|
|
48453
48998
|
var appUpdate = async (args) => {
|
|
48454
48999
|
const root = projectRoot();
|
|
48455
|
-
const config = readJson(
|
|
49000
|
+
const config = readJson(join17(root, "app.json"));
|
|
48456
49001
|
const platform = args._[1];
|
|
48457
49002
|
if (platform !== undefined && platform !== "ios" && platform !== "android") {
|
|
48458
49003
|
throw new CliError(`Unknown platform "${platform}" — use ios or android.`);
|
|
48459
49004
|
}
|
|
48460
|
-
const hasIos = !!config?.ios &&
|
|
48461
|
-
const hasAndroid = !!config?.android &&
|
|
49005
|
+
const hasIos = !!config?.ios && existsSync16(join17(root, "ios"));
|
|
49006
|
+
const hasAndroid = !!config?.android && existsSync16(join17(root, "android"));
|
|
48462
49007
|
if (platform === "ios" && !hasIos)
|
|
48463
49008
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
48464
49009
|
if (platform === "android" && !hasAndroid)
|
|
@@ -48476,7 +49021,7 @@ var appUpdate = async (args) => {
|
|
|
48476
49021
|
changed = await updateSdkPin(root, config, args, checkOnly, "ios") || changed;
|
|
48477
49022
|
if (doAndroid)
|
|
48478
49023
|
changed = await updateSdkPin(root, config, args, checkOnly, "android") || changed;
|
|
48479
|
-
const lockPath =
|
|
49024
|
+
const lockPath = join17(root, "plugins.lock.json");
|
|
48480
49025
|
const lock = readJson(lockPath)?.plugins ?? {};
|
|
48481
49026
|
const specs = Object.entries(config.plugins ?? {});
|
|
48482
49027
|
if (specs.length > 0 && !flagBool(args, "no-plugins")) {
|
|
@@ -48505,7 +49050,7 @@ var appUpdate = async (args) => {
|
|
|
48505
49050
|
log(`${c.green("plugin")} ${id} ${entry?.tag ?? "—"} → ${c.bold(tag)}`);
|
|
48506
49051
|
changed = true;
|
|
48507
49052
|
}
|
|
48508
|
-
if (!checkOnly &&
|
|
49053
|
+
if (!checkOnly && existsSync16(lockPath))
|
|
48509
49054
|
writeJson(lockPath, { plugins: kept });
|
|
48510
49055
|
}
|
|
48511
49056
|
log("");
|
|
@@ -48525,9 +49070,9 @@ var appUpdate = async (args) => {
|
|
|
48525
49070
|
};
|
|
48526
49071
|
var appDoctor = async (_args) => {
|
|
48527
49072
|
const root = projectRoot();
|
|
48528
|
-
const config = readJson(
|
|
48529
|
-
const hasIos = !!config?.ios &&
|
|
48530
|
-
const hasAndroid = !!config?.android &&
|
|
49073
|
+
const config = readJson(join17(root, "app.json"));
|
|
49074
|
+
const hasIos = !!config?.ios && existsSync16(join17(root, "ios"));
|
|
49075
|
+
const hasAndroid = !!config?.android && existsSync16(join17(root, "android"));
|
|
48531
49076
|
if (!hasIos && !hasAndroid) {
|
|
48532
49077
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
48533
49078
|
}
|
|
@@ -48538,14 +49083,14 @@ var appDoctor = async (_args) => {
|
|
|
48538
49083
|
failed++;
|
|
48539
49084
|
};
|
|
48540
49085
|
if (config?.icon) {
|
|
48541
|
-
check(
|
|
49086
|
+
check(existsSync16(join17(root, config.icon)), `Icon master ${config.icon}`, `app.json "icon" points at a missing file — sync will fail`);
|
|
48542
49087
|
}
|
|
48543
49088
|
if (hasIos)
|
|
48544
49089
|
await doctorIosChecks(root, config, check);
|
|
48545
49090
|
if (hasAndroid) {
|
|
48546
49091
|
await doctorAndroidChecks(root, config, check);
|
|
48547
49092
|
if (!hasIos) {
|
|
48548
|
-
const lock = readJson(
|
|
49093
|
+
const lock = readJson(join17(root, "plugins.lock.json"))?.plugins ?? {};
|
|
48549
49094
|
const wanted = Object.keys(config.plugins ?? {});
|
|
48550
49095
|
const inSync = wanted.every((id) => lock[id]) && Object.keys(lock).every((id) => wanted.includes(id));
|
|
48551
49096
|
check(wanted.length === 0 || inSync, "Plugins synced", "run `lecodes app sync`");
|
|
@@ -48564,22 +49109,22 @@ var doctorIosChecks = async (root, config, check) => {
|
|
|
48564
49109
|
} catch {}
|
|
48565
49110
|
check(xcodeVersion !== "", `Xcode CLI (${xcodeVersion || "not found"})`, "install Xcode + command line tools");
|
|
48566
49111
|
check(!!config.ios.teamId, "Development team set", "lecodes app init wrote app.json — fill ios.teamId", false);
|
|
48567
|
-
const iconDir =
|
|
48568
|
-
const hasIcon =
|
|
49112
|
+
const iconDir = join17(root, "ios", "App", "Assets.xcassets", "AppIcon.appiconset");
|
|
49113
|
+
const hasIcon = existsSync16(iconDir) && readdirSync7(iconDir).some((f3) => f3.endsWith(".png"));
|
|
48569
49114
|
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(
|
|
49115
|
+
const bundle = join17(root, "ios", "App", "Resources", "app.js");
|
|
49116
|
+
check(existsSync16(bundle), "Bundled app.js", "run `lecodes app sync`");
|
|
48572
49117
|
const requestedVariant = config.ios.variant ?? "auto";
|
|
48573
49118
|
const validVariant = requestedVariant === "auto" || SDK_VARIANTS.includes(requestedVariant);
|
|
48574
49119
|
check(validVariant, `ios.variant "${requestedVariant}"`, `use "auto" or one of ${SDK_VARIANTS.join(", ")}`);
|
|
48575
|
-
if (
|
|
48576
|
-
const { variant } = resolveVariant(requestedVariant,
|
|
48577
|
-
const pkg =
|
|
49120
|
+
if (existsSync16(bundle) && validVariant) {
|
|
49121
|
+
const { variant } = resolveVariant(requestedVariant, readFileSync15(bundle, "utf8"));
|
|
49122
|
+
const pkg = readFileSync15(join17(root, "ios", "LeCodesRuntime", "Package.swift"), "utf8");
|
|
48578
49123
|
const needsResources = variant === "3d" || variant === "full";
|
|
48579
|
-
const hasResources =
|
|
49124
|
+
const hasResources = existsSync16(join17(root, "ios", "App", "Resources", "LeCodesSDKResources.bundle"));
|
|
48580
49125
|
check(pkg.includes(`"LeCodesSDK-${variant}"`) && (!needsResources || hasResources), `SDK variant ${variant}${needsResources ? " (+ filament resources)" : ""}`, "run `lecodes app sync`");
|
|
48581
49126
|
}
|
|
48582
|
-
const linked = shellSdkVersion(
|
|
49127
|
+
const linked = shellSdkVersion(join17(root, "ios"));
|
|
48583
49128
|
const pin = config.ios.sdk ?? "latest";
|
|
48584
49129
|
check(!!linked, `SDK ${linked ?? "version unreadable"}`, "run `lecodes app sync`");
|
|
48585
49130
|
if (linked && pin !== "latest") {
|
|
@@ -48591,24 +49136,27 @@ var doctorIosChecks = async (root, config, check) => {
|
|
|
48591
49136
|
check(cmpVersion(linked, latest) >= 0, `SDK is the latest release (${latest})`, "run `lecodes app update`", false);
|
|
48592
49137
|
} catch {}
|
|
48593
49138
|
}
|
|
48594
|
-
const lock = readJson(
|
|
49139
|
+
const lock = readJson(join17(root, "plugins.lock.json"))?.plugins ?? {};
|
|
48595
49140
|
const wanted = Object.keys(config.plugins ?? {});
|
|
48596
49141
|
const inSync = wanted.every((id) => lock[id]) && Object.keys(lock).every((id) => wanted.includes(id));
|
|
48597
49142
|
check(wanted.length === 0 || inSync, "Plugins synced", "run `lecodes app sync`");
|
|
48598
|
-
const pbxFile =
|
|
48599
|
-
if (
|
|
48600
|
-
check(
|
|
49143
|
+
const pbxFile = join17(root, "ios", "App.xcodeproj", "project.pbxproj");
|
|
49144
|
+
if (existsSync16(pbxFile) && readFileSync15(pbxFile, "utf8").includes("CODE_SIGN_ENTITLEMENTS")) {
|
|
49145
|
+
check(existsSync16(join17(root, "ios", "App", "App.entitlements")), "App/App.entitlements present", "run `lecodes app sync`");
|
|
48601
49146
|
}
|
|
48602
49147
|
if (wanted.includes("push")) {
|
|
48603
|
-
const runtimeSwift =
|
|
48604
|
-
const baked =
|
|
49148
|
+
const runtimeSwift = join17(root, "ios", "LeCodesRuntime", "Sources", "LeCodesRuntime", "LeCodesRuntime.swift");
|
|
49149
|
+
const baked = existsSync16(runtimeSwift) && readFileSync15(runtimeSwift, "utf8").includes("bootProjectUuid");
|
|
48605
49150
|
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
49151
|
}
|
|
48607
49152
|
if (config.update) {
|
|
48608
|
-
const plist =
|
|
49153
|
+
const plist = readFileSync15(join17(root, "ios", "App", "Info.plist"), "utf8");
|
|
48609
49154
|
check(plist.includes("<key>LeCodesUpdateURL</key>"), "OTA update URL in Info.plist", "run `lecodes app sync`", false);
|
|
49155
|
+
const appDir = join17(root, "ios", "App");
|
|
49156
|
+
const callsUpdater = readdirSync7(appDir).some((f3) => f3.endsWith(".swift") && readFileSync15(join17(appDir, f3), "utf8").includes("LeCodesUpdater.activeBundle"));
|
|
49157
|
+
check(callsUpdater, "App code launches via LeCodesUpdater", `no App/*.swift calls LeCodesUpdater.activeBundle() — downloaded updates would never run; see "Over-the-air updates" in ios/README.md for the ViewController snippet`, false);
|
|
48610
49158
|
}
|
|
48611
|
-
const asc = readJson(
|
|
49159
|
+
const asc = readJson(join17(homedir6(), ".lecodes", "asc.json"));
|
|
48612
49160
|
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
49161
|
};
|
|
48614
49162
|
var ARCHIVE_PATH = "ios/build/App.xcarchive";
|
|
@@ -48616,8 +49164,10 @@ var appBuild = async (args) => {
|
|
|
48616
49164
|
const platform = args._[1];
|
|
48617
49165
|
if (platform === "android")
|
|
48618
49166
|
return buildAndroid(args);
|
|
49167
|
+
if (platform === "desktop")
|
|
49168
|
+
return buildDesktop(args);
|
|
48619
49169
|
if (platform !== undefined && platform !== "ios") {
|
|
48620
|
-
throw new CliError(`Unknown platform "${platform}" — use ios or
|
|
49170
|
+
throw new CliError(`Unknown platform "${platform}" — use ios, android or desktop.`);
|
|
48621
49171
|
}
|
|
48622
49172
|
const { root, config } = requireShell();
|
|
48623
49173
|
const configuration = flagStr(args, "configuration") ?? "Release";
|
|
@@ -48642,8 +49192,8 @@ var appBuild = async (args) => {
|
|
|
48642
49192
|
success(`Archive: ios/build/App.xcarchive`);
|
|
48643
49193
|
if (flagBool(args, "no-export"))
|
|
48644
49194
|
return;
|
|
48645
|
-
const optionsPath =
|
|
48646
|
-
|
|
49195
|
+
const optionsPath = join17(root, "ios", "build", "ExportOptions.plist");
|
|
49196
|
+
writeFileSync13(optionsPath, exportOptionsPlist(method, config.ios.teamId ?? ""));
|
|
48647
49197
|
note(`Exporting (.ipa, method ${method})…`);
|
|
48648
49198
|
runTool(root, "xcodebuild", [
|
|
48649
49199
|
"-exportArchive",
|
|
@@ -48662,10 +49212,10 @@ var appUpload = async (args) => {
|
|
|
48662
49212
|
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
49213
|
}
|
|
48664
49214
|
const { root, config } = requireShell();
|
|
48665
|
-
if (!
|
|
49215
|
+
if (!existsSync16(join17(root, ARCHIVE_PATH))) {
|
|
48666
49216
|
throw new CliError("No archive at ios/build/App.xcarchive — run `lecodes app build ios` first.");
|
|
48667
49217
|
}
|
|
48668
|
-
const ascPath =
|
|
49218
|
+
const ascPath = join17(homedir6(), ".lecodes", "asc.json");
|
|
48669
49219
|
const stored = readJson(ascPath) ?? {};
|
|
48670
49220
|
const keyPath = flagStr(args, "key-path") ?? stored.keyPath;
|
|
48671
49221
|
const keyId = flagStr(args, "key-id") ?? stored.keyId;
|
|
@@ -48674,15 +49224,15 @@ var appUpload = async (args) => {
|
|
|
48674
49224
|
throw new CliError(`App Store Connect API key needed: --key-path <AuthKey.p8> --key-id <id> --issuer-id <id>
|
|
48675
49225
|
(create one at appstoreconnect.apple.com → Users and Access → Integrations; stored in ~/.lecodes/asc.json after first use)`);
|
|
48676
49226
|
}
|
|
48677
|
-
if (!
|
|
49227
|
+
if (!existsSync16(keyPath))
|
|
48678
49228
|
throw new CliError(`API key file not found: ${keyPath}`);
|
|
48679
49229
|
if (flagStr(args, "key-path")) {
|
|
48680
|
-
|
|
49230
|
+
mkdirSync11(dirname12(ascPath), { recursive: true });
|
|
48681
49231
|
writeJson(ascPath, { keyPath, keyId, issuerId });
|
|
48682
49232
|
note("Stored the key reference in ~/.lecodes/asc.json");
|
|
48683
49233
|
}
|
|
48684
|
-
const optionsPath =
|
|
48685
|
-
|
|
49234
|
+
const optionsPath = join17(root, "ios", "build", "UploadOptions.plist");
|
|
49235
|
+
writeFileSync13(optionsPath, exportOptionsPlist("app-store-connect", config.ios.teamId ?? "", "upload"));
|
|
48686
49236
|
note("Uploading to App Store Connect… (processing continues on Apple's side)");
|
|
48687
49237
|
runTool(root, "xcodebuild", [
|
|
48688
49238
|
"-exportArchive",
|
|
@@ -48693,14 +49243,14 @@ var appUpload = async (args) => {
|
|
|
48693
49243
|
"-exportPath",
|
|
48694
49244
|
"ios/build/upload",
|
|
48695
49245
|
"-authenticationKeyPath",
|
|
48696
|
-
|
|
49246
|
+
resolve6(keyPath),
|
|
48697
49247
|
"-authenticationKeyID",
|
|
48698
49248
|
keyId,
|
|
48699
49249
|
"-authenticationKeyIssuerID",
|
|
48700
49250
|
issuerId,
|
|
48701
49251
|
"-allowProvisioningUpdates"
|
|
48702
49252
|
]);
|
|
48703
|
-
const configPath =
|
|
49253
|
+
const configPath = join17(root, "app.json");
|
|
48704
49254
|
const fresh = readJson(configPath);
|
|
48705
49255
|
fresh.ios.buildNumber = (fresh.ios.buildNumber ?? 1) + 1;
|
|
48706
49256
|
writeJson(configPath, fresh);
|
|
@@ -48711,19 +49261,19 @@ var appOpen = async (args) => {
|
|
|
48711
49261
|
const root = projectRoot();
|
|
48712
49262
|
let platform = args._[1];
|
|
48713
49263
|
if (!platform) {
|
|
48714
|
-
const hasIos =
|
|
48715
|
-
const hasAndroid =
|
|
49264
|
+
const hasIos = existsSync16(join17(root, "ios"));
|
|
49265
|
+
const hasAndroid = existsSync16(join17(root, "android"));
|
|
48716
49266
|
if (hasIos && hasAndroid)
|
|
48717
49267
|
throw new CliError("Both shells exist — `lecodes app open ios` or `lecodes app open android`.");
|
|
48718
49268
|
platform = hasAndroid ? "android" : "ios";
|
|
48719
49269
|
}
|
|
48720
49270
|
if (platform === "ios") {
|
|
48721
|
-
const proj =
|
|
48722
|
-
if (!
|
|
49271
|
+
const proj = join17(root, "ios", "App.xcodeproj");
|
|
49272
|
+
if (!existsSync16(proj))
|
|
48723
49273
|
throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
|
|
48724
49274
|
if (process.platform !== "darwin")
|
|
48725
49275
|
throw new CliError("Opening Xcode requires macOS.");
|
|
48726
|
-
|
|
49276
|
+
execFileSync4("open", [proj], { stdio: "ignore" });
|
|
48727
49277
|
success("Opened ios/App.xcodeproj in Xcode.");
|
|
48728
49278
|
return;
|
|
48729
49279
|
}
|
|
@@ -48749,15 +49299,15 @@ var app = async (args) => {
|
|
|
48749
49299
|
return appUpload(args);
|
|
48750
49300
|
if (sub === "open")
|
|
48751
49301
|
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`.");
|
|
49302
|
+
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
49303
|
};
|
|
48754
49304
|
|
|
48755
49305
|
// src/commands/clone.ts
|
|
48756
49306
|
init_api();
|
|
48757
49307
|
init_config();
|
|
48758
49308
|
init_manifest();
|
|
48759
|
-
import { existsSync as
|
|
48760
|
-
import { resolve as
|
|
49309
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync13, readdirSync as readdirSync9 } from "node:fs";
|
|
49310
|
+
import { resolve as resolve7 } from "node:path";
|
|
48761
49311
|
init_types4();
|
|
48762
49312
|
init_ignore();
|
|
48763
49313
|
init_util();
|
|
@@ -48778,11 +49328,11 @@ var clone3 = async (args) => {
|
|
|
48778
49328
|
const uuid = parseUuid(args._[0]);
|
|
48779
49329
|
const project = await getProject(apiUrl, token, uuid);
|
|
48780
49330
|
const dir = args._[1] ?? slug(project.name);
|
|
48781
|
-
const root =
|
|
48782
|
-
if (
|
|
49331
|
+
const root = resolve7(dir);
|
|
49332
|
+
if (existsSync18(root) && readdirSync9(root).length > 0) {
|
|
48783
49333
|
throw new CliError(`Directory "${dir}" already exists and is not empty.`);
|
|
48784
49334
|
}
|
|
48785
|
-
|
|
49335
|
+
mkdirSync13(root, { recursive: true });
|
|
48786
49336
|
info(c.dim(`Fetching project ${c.bold(project.name)}…`));
|
|
48787
49337
|
const files = await materializeAssets(root, project, apiUrl, token, progress);
|
|
48788
49338
|
endProgress();
|
|
@@ -48804,8 +49354,8 @@ init_config();
|
|
|
48804
49354
|
init_manifest();
|
|
48805
49355
|
init_types4();
|
|
48806
49356
|
init_ignore();
|
|
48807
|
-
import { existsSync as
|
|
48808
|
-
import { join as
|
|
49357
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync14, readdirSync as readdirSync10, writeFileSync as writeFileSync15 } from "node:fs";
|
|
49358
|
+
import { join as join19, resolve as resolve8 } from "node:path";
|
|
48809
49359
|
|
|
48810
49360
|
// src/commands/projectTemplates.ts
|
|
48811
49361
|
var appClaudeMd = (name) => `# ${name} — LeCodes app
|
|
@@ -48861,11 +49411,28 @@ optional \`expect\` (\`screen\`, \`node\`/\`notNode\`, \`text\`/\`notText\`,
|
|
|
48861
49411
|
with \`"fixtures": "fixtures.json"\` in its envelope.
|
|
48862
49412
|
- \`fill\` fills every empty input — use it before tapping a disabled-until-filled submit.
|
|
48863
49413
|
|
|
49414
|
+
## SDK documentation
|
|
49415
|
+
|
|
49416
|
+
\`.lecodes/types/\` is the exact API surface (signatures only). For semantics and examples,
|
|
49417
|
+
fetch the docs — they are served as raw markdown for AI tools:
|
|
49418
|
+
|
|
49419
|
+
- https://le.codes/llms.txt — the docs map; fetch it first to find the right page
|
|
49420
|
+
- https://le.codes/en/docs/api/<area>/<page>.md — one reference page as raw markdown
|
|
49421
|
+
(e.g. \`ui/styling.md\`, \`2d/scene2d.md\`, \`3d/scene.md\`, \`runtime/storage.md\`)
|
|
49422
|
+
- https://le.codes/en/docs/guide/<page>.md — guide pages (project anatomy, CLI, tracks)
|
|
49423
|
+
- https://le.codes/llms-full.txt — everything in one file (large; prefer per-page)
|
|
49424
|
+
|
|
49425
|
+
Always fetch the \`.md\` URLs — the HTML routes are a JS app and return an empty shell.
|
|
49426
|
+
|
|
48864
49427
|
## Other commands
|
|
48865
49428
|
|
|
48866
49429
|
\`lecodes dev\` (run on a device with hot reload + QR), \`lecodes compile -o app.js\`
|
|
48867
49430
|
(single-file bundle), \`lecodes design\` (screen-design board — the living spec).
|
|
48868
49431
|
`;
|
|
49432
|
+
var CLAUDE_SETTINGS = JSON.stringify({
|
|
49433
|
+
permissions: { allow: ["WebFetch(domain:le.codes)"] }
|
|
49434
|
+
}, null, 2) + `
|
|
49435
|
+
`;
|
|
48869
49436
|
var STARTER_FLOW = `{
|
|
48870
49437
|
"name": "counter",
|
|
48871
49438
|
"steps": [
|
|
@@ -48895,19 +49462,21 @@ var create = async (args) => {
|
|
|
48895
49462
|
if (!name)
|
|
48896
49463
|
throw new CliError("Usage: lecodes create <name> [directory]");
|
|
48897
49464
|
const dir = args._[1] ?? slug2(name);
|
|
48898
|
-
const root =
|
|
48899
|
-
if (
|
|
49465
|
+
const root = resolve8(dir);
|
|
49466
|
+
if (existsSync19(root) && readdirSync10(root).length > 0) {
|
|
48900
49467
|
throw new CliError(`Directory "${dir}" already exists and is not empty.`);
|
|
48901
49468
|
}
|
|
48902
|
-
|
|
49469
|
+
mkdirSync14(root, { recursive: true });
|
|
48903
49470
|
info(c.dim(`Creating project ${c.bold(name)}…`));
|
|
48904
49471
|
const { uuid } = await createProject(apiUrl, token, name, flagStr(args, "team"));
|
|
48905
49472
|
info(c.dim("Fetching type definitions…"));
|
|
48906
49473
|
await materializeTypes(root, apiUrl);
|
|
48907
49474
|
writeTsconfig(root);
|
|
48908
49475
|
writeDefaultIgnore(root);
|
|
48909
|
-
|
|
48910
|
-
|
|
49476
|
+
writeFileSync15(join19(root, "main.ts"), STARTER);
|
|
49477
|
+
writeFileSync15(join19(root, "CLAUDE.md"), appClaudeMd(name));
|
|
49478
|
+
mkdirSync14(join19(root, ".claude"), { recursive: true });
|
|
49479
|
+
writeFileSync15(join19(root, ".claude", "settings.json"), CLAUDE_SETTINGS);
|
|
48911
49480
|
const commits = await getCommits(apiUrl, token, uuid);
|
|
48912
49481
|
writeManifest(root, { uuid, apiUrl, name, baseHeadId: commits.headId, files: {} });
|
|
48913
49482
|
success(`Created ${c.bold(name)} in ${c.bold(dir)}/`);
|
|
@@ -48917,260 +49486,18 @@ var create = async (args) => {
|
|
|
48917
49486
|
// src/commands/desktop.ts
|
|
48918
49487
|
init_util();
|
|
48919
49488
|
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 });
|
|
49489
|
+
var resolveScale = (flag, cfg) => {
|
|
49490
|
+
const raw = flag ?? cfg;
|
|
49491
|
+
if (raw === undefined || raw === "system")
|
|
49492
|
+
return;
|
|
49493
|
+
if (raw === "fit")
|
|
49494
|
+
return "fit";
|
|
49495
|
+
const n2 = Number(raw);
|
|
49496
|
+
if (!Number.isFinite(n2) || n2 < 0.5 || n2 > 4) {
|
|
49497
|
+
throw new CliError(`scale must be a number 0.5..4, "fit" or "system" (got "${raw}")`);
|
|
49170
49498
|
}
|
|
49499
|
+
return n2;
|
|
49171
49500
|
};
|
|
49172
|
-
|
|
49173
|
-
// src/commands/desktop.ts
|
|
49174
49501
|
var run = async (args) => {
|
|
49175
49502
|
let root;
|
|
49176
49503
|
try {
|
|
@@ -49188,7 +49515,9 @@ var run = async (args) => {
|
|
|
49188
49515
|
minWidth: win?.minWidth,
|
|
49189
49516
|
minHeight: win?.minHeight,
|
|
49190
49517
|
resizable: win?.resizable,
|
|
49191
|
-
title: flagStr(args, "title") ?? win?.title ?? appCfg?.name
|
|
49518
|
+
title: flagStr(args, "title") ?? win?.title ?? appCfg?.name,
|
|
49519
|
+
fullscreen: flagBool(args, "fullscreen") ? true : flagBool(args, "windowed") ? false : win?.fullscreen,
|
|
49520
|
+
scale: resolveScale(flagStr(args, "scale"), win?.scale)
|
|
49192
49521
|
});
|
|
49193
49522
|
};
|
|
49194
49523
|
var status = async () => {
|
|
@@ -49224,15 +49553,15 @@ init_design();
|
|
|
49224
49553
|
// src/commands/dev.ts
|
|
49225
49554
|
init_manifest();
|
|
49226
49555
|
init_ignore();
|
|
49227
|
-
import { existsSync as
|
|
49556
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync16, readFileSync as readFileSync19, watch, writeFileSync as writeFileSync17 } from "node:fs";
|
|
49228
49557
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
49229
49558
|
import { networkInterfaces } from "node:os";
|
|
49230
|
-
import { basename as
|
|
49559
|
+
import { basename as basename9, join as join21 } from "node:path";
|
|
49231
49560
|
|
|
49232
49561
|
// src/dev/devServer.ts
|
|
49233
49562
|
init_localFiles();
|
|
49234
49563
|
import { createServer as createServer2 } from "node:http";
|
|
49235
|
-
import { createReadStream, statSync as
|
|
49564
|
+
import { createReadStream, statSync as statSync7 } from "node:fs";
|
|
49236
49565
|
init_projectCompile();
|
|
49237
49566
|
init_fonts();
|
|
49238
49567
|
init_assetIcons();
|
|
@@ -49500,14 +49829,14 @@ var sendText = (res, status2, type, body) => {
|
|
|
49500
49829
|
res.end(body);
|
|
49501
49830
|
};
|
|
49502
49831
|
var sendJson = (res, status2, body) => sendText(res, status2, "application/json", JSON.stringify(body));
|
|
49503
|
-
var readBody = (req) => new Promise((
|
|
49832
|
+
var readBody = (req) => new Promise((resolve9) => {
|
|
49504
49833
|
let body = "";
|
|
49505
49834
|
req.on("data", (c4) => {
|
|
49506
49835
|
if (body.length < 256 * 1024)
|
|
49507
49836
|
body += c4.toString("utf8");
|
|
49508
49837
|
});
|
|
49509
|
-
req.on("end", () =>
|
|
49510
|
-
req.on("error", () =>
|
|
49838
|
+
req.on("end", () => resolve9(body));
|
|
49839
|
+
req.on("error", () => resolve9(body));
|
|
49511
49840
|
});
|
|
49512
49841
|
var startDevServer = async (options) => {
|
|
49513
49842
|
const { root, manifest, name, origin, token, entryOverride } = options;
|
|
@@ -49607,7 +49936,7 @@ var startDevServer = async (options) => {
|
|
|
49607
49936
|
return sendText(res, 400, "text/plain", "Bad path");
|
|
49608
49937
|
}
|
|
49609
49938
|
const abs = resolveLocalPath(root, key);
|
|
49610
|
-
if (!abs || !
|
|
49939
|
+
if (!abs || !statSync7(abs).isFile())
|
|
49611
49940
|
return sendText(res, 404, "text/plain", "Not found");
|
|
49612
49941
|
res.writeHead(200, { "content-type": contentType(key), "cache-control": "no-store" });
|
|
49613
49942
|
createReadStream(abs).pipe(res);
|
|
@@ -49650,11 +49979,11 @@ var startDevServer = async (options) => {
|
|
|
49650
49979
|
if (conn)
|
|
49651
49980
|
clients.add(conn);
|
|
49652
49981
|
});
|
|
49653
|
-
await new Promise((
|
|
49982
|
+
await new Promise((resolve9, reject) => {
|
|
49654
49983
|
server.once("error", reject);
|
|
49655
49984
|
server.listen(options.port, options.bind ?? "0.0.0.0", () => {
|
|
49656
49985
|
server.removeListener("error", reject);
|
|
49657
|
-
|
|
49986
|
+
resolve9();
|
|
49658
49987
|
});
|
|
49659
49988
|
});
|
|
49660
49989
|
const address = server.address();
|
|
@@ -49702,24 +50031,24 @@ var lanCandidates = () => {
|
|
|
49702
50031
|
return out.sort((x2, y2) => x2.rank - y2.rank).map(({ address, iface }) => ({ address, iface }));
|
|
49703
50032
|
};
|
|
49704
50033
|
var loadDevToken = (root) => {
|
|
49705
|
-
const path2 =
|
|
50034
|
+
const path2 = join21(root, LECODES_DIR, "dev.json");
|
|
49706
50035
|
try {
|
|
49707
50036
|
const token2 = JSON.parse(readFileSync19(path2, "utf8")).token;
|
|
49708
50037
|
if (token2 && /^[a-z0-9]{8,}$/.test(token2))
|
|
49709
50038
|
return token2;
|
|
49710
50039
|
} catch {}
|
|
49711
50040
|
const token = randomBytes2(6).toString("hex");
|
|
49712
|
-
|
|
49713
|
-
|
|
50041
|
+
mkdirSync16(join21(root, LECODES_DIR), { recursive: true });
|
|
50042
|
+
writeFileSync17(path2, JSON.stringify({ token }, null, 2) + `
|
|
49714
50043
|
`);
|
|
49715
50044
|
return token;
|
|
49716
50045
|
};
|
|
49717
50046
|
var printQr = async (url) => {
|
|
49718
50047
|
try {
|
|
49719
50048
|
const qrcode = (await Promise.resolve().then(() => __toESM(require_main(), 1))).default;
|
|
49720
|
-
await new Promise((
|
|
50049
|
+
await new Promise((resolve9) => qrcode.generate(url, { small: true }, (out) => {
|
|
49721
50050
|
log(out);
|
|
49722
|
-
|
|
50051
|
+
resolve9();
|
|
49723
50052
|
}));
|
|
49724
50053
|
} catch {
|
|
49725
50054
|
note("(couldn't render a QR code — open the URL below on the device)");
|
|
@@ -49735,7 +50064,7 @@ var dev = async (args) => {
|
|
|
49735
50064
|
root = process.cwd();
|
|
49736
50065
|
manifest = null;
|
|
49737
50066
|
}
|
|
49738
|
-
const name = manifest?.name ??
|
|
50067
|
+
const name = manifest?.name ?? basename9(root);
|
|
49739
50068
|
if (typeof globalThis.Bun !== "undefined") {
|
|
49740
50069
|
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
50070
|
}
|
|
@@ -49827,7 +50156,7 @@ ${result.error}`);
|
|
|
49827
50156
|
if (alternatives.length > 0) {
|
|
49828
50157
|
note(`Device can't reach it? This machine also has ${alternatives.map((a2) => `${a2.address} (${a2.iface})`).join(", ")} — retry with --host <ip>.`);
|
|
49829
50158
|
}
|
|
49830
|
-
if (
|
|
50159
|
+
if (existsSync21(join21(root, LECODES_DIR, "manifest.json")))
|
|
49831
50160
|
note(`Token stored in ${LECODES_DIR}/dev.json (delete it to rotate the URL).`);
|
|
49832
50161
|
log("");
|
|
49833
50162
|
log(`Watching ${root} — save a file to hot-reload. Ctrl+C to stop.`);
|
|
@@ -49841,7 +50170,8 @@ ${result.error}`);
|
|
|
49841
50170
|
minWidth: win?.minWidth,
|
|
49842
50171
|
minHeight: win?.minHeight,
|
|
49843
50172
|
resizable: win?.resizable,
|
|
49844
|
-
title: win?.title ?? appCfg?.name ?? name
|
|
50173
|
+
title: win?.title ?? appCfg?.name ?? name,
|
|
50174
|
+
scale: resolveScale(undefined, win?.scale)
|
|
49845
50175
|
}).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
50176
|
}
|
|
49847
50177
|
};
|
|
@@ -49855,7 +50185,7 @@ init_localFiles();
|
|
|
49855
50185
|
// src/serverDiff.ts
|
|
49856
50186
|
init_localFiles();
|
|
49857
50187
|
import { readFileSync as readFileSync20 } from "node:fs";
|
|
49858
|
-
import { basename as
|
|
50188
|
+
import { basename as basename10 } from "node:path";
|
|
49859
50189
|
var isTextAsset = (t2) => t2 === "text" || t2 === "shader" || t2 === "scene";
|
|
49860
50190
|
var computeServerDiff = (project, scan2) => {
|
|
49861
50191
|
const pathById = buildPaths(project.assets);
|
|
@@ -49872,7 +50202,7 @@ var computeServerDiff = (project, scan2) => {
|
|
|
49872
50202
|
const server = serverByPath.get(path2);
|
|
49873
50203
|
const local = localByPath.get(path2);
|
|
49874
50204
|
if (local && !server) {
|
|
49875
|
-
const text = isTextKind(classifyKind(
|
|
50205
|
+
const text = isTextKind(classifyKind(basename10(path2)));
|
|
49876
50206
|
entries.push({ path: path2, status: "added", binary: !text, serverText: "", localText: text ? readFileSync20(local.absPath, "utf8") : undefined });
|
|
49877
50207
|
continue;
|
|
49878
50208
|
}
|
|
@@ -50047,8 +50377,8 @@ var statusLabel = (e2) => e2.status === "added" ? c.green(" added ") : e2.st
|
|
|
50047
50377
|
init_manifest();
|
|
50048
50378
|
init_types4();
|
|
50049
50379
|
init_ignore();
|
|
50050
|
-
import { existsSync as
|
|
50051
|
-
import { basename as
|
|
50380
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync17, writeFileSync as writeFileSync18 } from "node:fs";
|
|
50381
|
+
import { basename as basename11, dirname as dirname14, join as join22, resolve as resolve9 } from "node:path";
|
|
50052
50382
|
init_util();
|
|
50053
50383
|
var STARTER2 = `// Welcome to LeCodes — the whole SDK is available as globals, no imports needed.
|
|
50054
50384
|
// Run it live on a device with \`lecodes dev\`, or bundle it with \`lecodes compile -o app.js\`.
|
|
@@ -50153,18 +50483,18 @@ Full command list: \`lecodes --help\`.
|
|
|
50153
50483
|
`;
|
|
50154
50484
|
var init2 = async (args) => {
|
|
50155
50485
|
const dirArg = (args._[0] ?? ".").replace(/[/\\]+$/, "") || ".";
|
|
50156
|
-
const root =
|
|
50157
|
-
const name =
|
|
50158
|
-
|
|
50486
|
+
const root = resolve9(dirArg);
|
|
50487
|
+
const name = basename11(root);
|
|
50488
|
+
mkdirSync17(root, { recursive: true });
|
|
50159
50489
|
let created = 0;
|
|
50160
50490
|
const write = (rel, content) => {
|
|
50161
|
-
const abs =
|
|
50162
|
-
if (
|
|
50491
|
+
const abs = join22(root, rel);
|
|
50492
|
+
if (existsSync22(abs)) {
|
|
50163
50493
|
note(`kept ${rel}`);
|
|
50164
50494
|
return;
|
|
50165
50495
|
}
|
|
50166
|
-
|
|
50167
|
-
|
|
50496
|
+
mkdirSync17(dirname14(abs), { recursive: true });
|
|
50497
|
+
writeFileSync18(abs, content);
|
|
50168
50498
|
log(`${c.green("created")} ${rel}`);
|
|
50169
50499
|
created++;
|
|
50170
50500
|
};
|
|
@@ -50174,6 +50504,7 @@ var init2 = async (args) => {
|
|
|
50174
50504
|
if (forDesktop)
|
|
50175
50505
|
write("app.json", appJsonDesktop(name));
|
|
50176
50506
|
write("CLAUDE.md", appClaudeMd(name));
|
|
50507
|
+
write(".claude/settings.json", CLAUDE_SETTINGS);
|
|
50177
50508
|
write("tests/counter.flow.json", STARTER_FLOW);
|
|
50178
50509
|
write(".lecodesignore", DEFAULT_IGNORE_FILE);
|
|
50179
50510
|
const wroteTypes = materializeTypesLocal(root);
|
|
@@ -50202,8 +50533,8 @@ init_config();
|
|
|
50202
50533
|
init_ignore();
|
|
50203
50534
|
init_localFiles();
|
|
50204
50535
|
init_manifest();
|
|
50205
|
-
import { existsSync as
|
|
50206
|
-
import { basename as
|
|
50536
|
+
import { existsSync as existsSync23 } from "node:fs";
|
|
50537
|
+
import { basename as basename13, join as join23, resolve as resolve10 } from "node:path";
|
|
50207
50538
|
init_types4();
|
|
50208
50539
|
init_util();
|
|
50209
50540
|
|
|
@@ -50212,7 +50543,7 @@ init_api();
|
|
|
50212
50543
|
init_config();
|
|
50213
50544
|
init_manifest();
|
|
50214
50545
|
init_localFiles();
|
|
50215
|
-
import { basename as
|
|
50546
|
+
import { basename as basename12 } from "node:path";
|
|
50216
50547
|
import { readFileSync as readFileSync21 } from "node:fs";
|
|
50217
50548
|
init_util();
|
|
50218
50549
|
var push = async (args) => {
|
|
@@ -50269,14 +50600,14 @@ var push = async (args) => {
|
|
|
50269
50600
|
let uploaded = 0;
|
|
50270
50601
|
for (const file of scan2.files) {
|
|
50271
50602
|
const source = sourceFor(file.path);
|
|
50272
|
-
const kind = source?.type ?? classifyKind(
|
|
50603
|
+
const kind = source?.type ?? classifyKind(basename12(file.path));
|
|
50273
50604
|
const entry = { path: file.path, type: kind };
|
|
50274
50605
|
if (source)
|
|
50275
50606
|
entry.assetId = source.id;
|
|
50276
50607
|
if (isTextKind(kind)) {
|
|
50277
50608
|
entry.text = readFileSync21(file.absPath, "utf8");
|
|
50278
50609
|
} else if (!source || source.sha !== file.sha) {
|
|
50279
|
-
const blob = await uploadBlob(apiUrl, token, manifest.uuid,
|
|
50610
|
+
const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename12(file.path), readFileSync21(file.absPath));
|
|
50280
50611
|
entry.blobFileId = blob.id;
|
|
50281
50612
|
uploaded++;
|
|
50282
50613
|
}
|
|
@@ -50383,10 +50714,10 @@ var link = async (args) => {
|
|
|
50383
50714
|
const apiUrl = requireApiUrl(config);
|
|
50384
50715
|
const token = requireToken(config);
|
|
50385
50716
|
const dirArg = args._[0];
|
|
50386
|
-
const root = dirArg ?
|
|
50387
|
-
if (!
|
|
50717
|
+
const root = dirArg ? resolve10(dirArg) : process.cwd();
|
|
50718
|
+
if (!existsSync23(root))
|
|
50388
50719
|
throw new CliError(`No such directory: ${root}`);
|
|
50389
|
-
if (
|
|
50720
|
+
if (existsSync23(join23(root, LECODES_DIR, "manifest.json")) && !flagBool(args, "force")) {
|
|
50390
50721
|
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
50722
|
}
|
|
50392
50723
|
let enclosing = null;
|
|
@@ -50413,7 +50744,7 @@ var link = async (args) => {
|
|
|
50413
50744
|
}
|
|
50414
50745
|
name = project.name;
|
|
50415
50746
|
} else {
|
|
50416
|
-
name = flagStr(args, "name") ??
|
|
50747
|
+
name = flagStr(args, "name") ?? basename13(root);
|
|
50417
50748
|
info(c.dim(`Creating project ${c.bold(name)}…`));
|
|
50418
50749
|
uuid = (await createProject(apiUrl, token, name, flagStr(args, "team"))).uuid;
|
|
50419
50750
|
files = {};
|
|
@@ -50462,8 +50793,8 @@ var login = async (args) => {
|
|
|
50462
50793
|
};
|
|
50463
50794
|
|
|
50464
50795
|
// src/commands/render.ts
|
|
50465
|
-
import { mkdirSync as
|
|
50466
|
-
import { dirname as
|
|
50796
|
+
import { mkdirSync as mkdirSync18, readFileSync as readFileSync22, writeFileSync as writeFileSync19 } from "node:fs";
|
|
50797
|
+
import { dirname as dirname15, resolve as resolve11 } from "node:path";
|
|
50467
50798
|
|
|
50468
50799
|
// src/compile/headlessBundle.ts
|
|
50469
50800
|
init_api();
|
|
@@ -50574,9 +50905,9 @@ var requireRenderer = async () => {
|
|
|
50574
50905
|
};
|
|
50575
50906
|
var fileScenarioIO = (dir) => ({
|
|
50576
50907
|
write: (relPath, data) => {
|
|
50577
|
-
const abs =
|
|
50578
|
-
|
|
50579
|
-
|
|
50908
|
+
const abs = resolve11(dir, relPath);
|
|
50909
|
+
mkdirSync18(dirname15(abs), { recursive: true });
|
|
50910
|
+
writeFileSync19(abs, data);
|
|
50580
50911
|
}
|
|
50581
50912
|
});
|
|
50582
50913
|
var resolveFixtures = (value, baseDir) => {
|
|
@@ -50584,7 +50915,7 @@ var resolveFixtures = (value, baseDir) => {
|
|
|
50584
50915
|
return;
|
|
50585
50916
|
let map = value;
|
|
50586
50917
|
if (typeof value === "string") {
|
|
50587
|
-
const abs =
|
|
50918
|
+
const abs = resolve11(baseDir, value);
|
|
50588
50919
|
try {
|
|
50589
50920
|
map = JSON.parse(readFileSync22(abs, "utf8"));
|
|
50590
50921
|
} catch (e2) {
|
|
@@ -50599,7 +50930,7 @@ var resolveFixtures = (value, baseDir) => {
|
|
|
50599
50930
|
var readScenarioFile = (path2) => {
|
|
50600
50931
|
let raw;
|
|
50601
50932
|
try {
|
|
50602
|
-
raw = JSON.parse(readFileSync22(
|
|
50933
|
+
raw = JSON.parse(readFileSync22(resolve11(path2), "utf8"));
|
|
50603
50934
|
} catch (e2) {
|
|
50604
50935
|
throw new CliError(`Can't read scenario "${path2}": ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
50605
50936
|
}
|
|
@@ -50657,7 +50988,7 @@ var render = async (args) => {
|
|
|
50657
50988
|
const settleStr = flagStr(args, "settle") ?? (scenario?.settle !== undefined ? String(scenario.settle) : undefined);
|
|
50658
50989
|
const timeStr = flagStr(args, "time");
|
|
50659
50990
|
const onConsole = flagBool(args, "logs") ? (level, text2) => logErr(`${c.dim(`[${level}]`)} ${text2}`) : undefined;
|
|
50660
|
-
const envelopeFixtures = resolveFixtures(scenario?.fixtures, scriptFlag !== undefined ?
|
|
50991
|
+
const envelopeFixtures = resolveFixtures(scenario?.fixtures, scriptFlag !== undefined ? dirname15(resolve11(scriptFlag)) : ".");
|
|
50661
50992
|
const flagFixtures = resolveFixtures(flagStr(args, "fixtures"), ".");
|
|
50662
50993
|
const fixtures = envelopeFixtures || flagFixtures ? { ...envelopeFixtures, ...flagFixtures } : undefined;
|
|
50663
50994
|
if (fixtures) {
|
|
@@ -50696,7 +51027,7 @@ var render = async (args) => {
|
|
|
50696
51027
|
${c.red(st.error ?? "failed")}`);
|
|
50697
51028
|
}
|
|
50698
51029
|
if (!result2.ok && result2.failureArtifacts.length > 0) {
|
|
50699
|
-
logErr(c.dim(` failing frame: ${result2.failureArtifacts.map((p2) =>
|
|
51030
|
+
logErr(c.dim(` failing frame: ${result2.failureArtifacts.map((p2) => resolve11(outDir, p2)).join(", ")}`));
|
|
50700
51031
|
}
|
|
50701
51032
|
logErr(result2.ok ? c.green(`✓ ${result2.steps.length}/${steps.length} steps passed`) : c.red(`✗ failed at step ${result2.steps.length} of ${steps.length}`));
|
|
50702
51033
|
return done((cb) => cb(), result2.ok ? 0 : 1);
|
|
@@ -50718,7 +51049,7 @@ var render = async (args) => {
|
|
|
50718
51049
|
if (isNullSink(pngFlag)) {
|
|
50719
51050
|
note(`Discarded PNG (${pngFlag})`);
|
|
50720
51051
|
} else {
|
|
50721
|
-
|
|
51052
|
+
writeFileSync19(resolve11(pngFlag), png2);
|
|
50722
51053
|
note(`Wrote ${pngFlag} (${png2.length} bytes, ${width}x${height})`);
|
|
50723
51054
|
}
|
|
50724
51055
|
return done((cb) => cb());
|
|
@@ -50733,7 +51064,7 @@ var render = async (args) => {
|
|
|
50733
51064
|
return done((cb) => cb());
|
|
50734
51065
|
}
|
|
50735
51066
|
if (out) {
|
|
50736
|
-
|
|
51067
|
+
writeFileSync19(resolve11(out), result.text);
|
|
50737
51068
|
note(`Wrote ${out} (${result.text.length} bytes)`);
|
|
50738
51069
|
return done((cb) => cb());
|
|
50739
51070
|
}
|
|
@@ -50748,8 +51079,8 @@ init_api();
|
|
|
50748
51079
|
init_config();
|
|
50749
51080
|
init_manifest();
|
|
50750
51081
|
init_util();
|
|
50751
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
50752
|
-
import { join as
|
|
51082
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync24, readFileSync as readFileSync23, writeFileSync as writeFileSync20 } from "node:fs";
|
|
51083
|
+
import { join as join24 } from "node:path";
|
|
50753
51084
|
var ENV_VAR = "LECODES_PUSH_KEY";
|
|
50754
51085
|
var context = () => {
|
|
50755
51086
|
const root = findProjectRoot(process.cwd());
|
|
@@ -50893,11 +51224,11 @@ var pnKeysList = async (ctx) => {
|
|
|
50893
51224
|
};
|
|
50894
51225
|
var writeEnv = (root, key) => {
|
|
50895
51226
|
const touched = [];
|
|
50896
|
-
const envPath =
|
|
51227
|
+
const envPath = join24(root, ".env");
|
|
50897
51228
|
const line = `${ENV_VAR}=${key}`;
|
|
50898
|
-
const existing =
|
|
51229
|
+
const existing = existsSync24(envPath) ? readFileSync23(envPath, "utf8") : "";
|
|
50899
51230
|
if (new RegExp(`^${ENV_VAR}=`, "m").test(existing)) {
|
|
50900
|
-
|
|
51231
|
+
writeFileSync20(envPath, existing.replace(new RegExp(`^${ENV_VAR}=.*$`, "m"), line));
|
|
50901
51232
|
} else {
|
|
50902
51233
|
const separator = !existing || existing.endsWith(`
|
|
50903
51234
|
`) ? "" : `
|
|
@@ -50906,8 +51237,8 @@ var writeEnv = (root, key) => {
|
|
|
50906
51237
|
`);
|
|
50907
51238
|
}
|
|
50908
51239
|
touched.push(".env");
|
|
50909
|
-
const ignorePath =
|
|
50910
|
-
const ignore =
|
|
51240
|
+
const ignorePath = join24(root, ".gitignore");
|
|
51241
|
+
const ignore = existsSync24(ignorePath) ? readFileSync23(ignorePath, "utf8") : "";
|
|
50911
51242
|
if (!ignore.split(`
|
|
50912
51243
|
`).some((l2) => l2.trim() === ".env")) {
|
|
50913
51244
|
const separator = !ignore || ignore.endsWith(`
|
|
@@ -51006,8 +51337,8 @@ var pn = async (args) => {
|
|
|
51006
51337
|
init_api();
|
|
51007
51338
|
init_config();
|
|
51008
51339
|
init_manifest();
|
|
51009
|
-
import { existsSync as
|
|
51010
|
-
import { dirname as
|
|
51340
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync19, rmSync as rmSync6, writeFileSync as writeFileSync21 } from "node:fs";
|
|
51341
|
+
import { dirname as dirname16, join as join25 } from "node:path";
|
|
51011
51342
|
init_types4();
|
|
51012
51343
|
init_localFiles();
|
|
51013
51344
|
init_util();
|
|
@@ -51030,9 +51361,9 @@ var pull = async (args) => {
|
|
|
51030
51361
|
const remotePaths = new Set([...buildPaths(project.assets).entries()].filter(([id]) => project.assets.find((a2) => a2.id === id)?.type !== "folder").map(([, path2]) => path2));
|
|
51031
51362
|
for (const path2 of Object.keys(manifest.files)) {
|
|
51032
51363
|
if (!remotePaths.has(path2)) {
|
|
51033
|
-
const abs =
|
|
51034
|
-
if (
|
|
51035
|
-
|
|
51364
|
+
const abs = join25(root, ...path2.replace(/^\//, "").split("/"));
|
|
51365
|
+
if (existsSync25(abs))
|
|
51366
|
+
rmSync6(abs);
|
|
51036
51367
|
}
|
|
51037
51368
|
}
|
|
51038
51369
|
const files = await materializeAssets(root, project, apiUrl, token, progress);
|
|
@@ -51069,18 +51400,18 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
|
|
|
51069
51400
|
const files = { ...manifest.files };
|
|
51070
51401
|
for (const asset of targets) {
|
|
51071
51402
|
const key = pathById.get(asset.id);
|
|
51072
|
-
const abs =
|
|
51073
|
-
|
|
51403
|
+
const abs = join25(root, ...key.replace(/^\//, "").split("/"));
|
|
51404
|
+
mkdirSync19(dirname16(abs), { recursive: true });
|
|
51074
51405
|
let sha;
|
|
51075
51406
|
if (asset.type === "file") {
|
|
51076
51407
|
if (!asset.file?.src)
|
|
51077
51408
|
continue;
|
|
51078
51409
|
const bytes = await downloadBlob(apiUrl, asset.file.src);
|
|
51079
|
-
|
|
51410
|
+
writeFileSync21(abs, bytes);
|
|
51080
51411
|
sha = sha256(bytes);
|
|
51081
51412
|
} else {
|
|
51082
51413
|
const text = asset.text ?? "";
|
|
51083
|
-
|
|
51414
|
+
writeFileSync21(abs, text);
|
|
51084
51415
|
sha = sha256(text);
|
|
51085
51416
|
}
|
|
51086
51417
|
files[key] = { id: asset.id, type: kindOf3(asset.type), sha };
|
|
@@ -51093,8 +51424,8 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
|
|
|
51093
51424
|
|
|
51094
51425
|
// src/commands/scene.ts
|
|
51095
51426
|
init_manifest();
|
|
51096
|
-
import { existsSync as
|
|
51097
|
-
import { basename as
|
|
51427
|
+
import { existsSync as existsSync27, readFileSync as readFileSync25, readdirSync as readdirSync12, statSync as statSync8, writeFileSync as writeFileSync22 } from "node:fs";
|
|
51428
|
+
import { basename as basename14, dirname as dirname17, join as join27, resolve as resolve12 } from "node:path";
|
|
51098
51429
|
|
|
51099
51430
|
// src/compile/sceneCompile.ts
|
|
51100
51431
|
init_localFiles();
|
|
@@ -51102,17 +51433,17 @@ init_ignore();
|
|
|
51102
51433
|
init_distRoot();
|
|
51103
51434
|
init_util();
|
|
51104
51435
|
init_projectCompile();
|
|
51105
|
-
import { existsSync as
|
|
51106
|
-
import { join as
|
|
51436
|
+
import { existsSync as existsSync26, readFileSync as readFileSync24 } from "node:fs";
|
|
51437
|
+
import { join as join26 } from "node:path";
|
|
51107
51438
|
var TEXT_EXT = /\.(ts|js|json|svg|txt|md)$/i;
|
|
51108
51439
|
var rel = (key) => key.replace(/^\//, "");
|
|
51109
51440
|
var resolveHarness = (loadSceneHarness2) => {
|
|
51110
|
-
const inRepo =
|
|
51441
|
+
const inRepo = join26(distRoot, "..", "projects", "src", "scene-editor");
|
|
51111
51442
|
try {
|
|
51112
|
-
return loadSceneHarness2(
|
|
51443
|
+
return loadSceneHarness2(existsSync26(inRepo) ? inRepo : undefined);
|
|
51113
51444
|
} catch {
|
|
51114
|
-
const vendored =
|
|
51115
|
-
if (
|
|
51445
|
+
const vendored = join26(distRoot, "runtime", "scene-harness.json");
|
|
51446
|
+
if (existsSync26(vendored))
|
|
51116
51447
|
return JSON.parse(readFileSync24(vendored, "utf8"));
|
|
51117
51448
|
throw new CliError("scene-editor harness sources not found — reinstall the CLI or set LECODES_SCENE_HARNESS_DIR.");
|
|
51118
51449
|
}
|
|
@@ -51167,22 +51498,22 @@ var getContext2 = (args) => {
|
|
|
51167
51498
|
manifest = null;
|
|
51168
51499
|
const pathArg = args._[0];
|
|
51169
51500
|
if (pathArg) {
|
|
51170
|
-
const abs =
|
|
51171
|
-
if (
|
|
51501
|
+
const abs = resolve12(process.cwd(), pathArg);
|
|
51502
|
+
if (existsSync27(abs) && statSync8(abs).isDirectory())
|
|
51172
51503
|
root = abs;
|
|
51173
|
-
else if (abs.endsWith(".scene.ts") &&
|
|
51174
|
-
root =
|
|
51504
|
+
else if (abs.endsWith(".scene.ts") && existsSync27(abs))
|
|
51505
|
+
root = dirname17(abs);
|
|
51175
51506
|
else
|
|
51176
51507
|
throw new CliError(`No such folder or .scene.ts: ${pathArg}`);
|
|
51177
51508
|
}
|
|
51178
51509
|
}
|
|
51179
|
-
return { root, manifest, name: manifest?.name ??
|
|
51510
|
+
return { root, manifest, name: manifest?.name ?? basename14(root) };
|
|
51180
51511
|
};
|
|
51181
51512
|
var writeMcpConfig2 = (root, port) => {
|
|
51182
|
-
const path2 =
|
|
51513
|
+
const path2 = join27(root, ".mcp.json");
|
|
51183
51514
|
const entry = { type: "http", url: `http://localhost:${port}/mcp` };
|
|
51184
51515
|
let config = {};
|
|
51185
|
-
if (
|
|
51516
|
+
if (existsSync27(path2)) {
|
|
51186
51517
|
try {
|
|
51187
51518
|
config = JSON.parse(readFileSync25(path2, "utf8"));
|
|
51188
51519
|
} catch {
|
|
@@ -51197,17 +51528,17 @@ var writeMcpConfig2 = (root, port) => {
|
|
|
51197
51528
|
return;
|
|
51198
51529
|
}
|
|
51199
51530
|
config.mcpServers["lecodes-scene"] = entry;
|
|
51200
|
-
|
|
51531
|
+
writeFileSync22(path2, JSON.stringify(config, null, 2) + `
|
|
51201
51532
|
`);
|
|
51202
51533
|
note(`${existing ? "updated" : "created"} .mcp.json (lecodes-scene MCP → ${entry.url})`);
|
|
51203
51534
|
};
|
|
51204
51535
|
var countScenes = (dir) => {
|
|
51205
51536
|
let n2 = 0;
|
|
51206
|
-
for (const name of
|
|
51537
|
+
for (const name of readdirSync12(dir)) {
|
|
51207
51538
|
if (name === "node_modules" || name.startsWith("."))
|
|
51208
51539
|
continue;
|
|
51209
|
-
const full =
|
|
51210
|
-
if (
|
|
51540
|
+
const full = join27(dir, name);
|
|
51541
|
+
if (statSync8(full).isDirectory())
|
|
51211
51542
|
n2 += countScenes(full);
|
|
51212
51543
|
else if (name.endsWith(".scene.ts"))
|
|
51213
51544
|
n2++;
|
|
@@ -51280,8 +51611,8 @@ var status2 = async (_args) => {
|
|
|
51280
51611
|
};
|
|
51281
51612
|
|
|
51282
51613
|
// src/commands/test.ts
|
|
51283
|
-
import { existsSync as
|
|
51284
|
-
import { dirname as
|
|
51614
|
+
import { existsSync as existsSync28, readFileSync as readFileSync26, readdirSync as readdirSync13 } from "node:fs";
|
|
51615
|
+
import { dirname as dirname18, resolve as resolve13 } from "node:path";
|
|
51285
51616
|
init_util();
|
|
51286
51617
|
var deriveDesignFlows = (meta, entryScreen) => {
|
|
51287
51618
|
const edges = meta.edges ?? [];
|
|
@@ -51357,7 +51688,7 @@ var readFlowFile = (abs, displayName) => {
|
|
|
51357
51688
|
name: file.name ?? displayName,
|
|
51358
51689
|
kind: "file",
|
|
51359
51690
|
steps: file.steps,
|
|
51360
|
-
fixtures: resolveFixtures(file.fixtures,
|
|
51691
|
+
fixtures: resolveFixtures(file.fixtures, dirname18(abs)),
|
|
51361
51692
|
width: file.width,
|
|
51362
51693
|
height: file.height,
|
|
51363
51694
|
settle: file.settle,
|
|
@@ -51369,34 +51700,34 @@ var test = async (args) => {
|
|
|
51369
51700
|
const { js, warnings, useLocalAssets, root } = await compileHeadlessBundle(args);
|
|
51370
51701
|
const renderer = await requireRenderer();
|
|
51371
51702
|
const projectViewport = desktopDefaultViewport(root);
|
|
51372
|
-
const testsDir =
|
|
51373
|
-
const designMetaPath =
|
|
51703
|
+
const testsDir = resolve13(root, flagStr(args, "dir") ?? "tests");
|
|
51704
|
+
const designMetaPath = resolve13(root, flagStr(args, "design-dir") ?? "design", "meta.json");
|
|
51374
51705
|
const asJson = flagBool(args, "json");
|
|
51375
51706
|
const out = asJson ? logErr : log;
|
|
51376
51707
|
const cases = [];
|
|
51377
51708
|
if (args._.length > 0) {
|
|
51378
51709
|
for (const given of args._) {
|
|
51379
|
-
const candidates = [
|
|
51380
|
-
const abs = candidates.find((p2) =>
|
|
51710
|
+
const candidates = [resolve13(given), resolve13(testsDir, given), resolve13(testsDir, `${given}.flow.json`)];
|
|
51711
|
+
const abs = candidates.find((p2) => existsSync28(p2));
|
|
51381
51712
|
if (!abs)
|
|
51382
51713
|
throw new CliError(`No such test: "${given}" (looked in ${testsDir}).`);
|
|
51383
51714
|
cases.push(readFlowFile(abs, given));
|
|
51384
51715
|
}
|
|
51385
|
-
} else if (
|
|
51386
|
-
for (const f3 of
|
|
51387
|
-
cases.push(readFlowFile(
|
|
51716
|
+
} else if (existsSync28(testsDir)) {
|
|
51717
|
+
for (const f3 of readdirSync13(testsDir).filter((f4) => f4.endsWith(".flow.json")).sort()) {
|
|
51718
|
+
cases.push(readFlowFile(resolve13(testsDir, f3), f3));
|
|
51388
51719
|
}
|
|
51389
51720
|
}
|
|
51390
51721
|
const fixturesFlag = flagStr(args, "fixtures");
|
|
51391
|
-
const defaultPack =
|
|
51392
|
-
const flowFixtures = fixturesFlag !== undefined ? resolveFixtures(fixturesFlag, ".") :
|
|
51722
|
+
const defaultPack = resolve13(testsDir, "fixtures.json");
|
|
51723
|
+
const flowFixtures = fixturesFlag !== undefined ? resolveFixtures(fixturesFlag, ".") : existsSync28(defaultPack) ? resolveFixtures("fixtures.json", testsDir) : undefined;
|
|
51393
51724
|
const flowsOnly = flagBool(args, "flows");
|
|
51394
51725
|
const wantFlows = !flagBool(args, "no-flows") && args._.length === 0;
|
|
51395
51726
|
if (flowsOnly)
|
|
51396
51727
|
cases.length = 0;
|
|
51397
51728
|
let skipped = [];
|
|
51398
51729
|
let entryScreen = null;
|
|
51399
|
-
if ((wantFlows || flowsOnly) &&
|
|
51730
|
+
if ((wantFlows || flowsOnly) && existsSync28(designMetaPath)) {
|
|
51400
51731
|
let meta;
|
|
51401
51732
|
try {
|
|
51402
51733
|
meta = JSON.parse(readFileSync26(designMetaPath, "utf8"));
|
|
@@ -51431,7 +51762,7 @@ var test = async (args) => {
|
|
|
51431
51762
|
}, timeoutMs);
|
|
51432
51763
|
killer.unref?.();
|
|
51433
51764
|
const onConsole = flagBool(args, "logs") ? (level, text) => logErr(`${c.dim(`[${level}]`)} ${text}`) : undefined;
|
|
51434
|
-
const artifactsRoot =
|
|
51765
|
+
const artifactsRoot = resolve13(root, flagStr(args, "artifacts") ?? resolve13(testsDir, ".artifacts"));
|
|
51435
51766
|
const results = [];
|
|
51436
51767
|
for (const kase of cases) {
|
|
51437
51768
|
const flagSettle = flagStr(args, "settle");
|
|
@@ -51449,14 +51780,14 @@ var test = async (args) => {
|
|
|
51449
51780
|
onConsole
|
|
51450
51781
|
});
|
|
51451
51782
|
try {
|
|
51452
|
-
const io = fileScenarioIO(
|
|
51783
|
+
const io = fileScenarioIO(resolve13(artifactsRoot, slug3(kase.name)));
|
|
51453
51784
|
const outcome = await renderer.runScenario(session, steps, io);
|
|
51454
51785
|
result = {
|
|
51455
51786
|
name: kase.name,
|
|
51456
51787
|
kind: kase.kind,
|
|
51457
51788
|
ok: outcome.ok,
|
|
51458
51789
|
steps: outcome.steps.map((s2) => ({ desc: s2.desc, ok: s2.ok, error: s2.error })),
|
|
51459
|
-
artifacts: outcome.failureArtifacts.map((p2) =>
|
|
51790
|
+
artifacts: outcome.failureArtifacts.map((p2) => resolve13(artifactsRoot, slug3(kase.name), p2))
|
|
51460
51791
|
};
|
|
51461
51792
|
} finally {
|
|
51462
51793
|
session.dispose();
|
|
@@ -51518,11 +51849,11 @@ init_config();
|
|
|
51518
51849
|
init_manifest();
|
|
51519
51850
|
init_types4();
|
|
51520
51851
|
init_util();
|
|
51521
|
-
import { resolve as
|
|
51852
|
+
import { resolve as resolve14 } from "node:path";
|
|
51522
51853
|
var label = (bundle) => `${Object.keys(bundle).length} file(s)`;
|
|
51523
51854
|
var types4 = async (args) => {
|
|
51524
51855
|
const dirArg = args._[0];
|
|
51525
|
-
const root = dirArg ?
|
|
51856
|
+
const root = dirArg ? resolve14(dirArg) : (() => {
|
|
51526
51857
|
try {
|
|
51527
51858
|
return findProjectRoot(process.cwd());
|
|
51528
51859
|
} catch {
|
|
@@ -51581,23 +51912,23 @@ var types4 = async (args) => {
|
|
|
51581
51912
|
};
|
|
51582
51913
|
|
|
51583
51914
|
// src/index.ts
|
|
51584
|
-
import { existsSync as
|
|
51915
|
+
import { existsSync as existsSync30, readFileSync as readFileSync28, realpathSync as realpathSync2 } from "node:fs";
|
|
51585
51916
|
import { createRequire as createRequire4 } from "node:module";
|
|
51586
|
-
import { delimiter as delimiter2, dirname as
|
|
51917
|
+
import { delimiter as delimiter2, dirname as dirname20, join as join29 } from "node:path";
|
|
51587
51918
|
|
|
51588
51919
|
// src/commands/install.ts
|
|
51589
51920
|
init_util();
|
|
51590
|
-
import { execFileSync as
|
|
51591
|
-
import { appendFileSync as appendFileSync3, chmodSync as chmodSync2, cpSync as
|
|
51921
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
51922
|
+
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
51923
|
import { homedir as homedir7 } from "node:os";
|
|
51593
|
-
import { basename as
|
|
51924
|
+
import { basename as basename15, delimiter, dirname as dirname19, join as join28, resolve as resolve15, sep as sep2 } from "node:path";
|
|
51594
51925
|
var isWin = process.platform === "win32";
|
|
51595
51926
|
var HOME = homedir7();
|
|
51596
51927
|
var safeRealpath = (p2) => {
|
|
51597
51928
|
try {
|
|
51598
51929
|
return realpathSync(p2);
|
|
51599
51930
|
} catch {
|
|
51600
|
-
return
|
|
51931
|
+
return resolve15(p2);
|
|
51601
51932
|
}
|
|
51602
51933
|
};
|
|
51603
51934
|
var onPath = (dir) => {
|
|
@@ -51613,25 +51944,25 @@ var onPath = (dir) => {
|
|
|
51613
51944
|
});
|
|
51614
51945
|
};
|
|
51615
51946
|
var install = async (args) => {
|
|
51616
|
-
const bundle =
|
|
51617
|
-
const exeName2 =
|
|
51947
|
+
const bundle = dirname19(process.execPath);
|
|
51948
|
+
const exeName2 = basename15(process.execPath);
|
|
51618
51949
|
const chiselName = isWin ? "chisel.exe" : "chisel";
|
|
51619
|
-
if (!
|
|
51950
|
+
if (!existsSync29(join28(bundle, "runtime")) || !existsSync29(join28(bundle, chiselName))) {
|
|
51620
51951
|
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
51952
|
}
|
|
51622
|
-
const companions = [exeName2, chiselName, "runtime", "README.txt"].filter((f3) =>
|
|
51953
|
+
const companions = [exeName2, chiselName, "runtime", "README.txt"].filter((f3) => existsSync29(join28(bundle, f3)));
|
|
51623
51954
|
if (isWin)
|
|
51624
51955
|
return installWindows(args, bundle, companions);
|
|
51625
51956
|
return installUnix(args, bundle, companions);
|
|
51626
51957
|
};
|
|
51627
51958
|
var copyBundle = (bundle, target, companions) => {
|
|
51628
|
-
|
|
51959
|
+
mkdirSync20(target, { recursive: true });
|
|
51629
51960
|
if (safeRealpath(target) === safeRealpath(bundle))
|
|
51630
51961
|
return;
|
|
51631
51962
|
for (const f3 of companions) {
|
|
51632
|
-
const dest =
|
|
51633
|
-
|
|
51634
|
-
|
|
51963
|
+
const dest = join28(target, f3);
|
|
51964
|
+
rmSync7(dest, { recursive: true, force: true });
|
|
51965
|
+
cpSync4(join28(bundle, f3), dest, { recursive: true });
|
|
51635
51966
|
}
|
|
51636
51967
|
};
|
|
51637
51968
|
var permError = (e2, dir, system) => {
|
|
@@ -51643,41 +51974,41 @@ var permError = (e2, dir, system) => {
|
|
|
51643
51974
|
};
|
|
51644
51975
|
var installUnix = (args, bundle, companions) => {
|
|
51645
51976
|
const system = flagBool(args, "system");
|
|
51646
|
-
const libDir =
|
|
51647
|
-
const binDir =
|
|
51648
|
-
const link2 =
|
|
51977
|
+
const libDir = resolve15(flagStr(args, "dir") ?? (system ? "/usr/local/lib/lecodes-cli" : join28(HOME, ".local", "lib", "lecodes-cli")));
|
|
51978
|
+
const binDir = resolve15(flagStr(args, "bin") ?? (system ? "/usr/local/bin" : join28(HOME, ".local", "bin")));
|
|
51979
|
+
const link2 = join28(binDir, "lecodes");
|
|
51649
51980
|
try {
|
|
51650
51981
|
copyBundle(bundle, libDir, companions);
|
|
51651
|
-
chmodSync2(
|
|
51652
|
-
chmodSync2(
|
|
51982
|
+
chmodSync2(join28(libDir, "lecodes"), 493);
|
|
51983
|
+
chmodSync2(join28(libDir, "chisel"), 493);
|
|
51653
51984
|
} catch (e2) {
|
|
51654
51985
|
throw permError(e2, libDir, system);
|
|
51655
51986
|
}
|
|
51656
51987
|
try {
|
|
51657
|
-
|
|
51658
|
-
|
|
51659
|
-
symlinkSync(
|
|
51988
|
+
mkdirSync20(binDir, { recursive: true });
|
|
51989
|
+
rmSync7(link2, { force: true });
|
|
51990
|
+
symlinkSync(join28(libDir, "lecodes"), link2);
|
|
51660
51991
|
} catch (e2) {
|
|
51661
51992
|
throw permError(e2, binDir, system);
|
|
51662
51993
|
}
|
|
51663
51994
|
success(`Installed to ${c.bold(libDir)}`);
|
|
51664
|
-
log(` ${c.dim(link2 + " → " +
|
|
51995
|
+
log(` ${c.dim(link2 + " → " + join28(libDir, "lecodes"))}`);
|
|
51665
51996
|
finishPathUnix(args, binDir);
|
|
51666
51997
|
};
|
|
51667
51998
|
var shellRc = () => {
|
|
51668
|
-
const shell =
|
|
51999
|
+
const shell = basename15(process.env.SHELL ?? "");
|
|
51669
52000
|
if (shell === "zsh")
|
|
51670
|
-
return { file:
|
|
52001
|
+
return { file: join28(HOME, ".zshrc"), name: "~/.zshrc" };
|
|
51671
52002
|
if (shell === "bash")
|
|
51672
|
-
return { file:
|
|
51673
|
-
return { file:
|
|
52003
|
+
return { file: join28(HOME, ".bashrc"), name: "~/.bashrc" };
|
|
52004
|
+
return { file: join28(HOME, ".profile"), name: "~/.profile" };
|
|
51674
52005
|
};
|
|
51675
52006
|
var PATH_MARKER = "# Added by `lecodes install`";
|
|
51676
52007
|
var appendPathToShellRc = (binDir) => {
|
|
51677
52008
|
const { file, name } = shellRc();
|
|
51678
52009
|
const line = `export PATH="${binDir}:$PATH"`;
|
|
51679
52010
|
try {
|
|
51680
|
-
const existing =
|
|
52011
|
+
const existing = existsSync29(file) ? readFileSync27(file, "utf8") : "";
|
|
51681
52012
|
if (existing.includes(line))
|
|
51682
52013
|
return name;
|
|
51683
52014
|
appendFileSync3(file, `${existing && !existing.endsWith(`
|
|
@@ -51715,7 +52046,7 @@ var finishPathUnix = (args, binDir) => {
|
|
|
51715
52046
|
log(` • or install system-wide (no PATH edit): ${c.bold("sudo lecodes install --system")}`);
|
|
51716
52047
|
};
|
|
51717
52048
|
var installWindows = (args, bundle, companions) => {
|
|
51718
|
-
const target =
|
|
52049
|
+
const target = resolve15(flagStr(args, "dir") ?? join28(process.env.LOCALAPPDATA ?? join28(HOME, "AppData", "Local"), "Programs", "lecodes-cli"));
|
|
51719
52050
|
try {
|
|
51720
52051
|
copyBundle(bundle, target, companions);
|
|
51721
52052
|
} catch (e2) {
|
|
@@ -51743,7 +52074,7 @@ var installWindows = (args, bundle, companions) => {
|
|
|
51743
52074
|
var addToUserPathWindows = (dir) => {
|
|
51744
52075
|
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
52076
|
try {
|
|
51746
|
-
|
|
52077
|
+
execFileSync5("powershell", ["-NoProfile", "-NonInteractive", "-Command", script], { stdio: "ignore", env: { ...process.env, LECODES_INSTALL_DIR: dir } });
|
|
51747
52078
|
return true;
|
|
51748
52079
|
} catch {
|
|
51749
52080
|
return false;
|
|
@@ -51753,9 +52084,9 @@ var addToUserPathWindows = (dir) => {
|
|
|
51753
52084
|
// src/index.ts
|
|
51754
52085
|
init_util();
|
|
51755
52086
|
var STANDALONE = (() => {
|
|
51756
|
-
const dir =
|
|
52087
|
+
const dir = dirname20(process.execPath);
|
|
51757
52088
|
const chisel = process.platform === "win32" ? "chisel.exe" : "chisel";
|
|
51758
|
-
return
|
|
52089
|
+
return existsSync30(join29(dir, "runtime")) && existsSync30(join29(dir, chisel));
|
|
51759
52090
|
})();
|
|
51760
52091
|
var INSTALLED = STANDALONE && (() => {
|
|
51761
52092
|
const exe = process.platform === "win32" ? "lecodes.exe" : "lecodes";
|
|
@@ -51770,8 +52101,8 @@ var INSTALLED = STANDALONE && (() => {
|
|
|
51770
52101
|
if (!d)
|
|
51771
52102
|
return false;
|
|
51772
52103
|
try {
|
|
51773
|
-
const cand =
|
|
51774
|
-
return
|
|
52104
|
+
const cand = join29(d, exe);
|
|
52105
|
+
return existsSync30(cand) && realpathSync2(cand) === self2;
|
|
51775
52106
|
} catch {
|
|
51776
52107
|
return false;
|
|
51777
52108
|
}
|
|
@@ -51925,7 +52256,11 @@ ${c.bold("Commands:")}
|
|
|
51925
52256
|
run compile the current project and open it in a real
|
|
51926
52257
|
desktop window (size/min-size/title default from
|
|
51927
52258
|
app.json's desktop block; --width/--height/--title
|
|
51928
|
-
override; --
|
|
52259
|
+
override; --fullscreen/--windowed override the
|
|
52260
|
+
app.json fullscreen; --scale <0.5..4|fit> overrides
|
|
52261
|
+
the display scale — "fit" sizes a 1080p-designed app
|
|
52262
|
+
to the monitor with the density raised to match;
|
|
52263
|
+
--entry overrides the entrypoint)
|
|
51929
52264
|
update move to the newest published release
|
|
51930
52265
|
(cached in ~/.lecodes/renderer)
|
|
51931
52266
|
scene [path] LeCodes Scene Editor — visually edit a .scene.ts (tree, inspector,
|
|
@@ -51934,8 +52269,10 @@ ${c.bold("Commands:")}
|
|
|
51934
52269
|
--port <n> editor-server port (default 4488)
|
|
51935
52270
|
--no-open don't open the browser
|
|
51936
52271
|
--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)
|
|
52272
|
+
app init <ios|android|desktop> Generate a native shell in <project>/ios/ or <project>/android/
|
|
52273
|
+
(offline-first: runs the bundled app.js; never pushed to LeCodes).
|
|
52274
|
+
desktop: no shell needed — writes app.json's desktop block
|
|
52275
|
+
(window size; --fullscreen for kiosks)
|
|
51939
52276
|
--name <s> display name (default: app.json / folder name)
|
|
51940
52277
|
--bundle-id <id> iOS bundle identifier
|
|
51941
52278
|
--app-id <id> Android applicationId (default com.example.<slug>)
|
|
@@ -51977,6 +52314,15 @@ ${c.bold("Commands:")}
|
|
|
51977
52314
|
--configuration <c> Release (default) or Debug
|
|
51978
52315
|
--no-aab skip the .aab (APK only)
|
|
51979
52316
|
--no-sync skip the lecodes app sync step
|
|
52317
|
+
app build desktop Stage a portable desktop app folder (default dist/<name>/):
|
|
52318
|
+
the cached host renamed to <Name>.exe + app.js + every asset +
|
|
52319
|
+
app.json (the host ≥1.3.0 reads title/size/fullscreen from it)
|
|
52320
|
+
+ app.ico from app.json "icon". Duplicate asset basenames are
|
|
52321
|
+
a build error (they silently alias in the bundle).
|
|
52322
|
+
-o <dir> output folder (default dist/<name>)
|
|
52323
|
+
--slug <name> folder/zip name override (e.g. Latin-only)
|
|
52324
|
+
--probe headless-render the staged folder to a PNG
|
|
52325
|
+
--zip <out>.zip, UTF-8/forward-slash entries
|
|
51980
52326
|
app upload ios Upload the archive to App Store Connect (then bumps buildNumber)
|
|
51981
52327
|
--key-path <p8> --key-id <id> --issuer-id <id>
|
|
51982
52328
|
ASC API key; stored in ~/.lecodes/asc.json
|
|
@@ -52003,15 +52349,15 @@ var OPTIONAL_PEERS = [
|
|
|
52003
52349
|
var peerVersion = (name, probe) => {
|
|
52004
52350
|
try {
|
|
52005
52351
|
const require2 = createRequire4(import.meta.url);
|
|
52006
|
-
let dir =
|
|
52352
|
+
let dir = dirname20(require2.resolve(probe));
|
|
52007
52353
|
for (let depth = 0;depth < 16; depth++) {
|
|
52008
|
-
const manifest =
|
|
52009
|
-
if (
|
|
52354
|
+
const manifest = join29(dir, "package.json");
|
|
52355
|
+
if (existsSync30(manifest)) {
|
|
52010
52356
|
const parsed = JSON.parse(readFileSync28(manifest, "utf8"));
|
|
52011
52357
|
if (parsed.name === name)
|
|
52012
52358
|
return parsed.version ?? "unknown";
|
|
52013
52359
|
}
|
|
52014
|
-
const parent =
|
|
52360
|
+
const parent = dirname20(dir);
|
|
52015
52361
|
if (parent === dir)
|
|
52016
52362
|
break;
|
|
52017
52363
|
dir = parent;
|