lecodes-cli 0.20.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1065 -577
- package/package.json +8 -8
- package/runtime/materials/decal-relief.mat +178 -0
- package/runtime/materials/decal.mat +172 -0
- package/runtime/materials/lightmap-baked-lite.mat +175 -0
- package/runtime/materials/lightmap-baked.mat +176 -0
- package/runtime/materials/lightmap.mat +181 -0
- package/runtime/materials/lit.mat +43 -0
- package/runtime/materials/particles-quad.mat +171 -0
- package/runtime/materials/particles.mat +121 -0
- package/runtime/materials/shadow.mat +21 -0
- package/runtime/materials/terrain.mat +134 -0
- package/runtime/materials/unlit-transparent.mat +42 -0
- package/runtime/materials/unlit.mat +44 -0
- package/runtime/materials/video.mat +31 -0
- package/runtime/scene-harness.json +1 -1
- package/runtime/sdk-types.json +1 -1
- package/runtime/web/assets/{createViewerLite-BvHobVhp.js → createViewerLite-C_bkKXLg.js} +22 -22
- package/runtime/web/assets/{index-CfNCCBjN.js → index-BTB5KV9x.js} +1 -1
- package/runtime/web/embed.js +1 -1
- package/src/cmgenTool.ts +17 -9
- package/src/commands/appDesktop.ts +12 -2
- package/src/commands/appShared.ts +48 -0
- package/src/commands/assets.ts +3 -7
- package/src/commands/design.ts +9 -26
- package/src/commands/desktop.ts +3 -0
- package/src/commands/dev.ts +42 -0
- package/src/commands/projectTemplates.ts +13 -1
- package/src/commands/render.ts +4 -8
- package/src/commands/scene.ts +2 -6
- package/src/commands/shaders.ts +17 -1
- package/src/commands/shadersNew.ts +133 -0
- package/src/commands/thumbs.ts +3 -6
- package/src/commands/update.ts +42 -21
- package/src/desktopRenderer.ts +17 -0
- package/src/desktopScript.ts +1 -1
- package/src/dev/androidDev.ts +154 -0
- package/src/dev/devServer.ts +8 -2
- package/src/index.ts +21 -4
- package/src/matcTool.ts +31 -19
- package/src/peerInstall.ts +160 -0
- package/src/peers.ts +48 -14
- package/src/projectEnv.ts +4 -0
- package/src/releases.ts +16 -0
package/dist/index.js
CHANGED
|
@@ -47,6 +47,78 @@ var __export = (target, all) => {
|
|
|
47
47
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
48
48
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
49
49
|
|
|
50
|
+
// package.json
|
|
51
|
+
var package_default;
|
|
52
|
+
var init_package = __esm(() => {
|
|
53
|
+
package_default = {
|
|
54
|
+
name: "lecodes-cli",
|
|
55
|
+
version: "0.20.1",
|
|
56
|
+
dependencies: {
|
|
57
|
+
"@letary/chisel": "^0.10.0",
|
|
58
|
+
jimp: "^1.6.1",
|
|
59
|
+
"lecodes-sdk": "workspace:*"
|
|
60
|
+
},
|
|
61
|
+
devDependencies: {
|
|
62
|
+
"lecodes-design": "^0.9.0",
|
|
63
|
+
"lecodes-renderer": "workspace:*",
|
|
64
|
+
"qrcode-terminal": "^0.12.0",
|
|
65
|
+
"lecodes-3d-editor": "^0.7.0",
|
|
66
|
+
"lecodes-assets": "workspace:*",
|
|
67
|
+
"lecodes-creator-nav": "workspace:*",
|
|
68
|
+
"lecodes-viewer-lite": "workspace:*"
|
|
69
|
+
},
|
|
70
|
+
peerDependencies: {
|
|
71
|
+
"lecodes-design": "^0.9.0",
|
|
72
|
+
"lecodes-renderer": "^0.20.0",
|
|
73
|
+
"lecodes-3d-editor": "^0.7.0",
|
|
74
|
+
"lecodes-assets": "^0.2.1"
|
|
75
|
+
},
|
|
76
|
+
bin: {
|
|
77
|
+
lecodes: "dist/index.js"
|
|
78
|
+
},
|
|
79
|
+
description: "Clone, edit and push LeCodes projects from your machine.",
|
|
80
|
+
engines: {
|
|
81
|
+
node: ">=18"
|
|
82
|
+
},
|
|
83
|
+
files: [
|
|
84
|
+
"dist",
|
|
85
|
+
"src",
|
|
86
|
+
"runtime",
|
|
87
|
+
"README.md"
|
|
88
|
+
],
|
|
89
|
+
keywords: [
|
|
90
|
+
"lecodes",
|
|
91
|
+
"cli"
|
|
92
|
+
],
|
|
93
|
+
license: "MIT",
|
|
94
|
+
peerDependenciesMeta: {
|
|
95
|
+
"lecodes-design": {
|
|
96
|
+
optional: true
|
|
97
|
+
},
|
|
98
|
+
"lecodes-renderer": {
|
|
99
|
+
optional: true
|
|
100
|
+
},
|
|
101
|
+
"lecodes-3d-editor": {
|
|
102
|
+
optional: true
|
|
103
|
+
},
|
|
104
|
+
"lecodes-assets": {
|
|
105
|
+
optional: true
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
scripts: {
|
|
109
|
+
icon: "bun scripts/make-icon.ts",
|
|
110
|
+
"vendor-runtime": "bun scripts/vendor-runtime.ts",
|
|
111
|
+
"sync-peers": "bun scripts/sync-peer-versions.ts",
|
|
112
|
+
build: 'bun scripts/vendor-runtime.ts && bun build src/index.ts --target=node --outfile dist/index.js --external @letary/chisel --external lecodes-renderer --external lecodes-design --external lecodes-3d-editor --external lecodes-assets --banner="#!/usr/bin/env node"',
|
|
113
|
+
"build:bin": "bun scripts/build-bin.ts",
|
|
114
|
+
typecheck: "tsc --noEmit",
|
|
115
|
+
dev: "bun run src/index.ts",
|
|
116
|
+
prepublishOnly: "bun run sync-peers && bun run build"
|
|
117
|
+
},
|
|
118
|
+
type: "module"
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
|
|
50
122
|
// ../sdk/src/compile/assetName.ts
|
|
51
123
|
var inRanges = (cp, ranges) => ranges.some(([from, to]) => cp >= from && cp <= to), INVISIBLE, ODD_SPACE, ILLEGAL = '/\\:*?"<>|', sanitizeAssetName = (name) => {
|
|
52
124
|
let out = "";
|
|
@@ -3177,7 +3249,7 @@ var require_source_map_support = __commonJS((exports, module) => {
|
|
|
3177
3249
|
|
|
3178
3250
|
// ../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/typescript.js
|
|
3179
3251
|
var require_typescript = __commonJS((exports, module) => {
|
|
3180
|
-
var __dirname = "
|
|
3252
|
+
var __dirname = "C:\\Users\\Den\\Desktop\\LeCodes\\lecodes\\node_modules\\.bun\\typescript@5.8.3\\node_modules\\typescript\\lib", __filename = "C:\\Users\\Den\\Desktop\\LeCodes\\lecodes\\node_modules\\.bun\\typescript@5.8.3\\node_modules\\typescript\\lib\\typescript.js";
|
|
3181
3253
|
/*! *****************************************************************************
|
|
3182
3254
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3183
3255
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
@@ -172165,17 +172237,12 @@ var buildHeader = (name, code, opts = {}) => {
|
|
|
172165
172237
|
};
|
|
172166
172238
|
|
|
172167
172239
|
// ../sdk/src/compile/compileProject.ts
|
|
172168
|
-
var
|
|
172169
|
-
const entries = stubServerEntries(opts.entries, opts.serverUrl ?? "");
|
|
172170
|
-
const entry = opts.entryOverride ?? detectEntry(entries, scanModulesChisel).entry;
|
|
172171
|
-
if (!entry) {
|
|
172172
|
-
throw new Error("Couldn't find an entrypoint (no .ts/.js project file — name yours main.ts). Pass entryOverride.");
|
|
172173
|
-
}
|
|
172240
|
+
var entriesToFiles = (entries, opts) => {
|
|
172174
172241
|
const files = {};
|
|
172175
172242
|
for (const e of entries) {
|
|
172176
172243
|
if (e.type === "resource") {
|
|
172177
172244
|
if (opts.localAssets && !e.remote) {
|
|
172178
|
-
const name = e.path.split(/[
|
|
172245
|
+
const name = e.path.split(/[\/]/).pop() ?? e.path;
|
|
172179
172246
|
files[e.path] = `export default ("id:" + _creatorUtils.fetchLocal(${JSON.stringify(name)}))`;
|
|
172180
172247
|
} else {
|
|
172181
172248
|
files[e.path] = `export default "${opts.publicUrl}${e.fileSrc ?? ""}"`;
|
|
@@ -172183,7 +172250,7 @@ var compileProject = async (opts) => {
|
|
|
172183
172250
|
} else if (e.type === "shader") {
|
|
172184
172251
|
const shaders = e.shaders ?? [];
|
|
172185
172252
|
if (e.shadersLocal && shaders.length > 0) {
|
|
172186
|
-
const name = shaders[0].src.split(/[
|
|
172253
|
+
const name = shaders[0].src.split(/[\/]/).pop() ?? "";
|
|
172187
172254
|
const [prefix, suffix = ".filamat"] = name.split("_opengl");
|
|
172188
172255
|
files[e.path] = `export default ("id:" + _creatorUtils.fetchLocal(${JSON.stringify(prefix + "_")} + _creator.backend + ${JSON.stringify(suffix)}))`;
|
|
172189
172256
|
} else if (shaders.length > 0) {
|
|
@@ -172199,17 +172266,24 @@ var compileProject = async (opts) => {
|
|
|
172199
172266
|
files[e.path] = e.text ?? "";
|
|
172200
172267
|
}
|
|
172201
172268
|
}
|
|
172202
|
-
|
|
172203
|
-
|
|
172204
|
-
|
|
172205
|
-
|
|
172269
|
+
return files;
|
|
172270
|
+
}, compileProjectWithMap = async (opts) => {
|
|
172271
|
+
const entries = stubServerEntries(opts.entries, opts.serverUrl ?? "");
|
|
172272
|
+
const entry = opts.entryOverride ?? detectEntry(entries, scanModulesChisel).entry;
|
|
172273
|
+
if (!entry) {
|
|
172274
|
+
throw new Error("Couldn't find an entrypoint (no .ts/.js project file — name yours main.ts). Pass entryOverride.");
|
|
172206
172275
|
}
|
|
172276
|
+
const files = entriesToFiles(entries, opts);
|
|
172277
|
+
const { code, map } = await bundleProjectWithMap(entry, files, opts.format ?? "esm", opts.minify, opts.editor, opts.libraries, opts.sourcemap ?? true);
|
|
172278
|
+
if (opts.header === false)
|
|
172279
|
+
return { code, map, entry };
|
|
172207
172280
|
const header = buildHeader(opts.name, code, { icon: opts.icon, fonts: opts.fontsHeader, icons: opts.iconsHeader });
|
|
172208
|
-
if (!map)
|
|
172209
|
-
return header + code;
|
|
172210
172281
|
const headerLines = (header.match(/\n/g) ?? []).length;
|
|
172211
|
-
return
|
|
172212
|
-
|
|
172282
|
+
return { code: header + code, map: map ? offsetSourceMap(map, headerLines) : null, entry };
|
|
172283
|
+
}, compileProject = async (opts) => {
|
|
172284
|
+
const { code, map } = await compileProjectWithMap(opts);
|
|
172285
|
+
return map ? `${code}
|
|
172286
|
+
${inlineSourceMapComment(map)}` : code;
|
|
172213
172287
|
};
|
|
172214
172288
|
var init_compileProject = __esm(() => {
|
|
172215
172289
|
init_bundler();
|
|
@@ -173144,10 +173218,12 @@ __export(exports_compile, {
|
|
|
173144
173218
|
faceFileName: () => faceFileName,
|
|
173145
173219
|
extractMatBlocks: () => extractMatBlocks,
|
|
173146
173220
|
externalSamplerReg: () => externalSamplerReg,
|
|
173221
|
+
entriesToFiles: () => entriesToFiles,
|
|
173147
173222
|
detectProjectKind: () => detectProjectKind,
|
|
173148
173223
|
detectEntry: () => detectEntry,
|
|
173149
173224
|
deriveEndpointParams: () => deriveEndpointParams,
|
|
173150
173225
|
compileServerBundle: () => compileServerBundle,
|
|
173226
|
+
compileProjectWithMap: () => compileProjectWithMap,
|
|
173151
173227
|
compileProject: () => compileProject,
|
|
173152
173228
|
collectVendoredIcons: () => collectVendoredIcons,
|
|
173153
173229
|
bundleProjectWithMap: () => bundleProjectWithMap,
|
|
@@ -214370,7 +214446,7 @@ var init_browserAuth = __esm(() => {
|
|
|
214370
214446
|
init_util();
|
|
214371
214447
|
});
|
|
214372
214448
|
|
|
214373
|
-
//
|
|
214449
|
+
// ../../node_modules/.bun/lecodes-design@0.9.0+edf5a69441922373/node_modules/lecodes-design/src/core/concept.ts
|
|
214374
214450
|
var SPEC_TEMPLATE = `# Concept
|
|
214375
214451
|
|
|
214376
214452
|
The narrative half of this design. \`meta.json\` + the canvas hold the *visual/flow* half (screens,
|
|
@@ -214406,7 +214482,7 @@ Behaviour that isn't visible in a single screen — validation, permissions, not
|
|
|
214406
214482
|
cases — and the decisions you've made and why, so they aren't re-litigated later.
|
|
214407
214483
|
`;
|
|
214408
214484
|
|
|
214409
|
-
//
|
|
214485
|
+
// ../../node_modules/.bun/lecodes-design@0.9.0+edf5a69441922373/node_modules/lecodes-design/src/templates.ts
|
|
214410
214486
|
var SPEC_MD, META_JSON = `{
|
|
214411
214487
|
"screens": {
|
|
214412
214488
|
"home": { "pos": [0, 0], "desc": "Example screen — replace me", "role": "list" }
|
|
@@ -214845,12 +214921,179 @@ var init_designTemplates = __esm(() => {
|
|
|
214845
214921
|
init_templates();
|
|
214846
214922
|
});
|
|
214847
214923
|
|
|
214924
|
+
// src/peers.ts
|
|
214925
|
+
import { existsSync as existsSync24, readFileSync as readFileSync20 } from "node:fs";
|
|
214926
|
+
import { createRequire as createRequire5 } from "node:module";
|
|
214927
|
+
import { homedir as homedir8 } from "node:os";
|
|
214928
|
+
import { dirname as dirname14, join as join24, resolve as resolve11 } from "node:path";
|
|
214929
|
+
var OPTIONAL_PEERS, peersDir = () => process.env.LECODES_PEERS_DIR ? resolve11(process.env.LECODES_PEERS_DIR) : join24(homedir8(), ".lecodes", "peers"), peerDirInPrefix = (name) => join24(peersDir(), "node_modules", ...name.split("/")), readVersion = (dir) => {
|
|
214930
|
+
try {
|
|
214931
|
+
return JSON.parse(readFileSync20(join24(dir, "package.json"), "utf8")).version ?? "unknown";
|
|
214932
|
+
} catch {
|
|
214933
|
+
return null;
|
|
214934
|
+
}
|
|
214935
|
+
}, peerVersionGlobal = (name, probe) => {
|
|
214936
|
+
try {
|
|
214937
|
+
const require2 = createRequire5(import.meta.url);
|
|
214938
|
+
let dir = dirname14(require2.resolve(probe));
|
|
214939
|
+
for (let depth = 0;depth < 16; depth++) {
|
|
214940
|
+
const manifest = join24(dir, "package.json");
|
|
214941
|
+
if (existsSync24(manifest)) {
|
|
214942
|
+
const parsed = JSON.parse(readFileSync20(manifest, "utf8"));
|
|
214943
|
+
if (parsed.name === name)
|
|
214944
|
+
return parsed.version ?? "unknown";
|
|
214945
|
+
}
|
|
214946
|
+
const parent = dirname14(dir);
|
|
214947
|
+
if (parent === dir)
|
|
214948
|
+
break;
|
|
214949
|
+
dir = parent;
|
|
214950
|
+
}
|
|
214951
|
+
} catch {}
|
|
214952
|
+
return null;
|
|
214953
|
+
}, peerVersionPrefix = (name) => readVersion(peerDirInPrefix(name)), peerVersion = (name, probe) => peerVersionGlobal(name, probe) ?? peerVersionPrefix(name), peerSource = (name, probe) => peerVersionGlobal(name, probe) !== null ? "global" : peerVersionPrefix(name) !== null ? "prefix" : null;
|
|
214954
|
+
var init_peers = __esm(() => {
|
|
214955
|
+
OPTIONAL_PEERS = [
|
|
214956
|
+
["lecodes-renderer", "lecodes-renderer/headless"],
|
|
214957
|
+
["lecodes-design", "lecodes-design/server"],
|
|
214958
|
+
["lecodes-3d-editor", "lecodes-3d-editor/server"],
|
|
214959
|
+
["lecodes-assets", "lecodes-assets/cli"],
|
|
214960
|
+
["@napi-rs/canvas", "@napi-rs/canvas"]
|
|
214961
|
+
];
|
|
214962
|
+
});
|
|
214963
|
+
|
|
214964
|
+
// src/peerInstall.ts
|
|
214965
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
214966
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync18, readFileSync as readFileSync21, writeFileSync as writeFileSync19 } from "node:fs";
|
|
214967
|
+
import { join as join25 } from "node:path";
|
|
214968
|
+
import { pathToFileURL } from "node:url";
|
|
214969
|
+
async function loadPeer(name, subpath, opts) {
|
|
214970
|
+
const spec = subpath ? `${name}/${subpath}` : name;
|
|
214971
|
+
try {
|
|
214972
|
+
return await import(spec);
|
|
214973
|
+
} catch (e2) {
|
|
214974
|
+
if (!isNotFound(e2, spec))
|
|
214975
|
+
throw e2;
|
|
214976
|
+
}
|
|
214977
|
+
const dir = peerDirInPrefix(name);
|
|
214978
|
+
if (!existsSync25(join25(dir, "package.json"))) {
|
|
214979
|
+
if (opts.install === false)
|
|
214980
|
+
return null;
|
|
214981
|
+
if (peerVersionGlobal(name, spec) !== null)
|
|
214982
|
+
throw new CliError(`${name} is installed but "${spec}" can't be loaded from it — reinstall it: npm install -g ${peerSpec(name)}`);
|
|
214983
|
+
await installPeers(companions(name), opts.for);
|
|
214984
|
+
}
|
|
214985
|
+
return await import(pathToFileURL(resolveEntry(dir, subpath)).href);
|
|
214986
|
+
}
|
|
214987
|
+
var CANVAS_RANGE_FALLBACK = "^0.1.65", readJson2 = (path2) => {
|
|
214988
|
+
try {
|
|
214989
|
+
return JSON.parse(readFileSync21(path2, "utf8"));
|
|
214990
|
+
} catch {
|
|
214991
|
+
return null;
|
|
214992
|
+
}
|
|
214993
|
+
}, peerSpec = (name) => {
|
|
214994
|
+
if (name === "lecodes-renderer")
|
|
214995
|
+
return `lecodes-renderer@${package_default.version}`;
|
|
214996
|
+
if (name === "@napi-rs/canvas") {
|
|
214997
|
+
const renderer = readJson2(join25(peerDirInPrefix("lecodes-renderer"), "package.json"));
|
|
214998
|
+
const range2 = renderer?.peerDependencies?.["@napi-rs/canvas"] ?? renderer?.optionalDependencies?.["@napi-rs/canvas"] ?? CANVAS_RANGE_FALLBACK;
|
|
214999
|
+
return `@napi-rs/canvas@${range2}`;
|
|
215000
|
+
}
|
|
215001
|
+
const range = package_default.peerDependencies?.[name];
|
|
215002
|
+
return range ? `${name}@${range}` : `${name}@latest`;
|
|
215003
|
+
}, companions = (name) => name === "lecodes-renderer" ? ["lecodes-renderer", "@napi-rs/canvas"] : [name], onPath = (cmd) => {
|
|
215004
|
+
try {
|
|
215005
|
+
return spawnSync6(cmd, ["--version"], { stdio: "ignore", shell: process.platform === "win32" }).status === 0;
|
|
215006
|
+
} catch {
|
|
215007
|
+
return false;
|
|
215008
|
+
}
|
|
215009
|
+
}, pickInstaller = () => onPath("npm") ? "npm" : onPath("bun") ? "bun" : null, manualCommand = (dir, specs) => `npm install --prefix "${dir}" ${specs.join(" ")}`, installPeers = async (names3, why, opts = {}) => {
|
|
215010
|
+
const dir = peersDir();
|
|
215011
|
+
mkdirSync18(dir, { recursive: true });
|
|
215012
|
+
const manifest = join25(dir, "package.json");
|
|
215013
|
+
if (!existsSync25(manifest)) {
|
|
215014
|
+
writeFileSync19(manifest, JSON.stringify({
|
|
215015
|
+
name: "lecodes-peers",
|
|
215016
|
+
private: true,
|
|
215017
|
+
description: "Optional lecodes-cli peers, installed on first use. Safe to delete — the CLI re-installs what a command needs."
|
|
215018
|
+
}, null, 2) + `
|
|
215019
|
+
`);
|
|
215020
|
+
}
|
|
215021
|
+
const ordered = [...names3].sort((a2, b) => a2 === "lecodes-renderer" ? -1 : b === "lecodes-renderer" ? 1 : 0);
|
|
215022
|
+
const specsNow = ordered.map(peerSpec);
|
|
215023
|
+
const yes = opts.yes === true || !!process.env.LECODES_YES;
|
|
215024
|
+
if (!yes) {
|
|
215025
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
215026
|
+
if (!interactive) {
|
|
215027
|
+
throw new CliError(`${why} needs ${names3.join(" + ")}, which isn't installed. Re-run with --yes to install it into ${dir}, or install by hand: ${manualCommand(dir, specsNow)}`);
|
|
215028
|
+
}
|
|
215029
|
+
log(`${why} needs ${c.bold(names3.join(" + "))}.`);
|
|
215030
|
+
log(c.dim(`It installs once into ${dir} (no admin rights); a newer CLI re-installs its own version.`));
|
|
215031
|
+
if (!await confirm("Install now?", true)) {
|
|
215032
|
+
throw new CliError(`Not installed. When you want it: ${manualCommand(dir, specsNow)}`);
|
|
215033
|
+
}
|
|
215034
|
+
}
|
|
215035
|
+
const tool = pickInstaller();
|
|
215036
|
+
if (!tool)
|
|
215037
|
+
throw new CliError(`Neither npm nor bun is on PATH, so ${names3.join(" + ")} can't be installed automatically. Install one, or run on a machine with Node: ${manualCommand(dir, specsNow)}`);
|
|
215038
|
+
for (const name of ordered) {
|
|
215039
|
+
const spec = peerSpec(name);
|
|
215040
|
+
const argv = tool === "npm" ? ["install", "--prefix", dir, "--no-audit", "--no-fund", "--loglevel=error", spec] : ["add", "--cwd", dir, spec];
|
|
215041
|
+
note(`$ ${tool} ${argv.join(" ")}`);
|
|
215042
|
+
const r2 = spawnSync6(tool, argv, { stdio: "inherit", shell: process.platform === "win32", timeout: 10 * 60000 });
|
|
215043
|
+
if (r2.error)
|
|
215044
|
+
throw new CliError(`${tool} could not run: ${r2.error.message}`);
|
|
215045
|
+
if (r2.status !== 0)
|
|
215046
|
+
throw new CliError(`${tool} exited with ${r2.status ?? "a signal"} while installing ${spec}. By hand: ${manualCommand(dir, [spec])}`);
|
|
215047
|
+
if (!existsSync25(join25(peerDirInPrefix(name), "package.json")))
|
|
215048
|
+
throw new CliError(`${name} did not land in ${dir} — the installer finished but the package is missing`);
|
|
215049
|
+
}
|
|
215050
|
+
}, resolveEntry = (pkgDir, subpath) => {
|
|
215051
|
+
const manifest = readJson2(join25(pkgDir, "package.json"));
|
|
215052
|
+
if (!manifest)
|
|
215053
|
+
throw new CliError(`${pkgDir} holds no package.json`);
|
|
215054
|
+
const key = subpath ? `./${subpath}` : ".";
|
|
215055
|
+
const pick = (target) => {
|
|
215056
|
+
if (typeof target === "string")
|
|
215057
|
+
return target;
|
|
215058
|
+
if (target && typeof target === "object") {
|
|
215059
|
+
const t2 = target;
|
|
215060
|
+
for (const cond of ["import", "node", "default", "require"])
|
|
215061
|
+
if (cond in t2) {
|
|
215062
|
+
const p2 = pick(t2[cond]);
|
|
215063
|
+
if (p2)
|
|
215064
|
+
return p2;
|
|
215065
|
+
}
|
|
215066
|
+
}
|
|
215067
|
+
return null;
|
|
215068
|
+
};
|
|
215069
|
+
let rel = null;
|
|
215070
|
+
const exp = manifest.exports;
|
|
215071
|
+
if (typeof exp === "string")
|
|
215072
|
+
rel = key === "." ? exp : null;
|
|
215073
|
+
else if (exp && typeof exp === "object")
|
|
215074
|
+
rel = pick(exp[key]);
|
|
215075
|
+
if (!rel && key === ".")
|
|
215076
|
+
rel = typeof manifest.main === "string" ? manifest.main : "index.js";
|
|
215077
|
+
if (!rel)
|
|
215078
|
+
throw new CliError(`${manifest.name} has no export "${key}"`);
|
|
215079
|
+
return join25(pkgDir, rel);
|
|
215080
|
+
}, isNotFound = (e2, spec) => {
|
|
215081
|
+
const code = e2?.code;
|
|
215082
|
+
const msg = e2 instanceof Error ? e2.message : String(e2);
|
|
215083
|
+
return (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || /Cannot find (module|package)/i.test(msg)) && msg.includes(spec.split("/")[0]);
|
|
215084
|
+
};
|
|
215085
|
+
var init_peerInstall = __esm(() => {
|
|
215086
|
+
init_package();
|
|
215087
|
+
init_peers();
|
|
215088
|
+
init_util();
|
|
215089
|
+
});
|
|
215090
|
+
|
|
214848
215091
|
// src/designMeta.ts
|
|
214849
|
-
import { readFileSync as
|
|
214850
|
-
import { join as
|
|
215092
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
215093
|
+
import { join as join26 } from "node:path";
|
|
214851
215094
|
var PHONE_SIZE, readMetaFrames = (designDir) => {
|
|
214852
215095
|
try {
|
|
214853
|
-
const meta = JSON.parse(
|
|
215096
|
+
const meta = JSON.parse(readFileSync22(join26(designDir, "meta.json"), "utf8"));
|
|
214854
215097
|
const d = meta?.defaultSize;
|
|
214855
215098
|
const valid = Array.isArray(d) && d.length === 2 && d.every((n2) => typeof n2 === "number" && Number.isFinite(n2) && n2 >= 64 && n2 <= 8192);
|
|
214856
215099
|
return {
|
|
@@ -214874,8 +215117,8 @@ __export(exports_design, {
|
|
|
214874
215117
|
flushDesignComments: () => flushDesignComments,
|
|
214875
215118
|
design: () => design
|
|
214876
215119
|
});
|
|
214877
|
-
import { existsSync as
|
|
214878
|
-
import { basename as basename11, join as
|
|
215120
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync19, readFileSync as readFileSync23, readdirSync as readdirSync14, writeFileSync as writeFileSync20, appendFileSync } from "node:fs";
|
|
215121
|
+
import { basename as basename11, join as join27 } from "node:path";
|
|
214879
215122
|
var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
214880
215123
|
let root;
|
|
214881
215124
|
let manifest;
|
|
@@ -214892,19 +215135,14 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
214892
215135
|
manifest,
|
|
214893
215136
|
name: manifest?.name ?? basename11(root),
|
|
214894
215137
|
dirName,
|
|
214895
|
-
designDir:
|
|
215138
|
+
designDir: join27(root, dirName),
|
|
214896
215139
|
designKey: "/" + dirName.replace(/\\/g, "/")
|
|
214897
215140
|
};
|
|
214898
215141
|
}, loadDesignServer = async (ctx, needs = []) => {
|
|
214899
|
-
if (!
|
|
215142
|
+
if (!existsSync26(ctx.designDir)) {
|
|
214900
215143
|
throw new CliError(`No ${ctx.dirName}/ folder here. Run "lecodes design init" to scaffold it.`);
|
|
214901
215144
|
}
|
|
214902
|
-
|
|
214903
|
-
try {
|
|
214904
|
-
mod = await import("lecodes-design/server");
|
|
214905
|
-
} catch {
|
|
214906
|
-
throw new CliError("The design canvas isn't installed. Run: npm install -g lecodes-design");
|
|
214907
|
-
}
|
|
215145
|
+
const mod = await loadPeer("lecodes-design", "server", { for: "lecodes design" });
|
|
214908
215146
|
const missing = needs.filter((name) => typeof mod[name] !== "function");
|
|
214909
215147
|
if (missing.length) {
|
|
214910
215148
|
throw new CliError(`This needs a newer lecodes-design — the installed one has no \`${missing[0]}\`. Run: npm install -g lecodes-design@latest`);
|
|
@@ -214912,7 +215150,7 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
214912
215150
|
return mod;
|
|
214913
215151
|
}, requirePlatform = (ctx, verb) => {
|
|
214914
215152
|
if (!ctx.manifest) {
|
|
214915
|
-
throw new CliError(`\`lecodes design comments ${verb}\` needs a cloned project. This folder is standalone, so
|
|
215153
|
+
throw new CliError(`\`lecodes design comments ${verb}\` needs a cloned project. This folder is standalone, so ` + `${ctx.dirName}/comments/ is already the only store — there is nothing to sync with.`);
|
|
214916
215154
|
}
|
|
214917
215155
|
const config = loadConfig();
|
|
214918
215156
|
return { apiUrl: requireApiUrl(config), token: requireToken(config), uuid: ctx.manifest.uuid };
|
|
@@ -214928,17 +215166,17 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
214928
215166
|
`).find((l2) => l2.trim())?.trim() ?? "";
|
|
214929
215167
|
return line.length > max ? line.slice(0, max - 1) + "…" : line;
|
|
214930
215168
|
}, listScreens = (designDir) => {
|
|
214931
|
-
const dir =
|
|
214932
|
-
if (!
|
|
215169
|
+
const dir = join27(designDir, "screens");
|
|
215170
|
+
if (!existsSync26(dir))
|
|
214933
215171
|
return [];
|
|
214934
215172
|
return readdirSync14(dir).filter((f3) => f3.endsWith(".ts") && !f3.startsWith(".")).map((f3) => f3.slice(0, -3)).sort();
|
|
214935
215173
|
}, writeMcpConfig = (ctx, port) => {
|
|
214936
|
-
const path2 =
|
|
215174
|
+
const path2 = join27(ctx.root, ".mcp.json");
|
|
214937
215175
|
const entry = { type: "http", url: `http://localhost:${port}/mcp` };
|
|
214938
215176
|
let config = {};
|
|
214939
|
-
if (
|
|
215177
|
+
if (existsSync26(path2)) {
|
|
214940
215178
|
try {
|
|
214941
|
-
config = JSON.parse(
|
|
215179
|
+
config = JSON.parse(readFileSync23(path2, "utf8"));
|
|
214942
215180
|
} catch {
|
|
214943
215181
|
warnErr(".mcp.json exists but isn't valid JSON — left it untouched. Add the lecodes-design server by hand.");
|
|
214944
215182
|
return;
|
|
@@ -214951,7 +215189,7 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
214951
215189
|
return;
|
|
214952
215190
|
}
|
|
214953
215191
|
config.mcpServers["lecodes-design"] = entry;
|
|
214954
|
-
|
|
215192
|
+
writeFileSync20(path2, JSON.stringify(config, null, 2) + `
|
|
214955
215193
|
`);
|
|
214956
215194
|
log(`${c.green(existing ? "updated" : "created")} .mcp.json (lecodes-design MCP → ${entry.url})`);
|
|
214957
215195
|
}, init = async (ctx, args) => {
|
|
@@ -214960,26 +215198,26 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
214960
215198
|
["spec.md", SPEC_MD],
|
|
214961
215199
|
["README.md", README_HINT],
|
|
214962
215200
|
["CLAUDE.md", CLAUDE_MD],
|
|
214963
|
-
[
|
|
214964
|
-
[
|
|
214965
|
-
[
|
|
215201
|
+
[join27("shared", "tokens.ts"), TOKENS_TS],
|
|
215202
|
+
[join27("shared", "tabs.ts"), TABS_TS],
|
|
215203
|
+
[join27("screens", "home.ts"), HOME_SCREEN_TS]
|
|
214966
215204
|
];
|
|
214967
|
-
|
|
214968
|
-
|
|
215205
|
+
mkdirSync19(join27(ctx.designDir, "screens"), { recursive: true });
|
|
215206
|
+
mkdirSync19(join27(ctx.designDir, "shared"), { recursive: true });
|
|
214969
215207
|
let created = 0;
|
|
214970
215208
|
for (const [rel, content] of files) {
|
|
214971
|
-
const abs =
|
|
214972
|
-
if (
|
|
215209
|
+
const abs = join27(ctx.designDir, rel);
|
|
215210
|
+
if (existsSync26(abs)) {
|
|
214973
215211
|
note(`kept ${ctx.dirName}/${rel.replace(/\\/g, "/")}`);
|
|
214974
215212
|
continue;
|
|
214975
215213
|
}
|
|
214976
|
-
|
|
215214
|
+
writeFileSync20(abs, content);
|
|
214977
215215
|
log(`${c.green("created")} ${ctx.dirName}/${rel.replace(/\\/g, "/")}`);
|
|
214978
215216
|
created++;
|
|
214979
215217
|
}
|
|
214980
|
-
const ignorePath =
|
|
215218
|
+
const ignorePath = join27(ctx.root, ".lecodesignore");
|
|
214981
215219
|
const ignoreLines = [`${ctx.dirName}/.snapshots/`, `${ctx.dirName}/comments/`];
|
|
214982
|
-
const current =
|
|
215220
|
+
const current = existsSync26(ignorePath) ? readFileSync23(ignorePath, "utf8") : "";
|
|
214983
215221
|
const present = new Set(current.split(/\r?\n/));
|
|
214984
215222
|
const missing = ignoreLines.filter((line) => !present.has(line));
|
|
214985
215223
|
if (missing.length) {
|
|
@@ -214994,8 +215232,8 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
214994
215232
|
writeMcpConfig(ctx, Number(flagStr(args, "port")) || DEFAULT_PORT);
|
|
214995
215233
|
const wroteTypes = materializeTypesLocal(ctx.designDir);
|
|
214996
215234
|
writeTsconfig(ctx.designDir);
|
|
214997
|
-
|
|
214998
|
-
|
|
215235
|
+
mkdirSync19(join27(ctx.designDir, ".lecodes", "types"), { recursive: true });
|
|
215236
|
+
writeFileSync20(join27(ctx.designDir, ".lecodes", "types", "design-macros.d.ts"), DESIGN_MACROS_DTS);
|
|
214999
215237
|
log(`${c.green("created")} ${ctx.dirName}/tsconfig.json${wroteTypes ? ` + ${ctx.dirName}/.lecodes/types/` : ""} (IDE types)`);
|
|
215000
215238
|
if (!wroteTypes)
|
|
215001
215239
|
warnErr("Bundled SDK types not found — tsconfig written, but globals may show unresolved until the CLI is rebuilt with vendored types.");
|
|
@@ -215018,23 +215256,14 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
215018
215256
|
const allStates = flagBool(args, "all-states");
|
|
215019
215257
|
if (stateFlag !== undefined && allStates)
|
|
215020
215258
|
throw new CliError("Use either --state <name> or --all-states, not both.");
|
|
215021
|
-
|
|
215022
|
-
try {
|
|
215023
|
-
renderer = await import("lecodes-renderer/headless");
|
|
215024
|
-
} catch {
|
|
215025
|
-
throw new CliError("The renderer isn't installed. Run: npm install -g lecodes-renderer");
|
|
215026
|
-
}
|
|
215259
|
+
const renderer = await loadPeer("lecodes-renderer", "headless", { for: "lecodes design render" });
|
|
215027
215260
|
let discover = null;
|
|
215028
215261
|
if (stateFlag !== undefined || allStates) {
|
|
215029
|
-
|
|
215030
|
-
|
|
215031
|
-
|
|
215032
|
-
|
|
215033
|
-
|
|
215034
|
-
};
|
|
215035
|
-
} catch {
|
|
215036
|
-
throw new CliError("Rendering specific states needs the design canvas. Run: npm install -g lecodes-design");
|
|
215037
|
-
}
|
|
215262
|
+
const ds = await loadPeer("lecodes-design", "server", { for: "lecodes design render --state" });
|
|
215263
|
+
discover = (src) => {
|
|
215264
|
+
const d = ds.discoverScreenStates(src);
|
|
215265
|
+
return { states: d.states, canonical: d.canonical };
|
|
215266
|
+
};
|
|
215038
215267
|
}
|
|
215039
215268
|
const killer = setTimeout(() => {
|
|
215040
215269
|
warnErr("Snapshot timed out after 120s.");
|
|
@@ -215043,16 +215272,16 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
215043
215272
|
killer.unref?.();
|
|
215044
215273
|
const png2 = flagBool(args, "png") || flagStr(args, "png") !== undefined;
|
|
215045
215274
|
const frames = readMetaFrames(ctx.designDir);
|
|
215046
|
-
const outDir = flagStr(args, "out-dir") ??
|
|
215275
|
+
const outDir = flagStr(args, "out-dir") ?? join27(ctx.designDir, ".snapshots");
|
|
215047
215276
|
const single = targets.length === 1 && requested.length === 1 && !png2 && !allStates && !flagStr(args, "out-dir");
|
|
215048
215277
|
const onConsole = flagBool(args, "logs") ? (level, text) => logErr(`${c.dim(`[${level}]`)} ${text}`) : undefined;
|
|
215049
215278
|
if (!single)
|
|
215050
|
-
|
|
215279
|
+
mkdirSync19(outDir, { recursive: true });
|
|
215051
215280
|
for (const id of targets) {
|
|
215052
215281
|
const [width, height] = frames.screens[id]?.size ?? frames.defaultSize;
|
|
215053
215282
|
let plan = [{ state: undefined, bases: [id] }];
|
|
215054
215283
|
if (discover) {
|
|
215055
|
-
const { states, canonical } = discover(
|
|
215284
|
+
const { states, canonical } = discover(readFileSync23(join27(ctx.designDir, "screens", `${id}.ts`), "utf8"));
|
|
215056
215285
|
if (stateFlag !== undefined) {
|
|
215057
215286
|
if (!states.includes(stateFlag)) {
|
|
215058
215287
|
if (requested.length === 1)
|
|
@@ -215093,11 +215322,11 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
215093
215322
|
for (const w of pngResult.warnings)
|
|
215094
215323
|
warnErr(`${tag}: ${w}`);
|
|
215095
215324
|
for (const base of bases) {
|
|
215096
|
-
|
|
215097
|
-
log(`${c.green("✓")} ${tag} → ${
|
|
215325
|
+
writeFileSync20(join27(outDir, `${base}.json`), result.text);
|
|
215326
|
+
log(`${c.green("✓")} ${tag} → ${join27(outDir, `${base}.json`)}`);
|
|
215098
215327
|
if (pngResult) {
|
|
215099
|
-
|
|
215100
|
-
log(`${c.green("✓")} ${tag} → ${
|
|
215328
|
+
writeFileSync20(join27(outDir, `${base}.png`), pngResult.png);
|
|
215329
|
+
log(`${c.green("✓")} ${tag} → ${join27(outDir, `${base}.png`)}`);
|
|
215101
215330
|
}
|
|
215102
215331
|
}
|
|
215103
215332
|
}
|
|
@@ -215105,12 +215334,7 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
215105
215334
|
}, serve = async (ctx, args) => {
|
|
215106
215335
|
const designServer = await loadDesignServer(ctx);
|
|
215107
215336
|
const port = Number(flagStr(args, "port")) || DEFAULT_PORT;
|
|
215108
|
-
|
|
215109
|
-
try {
|
|
215110
|
-
renderer = await import("lecodes-renderer/headless");
|
|
215111
|
-
} catch {
|
|
215112
|
-
renderer = null;
|
|
215113
|
-
}
|
|
215337
|
+
const renderer = await loadPeer("lecodes-renderer", "headless", { for: "lecodes design (render_screen)", install: false });
|
|
215114
215338
|
const renderScreen = renderer ? async (id, format, state) => {
|
|
215115
215339
|
const r2 = renderer;
|
|
215116
215340
|
const js = await compileDesignScreen({
|
|
@@ -215399,17 +215623,14 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
|
|
|
215399
215623
|
note("Read them with: lecodes design comments");
|
|
215400
215624
|
}, flushDesignComments = async (args) => {
|
|
215401
215625
|
const ctx = getContext(args);
|
|
215402
|
-
if (!
|
|
215626
|
+
if (!existsSync26(ctx.designDir))
|
|
215403
215627
|
return null;
|
|
215404
215628
|
const platform = optionalPlatform(ctx);
|
|
215405
215629
|
if (!platform)
|
|
215406
215630
|
return null;
|
|
215407
|
-
|
|
215408
|
-
|
|
215409
|
-
designServer = await import("lecodes-design/server");
|
|
215410
|
-
} catch {
|
|
215631
|
+
const designServer = await loadPeer("lecodes-design", "server", { for: "lecodes design", install: false });
|
|
215632
|
+
if (!designServer)
|
|
215411
215633
|
return null;
|
|
215412
|
-
}
|
|
215413
215634
|
if (typeof designServer.queuedThreads !== "function")
|
|
215414
215635
|
return null;
|
|
215415
215636
|
if (typeof designServer.settleQueued !== "function" || designServer.settleQueued.length < 4)
|
|
@@ -215516,6 +215737,7 @@ var init_design = __esm(() => {
|
|
|
215516
215737
|
init_browserAuth();
|
|
215517
215738
|
init_designTemplates();
|
|
215518
215739
|
init_util();
|
|
215740
|
+
init_peerInstall();
|
|
215519
215741
|
init_config();
|
|
215520
215742
|
init_api();
|
|
215521
215743
|
init_types4();
|
|
@@ -216538,74 +216760,9 @@ var require_main = __commonJS((exports, module) => {
|
|
|
216538
216760
|
}
|
|
216539
216761
|
};
|
|
216540
216762
|
});
|
|
216541
|
-
|
|
216542
|
-
|
|
216543
|
-
|
|
216544
|
-
version: "0.20.0",
|
|
216545
|
-
dependencies: {
|
|
216546
|
-
"@letary/chisel": "^0.10.0",
|
|
216547
|
-
jimp: "^1.6.1",
|
|
216548
|
-
"lecodes-sdk": "workspace:*"
|
|
216549
|
-
},
|
|
216550
|
-
devDependencies: {
|
|
216551
|
-
"lecodes-design": "link:lecodes-design",
|
|
216552
|
-
"lecodes-renderer": "workspace:*",
|
|
216553
|
-
"qrcode-terminal": "^0.12.0",
|
|
216554
|
-
"lecodes-3d-editor": "link:lecodes-3d-editor",
|
|
216555
|
-
"lecodes-assets": "workspace:*",
|
|
216556
|
-
"lecodes-creator-nav": "workspace:*",
|
|
216557
|
-
"lecodes-viewer-lite": "workspace:*"
|
|
216558
|
-
},
|
|
216559
|
-
peerDependencies: {
|
|
216560
|
-
"lecodes-design": "^0.9.0",
|
|
216561
|
-
"lecodes-renderer": "^0.20.0",
|
|
216562
|
-
"lecodes-3d-editor": "^0.7.0",
|
|
216563
|
-
"lecodes-assets": "^0.2.1"
|
|
216564
|
-
},
|
|
216565
|
-
bin: {
|
|
216566
|
-
lecodes: "dist/index.js"
|
|
216567
|
-
},
|
|
216568
|
-
description: "Clone, edit and push LeCodes projects from your machine.",
|
|
216569
|
-
engines: {
|
|
216570
|
-
node: ">=18"
|
|
216571
|
-
},
|
|
216572
|
-
files: [
|
|
216573
|
-
"dist",
|
|
216574
|
-
"src",
|
|
216575
|
-
"runtime",
|
|
216576
|
-
"README.md"
|
|
216577
|
-
],
|
|
216578
|
-
keywords: [
|
|
216579
|
-
"lecodes",
|
|
216580
|
-
"cli"
|
|
216581
|
-
],
|
|
216582
|
-
license: "MIT",
|
|
216583
|
-
peerDependenciesMeta: {
|
|
216584
|
-
"lecodes-design": {
|
|
216585
|
-
optional: true
|
|
216586
|
-
},
|
|
216587
|
-
"lecodes-renderer": {
|
|
216588
|
-
optional: true
|
|
216589
|
-
},
|
|
216590
|
-
"lecodes-3d-editor": {
|
|
216591
|
-
optional: true
|
|
216592
|
-
},
|
|
216593
|
-
"lecodes-assets": {
|
|
216594
|
-
optional: true
|
|
216595
|
-
}
|
|
216596
|
-
},
|
|
216597
|
-
scripts: {
|
|
216598
|
-
icon: "bun scripts/make-icon.ts",
|
|
216599
|
-
"vendor-runtime": "bun scripts/vendor-runtime.ts",
|
|
216600
|
-
"sync-peers": "bun scripts/sync-peer-versions.ts",
|
|
216601
|
-
build: 'bun scripts/vendor-runtime.ts && bun build src/index.ts --target=node --outfile dist/index.js --external @letary/chisel --external lecodes-renderer --external lecodes-design --external lecodes-3d-editor --external lecodes-assets --banner="#!/usr/bin/env node"',
|
|
216602
|
-
"build:bin": "bun scripts/build-bin.ts",
|
|
216603
|
-
typecheck: "tsc --noEmit",
|
|
216604
|
-
dev: "bun run src/index.ts",
|
|
216605
|
-
prepublishOnly: "bun run sync-peers && bun run build"
|
|
216606
|
-
},
|
|
216607
|
-
type: "module"
|
|
216608
|
-
};
|
|
216763
|
+
|
|
216764
|
+
// src/index.ts
|
|
216765
|
+
init_package();
|
|
216609
216766
|
|
|
216610
216767
|
// src/commands/app.ts
|
|
216611
216768
|
init_manifest();
|
|
@@ -216769,6 +216926,11 @@ var semverNewer = (a, b) => {
|
|
|
216769
216926
|
return (pb[i] ?? 0) - (pa[i] ?? 0);
|
|
216770
216927
|
return 0;
|
|
216771
216928
|
};
|
|
216929
|
+
var newestForPlatform = (versions, key) => {
|
|
216930
|
+
const carrying = versions.filter((v) => v.files[key]).sort((a, b) => semverNewer(a.version, b.version));
|
|
216931
|
+
const newest = carrying[0];
|
|
216932
|
+
return newest ? { version: newest.version, file: newest.files[key] } : null;
|
|
216933
|
+
};
|
|
216772
216934
|
var fetchReleaseProduct = async (product, label) => {
|
|
216773
216935
|
let manifest;
|
|
216774
216936
|
try {
|
|
@@ -216873,12 +217035,15 @@ var installedMatcVersions = () => {
|
|
|
216873
217035
|
}
|
|
216874
217036
|
};
|
|
216875
217037
|
var fetchMatcManifest = () => fetchReleaseProduct("matc", "matc (shader compiler)");
|
|
216876
|
-
var
|
|
217038
|
+
var pickMatc = (versions) => {
|
|
216877
217039
|
const key = platformKey();
|
|
216878
|
-
const
|
|
216879
|
-
if (!
|
|
216880
|
-
throw new CliError(`matc
|
|
217040
|
+
const hit = newestForPlatform(versions, key);
|
|
217041
|
+
if (!hit) {
|
|
217042
|
+
throw new CliError(`matc isn't published for ${key} — push the project once so shaders compile on the ` + `server, or set LECODES_MATC to a locally built matc.`);
|
|
216881
217043
|
}
|
|
217044
|
+
return hit;
|
|
217045
|
+
};
|
|
217046
|
+
var downloadMatc = async (version, file, dest) => {
|
|
216882
217047
|
note(`Downloading the shader compiler (matc ${version}, ${(file.size / 1024 / 1024).toFixed(1)} MB)…`);
|
|
216883
217048
|
await downloadReleaseArchive(file, {
|
|
216884
217049
|
cacheRoot: cacheRoot(),
|
|
@@ -216900,24 +217065,26 @@ var resolveMatcExe = async () => {
|
|
|
216900
217065
|
const cached = resolveMatcExeCached();
|
|
216901
217066
|
if (cached)
|
|
216902
217067
|
return cached;
|
|
216903
|
-
const {
|
|
216904
|
-
const
|
|
216905
|
-
|
|
217068
|
+
const { versions } = await fetchMatcManifest();
|
|
217069
|
+
const { version, file } = pickMatc(versions);
|
|
217070
|
+
const dir = join12(cacheRoot(), `matc-${version}`);
|
|
217071
|
+
await downloadMatc(version, file, dir);
|
|
216906
217072
|
const exe = findMatc(dir);
|
|
216907
217073
|
if (!exe)
|
|
216908
217074
|
throw new CliError(`matc cache at ${dir} is broken — delete the folder and retry.`);
|
|
216909
217075
|
return exe;
|
|
216910
217076
|
};
|
|
216911
217077
|
var updateMatc = async () => {
|
|
216912
|
-
const {
|
|
217078
|
+
const { versions } = await fetchMatcManifest();
|
|
217079
|
+
const { version, file } = pickMatc(versions);
|
|
216913
217080
|
const before = installedMatcVersions()[0];
|
|
216914
|
-
const dir = join12(cacheRoot(), `matc-${
|
|
217081
|
+
const dir = join12(cacheRoot(), `matc-${version}`);
|
|
216915
217082
|
if (existsSync10(dir) && findMatc(dir) !== null) {
|
|
216916
|
-
note(`Already up to date: matc ${
|
|
217083
|
+
note(`Already up to date: matc ${version}.`);
|
|
216917
217084
|
return;
|
|
216918
217085
|
}
|
|
216919
|
-
await downloadMatc(
|
|
216920
|
-
note(before ? `matc updated to ${
|
|
217086
|
+
await downloadMatc(version, file, dir);
|
|
217087
|
+
note(before ? `matc updated to ${version} (was ${before}).` : `matc ${version} installed.`);
|
|
216921
217088
|
};
|
|
216922
217089
|
|
|
216923
217090
|
// src/compile/shaders.ts
|
|
@@ -220133,6 +220300,8 @@ var windowEnv = (opts) => {
|
|
|
220133
220300
|
env.CREATOR_MIN_WINDOW_SIZE = `${opts.minWidth}x${opts.minHeight}`;
|
|
220134
220301
|
if (opts.title)
|
|
220135
220302
|
env.CREATOR_WINDOW_TITLE = opts.title;
|
|
220303
|
+
if (opts.storageScope)
|
|
220304
|
+
env.CREATOR_STORAGE_SCOPE = opts.storageScope;
|
|
220136
220305
|
if (opts.resizable === false)
|
|
220137
220306
|
env.CREATOR_WINDOW_FIXED = "1";
|
|
220138
220307
|
if (opts.fullscreen !== undefined)
|
|
@@ -220148,6 +220317,12 @@ var windowEnv = (opts) => {
|
|
|
220148
220317
|
}
|
|
220149
220318
|
if (opts.fps)
|
|
220150
220319
|
env.CREATOR_FPS = "1";
|
|
220320
|
+
if (opts.mouseEmulateTouch !== undefined && process.env.CREATOR_MOUSE_TOUCH === undefined) {
|
|
220321
|
+
env.CREATOR_MOUSE_TOUCH = opts.mouseEmulateTouch ? "1" : "0";
|
|
220322
|
+
}
|
|
220323
|
+
if (opts.virtualKeyboard !== undefined && process.env.CREATOR_VIRTUAL_KEYBOARD === undefined) {
|
|
220324
|
+
env.CREATOR_VIRTUAL_KEYBOARD = typeof opts.virtualKeyboard === "boolean" ? opts.virtualKeyboard ? "1" : "0" : JSON.stringify(opts.virtualKeyboard);
|
|
220325
|
+
}
|
|
220151
220326
|
return env;
|
|
220152
220327
|
};
|
|
220153
220328
|
var netArgs = (net) => {
|
|
@@ -220783,7 +220958,15 @@ var buildDesktop = async (args) => {
|
|
|
220783
220958
|
assetCount++;
|
|
220784
220959
|
}
|
|
220785
220960
|
note(` assets: ${assetCount} files, ${mb2(assetBytes)} → ${ASSETS_DIR}/`);
|
|
220786
|
-
|
|
220961
|
+
const desktop = config.desktop;
|
|
220962
|
+
writeJson(join20(out, "app.json"), {
|
|
220963
|
+
name,
|
|
220964
|
+
...desktop ? { desktop: {
|
|
220965
|
+
window: desktop.window,
|
|
220966
|
+
...desktop.mouseEmulateTouch !== undefined ? { mouseEmulateTouch: desktop.mouseEmulateTouch } : {},
|
|
220967
|
+
...desktop.virtualKeyboard !== undefined ? { virtualKeyboard: desktop.virtualKeyboard } : {}
|
|
220968
|
+
} } : {}
|
|
220969
|
+
});
|
|
220787
220970
|
if (config.icon) {
|
|
220788
220971
|
await writeIco(root, config, join20(out, "app.ico"));
|
|
220789
220972
|
note(" app.ico rendered from " + config.icon);
|
|
@@ -221741,7 +221924,7 @@ from \`main.ts\`.
|
|
|
221741
221924
|
|
|
221742
221925
|
\`\`\`
|
|
221743
221926
|
lecodes render # semantic JSON of the launched UI (structure, text, names, values)
|
|
221744
|
-
lecodes render --png shot.png # pixels (
|
|
221927
|
+
lecodes render --png shot.png # pixels (the renderer + @napi-rs/canvas install themselves on first use; --yes skips the prompt)
|
|
221745
221928
|
lecodes render --png card.png --clip card # ONE component/region: a node name or x,y,w,h
|
|
221746
221929
|
lecodes render src/screens/cart.ts # ONE screen module, without booting the app: any file that
|
|
221747
221930
|
# \`export default\`s a UIScreen (or a (state) => UIScreen —
|
|
@@ -221761,6 +221944,11 @@ lecodes render --desktop --script <file> # the same scenario on the NATIVE host
|
|
|
221761
221944
|
lecodes test # all tests/*.flow.json + design-derived flows; run before "done"
|
|
221762
221945
|
lecodes test --desktop # the file flows on the NATIVE host (3D/Jolt/real GPU; coordinate
|
|
221763
221946
|
# flows only — design flows stay headless)
|
|
221947
|
+
lecodes dev --android --time 12 --png phone.png # the REAL phone over adb (USB): launches the app on
|
|
221948
|
+
# the dev url via adb reverse, streams device logs as [device:log],
|
|
221949
|
+
# screenshots after 12 s, force-stops, exit 1 on a crash. --serial
|
|
221950
|
+
# <id> with several devices, --apk <file> to install a build first.
|
|
221951
|
+
# Without --time it runs until Ctrl+C (which stops the app).
|
|
221764
221952
|
\`\`\`
|
|
221765
221953
|
|
|
221766
221954
|
Iterate with \`--script\` scenarios and keep the useful ones as \`tests/<name>.flow.json\` — they
|
|
@@ -221815,6 +222003,13 @@ channel for game state that has no UI.
|
|
|
221815
222003
|
with \`"fixtures": "fixtures.json"\` in its envelope.
|
|
221816
222004
|
- \`fill\` fills every empty input — use it before tapping a disabled-until-filled submit.
|
|
221817
222005
|
|
|
222006
|
+
## 3D scenes
|
|
222007
|
+
|
|
222008
|
+
- A HUD over a \`Scene\` is a widget ATTACHED to it: \`const hud = UIWidget(...).style({ left: 16,
|
|
222009
|
+
bottom: 16, pointerEvents: "none" }); hud.attachTo(scene); hud.show()\`. Never a \`UIScreen\`:
|
|
222010
|
+
a screen is a destination, so \`UIScreen(...).open()\` after \`scene.open()\` CLOSES the scene
|
|
222011
|
+
(the host logs "3D scene closed" and you see only the screen) — no background makes it transparent.
|
|
222012
|
+
|
|
221818
222013
|
## SDK documentation
|
|
221819
222014
|
|
|
221820
222015
|
\`.lecodes/types/\` is the exact API surface (signatures only; generated and gitignored — run
|
|
@@ -221938,10 +222133,13 @@ var run = async (args) => {
|
|
|
221938
222133
|
minHeight: win?.minHeight,
|
|
221939
222134
|
resizable: win?.resizable,
|
|
221940
222135
|
title: flagStr(args, "title") ?? win?.title ?? appCfg?.name,
|
|
222136
|
+
storageScope: appCfg?.name,
|
|
221941
222137
|
fullscreen: flagBool(args, "fullscreen") ? true : flagBool(args, "windowed") ? false : win?.fullscreen,
|
|
221942
222138
|
scale: resolveScale(flagStr(args, "scale"), win?.scale),
|
|
221943
222139
|
renderScale: resolveRenderScale(flagStr(args, "render-scale"), appCfg?.desktop?.renderScale),
|
|
221944
222140
|
maxRenderSize: appCfg?.desktop?.maxRenderSize,
|
|
222141
|
+
mouseEmulateTouch: appCfg?.desktop?.mouseEmulateTouch,
|
|
222142
|
+
virtualKeyboard: appCfg?.desktop?.virtualKeyboard,
|
|
221945
222143
|
fps: flagBool(args, "fps"),
|
|
221946
222144
|
net: flagBool(args, "server") ? { role: "server", port: Number(flagStr(args, "port")) || undefined, maxClients: Number(flagStr(args, "max-clients")) || undefined } : flagStr(args, "connect") ? { role: "client", address: flagStr(args, "connect") } : undefined
|
|
221947
222145
|
});
|
|
@@ -221985,16 +222183,16 @@ init_design();
|
|
|
221985
222183
|
// src/commands/dev.ts
|
|
221986
222184
|
init_manifest();
|
|
221987
222185
|
init_ignore();
|
|
221988
|
-
import { existsSync as
|
|
222186
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync20, readFileSync as readFileSync24, watch, writeFileSync as writeFileSync22 } from "node:fs";
|
|
221989
222187
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
221990
222188
|
import { networkInterfaces } from "node:os";
|
|
221991
|
-
import { basename as basename12, join as
|
|
222189
|
+
import { basename as basename12, join as join31 } from "node:path";
|
|
221992
222190
|
|
|
221993
222191
|
// src/dev/devServer.ts
|
|
221994
222192
|
init_localFiles();
|
|
221995
222193
|
import { createServer as createServer2 } from "node:http";
|
|
221996
222194
|
import { createReadStream, statSync as statSync10 } from "node:fs";
|
|
221997
|
-
import { join as
|
|
222195
|
+
import { join as join28 } from "node:path";
|
|
221998
222196
|
init_projectCompile();
|
|
221999
222197
|
init_fonts();
|
|
222000
222198
|
init_assetIcons();
|
|
@@ -222273,14 +222471,14 @@ var sendText = (res, status2, type, body) => {
|
|
|
222273
222471
|
res.end(body);
|
|
222274
222472
|
};
|
|
222275
222473
|
var sendJson = (res, status2, body) => sendText(res, status2, "application/json", JSON.stringify(body));
|
|
222276
|
-
var readBody = (req) => new Promise((
|
|
222474
|
+
var readBody = (req) => new Promise((resolve12) => {
|
|
222277
222475
|
let body = "";
|
|
222278
222476
|
req.on("data", (c4) => {
|
|
222279
222477
|
if (body.length < 256 * 1024)
|
|
222280
222478
|
body += c4.toString("utf8");
|
|
222281
222479
|
});
|
|
222282
|
-
req.on("end", () =>
|
|
222283
|
-
req.on("error", () =>
|
|
222480
|
+
req.on("end", () => resolve12(body));
|
|
222481
|
+
req.on("error", () => resolve12(body));
|
|
222284
222482
|
});
|
|
222285
222483
|
var startDevServer = async (options) => {
|
|
222286
222484
|
const { root, manifest, name, origin, token, entryOverride } = options;
|
|
@@ -222361,8 +222559,8 @@ var startDevServer = async (options) => {
|
|
|
222361
222559
|
const t2 = url.searchParams.get("t");
|
|
222362
222560
|
return t2 === "poll" ? "poll" : t2 === "web" ? "web" : "ws";
|
|
222363
222561
|
};
|
|
222364
|
-
const withPrelude = (js, url, v2) => {
|
|
222365
|
-
const transport = transportOf(url);
|
|
222562
|
+
const withPrelude = (js, url, v2, forced) => {
|
|
222563
|
+
const transport = forced ?? transportOf(url);
|
|
222366
222564
|
return transport === "web" ? js : js + `
|
|
222367
222565
|
` + renderPrelude({ origin, base, transport, v: v2 });
|
|
222368
222566
|
};
|
|
@@ -222381,6 +222579,9 @@ var startDevServer = async (options) => {
|
|
|
222381
222579
|
if (route === "/dev.js") {
|
|
222382
222580
|
return sendText(res, 200, "text/javascript", withPrelude(devJs, url, -1));
|
|
222383
222581
|
}
|
|
222582
|
+
if (route === "/dev-poll.js") {
|
|
222583
|
+
return sendText(res, 200, "text/javascript", withPrelude(devJs, url, -1, "poll"));
|
|
222584
|
+
}
|
|
222384
222585
|
if (route === "/bundle.js") {
|
|
222385
222586
|
if (bundleJs === null)
|
|
222386
222587
|
return sendText(res, 503, "text/plain", "No successful compile yet — check the terminal.");
|
|
@@ -222398,7 +222599,7 @@ var startDevServer = async (options) => {
|
|
|
222398
222599
|
const rel = route === "/web/" ? "embed.html" : route.slice("/web/".length);
|
|
222399
222600
|
if (rel.split("/").some((seg) => seg === ".." || seg === "" || seg === "."))
|
|
222400
222601
|
return sendText(res, 400, "text/plain", "Bad path");
|
|
222401
|
-
const abs =
|
|
222602
|
+
const abs = join28(webDir, ...rel.split("/"));
|
|
222402
222603
|
try {
|
|
222403
222604
|
if (!statSync10(abs).isFile())
|
|
222404
222605
|
throw new Error("not a file");
|
|
@@ -222421,7 +222622,7 @@ var startDevServer = async (options) => {
|
|
|
222421
222622
|
const name2 = route.slice("/shaders/".length);
|
|
222422
222623
|
if (!isShaderArtifactName(name2))
|
|
222423
222624
|
return sendText(res, 400, "text/plain", "Bad path");
|
|
222424
|
-
const abs =
|
|
222625
|
+
const abs = join28(shadersCacheDir(root), name2);
|
|
222425
222626
|
try {
|
|
222426
222627
|
if (!statSync10(abs).isFile())
|
|
222427
222628
|
throw new Error("not a file");
|
|
@@ -222483,11 +222684,11 @@ var startDevServer = async (options) => {
|
|
|
222483
222684
|
if (conn)
|
|
222484
222685
|
clients.add(conn);
|
|
222485
222686
|
});
|
|
222486
|
-
await new Promise((
|
|
222687
|
+
await new Promise((resolve12, reject) => {
|
|
222487
222688
|
server2.once("error", reject);
|
|
222488
222689
|
server2.listen(options.port, options.bind ?? "0.0.0.0", () => {
|
|
222489
222690
|
server2.removeListener("error", reject);
|
|
222490
|
-
|
|
222691
|
+
resolve12();
|
|
222491
222692
|
});
|
|
222492
222693
|
});
|
|
222493
222694
|
const address = server2.address();
|
|
@@ -222510,23 +222711,146 @@ var startDevServer = async (options) => {
|
|
|
222510
222711
|
|
|
222511
222712
|
// src/dev/webRunner.ts
|
|
222512
222713
|
init_distRoot();
|
|
222513
|
-
import { existsSync as
|
|
222514
|
-
import { createRequire as
|
|
222515
|
-
import { dirname as
|
|
222714
|
+
import { existsSync as existsSync27 } from "node:fs";
|
|
222715
|
+
import { createRequire as createRequire6 } from "node:module";
|
|
222716
|
+
import { dirname as dirname15, join as join29 } from "node:path";
|
|
222516
222717
|
var resolveWebRunnerDir = () => {
|
|
222517
|
-
const candidates = [
|
|
222718
|
+
const candidates = [join29(distRoot, "runtime", "web")];
|
|
222518
222719
|
try {
|
|
222519
|
-
const pkg =
|
|
222520
|
-
candidates.push(
|
|
222720
|
+
const pkg = createRequire6(import.meta.url).resolve("lecodes-viewer-lite/package.json");
|
|
222721
|
+
candidates.push(join29(dirname15(pkg), "dist-embed"));
|
|
222521
222722
|
} catch {}
|
|
222522
222723
|
for (const dir of candidates)
|
|
222523
|
-
if (
|
|
222724
|
+
if (existsSync27(join29(dir, "embed.html")) && existsSync27(join29(dir, "embed.js")))
|
|
222524
222725
|
return dir;
|
|
222525
222726
|
return null;
|
|
222526
222727
|
};
|
|
222527
222728
|
|
|
222528
222729
|
// src/commands/dev.ts
|
|
222529
222730
|
init_browserAuth();
|
|
222731
|
+
|
|
222732
|
+
// src/dev/androidDev.ts
|
|
222733
|
+
init_util();
|
|
222734
|
+
import { existsSync as existsSync28, writeFileSync as writeFileSync21 } from "node:fs";
|
|
222735
|
+
import { join as join30 } from "node:path";
|
|
222736
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
222737
|
+
var DEFAULT_ANDROID_PACKAGE = "com.letary.lecodes";
|
|
222738
|
+
var adbCandidates = () => {
|
|
222739
|
+
const out = [];
|
|
222740
|
+
if (process.env.LECODES_ADB)
|
|
222741
|
+
out.push(process.env.LECODES_ADB);
|
|
222742
|
+
out.push("adb");
|
|
222743
|
+
const exe = process.platform === "win32" ? "adb.exe" : "adb";
|
|
222744
|
+
for (const home of [process.env.ANDROID_HOME, process.env.ANDROID_SDK_ROOT]) {
|
|
222745
|
+
if (home)
|
|
222746
|
+
out.push(join30(home, "platform-tools", exe));
|
|
222747
|
+
}
|
|
222748
|
+
if (process.platform === "win32" && process.env.LOCALAPPDATA)
|
|
222749
|
+
out.push(join30(process.env.LOCALAPPDATA, "Android", "Sdk", "platform-tools", exe));
|
|
222750
|
+
if (process.env.HOME)
|
|
222751
|
+
out.push(join30(process.env.HOME, "Android", "Sdk", "platform-tools", exe), join30(process.env.HOME, "Library", "Android", "sdk", "platform-tools", exe));
|
|
222752
|
+
return out;
|
|
222753
|
+
};
|
|
222754
|
+
var run2 = (adb, args, input) => {
|
|
222755
|
+
const r2 = spawnSync7(adb, args, { encoding: "utf8", input, windowsHide: true, maxBuffer: 64 * 1024 * 1024 });
|
|
222756
|
+
const out = `${r2.stdout ?? ""}${r2.stderr ?? ""}`.trim();
|
|
222757
|
+
return { ok: r2.status === 0, out };
|
|
222758
|
+
};
|
|
222759
|
+
var resolveAdb = () => {
|
|
222760
|
+
for (const cand of adbCandidates()) {
|
|
222761
|
+
if (cand !== "adb" && !existsSync28(cand))
|
|
222762
|
+
continue;
|
|
222763
|
+
const r2 = spawnSync7(cand, ["version"], { encoding: "utf8", windowsHide: true });
|
|
222764
|
+
if (r2.status === 0)
|
|
222765
|
+
return cand;
|
|
222766
|
+
}
|
|
222767
|
+
throw new CliError("adb not found — install Android platform-tools (or point LECODES_ADB at adb).");
|
|
222768
|
+
};
|
|
222769
|
+
var listDevices = (adb) => {
|
|
222770
|
+
const { out } = run2(adb, ["devices", "-l"]);
|
|
222771
|
+
const devices = [];
|
|
222772
|
+
for (const line of out.split(/\r?\n/).slice(1)) {
|
|
222773
|
+
const m = /^(\S+)\s+(\S+)(.*)$/.exec(line.trim());
|
|
222774
|
+
if (!m)
|
|
222775
|
+
continue;
|
|
222776
|
+
const model = /model:(\S+)/.exec(m[3] ?? "")?.[1];
|
|
222777
|
+
devices.push({ serial: m[1], state: m[2], model });
|
|
222778
|
+
}
|
|
222779
|
+
return devices;
|
|
222780
|
+
};
|
|
222781
|
+
var pickDevice = (adb, serial) => {
|
|
222782
|
+
const all = listDevices(adb);
|
|
222783
|
+
if (serial) {
|
|
222784
|
+
const d = all.find((x2) => x2.serial === serial);
|
|
222785
|
+
if (!d)
|
|
222786
|
+
throw new CliError(`No device "${serial}" — adb sees: ${all.map((x2) => `${x2.serial} (${x2.state})`).join(", ") || "nothing"}.`);
|
|
222787
|
+
if (d.state !== "device")
|
|
222788
|
+
throw new CliError(`Device ${serial} is "${d.state}" — accept the USB-debugging prompt on the phone.`);
|
|
222789
|
+
return serial;
|
|
222790
|
+
}
|
|
222791
|
+
const ready = all.filter((d) => d.state === "device");
|
|
222792
|
+
if (ready.length === 1)
|
|
222793
|
+
return ready[0].serial;
|
|
222794
|
+
if (ready.length === 0) {
|
|
222795
|
+
const other = all.map((d) => `${d.serial} (${d.state})`).join(", ");
|
|
222796
|
+
throw new CliError(other ? `No usable device: adb sees ${other}. "unauthorized" = accept the USB-debugging prompt on the phone; "offline" = replug.` : "No Android device: enable USB debugging (Developer options), plug the phone in, accept the prompt — or `adb connect <ip>:5555` for Wi-Fi debugging.");
|
|
222797
|
+
}
|
|
222798
|
+
throw new CliError(`Several devices: ${ready.map((d) => `${d.serial}${d.model ? ` (${d.model})` : ""}`).join(", ")} — pick one with --serial <id>.`);
|
|
222799
|
+
};
|
|
222800
|
+
var launchOnAndroid = (opts) => {
|
|
222801
|
+
const adb = resolveAdb();
|
|
222802
|
+
const serial = pickDevice(adb, opts.serial);
|
|
222803
|
+
const packageName = opts.packageName ?? process.env.LECODES_ANDROID_PACKAGE ?? DEFAULT_ANDROID_PACKAGE;
|
|
222804
|
+
const dev = (args) => run2(adb, ["-s", serial, ...args]);
|
|
222805
|
+
if (opts.apk) {
|
|
222806
|
+
if (!existsSync28(opts.apk))
|
|
222807
|
+
throw new CliError(`--apk: ${opts.apk} does not exist.`);
|
|
222808
|
+
note(`Installing ${opts.apk}…`);
|
|
222809
|
+
const r2 = dev(["install", "-r", opts.apk]);
|
|
222810
|
+
if (!r2.ok || !/Success/.test(r2.out)) {
|
|
222811
|
+
throw new CliError(`adb install failed: ${r2.out}${/USER_RESTRICTED/.test(r2.out) ? `
|
|
222812
|
+
(MIUI: enable "Install via USB" in Developer options.)` : ""}`);
|
|
222813
|
+
}
|
|
222814
|
+
}
|
|
222815
|
+
const installed = dev(["shell", "pm", "path", packageName]);
|
|
222816
|
+
if (!installed.ok || !installed.out.includes("package:")) {
|
|
222817
|
+
throw new CliError(`${packageName} is not installed on ${serial} — pass --apk <file> (lecodes-android: .\\gradlew :app:assembleDebug → app/build/outputs/apk/debug/app-debug.apk).`);
|
|
222818
|
+
}
|
|
222819
|
+
const rev = dev(["reverse", `tcp:${opts.port}`, `tcp:${opts.port}`]);
|
|
222820
|
+
if (!rev.ok)
|
|
222821
|
+
warnErr(`adb reverse failed (${rev.out}) — the phone must reach the LAN url instead.`);
|
|
222822
|
+
dev(["shell", "am", "force-stop", packageName]);
|
|
222823
|
+
dev(["logcat", "-c"]);
|
|
222824
|
+
const start = dev(["shell", "am", "start", "-n", `${packageName}/.MainActivity`, "-a", "android.intent.action.VIEW", "-d", opts.url]);
|
|
222825
|
+
if (!start.ok || /Error/.test(start.out))
|
|
222826
|
+
throw new CliError(`Could not launch ${packageName}: ${start.out}`);
|
|
222827
|
+
let stopped = false;
|
|
222828
|
+
return {
|
|
222829
|
+
serial,
|
|
222830
|
+
packageName,
|
|
222831
|
+
adb,
|
|
222832
|
+
screenshot: (file) => {
|
|
222833
|
+
const r2 = spawnSync7(adb, ["-s", serial, "exec-out", "screencap", "-p"], { windowsHide: true, maxBuffer: 64 * 1024 * 1024 });
|
|
222834
|
+
if (r2.status !== 0 || !r2.stdout || r2.stdout.length === 0)
|
|
222835
|
+
return false;
|
|
222836
|
+
writeFileSync21(file, r2.stdout);
|
|
222837
|
+
return true;
|
|
222838
|
+
},
|
|
222839
|
+
stop: () => {
|
|
222840
|
+
if (stopped)
|
|
222841
|
+
return;
|
|
222842
|
+
stopped = true;
|
|
222843
|
+
dev(["shell", "am", "force-stop", packageName]);
|
|
222844
|
+
dev(["reverse", "--remove", `tcp:${opts.port}`]);
|
|
222845
|
+
}
|
|
222846
|
+
};
|
|
222847
|
+
};
|
|
222848
|
+
var androidCrashLines = (session) => {
|
|
222849
|
+
const { out } = run2(session.adb, ["-s", session.serial, "logcat", "-d", "-v", "brief"]);
|
|
222850
|
+
return out.split(/\r?\n/).filter((l2) => /FATAL EXCEPTION|Fatal signal|\[crash\]/.test(l2) || /AndroidRuntime/.test(l2) && l2.includes(session.packageName)).slice(-20);
|
|
222851
|
+
};
|
|
222852
|
+
|
|
222853
|
+
// src/commands/dev.ts
|
|
222530
222854
|
init_util();
|
|
222531
222855
|
var DEFAULT_PORT2 = 4499;
|
|
222532
222856
|
var DEBOUNCE_MS = 120;
|
|
@@ -222554,24 +222878,24 @@ var lanCandidates = () => {
|
|
|
222554
222878
|
return out.sort((x2, y2) => x2.rank - y2.rank).map(({ address, iface }) => ({ address, iface }));
|
|
222555
222879
|
};
|
|
222556
222880
|
var loadDevToken = (root) => {
|
|
222557
|
-
const path2 =
|
|
222881
|
+
const path2 = join31(root, LECODES_DIR, "dev.json");
|
|
222558
222882
|
try {
|
|
222559
|
-
const token2 = JSON.parse(
|
|
222883
|
+
const token2 = JSON.parse(readFileSync24(path2, "utf8")).token;
|
|
222560
222884
|
if (token2 && /^[a-z0-9]{8,}$/.test(token2))
|
|
222561
222885
|
return token2;
|
|
222562
222886
|
} catch {}
|
|
222563
222887
|
const token = randomBytes3(6).toString("hex");
|
|
222564
|
-
|
|
222565
|
-
|
|
222888
|
+
mkdirSync20(join31(root, LECODES_DIR), { recursive: true });
|
|
222889
|
+
writeFileSync22(path2, JSON.stringify({ token }, null, 2) + `
|
|
222566
222890
|
`);
|
|
222567
222891
|
return token;
|
|
222568
222892
|
};
|
|
222569
222893
|
var printQr = async (url) => {
|
|
222570
222894
|
try {
|
|
222571
222895
|
const qrcode = (await Promise.resolve().then(() => __toESM(require_main(), 1))).default;
|
|
222572
|
-
await new Promise((
|
|
222896
|
+
await new Promise((resolve12) => qrcode.generate(url, { small: true }, (out) => {
|
|
222573
222897
|
log(out);
|
|
222574
|
-
|
|
222898
|
+
resolve12();
|
|
222575
222899
|
}));
|
|
222576
222900
|
} catch {
|
|
222577
222901
|
note("(couldn't render a QR code — open the URL below on the device)");
|
|
@@ -222645,6 +222969,7 @@ ${first.error}`);
|
|
|
222645
222969
|
return true;
|
|
222646
222970
|
};
|
|
222647
222971
|
const debug2 = process.env.LECODES_DEV_DEBUG === "1";
|
|
222972
|
+
const underBun = typeof globalThis.Bun !== "undefined";
|
|
222648
222973
|
let timer = null;
|
|
222649
222974
|
const onChange = (file) => {
|
|
222650
222975
|
if (debug2)
|
|
@@ -222687,7 +223012,7 @@ ${result.error}`);
|
|
|
222687
223012
|
if (alternatives.length > 0) {
|
|
222688
223013
|
note(`Device can't reach it? This machine also has ${alternatives.map((a2) => `${a2.address} (${a2.iface})`).join(", ")} — retry with --host <ip>.`);
|
|
222689
223014
|
}
|
|
222690
|
-
if (
|
|
223015
|
+
if (existsSync29(join31(root, LECODES_DIR, "manifest.json")))
|
|
222691
223016
|
note(`Token stored in ${LECODES_DIR}/dev.json (delete it to rotate the URL).`);
|
|
222692
223017
|
log("");
|
|
222693
223018
|
log(`Watching ${root} — save a file to hot-reload. Ctrl+C to stop.`);
|
|
@@ -222695,6 +223020,48 @@ ${result.error}`);
|
|
|
222695
223020
|
note("Opening the browser…");
|
|
222696
223021
|
openBrowser(webLocal);
|
|
222697
223022
|
}
|
|
223023
|
+
if (flagBool(args, "android")) {
|
|
223024
|
+
const seconds = Number(flagStr(args, "time")) || 0;
|
|
223025
|
+
const png2 = flagStr(args, "png");
|
|
223026
|
+
const deviceUrl = `http://127.0.0.1:${port}${server2.url.slice(origin.length)}`.replace(/\/dev\.js$/, underBun ? "/dev-poll.js" : "/dev.js");
|
|
223027
|
+
let session;
|
|
223028
|
+
try {
|
|
223029
|
+
session = launchOnAndroid({ port, url: deviceUrl, serial: flagStr(args, "serial"), apk: flagStr(args, "apk"), packageName: flagStr(args, "package") });
|
|
223030
|
+
} catch (e2) {
|
|
223031
|
+
server2.close();
|
|
223032
|
+
watcher?.close();
|
|
223033
|
+
throw e2;
|
|
223034
|
+
}
|
|
223035
|
+
note(`Android: ${session.serial} → ${deviceUrl} (adb reverse; device logs stream here as [device:log])`);
|
|
223036
|
+
const finish = (code) => {
|
|
223037
|
+
session.stop();
|
|
223038
|
+
server2.close();
|
|
223039
|
+
watcher?.close();
|
|
223040
|
+
process.exit(code);
|
|
223041
|
+
};
|
|
223042
|
+
process.on("SIGINT", () => {
|
|
223043
|
+
note("Stopping the app on the phone…");
|
|
223044
|
+
finish(0);
|
|
223045
|
+
});
|
|
223046
|
+
if (seconds > 0) {
|
|
223047
|
+
setTimeout(() => {
|
|
223048
|
+
const crashes = androidCrashLines(session);
|
|
223049
|
+
if (png2) {
|
|
223050
|
+
if (session.screenshot(png2))
|
|
223051
|
+
note(`Wrote ${png2} (device screenshot after ${seconds} s)`);
|
|
223052
|
+
else
|
|
223053
|
+
warnErr(`Screenshot failed (adb screencap).`);
|
|
223054
|
+
}
|
|
223055
|
+
if (crashes.length > 0) {
|
|
223056
|
+
warnErr(`The app crashed on the phone:
|
|
223057
|
+
${crashes.join(`
|
|
223058
|
+
`)}`);
|
|
223059
|
+
finish(1);
|
|
223060
|
+
}
|
|
223061
|
+
finish(0);
|
|
223062
|
+
}, seconds * 1000);
|
|
223063
|
+
}
|
|
223064
|
+
}
|
|
222698
223065
|
if (flagBool(args, "desktop")) {
|
|
222699
223066
|
const appCfg = readAppConfigAt(root);
|
|
222700
223067
|
const win = appCfg?.desktop?.window;
|
|
@@ -222720,7 +223087,7 @@ init_localFiles();
|
|
|
222720
223087
|
|
|
222721
223088
|
// src/serverDiff.ts
|
|
222722
223089
|
init_localFiles();
|
|
222723
|
-
import { readFileSync as
|
|
223090
|
+
import { readFileSync as readFileSync25 } from "node:fs";
|
|
222724
223091
|
import { basename as basename13 } from "node:path";
|
|
222725
223092
|
var isTextAsset = (t2) => t2 === "text" || t2 === "shader" || t2 === "scene";
|
|
222726
223093
|
var computeServerDiff = (project, scan2) => {
|
|
@@ -222739,7 +223106,7 @@ var computeServerDiff = (project, scan2) => {
|
|
|
222739
223106
|
const local = localByPath.get(path2);
|
|
222740
223107
|
if (local && !server2) {
|
|
222741
223108
|
const text = isTextKind(classifyKind(basename13(path2)));
|
|
222742
|
-
entries.push({ path: path2, status: "added", binary: !text, serverText: "", localText: text ?
|
|
223109
|
+
entries.push({ path: path2, status: "added", binary: !text, serverText: "", localText: text ? readFileSync25(local.absPath, "utf8") : undefined });
|
|
222743
223110
|
continue;
|
|
222744
223111
|
}
|
|
222745
223112
|
if (server2 && !local) {
|
|
@@ -222750,12 +223117,12 @@ var computeServerDiff = (project, scan2) => {
|
|
|
222750
223117
|
if (server2 && local) {
|
|
222751
223118
|
if (isTextAsset(server2.type)) {
|
|
222752
223119
|
const serverText = server2.text ?? "";
|
|
222753
|
-
const localText =
|
|
223120
|
+
const localText = readFileSync25(local.absPath, "utf8");
|
|
222754
223121
|
if (serverText !== localText)
|
|
222755
223122
|
entries.push({ path: path2, status: "modified", binary: false, serverText, localText });
|
|
222756
223123
|
} else {
|
|
222757
223124
|
const serverSize = server2.file?.size;
|
|
222758
|
-
const localSize = Buffer.byteLength(
|
|
223125
|
+
const localSize = Buffer.byteLength(readFileSync25(local.absPath));
|
|
222759
223126
|
if (serverSize !== undefined && serverSize !== localSize) {
|
|
222760
223127
|
entries.push({ path: path2, status: "modified", binary: true });
|
|
222761
223128
|
}
|
|
@@ -222913,8 +223280,8 @@ var statusLabel = (e2) => e2.status === "added" ? c.green(" added ") : e2.st
|
|
|
222913
223280
|
init_manifest();
|
|
222914
223281
|
init_types4();
|
|
222915
223282
|
init_ignore();
|
|
222916
|
-
import { existsSync as
|
|
222917
|
-
import { basename as basename14, dirname as
|
|
223283
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync21, writeFileSync as writeFileSync23 } from "node:fs";
|
|
223284
|
+
import { basename as basename14, dirname as dirname16, join as join32, resolve as resolve12 } from "node:path";
|
|
222918
223285
|
init_util();
|
|
222919
223286
|
var STARTER2 = `// Welcome to LeCodes — the whole SDK is available as globals, no imports needed.
|
|
222920
223287
|
// Run it live on a device with \`lecodes dev\`, or bundle it with \`lecodes compile -o app.js\`.
|
|
@@ -223022,18 +223389,18 @@ Full command list: \`lecodes --help\`.
|
|
|
223022
223389
|
`;
|
|
223023
223390
|
var init2 = async (args) => {
|
|
223024
223391
|
const dirArg = (args._[0] ?? ".").replace(/[/\\]+$/, "") || ".";
|
|
223025
|
-
const root =
|
|
223392
|
+
const root = resolve12(dirArg);
|
|
223026
223393
|
const name = basename14(root);
|
|
223027
|
-
|
|
223394
|
+
mkdirSync21(root, { recursive: true });
|
|
223028
223395
|
let created = 0;
|
|
223029
223396
|
const write = (rel, content) => {
|
|
223030
|
-
const abs =
|
|
223031
|
-
if (
|
|
223397
|
+
const abs = join32(root, rel);
|
|
223398
|
+
if (existsSync30(abs)) {
|
|
223032
223399
|
note(`kept ${rel}`);
|
|
223033
223400
|
return;
|
|
223034
223401
|
}
|
|
223035
|
-
|
|
223036
|
-
|
|
223402
|
+
mkdirSync21(dirname16(abs), { recursive: true });
|
|
223403
|
+
writeFileSync23(abs, content);
|
|
223037
223404
|
log(`${c.green("created")} ${rel}`);
|
|
223038
223405
|
created++;
|
|
223039
223406
|
};
|
|
@@ -223080,8 +223447,8 @@ init_config();
|
|
|
223080
223447
|
init_ignore();
|
|
223081
223448
|
init_localFiles();
|
|
223082
223449
|
init_manifest();
|
|
223083
|
-
import { existsSync as
|
|
223084
|
-
import { basename as basename16, join as
|
|
223450
|
+
import { existsSync as existsSync31 } from "node:fs";
|
|
223451
|
+
import { basename as basename16, join as join33, resolve as resolve13 } from "node:path";
|
|
223085
223452
|
init_types4();
|
|
223086
223453
|
init_util();
|
|
223087
223454
|
|
|
@@ -223091,7 +223458,7 @@ init_config();
|
|
|
223091
223458
|
init_manifest();
|
|
223092
223459
|
init_localFiles();
|
|
223093
223460
|
import { basename as basename15 } from "node:path";
|
|
223094
|
-
import { readFileSync as
|
|
223461
|
+
import { readFileSync as readFileSync26 } from "node:fs";
|
|
223095
223462
|
init_util();
|
|
223096
223463
|
var push = async (args) => {
|
|
223097
223464
|
const root = findProjectRoot(process.cwd());
|
|
@@ -223152,9 +223519,9 @@ var push = async (args) => {
|
|
|
223152
223519
|
if (source)
|
|
223153
223520
|
entry.assetId = source.id;
|
|
223154
223521
|
if (isTextKind(kind)) {
|
|
223155
|
-
entry.text =
|
|
223522
|
+
entry.text = readFileSync26(file.absPath, "utf8");
|
|
223156
223523
|
} else if (!source || source.sha !== file.sha) {
|
|
223157
|
-
const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename15(file.path),
|
|
223524
|
+
const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename15(file.path), readFileSync26(file.absPath));
|
|
223158
223525
|
entry.blobFileId = blob.id;
|
|
223159
223526
|
uploaded++;
|
|
223160
223527
|
}
|
|
@@ -223261,10 +223628,10 @@ var link = async (args) => {
|
|
|
223261
223628
|
const apiUrl = requireApiUrl(config);
|
|
223262
223629
|
const token = requireToken(config);
|
|
223263
223630
|
const dirArg = args._[0];
|
|
223264
|
-
const root = dirArg ?
|
|
223265
|
-
if (!
|
|
223631
|
+
const root = dirArg ? resolve13(dirArg) : process.cwd();
|
|
223632
|
+
if (!existsSync31(root))
|
|
223266
223633
|
throw new CliError(`No such directory: ${root}`);
|
|
223267
|
-
if (
|
|
223634
|
+
if (existsSync31(join33(root, LECODES_DIR, "manifest.json")) && !flagBool(args, "force")) {
|
|
223268
223635
|
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.");
|
|
223269
223636
|
}
|
|
223270
223637
|
let enclosing = null;
|
|
@@ -223328,10 +223695,10 @@ var link = async (args) => {
|
|
|
223328
223695
|
|
|
223329
223696
|
// src/commands/lightmap.ts
|
|
223330
223697
|
init_manifest();
|
|
223331
|
-
import { existsSync as
|
|
223332
|
-
import { spawn as spawn6, spawnSync as
|
|
223698
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync22, mkdtempSync as mkdtempSync4, readFileSync as readFileSync27, renameSync as renameSync3, rmSync as rmSync9, statSync as statSync11, unlinkSync, writeFileSync as writeFileSync24 } from "node:fs";
|
|
223699
|
+
import { spawn as spawn6, spawnSync as spawnSync8 } from "node:child_process";
|
|
223333
223700
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
223334
|
-
import { join as
|
|
223701
|
+
import { join as join34, relative as relative4, resolve as resolve14 } from "node:path";
|
|
223335
223702
|
init_util();
|
|
223336
223703
|
var num = (args, key, fallback, min, max) => {
|
|
223337
223704
|
const raw = flagStr(args, key);
|
|
@@ -223344,20 +223711,20 @@ var num = (args, key, fallback, min, max) => {
|
|
|
223344
223711
|
};
|
|
223345
223712
|
var findToktx = () => {
|
|
223346
223713
|
const env = process.env.KTX_TOKTX;
|
|
223347
|
-
if (env &&
|
|
223714
|
+
if (env && existsSync32(env))
|
|
223348
223715
|
return env;
|
|
223349
|
-
for (const base of [process.env.LOCALAPPDATA &&
|
|
223716
|
+
for (const base of [process.env.LOCALAPPDATA && join34(process.env.LOCALAPPDATA, "Programs"), process.env.ProgramFiles]) {
|
|
223350
223717
|
if (!base)
|
|
223351
223718
|
continue;
|
|
223352
|
-
const guess =
|
|
223353
|
-
if (
|
|
223719
|
+
const guess = join34(base, "KTX-Software", "bin", "toktx.exe");
|
|
223720
|
+
if (existsSync32(guess))
|
|
223354
223721
|
return guess;
|
|
223355
223722
|
}
|
|
223356
223723
|
return "toktx";
|
|
223357
223724
|
};
|
|
223358
223725
|
var checkToktx = (toktx) => {
|
|
223359
223726
|
try {
|
|
223360
|
-
const p2 =
|
|
223727
|
+
const p2 = spawnSync8(toktx, ["--version"], { encoding: "utf8" });
|
|
223361
223728
|
if (p2.status === 0)
|
|
223362
223729
|
return (p2.stdout ?? "").toString().trim() || "toktx";
|
|
223363
223730
|
} catch {}
|
|
@@ -223371,7 +223738,7 @@ var requireToktx = () => {
|
|
|
223371
223738
|
}
|
|
223372
223739
|
return toktx;
|
|
223373
223740
|
};
|
|
223374
|
-
var encodeKtx2 = (toktx, png2, ktx2) => new Promise((
|
|
223741
|
+
var encodeKtx2 = (toktx, png2, ktx2) => new Promise((resolve15, reject) => {
|
|
223375
223742
|
const args = ["--t2", "--encode", "uastc", "--uastc_quality", "2", "--zcmp", "18", "--genmipmap", "--assign_oetf", "srgb", ktx2, png2];
|
|
223376
223743
|
const p2 = spawn6(toktx, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
223377
223744
|
let out = "";
|
|
@@ -223384,7 +223751,7 @@ var encodeKtx2 = (toktx, png2, ktx2) => new Promise((resolve14, reject) => {
|
|
|
223384
223751
|
p2.on("error", (e2) => reject(new CliError(`toktx failed (${e2.message}) while encoding ${ktx2}`)));
|
|
223385
223752
|
p2.on("close", (status2) => {
|
|
223386
223753
|
if (status2 === 0)
|
|
223387
|
-
|
|
223754
|
+
resolve15();
|
|
223388
223755
|
else
|
|
223389
223756
|
reject(new CliError(`toktx failed (${status2 ?? "?"}) while encoding ${ktx2}:
|
|
223390
223757
|
${out.trim()}`));
|
|
@@ -223399,13 +223766,13 @@ var bake = async (args) => {
|
|
|
223399
223766
|
root = process.cwd();
|
|
223400
223767
|
}
|
|
223401
223768
|
const toktx = requireToktx();
|
|
223402
|
-
const outDir =
|
|
223769
|
+
const outDir = resolve14(root, flagStr(args, "out") ?? "assets/lightmap");
|
|
223403
223770
|
const stem = flagStr(args, "name") ?? "lightmap";
|
|
223404
223771
|
if (!/^[A-Za-z0-9_-]+$/.test(stem))
|
|
223405
223772
|
throw new CliError(`--name wants a plain file stem (letters, digits, - and _), got "${stem}".`);
|
|
223406
223773
|
const entryFlag = flagStr(args, "entry");
|
|
223407
223774
|
const entryFile = entryFlag ?? "main.ts";
|
|
223408
|
-
if (!
|
|
223775
|
+
if (!existsSync32(resolve14(root, entryFile))) {
|
|
223409
223776
|
throw new CliError(`No ${entryFile} in ${root} — pass --entry <file> (the file that builds the scene and calls Lightmap.load).`);
|
|
223410
223777
|
}
|
|
223411
223778
|
const cfg = {
|
|
@@ -223437,7 +223804,7 @@ var bake = async (args) => {
|
|
|
223437
223804
|
const entryText = `globalThis.__lecodesLightmap = ${JSON.stringify(cfg)}
|
|
223438
223805
|
import ${JSON.stringify(importPath)}
|
|
223439
223806
|
`;
|
|
223440
|
-
|
|
223807
|
+
mkdirSync22(outDir, { recursive: true });
|
|
223441
223808
|
ensurePlaceholders(outDir, stem);
|
|
223442
223809
|
const desktopRenderer = resolveDesktopRenderer(root, args);
|
|
223443
223810
|
const { js, resources } = await buildBundle(args, undefined, {
|
|
@@ -223446,8 +223813,8 @@ import ${JSON.stringify(importPath)}
|
|
|
223446
223813
|
extraEntries: [{ path: entryPath, type: "text", text: entryText }],
|
|
223447
223814
|
entryOverride: entryPath
|
|
223448
223815
|
});
|
|
223449
|
-
const scratch = mkdtempSync4(
|
|
223450
|
-
const outPng =
|
|
223816
|
+
const scratch = mkdtempSync4(join34(tmpdir4(), "lecodes-lightmap-"));
|
|
223817
|
+
const outPng = join34(scratch, "frame.png");
|
|
223451
223818
|
note(`Baking up to ${cfg.pages} × ${cfg.size}² at ${(cfg.texel * 100).toFixed(1)} cm texels (${cfg.sunRays} sun + ${cfg.aoRays} AO + ${cfg.lightRays} light rays)${cfg.volumeCell ? ` + a ${cfg.volumeCell} m light volume` : ""} → ${relative4(root, outDir) || "."}/${stem}.*…`);
|
|
223452
223819
|
let output = "";
|
|
223453
223820
|
try {
|
|
@@ -223481,18 +223848,18 @@ import ${JSON.stringify(importPath)}
|
|
|
223481
223848
|
logErr(text.startsWith("error") ? c.red(`✗ ${text}`) : text.startsWith("warning") ? c.yellow(` ${text}`) : c.dim(` ${text}`));
|
|
223482
223849
|
}
|
|
223483
223850
|
if (/\[lightmap\] done/.test(output)) {
|
|
223484
|
-
const bakePath =
|
|
223485
|
-
if (!
|
|
223851
|
+
const bakePath = join34(outDir, "lightmap.bake");
|
|
223852
|
+
if (!existsSync32(bakePath))
|
|
223486
223853
|
throw new CliError(`The host reported a bake but ${bakePath} is missing.`);
|
|
223487
|
-
const data = JSON.parse(
|
|
223854
|
+
const data = JSON.parse(readFileSync27(bakePath, "utf8"));
|
|
223488
223855
|
const pages = Math.max(1, data.pages ?? 1);
|
|
223489
223856
|
const hasLight = !!data.lightTextures?.length;
|
|
223490
223857
|
const t0 = Date.now();
|
|
223491
223858
|
let pngKb = 0, ktxKb = 0;
|
|
223492
223859
|
const encodePage = async (page, light) => {
|
|
223493
|
-
const png2 =
|
|
223494
|
-
const ktx2 =
|
|
223495
|
-
if (!
|
|
223860
|
+
const png2 = join34(outDir, pageFile("lightmap", page, light, ".png"));
|
|
223861
|
+
const ktx2 = join34(outDir, pageFile(stem, page, light));
|
|
223862
|
+
if (!existsSync32(png2))
|
|
223496
223863
|
throw new CliError(`The host reported a bake but ${png2} is missing.`);
|
|
223497
223864
|
await encodeKtx2(toktx, png2, ktx2);
|
|
223498
223865
|
pngKb += statSync11(png2).size / 1024;
|
|
@@ -223508,15 +223875,15 @@ import ${JSON.stringify(importPath)}
|
|
|
223508
223875
|
await Promise.all(encodes);
|
|
223509
223876
|
for (let p2 = pages;p2 < 16; p2++) {
|
|
223510
223877
|
for (const light of [false, true]) {
|
|
223511
|
-
const stale =
|
|
223512
|
-
if (
|
|
223513
|
-
|
|
223878
|
+
const stale = join34(outDir, pageFile(stem, p2, light));
|
|
223879
|
+
if (existsSync32(stale) && statSync11(stale).size !== PLACEHOLDER_KTX2.length)
|
|
223880
|
+
writeFileSync24(stale, PLACEHOLDER_KTX2);
|
|
223514
223881
|
}
|
|
223515
223882
|
}
|
|
223516
223883
|
if (!hasLight) {
|
|
223517
|
-
const light =
|
|
223518
|
-
if (
|
|
223519
|
-
|
|
223884
|
+
const light = join34(outDir, pageFile(stem, 0, true));
|
|
223885
|
+
if (existsSync32(light) && statSync11(light).size !== PLACEHOLDER_KTX2.length)
|
|
223886
|
+
writeFileSync24(light, PLACEHOLDER_KTX2);
|
|
223520
223887
|
}
|
|
223521
223888
|
data.texture = pageFile(stem, 0, false);
|
|
223522
223889
|
data.textures = Array.from({ length: pages }, (_, p2) => pageFile(stem, p2, false));
|
|
@@ -223524,19 +223891,19 @@ import ${JSON.stringify(importPath)}
|
|
|
223524
223891
|
data.lightTextures = Array.from({ length: pages }, (_, p2) => pageFile(stem, p2, true));
|
|
223525
223892
|
if (data.volume)
|
|
223526
223893
|
data.volume.file = `${stem}.volume`;
|
|
223527
|
-
|
|
223894
|
+
writeFileSync24(join34(outDir, `${stem}.bake`), JSON.stringify(data, null, 1) + `
|
|
223528
223895
|
`);
|
|
223529
223896
|
if (stem !== "lightmap")
|
|
223530
223897
|
unlinkSync(bakePath);
|
|
223531
|
-
const volumeSrc =
|
|
223532
|
-
const hasVolume = cfg.volumeCell > 0 &&
|
|
223898
|
+
const volumeSrc = join34(outDir, "lightmap.volume");
|
|
223899
|
+
const hasVolume = cfg.volumeCell > 0 && existsSync32(volumeSrc);
|
|
223533
223900
|
if (hasVolume && stem !== "lightmap") {
|
|
223534
|
-
rmSync9(
|
|
223535
|
-
renameSync3(volumeSrc,
|
|
223901
|
+
rmSync9(join34(outDir, `${stem}.volume`), { force: true });
|
|
223902
|
+
renameSync3(volumeSrc, join34(outDir, `${stem}.volume`));
|
|
223536
223903
|
}
|
|
223537
223904
|
logErr(c.dim(` encoded ${pages} page(s)${hasLight ? " + the light atlas" : ""} (UASTC + mips, ${Math.round(ktxKb)} KB from ${Math.round(pngKb)} KB of rasters) in ${((Date.now() - t0) / 1000).toFixed(1)} s`));
|
|
223538
223905
|
logErr(c.green(`✓ baked → ${relative4(root, outDir) || "."}/${stem}.bake + ${data.textures.join(", ")}${hasLight ? ` + ${data.lightTextures.join(", ")}` : ""}${hasVolume ? ` + ${stem}.volume` : ""}`));
|
|
223539
|
-
const rel = (f3) => relative4(root,
|
|
223906
|
+
const rel = (f3) => relative4(root, join34(outDir, f3)).replace(/\\/g, "/");
|
|
223540
223907
|
const assets = (files) => files.length === 1 ? `asset('<rel>/${rel(files[0])}')` : `[${files.map((f3) => `asset('<rel>/${rel(f3)}')`).join(", ")}]`;
|
|
223541
223908
|
logErr(c.dim(` scene file: env.lightmap: { data: asset('<rel>/${rel(`${stem}.bake`)}'), texture: ${assets(data.textures)}${hasLight ? `, light: ${assets(data.lightTextures)}` : ""}${hasVolume ? `, volume: asset('<rel>/${rel(`${stem}.volume`)}')` : ""} } — asset() paths are relative to the scene file`));
|
|
223542
223909
|
return;
|
|
@@ -223566,20 +223933,20 @@ var placeholderVolume = () => {
|
|
|
223566
223933
|
return b;
|
|
223567
223934
|
};
|
|
223568
223935
|
var ensurePlaceholders = (outDir, stem) => {
|
|
223569
|
-
const missing = (f3) => !
|
|
223570
|
-
const bake2 =
|
|
223571
|
-
const ktx2 =
|
|
223572
|
-
const light =
|
|
223573
|
-
const volume =
|
|
223936
|
+
const missing = (f3) => !existsSync32(f3) || statSync11(f3).size === 0;
|
|
223937
|
+
const bake2 = join34(outDir, `${stem}.bake`);
|
|
223938
|
+
const ktx2 = join34(outDir, pageFile(stem, 0, false));
|
|
223939
|
+
const light = join34(outDir, pageFile(stem, 0, true));
|
|
223940
|
+
const volume = join34(outDir, `${stem}.volume`);
|
|
223574
223941
|
if (missing(bake2))
|
|
223575
|
-
|
|
223942
|
+
writeFileSync24(bake2, JSON.stringify({ version: 2, placeholder: true, size: 1, texel: 0, pages: 1, instances: [] }) + `
|
|
223576
223943
|
`);
|
|
223577
223944
|
if (missing(ktx2))
|
|
223578
|
-
|
|
223945
|
+
writeFileSync24(ktx2, PLACEHOLDER_KTX2);
|
|
223579
223946
|
if (missing(light))
|
|
223580
|
-
|
|
223947
|
+
writeFileSync24(light, PLACEHOLDER_KTX2);
|
|
223581
223948
|
if (missing(volume))
|
|
223582
|
-
|
|
223949
|
+
writeFileSync24(volume, placeholderVolume());
|
|
223583
223950
|
};
|
|
223584
223951
|
var lightmap = async (args) => {
|
|
223585
223952
|
const sub = args._[0];
|
|
@@ -223590,9 +223957,9 @@ var lightmap = async (args) => {
|
|
|
223590
223957
|
|
|
223591
223958
|
// src/commands/navmesh.ts
|
|
223592
223959
|
init_manifest();
|
|
223593
|
-
import { existsSync as
|
|
223960
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync23, mkdtempSync as mkdtempSync5, readdirSync as readdirSync15, readFileSync as readFileSync28, rmSync as rmSync10, statSync as statSync12, unlinkSync as unlinkSync2, writeFileSync as writeFileSync25 } from "node:fs";
|
|
223594
223961
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
223595
|
-
import { basename as basename17, join as
|
|
223962
|
+
import { basename as basename17, join as join35, relative as relative5, resolve as resolve15 } from "node:path";
|
|
223596
223963
|
init_util();
|
|
223597
223964
|
|
|
223598
223965
|
// ../creator-nav/dist/creatorNav.js
|
|
@@ -223601,16 +223968,16 @@ var createCreatorNav = (() => {
|
|
|
223601
223968
|
var moduleRtn;
|
|
223602
223969
|
var Module = moduleArg;
|
|
223603
223970
|
var readyPromiseResolve, readyPromiseReject;
|
|
223604
|
-
var readyPromise = new Promise((
|
|
223605
|
-
readyPromiseResolve =
|
|
223971
|
+
var readyPromise = new Promise((resolve15, reject) => {
|
|
223972
|
+
readyPromiseResolve = resolve15;
|
|
223606
223973
|
readyPromiseReject = reject;
|
|
223607
223974
|
});
|
|
223608
223975
|
var ENVIRONMENT_IS_WEB = typeof window == "object";
|
|
223609
223976
|
var ENVIRONMENT_IS_WORKER = typeof WorkerGlobalScope != "undefined";
|
|
223610
223977
|
var ENVIRONMENT_IS_NODE = typeof process == "object" && process.versions?.node && process.type != "renderer";
|
|
223611
223978
|
if (ENVIRONMENT_IS_NODE) {
|
|
223612
|
-
const { createRequire:
|
|
223613
|
-
var require2 =
|
|
223979
|
+
const { createRequire: createRequire7 } = await import("module");
|
|
223980
|
+
var require2 = createRequire7(import.meta.url);
|
|
223614
223981
|
}
|
|
223615
223982
|
var arguments_ = [];
|
|
223616
223983
|
var thisProgram = "./this.program";
|
|
@@ -223780,9 +224147,9 @@ var createCreatorNav = (() => {
|
|
|
223780
224147
|
}
|
|
223781
224148
|
var info2 = getWasmImports();
|
|
223782
224149
|
if (Module["instantiateWasm"]) {
|
|
223783
|
-
return new Promise((
|
|
224150
|
+
return new Promise((resolve15, reject) => {
|
|
223784
224151
|
Module["instantiateWasm"](info2, (mod, inst) => {
|
|
223785
|
-
|
|
224152
|
+
resolve15(receiveInstance(mod, inst));
|
|
223786
224153
|
});
|
|
223787
224154
|
});
|
|
223788
224155
|
}
|
|
@@ -224138,14 +224505,14 @@ var createCreatorNav = (() => {
|
|
|
224138
224505
|
var __emscripten_stack_restore = wasmExports["_emscripten_stack_restore"];
|
|
224139
224506
|
var __emscripten_stack_alloc = wasmExports["_emscripten_stack_alloc"];
|
|
224140
224507
|
var _emscripten_stack_get_current = wasmExports["emscripten_stack_get_current"];
|
|
224141
|
-
function
|
|
224508
|
+
function run3() {
|
|
224142
224509
|
if (runDependencies > 0) {
|
|
224143
|
-
dependenciesFulfilled =
|
|
224510
|
+
dependenciesFulfilled = run3;
|
|
224144
224511
|
return;
|
|
224145
224512
|
}
|
|
224146
224513
|
preRun();
|
|
224147
224514
|
if (runDependencies > 0) {
|
|
224148
|
-
dependenciesFulfilled =
|
|
224515
|
+
dependenciesFulfilled = run3;
|
|
224149
224516
|
return;
|
|
224150
224517
|
}
|
|
224151
224518
|
function doRun() {
|
|
@@ -224177,7 +224544,7 @@ var createCreatorNav = (() => {
|
|
|
224177
224544
|
}
|
|
224178
224545
|
}
|
|
224179
224546
|
preInit();
|
|
224180
|
-
|
|
224547
|
+
run3();
|
|
224181
224548
|
moduleRtn = readyPromise;
|
|
224182
224549
|
return moduleRtn;
|
|
224183
224550
|
};
|
|
@@ -224389,9 +224756,9 @@ var num2 = (args, key, fallback, min, max) => {
|
|
|
224389
224756
|
return n2;
|
|
224390
224757
|
};
|
|
224391
224758
|
var guessName = (root) => {
|
|
224392
|
-
const dirs = [
|
|
224759
|
+
const dirs = [join35(root, "src"), root];
|
|
224393
224760
|
for (const dir of dirs) {
|
|
224394
|
-
if (!
|
|
224761
|
+
if (!existsSync33(dir))
|
|
224395
224762
|
continue;
|
|
224396
224763
|
let entries;
|
|
224397
224764
|
try {
|
|
@@ -224403,7 +224770,7 @@ var guessName = (root) => {
|
|
|
224403
224770
|
if (!f3.endsWith(".scene.ts"))
|
|
224404
224771
|
continue;
|
|
224405
224772
|
try {
|
|
224406
|
-
if (/\bnavmesh\s*:/.test(
|
|
224773
|
+
if (/\bnavmesh\s*:/.test(readFileSync28(join35(dir, f3), "utf8")))
|
|
224407
224774
|
return f3.slice(0, -".scene.ts".length);
|
|
224408
224775
|
} catch {}
|
|
224409
224776
|
}
|
|
@@ -224432,7 +224799,7 @@ var writeObj = (path2, tris) => {
|
|
|
224432
224799
|
lines.push(`v ${tris[i2].toFixed(4)} ${tris[i2 + 1].toFixed(4)} ${tris[i2 + 2].toFixed(4)}`);
|
|
224433
224800
|
for (let i2 = 0;i2 < tris.length / 3; i2 += 3)
|
|
224434
224801
|
lines.push(`f ${i2 + 1} ${i2 + 2} ${i2 + 3}`);
|
|
224435
|
-
|
|
224802
|
+
writeFileSync25(path2, lines.join(`
|
|
224436
224803
|
`) + `
|
|
224437
224804
|
`);
|
|
224438
224805
|
};
|
|
@@ -224443,10 +224810,10 @@ var bake2 = async (args) => {
|
|
|
224443
224810
|
} catch {
|
|
224444
224811
|
root = process.cwd();
|
|
224445
224812
|
}
|
|
224446
|
-
const outDir =
|
|
224813
|
+
const outDir = resolve15(root, flagStr(args, "out") ?? "assets/nav");
|
|
224447
224814
|
const entryFlag = flagStr(args, "entry");
|
|
224448
224815
|
const entryFile = entryFlag ?? "main.ts";
|
|
224449
|
-
if (!
|
|
224816
|
+
if (!existsSync33(resolve15(root, entryFile))) {
|
|
224450
224817
|
throw new CliError(`No ${entryFile} in ${root} — pass --entry <file> (the file that builds the level: a scene file with env.navmesh, or code calling NavMesh.load).`);
|
|
224451
224818
|
}
|
|
224452
224819
|
const name = flagStr(args, "name") ?? guessName(root) ?? "scene";
|
|
@@ -224457,10 +224824,10 @@ var bake2 = async (args) => {
|
|
|
224457
224824
|
const logs = flagBool(args, "logs");
|
|
224458
224825
|
const keepGeometry = flagBool(args, "keep-geometry");
|
|
224459
224826
|
const obj = flagBool(args, "obj");
|
|
224460
|
-
|
|
224461
|
-
const navmeshPath =
|
|
224462
|
-
if (!
|
|
224463
|
-
|
|
224827
|
+
mkdirSync23(outDir, { recursive: true });
|
|
224828
|
+
const navmeshPath = join35(outDir, `${name}.navmesh`);
|
|
224829
|
+
if (!existsSync33(navmeshPath))
|
|
224830
|
+
writeFileSync25(navmeshPath, placeholderLnav());
|
|
224464
224831
|
const importPath = "./" + entryFile.replace(/\\/g, "/").replace(/\.tsx?$/, "");
|
|
224465
224832
|
const entryPath = "/.navmesh-entry.ts";
|
|
224466
224833
|
const cfg = { outDir: outDir.replace(/\\/g, "/"), name };
|
|
@@ -224474,8 +224841,8 @@ import ${JSON.stringify(importPath)}
|
|
|
224474
224841
|
extraEntries: [{ path: entryPath, type: "text", text: entryText }],
|
|
224475
224842
|
entryOverride: entryPath
|
|
224476
224843
|
});
|
|
224477
|
-
const scratch = mkdtempSync5(
|
|
224478
|
-
const outPng =
|
|
224844
|
+
const scratch = mkdtempSync5(join35(tmpdir5(), "lecodes-navmesh-"));
|
|
224845
|
+
const outPng = join35(scratch, "frame.png");
|
|
224479
224846
|
note(`Dumping the level's static collision geometry through the desktop host → ${relative5(root, outDir) || "."}/${name}.navgeo…`);
|
|
224480
224847
|
let output = "";
|
|
224481
224848
|
try {
|
|
@@ -224528,10 +224895,10 @@ import ${JSON.stringify(importPath)}
|
|
|
224528
224895
|
}
|
|
224529
224896
|
if (!config)
|
|
224530
224897
|
throw new CliError("The host reported a dump but no `[navmesh] config` line — SDK/host mismatch?");
|
|
224531
|
-
const geoPath =
|
|
224532
|
-
if (!
|
|
224898
|
+
const geoPath = join35(outDir, `${name}.navgeo`);
|
|
224899
|
+
if (!existsSync33(geoPath))
|
|
224533
224900
|
throw new CliError(`The host reported a dump but ${geoPath} is missing.`);
|
|
224534
|
-
const geo = readNgeo(new Uint8Array(
|
|
224901
|
+
const geo = readNgeo(new Uint8Array(readFileSync28(geoPath)));
|
|
224535
224902
|
const params = {
|
|
224536
224903
|
agentRadius: num2(args, "radius", config.agent.radius, 0.05, 10),
|
|
224537
224904
|
agentHeight: num2(args, "height", config.agent.height, 0.1, 20),
|
|
@@ -224577,9 +224944,9 @@ import ${JSON.stringify(importPath)}
|
|
|
224577
224944
|
if (result.polys === 0) {
|
|
224578
224945
|
throw new CliError("The build produced no polygons — nothing walkable for this agent. Check the cell size against the level's scale, the slope, and that the floor is a STATIC body (Shape + Physics { motion: 'static' }).");
|
|
224579
224946
|
}
|
|
224580
|
-
|
|
224947
|
+
writeFileSync25(navmeshPath, result.data);
|
|
224581
224948
|
if (obj) {
|
|
224582
|
-
const objPath =
|
|
224949
|
+
const objPath = join35(outDir, `${name}.navmesh.obj`);
|
|
224583
224950
|
writeObj(objPath, navMeshTriangles(mod, result.data));
|
|
224584
224951
|
logErr(c.dim(` wrote ${relative5(root, objPath)} (the polygon mesh, for inspection)`));
|
|
224585
224952
|
}
|
|
@@ -224617,8 +224984,131 @@ var login = async (args) => {
|
|
|
224617
224984
|
|
|
224618
224985
|
// src/commands/shaders.ts
|
|
224619
224986
|
init_manifest();
|
|
224620
|
-
import { readFileSync as
|
|
224987
|
+
import { readFileSync as readFileSync30 } from "node:fs";
|
|
224988
|
+
import { relative as relative7 } from "node:path";
|
|
224621
224989
|
init_util();
|
|
224990
|
+
|
|
224991
|
+
// src/commands/shadersNew.ts
|
|
224992
|
+
init_compile();
|
|
224993
|
+
init_distRoot();
|
|
224994
|
+
init_util();
|
|
224995
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync24, readdirSync as readdirSync16, readFileSync as readFileSync29, writeFileSync as writeFileSync26 } from "node:fs";
|
|
224996
|
+
import { basename as basename18, join as join36, relative as relative6, resolve as resolve16 } from "node:path";
|
|
224997
|
+
var engineMaterialsDir = () => {
|
|
224998
|
+
const candidates = [
|
|
224999
|
+
join36(distRoot, "runtime", "materials"),
|
|
225000
|
+
join36(distRoot, "..", "creator-gl", "materials", "src")
|
|
225001
|
+
];
|
|
225002
|
+
for (const c4 of candidates)
|
|
225003
|
+
if (existsSync34(join36(c4, "unlit.mat")))
|
|
225004
|
+
return c4;
|
|
225005
|
+
return null;
|
|
225006
|
+
};
|
|
225007
|
+
var engineMaterialNames = () => {
|
|
225008
|
+
const dir = engineMaterialsDir();
|
|
225009
|
+
if (!dir)
|
|
225010
|
+
return [];
|
|
225011
|
+
return readdirSync16(dir).filter((f3) => f3.endsWith(".mat") && !f3.startsWith("camera") && f3 !== "shader360.mat").map((f3) => f3.slice(0, -4)).sort();
|
|
225012
|
+
};
|
|
225013
|
+
var NAME_RE = /^[a-z][a-z0-9_-]*$/i;
|
|
225014
|
+
var matName = (name) => name.replace(/-/g, "_");
|
|
225015
|
+
var starterMat = (name) => `// ${name}.mat — a custom material (lecodes shaders new).
|
|
225016
|
+
//
|
|
225017
|
+
// Filament's material language: the \`material\` block declares the parameters and the render state,
|
|
225018
|
+
// \`fragment\` computes the surface. Read it with \`Material.load(asset('./${name}.mat'))\` or the
|
|
225019
|
+
// ${name}.material.ts asset beside it; set parameters with material.set('tint', '#ff8800').
|
|
225020
|
+
// \`lecodes shaders compile\` (or \`lecodes dev\`) compiles it for every host into .lecodes/shaders.
|
|
225021
|
+
//
|
|
225022
|
+
// The comment on a parameter's line is what the scene editor shows for it:
|
|
225023
|
+
// @color a colour picker (also inferred from a name like tint / color / emissive)
|
|
225024
|
+
// @range(min, max) a slider, @step(s) its step, @default(v) the value a new material starts with
|
|
225025
|
+
// the rest of the line is the tooltip
|
|
225026
|
+
|
|
225027
|
+
material {
|
|
225028
|
+
name : ${matName(name)},
|
|
225029
|
+
shadingModel : unlit,
|
|
225030
|
+
parameters : [
|
|
225031
|
+
{ type : float3, name : tint }, // @color @default(#ffffff) the flat colour
|
|
225032
|
+
{ type : float, name : glow }, // @range(0, 2) @step(0.05) @default(1) brightness multiplier
|
|
225033
|
+
{ type : sampler2d, name : map } // the texture, white when unset
|
|
225034
|
+
],
|
|
225035
|
+
requires : [
|
|
225036
|
+
uv0
|
|
225037
|
+
]
|
|
225038
|
+
}
|
|
225039
|
+
|
|
225040
|
+
fragment {
|
|
225041
|
+
void material(inout MaterialInputs material) {
|
|
225042
|
+
prepareMaterial(material);
|
|
225043
|
+
vec4 base = texture(materialParams_map, getUV0());
|
|
225044
|
+
material.baseColor.rgb = base.rgb * materialParams.tint * materialParams.glow;
|
|
225045
|
+
}
|
|
225046
|
+
}
|
|
225047
|
+
`;
|
|
225048
|
+
var refMat = (name, ref, source) => {
|
|
225049
|
+
const renamed = source.replace(/(\bname\s*:\s*)([A-Za-z_][A-Za-z0-9_]*)/, `$1${matName(name)}`);
|
|
225050
|
+
return `// ${name}.mat — started from the engine's ${ref}.mat (lecodes shaders new --ref ${ref}); edit freely,
|
|
225051
|
+
// the engine keeps its own. \`lecodes shaders compile\` builds it for every host into .lecodes/shaders.
|
|
225052
|
+
// Load it with Material.load(asset('./${name}.mat')) or the ${name}.material.ts asset beside it.
|
|
225053
|
+
|
|
225054
|
+
${renamed}`;
|
|
225055
|
+
};
|
|
225056
|
+
var materialAsset = (name, mat) => {
|
|
225057
|
+
const schema = parseShaderSchema(mat);
|
|
225058
|
+
const lines = [];
|
|
225059
|
+
for (const p2 of schema.params) {
|
|
225060
|
+
if (p2.editor === "texture") {
|
|
225061
|
+
lines.push(` // ${p2.name}: asset('./texture.png'), // a sampler takes an image asset`);
|
|
225062
|
+
continue;
|
|
225063
|
+
}
|
|
225064
|
+
if (p2.default === null || p2.default === undefined)
|
|
225065
|
+
continue;
|
|
225066
|
+
lines.push(` ${p2.name}: ${JSON.stringify(p2.default)},${p2.tooltip ? ` // ${p2.tooltip}` : ""}`);
|
|
225067
|
+
}
|
|
225068
|
+
return `// ${name}.material.ts — the material as a project asset: ONE shared instance a scene file takes as
|
|
225069
|
+
// \`material: ${camel(name)}\` and code loads with \`await ${camel(name)}.load()\`. The parameters and their
|
|
225070
|
+
// widgets come from the annotations in ${name}.mat.
|
|
225071
|
+
export default defineMaterial({
|
|
225072
|
+
shader: asset('./${name}.mat'),
|
|
225073
|
+
params: {
|
|
225074
|
+
${lines.join(`
|
|
225075
|
+
`)}
|
|
225076
|
+
},
|
|
225077
|
+
})
|
|
225078
|
+
`;
|
|
225079
|
+
};
|
|
225080
|
+
var camel = (name) => name.replace(/[-_]+(.)/g, (_, ch) => ch.toUpperCase());
|
|
225081
|
+
var writeNewShader = (o2) => {
|
|
225082
|
+
const name = o2.name.endsWith(".mat") ? o2.name.slice(0, -4) : o2.name;
|
|
225083
|
+
if (!NAME_RE.test(name))
|
|
225084
|
+
throw new CliError(`"${o2.name}" is not a shader name — letters, digits, - and _ (starting with a letter), e.g. hologram or rim-light`);
|
|
225085
|
+
const dir = resolve16(o2.cwd ?? process.cwd(), o2.dir ?? ".");
|
|
225086
|
+
const matPath = join36(dir, `${name}.mat`), assetPath = join36(dir, `${name}.material.ts`);
|
|
225087
|
+
if (!o2.force) {
|
|
225088
|
+
for (const p2 of [matPath, assetPath])
|
|
225089
|
+
if (existsSync34(p2))
|
|
225090
|
+
throw new CliError(`${relative6(o2.cwd ?? process.cwd(), p2) || basename18(p2)} exists — pick another name or pass --force to overwrite`);
|
|
225091
|
+
}
|
|
225092
|
+
let mat;
|
|
225093
|
+
let ref = null;
|
|
225094
|
+
if (o2.ref) {
|
|
225095
|
+
ref = o2.ref.endsWith(".mat") ? o2.ref.slice(0, -4) : o2.ref;
|
|
225096
|
+
const srcDir = engineMaterialsDir();
|
|
225097
|
+
if (!srcDir)
|
|
225098
|
+
throw new CliError("this CLI carries no engine material sources (runtime/materials) — --ref needs a CLI built with them");
|
|
225099
|
+
const known = engineMaterialNames();
|
|
225100
|
+
if (!known.includes(ref))
|
|
225101
|
+
throw new CliError(`no engine material "${ref}". Known: ${known.join(", ")}`);
|
|
225102
|
+
mat = refMat(name, ref, readFileSync29(join36(srcDir, `${ref}.mat`), "utf8"));
|
|
225103
|
+
} else
|
|
225104
|
+
mat = starterMat(name);
|
|
225105
|
+
mkdirSync24(dir, { recursive: true });
|
|
225106
|
+
writeFileSync26(matPath, mat);
|
|
225107
|
+
writeFileSync26(assetPath, materialAsset(name, mat));
|
|
225108
|
+
return { mat: matPath, asset: assetPath, ref };
|
|
225109
|
+
};
|
|
225110
|
+
|
|
225111
|
+
// src/commands/shaders.ts
|
|
224622
225112
|
init_compile();
|
|
224623
225113
|
var collectShaders = () => {
|
|
224624
225114
|
let root;
|
|
@@ -224649,7 +225139,7 @@ var status2 = async () => {
|
|
|
224649
225139
|
if (matcId === null)
|
|
224650
225140
|
state = c.dim("unknown (matc not downloaded)");
|
|
224651
225141
|
else {
|
|
224652
|
-
const text =
|
|
225142
|
+
const text = readFileSync30(shader.absPath, "utf8");
|
|
224653
225143
|
if (shaderIsCompiled(root, text, matcId))
|
|
224654
225144
|
state = "compiled";
|
|
224655
225145
|
else {
|
|
@@ -224658,7 +225148,7 @@ var status2 = async () => {
|
|
|
224658
225148
|
}
|
|
224659
225149
|
}
|
|
224660
225150
|
log(` ${shader.path.padEnd(width)} ${state}`);
|
|
224661
|
-
const schema = parseShaderSchema(
|
|
225151
|
+
const schema = parseShaderSchema(readFileSync30(shader.absPath, "utf8"));
|
|
224662
225152
|
for (const p2 of schema.params) {
|
|
224663
225153
|
const range = p2.min !== undefined ? ` [${p2.min}..${p2.max}]` : "";
|
|
224664
225154
|
const def = p2.default !== null && p2.default !== undefined ? ` = ${JSON.stringify(p2.default)}` : "";
|
|
@@ -224701,7 +225191,7 @@ var compile3 = async (args) => {
|
|
|
224701
225191
|
}
|
|
224702
225192
|
const matcExe = await resolveMatcExe();
|
|
224703
225193
|
const matcId = matcIdentity(matcExe);
|
|
224704
|
-
const wasCompiled = new Map(shaders.map((s2) => [s2.path, shaderIsCompiled(root,
|
|
225194
|
+
const wasCompiled = new Map(shaders.map((s2) => [s2.path, shaderIsCompiled(root, readFileSync30(s2.absPath, "utf8"), matcId)]));
|
|
224705
225195
|
const errors2 = [];
|
|
224706
225196
|
for (const shader of shaders) {
|
|
224707
225197
|
try {
|
|
@@ -224723,6 +225213,16 @@ ${c.red("✗")} ${e2}`);
|
|
|
224723
225213
|
const scope = platforms ? ` [${platforms.join(", ")}]` : "";
|
|
224724
225214
|
note(`${shaders.length} shader${shaders.length === 1 ? "" : "s"} compiled${scope} → ${shadersCacheDir(root)}`);
|
|
224725
225215
|
};
|
|
225216
|
+
var newShader = (args) => {
|
|
225217
|
+
const name = args._[1];
|
|
225218
|
+
if (!name)
|
|
225219
|
+
throw new CliError(`lecodes shaders new <name> [--ref ${engineMaterialNames().join("|") || "lit"}] [--dir <folder>] [--force]`);
|
|
225220
|
+
const res = writeNewShader({ name, ref: flagStr(args, "ref"), dir: flagStr(args, "dir"), force: args.flags.force === true });
|
|
225221
|
+
const rel = (p2) => relative7(process.cwd(), p2);
|
|
225222
|
+
log(`${rel(res.mat)} ${c.dim(res.ref ? `(from the engine's ${res.ref}.mat)` : "(the starter template)")}`);
|
|
225223
|
+
log(`${rel(res.asset)} ${c.dim("(defineMaterial asset — a scene takes it as `material:`)")}`);
|
|
225224
|
+
note(`next: edit it, then \`lecodes shaders compile\` (or just \`lecodes dev\`); in code: Material.load(asset('./${name.replace(/\.mat$/, "")}.mat'))`);
|
|
225225
|
+
};
|
|
224726
225226
|
var shaders = async (args) => {
|
|
224727
225227
|
const sub = args._[0];
|
|
224728
225228
|
if (sub === undefined)
|
|
@@ -224731,12 +225231,14 @@ var shaders = async (args) => {
|
|
|
224731
225231
|
return compile3(args);
|
|
224732
225232
|
if (sub === "update")
|
|
224733
225233
|
return updateMatc();
|
|
224734
|
-
|
|
225234
|
+
if (sub === "new")
|
|
225235
|
+
return newShader(args);
|
|
225236
|
+
throw new CliError(`Unknown subcommand "shaders ${sub}". Use \`lecodes shaders\` (status), \`lecodes shaders compile [--platform a,b]\`, \`lecodes shaders new <name> [--ref lit]\` or \`lecodes shaders update\`.`);
|
|
224735
225237
|
};
|
|
224736
225238
|
|
|
224737
225239
|
// src/commands/render.ts
|
|
224738
|
-
import { mkdirSync as
|
|
224739
|
-
import { dirname as
|
|
225240
|
+
import { mkdirSync as mkdirSync25, readFileSync as readFileSync32, writeFileSync as writeFileSync27 } from "node:fs";
|
|
225241
|
+
import { dirname as dirname17, resolve as resolve19 } from "node:path";
|
|
224740
225242
|
|
|
224741
225243
|
// src/compile/headlessBundle.ts
|
|
224742
225244
|
init_api();
|
|
@@ -224820,23 +225322,23 @@ var compileHeadlessBundle = async (args, opts = {}) => {
|
|
|
224820
225322
|
// src/compile/screenEntry.ts
|
|
224821
225323
|
init_designMeta();
|
|
224822
225324
|
init_util();
|
|
224823
|
-
import { existsSync as
|
|
224824
|
-
import { isAbsolute, join as
|
|
225325
|
+
import { existsSync as existsSync35, readFileSync as readFileSync31, statSync as statSync13 } from "node:fs";
|
|
225326
|
+
import { isAbsolute, join as join37, relative as relative8, resolve as resolve17 } from "node:path";
|
|
224825
225327
|
var SCREEN_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs"];
|
|
224826
225328
|
var SCREEN_ENTRY_PATH = "/.screen-entry.ts";
|
|
224827
225329
|
var DEFAULT_DESIGN_DIR = "design";
|
|
224828
225330
|
var screenArg = (args) => args._[0];
|
|
224829
225331
|
var hasSeparator = (file) => file.includes("/") || file.includes("\\");
|
|
224830
225332
|
var findScreenFile = (root, file, dirFlag) => {
|
|
224831
|
-
const candidates = [
|
|
225333
|
+
const candidates = [resolve17(process.cwd(), file)];
|
|
224832
225334
|
if (!hasSeparator(file)) {
|
|
224833
|
-
candidates.push(
|
|
225335
|
+
candidates.push(join37(root, (dirFlag ?? DEFAULT_DESIGN_DIR).replace(/[/\\]+$/, ""), "screens", file));
|
|
224834
225336
|
}
|
|
224835
225337
|
for (const base of candidates) {
|
|
224836
|
-
if (
|
|
225338
|
+
if (existsSync35(base) && statSync13(base).isFile())
|
|
224837
225339
|
return base;
|
|
224838
225340
|
for (const ext2 of SCREEN_EXTS)
|
|
224839
|
-
if (
|
|
225341
|
+
if (existsSync35(base + ext2))
|
|
224840
225342
|
return base + ext2;
|
|
224841
225343
|
}
|
|
224842
225344
|
throw new CliError(`No screen file at "${file}".` + (hasSeparator(file) ? "" : ` Pass a path, or a screen id that exists in ${dirFlag ?? DEFAULT_DESIGN_DIR}/screens/.`));
|
|
@@ -224845,17 +225347,17 @@ var designDirOf = (root, key) => {
|
|
|
224845
225347
|
const m = /^\/(.*)screens\/([^/]+)\.[^./]+$/.exec(key);
|
|
224846
225348
|
if (!m)
|
|
224847
225349
|
return null;
|
|
224848
|
-
const dir =
|
|
224849
|
-
return
|
|
225350
|
+
const dir = join37(root, m[1]);
|
|
225351
|
+
return existsSync35(join37(dir, "meta.json")) ? { dir, id: m[2] } : null;
|
|
224850
225352
|
};
|
|
224851
225353
|
var resolveScreenTarget = (root, file, opts = {}) => {
|
|
224852
225354
|
const abs = findScreenFile(root, file, opts.dir);
|
|
224853
|
-
const rel =
|
|
225355
|
+
const rel = relative8(root, abs);
|
|
224854
225356
|
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
224855
225357
|
throw new CliError(`"${file}" is outside the project (${root}) — a screen renders as part of its project, so its ` + `imports and asset() files resolve from the same tree.`);
|
|
224856
225358
|
}
|
|
224857
225359
|
const key = toKey(rel);
|
|
224858
|
-
if (!/^\s*export\s+default\b/m.test(
|
|
225360
|
+
if (!/^\s*export\s+default\b/m.test(readFileSync31(abs, "utf8"))) {
|
|
224859
225361
|
throw new CliError(`${key.slice(1)} has no default export. A screen file ends with ` + `\`export default UIScreen([...])\` — or \`export default (state) => UIScreen([...])\` for screen states.`);
|
|
224860
225362
|
}
|
|
224861
225363
|
const invoke = opts.state !== undefined ? `screen(${JSON.stringify(opts.state)})` : "screen()";
|
|
@@ -224894,11 +225396,11 @@ init_manifest();
|
|
|
224894
225396
|
|
|
224895
225397
|
// src/desktopScript.ts
|
|
224896
225398
|
init_util();
|
|
224897
|
-
import { resolve as
|
|
225399
|
+
import { resolve as resolve18 } from "node:path";
|
|
224898
225400
|
var isPoint = (v) => typeof v === "object" && v !== null && typeof v.x === "number" && typeof v.y === "number";
|
|
224899
225401
|
var FRAME_MS = 16;
|
|
224900
225402
|
var ACTIONS = ["tap", "type", "fill", "scroll", "wait", "frames", "drag", "hold", "key", "keyDown", "keyUp", "look", "mouseTo", "wheel", "stick", "gamepad", "camera", "view"];
|
|
224901
|
-
var SEMANTIC_EXPECT = ["text", "notText", "node", "notNode", "screen", "value"];
|
|
225403
|
+
var SEMANTIC_EXPECT = ["text", "notText", "node", "notNode", "screen", "value", "audio"];
|
|
224902
225404
|
var VIEW_NAMES = ["iso", "top", "front", "side", "back"];
|
|
224903
225405
|
var isVec3 = (v) => Array.isArray(v) && v.length === 3 && v.every((n2) => typeof n2 === "number");
|
|
224904
225406
|
var pt = (p2) => `(${p2.x},${p2.y})`;
|
|
@@ -225095,7 +225597,7 @@ var translateScenario = (rawSteps, opts) => {
|
|
|
225095
225597
|
if (s2.screenshot !== undefined) {
|
|
225096
225598
|
if (typeof s2.screenshot !== "string")
|
|
225097
225599
|
throw new CliError(`${at}: "screenshot" must be a path.`);
|
|
225098
|
-
plan.screenshot =
|
|
225600
|
+
plan.screenshot = resolve18(opts.outDir, s2.screenshot);
|
|
225099
225601
|
plan.lines.push(emit(`frames 2`));
|
|
225100
225602
|
plan.lines.push(emit(`shot ${plan.screenshot}`));
|
|
225101
225603
|
budgetMs += 3 * FRAME_MS;
|
|
@@ -225175,6 +225677,7 @@ var evaluateDesktopRun = (plan, output) => {
|
|
|
225175
225677
|
|
|
225176
225678
|
// src/commands/render.ts
|
|
225177
225679
|
init_util();
|
|
225680
|
+
init_peerInstall();
|
|
225178
225681
|
var DEVICE_SAFE_AREA = {
|
|
225179
225682
|
none: { insets: [0, 0, 0, 0], bars: [] },
|
|
225180
225683
|
iphone: { insets: [59, 0, 34, 0], bars: [] },
|
|
@@ -225195,18 +225698,12 @@ var resolveSafeAreaChoice = (explicit, device) => {
|
|
|
225195
225698
|
}
|
|
225196
225699
|
return;
|
|
225197
225700
|
};
|
|
225198
|
-
var requireRenderer = async () => {
|
|
225199
|
-
try {
|
|
225200
|
-
return await import("lecodes-renderer/headless");
|
|
225201
|
-
} catch {
|
|
225202
|
-
throw new CliError("The renderer isn't installed. Run: npm install -g lecodes-renderer");
|
|
225203
|
-
}
|
|
225204
|
-
};
|
|
225701
|
+
var requireRenderer = async () => loadPeer("lecodes-renderer", "headless", { for: "lecodes render" });
|
|
225205
225702
|
var fileScenarioIO = (dir) => ({
|
|
225206
225703
|
write: (relPath, data) => {
|
|
225207
|
-
const abs =
|
|
225208
|
-
|
|
225209
|
-
|
|
225704
|
+
const abs = resolve19(dir, relPath);
|
|
225705
|
+
mkdirSync25(dirname17(abs), { recursive: true });
|
|
225706
|
+
writeFileSync27(abs, data);
|
|
225210
225707
|
}
|
|
225211
225708
|
});
|
|
225212
225709
|
var resolveFixtures = (value, baseDir) => {
|
|
@@ -225214,9 +225711,9 @@ var resolveFixtures = (value, baseDir) => {
|
|
|
225214
225711
|
return;
|
|
225215
225712
|
let map = value;
|
|
225216
225713
|
if (typeof value === "string") {
|
|
225217
|
-
const abs =
|
|
225714
|
+
const abs = resolve19(baseDir, value);
|
|
225218
225715
|
try {
|
|
225219
|
-
map = JSON.parse(
|
|
225716
|
+
map = JSON.parse(readFileSync32(abs, "utf8"));
|
|
225220
225717
|
} catch (e2) {
|
|
225221
225718
|
throw new CliError(`Can't read fixtures "${value}": ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
225222
225719
|
}
|
|
@@ -225229,7 +225726,7 @@ var resolveFixtures = (value, baseDir) => {
|
|
|
225229
225726
|
var readScenarioFile = (path2) => {
|
|
225230
225727
|
let raw;
|
|
225231
225728
|
try {
|
|
225232
|
-
raw = JSON.parse(
|
|
225729
|
+
raw = JSON.parse(readFileSync32(resolve19(path2), "utf8"));
|
|
225233
225730
|
} catch (e2) {
|
|
225234
225731
|
throw new CliError(`Can't read scenario "${path2}": ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
225235
225732
|
}
|
|
@@ -225287,13 +225784,13 @@ var renderDesktop = async (args) => {
|
|
|
225287
225784
|
extraEntries: screen2 ? [screen2.entry] : undefined,
|
|
225288
225785
|
entryOverride: screen2?.entryPath
|
|
225289
225786
|
});
|
|
225290
|
-
|
|
225787
|
+
mkdirSync25(dirname17(resolve19(pngFlag)), { recursive: true });
|
|
225291
225788
|
if (scenario) {
|
|
225292
225789
|
const outDir = flagStr(args, "out-dir") ?? ".";
|
|
225293
225790
|
const plan = translateScenario(scenario.steps, { outDir, headroomFrames: frames });
|
|
225294
225791
|
for (const st of plan.steps)
|
|
225295
225792
|
if (st.screenshot)
|
|
225296
|
-
|
|
225793
|
+
mkdirSync25(dirname17(st.screenshot), { recursive: true });
|
|
225297
225794
|
const timeoutMs2 = Number(flagStr(args, "timeout")) || 60000 + plan.steps.length * 5000;
|
|
225298
225795
|
note(`Running ${plan.steps.length} step${plan.steps.length === 1 ? "" : "s"} (native)…`);
|
|
225299
225796
|
const { output } = await runDesktopRender(js, resources, {
|
|
@@ -225399,7 +225896,7 @@ var render = async (args) => {
|
|
|
225399
225896
|
const settleStr = flagStr(args, "settle") ?? (scenario?.settle !== undefined ? String(scenario.settle) : undefined);
|
|
225400
225897
|
const timeStr = flagStr(args, "time");
|
|
225401
225898
|
const onConsole = flagBool(args, "logs") ? (level, text2) => logErr(`${c.dim(`[${level}]`)} ${text2}`) : undefined;
|
|
225402
|
-
const envelopeFixtures = resolveFixtures(scenario?.fixtures, scriptFlag !== undefined ?
|
|
225899
|
+
const envelopeFixtures = resolveFixtures(scenario?.fixtures, scriptFlag !== undefined ? dirname17(resolve19(scriptFlag)) : ".");
|
|
225403
225900
|
const flagFixtures = resolveFixtures(flagStr(args, "fixtures"), ".");
|
|
225404
225901
|
const fixtures = envelopeFixtures || flagFixtures ? { ...envelopeFixtures, ...flagFixtures } : undefined;
|
|
225405
225902
|
if (fixtures) {
|
|
@@ -225438,7 +225935,7 @@ var render = async (args) => {
|
|
|
225438
225935
|
${c.red(st.error ?? "failed")}`);
|
|
225439
225936
|
}
|
|
225440
225937
|
if (!result2.ok && result2.failureArtifacts.length > 0) {
|
|
225441
|
-
logErr(c.dim(` failing frame: ${result2.failureArtifacts.map((p2) =>
|
|
225938
|
+
logErr(c.dim(` failing frame: ${result2.failureArtifacts.map((p2) => resolve19(outDir, p2)).join(", ")}`));
|
|
225442
225939
|
}
|
|
225443
225940
|
logErr(result2.ok ? c.green(`✓ ${result2.steps.length}/${steps.length} steps passed`) : c.red(`✗ failed at step ${result2.steps.length} of ${steps.length}`));
|
|
225444
225941
|
return done((cb) => cb(), result2.ok ? 0 : 1);
|
|
@@ -225459,10 +225956,10 @@ var render = async (args) => {
|
|
|
225459
225956
|
for (const w of [...warnings, ...mapWarnings])
|
|
225460
225957
|
warnErr(w);
|
|
225461
225958
|
const pngPath = mapFlag.endsWith(".png") ? mapFlag : mapFlag + ".png";
|
|
225462
|
-
|
|
225463
|
-
|
|
225959
|
+
mkdirSync25(dirname17(resolve19(pngPath)), { recursive: true });
|
|
225960
|
+
writeFileSync27(resolve19(pngPath), png2);
|
|
225464
225961
|
const jsonPath = pngPath.slice(0, -4) + ".json";
|
|
225465
|
-
|
|
225962
|
+
writeFileSync27(resolve19(jsonPath), JSON.stringify(data, null, 2));
|
|
225466
225963
|
note(`Wrote ${pngPath} + ${jsonPath}`);
|
|
225467
225964
|
return done((cb) => cb());
|
|
225468
225965
|
}
|
|
@@ -225482,8 +225979,8 @@ var render = async (args) => {
|
|
|
225482
225979
|
if (isNullSink(pngFlag)) {
|
|
225483
225980
|
note(`Discarded PNG (${pngFlag})`);
|
|
225484
225981
|
} else {
|
|
225485
|
-
|
|
225486
|
-
|
|
225982
|
+
mkdirSync25(dirname17(resolve19(pngFlag)), { recursive: true });
|
|
225983
|
+
writeFileSync27(resolve19(pngFlag), png2);
|
|
225487
225984
|
note(`Wrote ${pngFlag} (${png2.length} bytes, ${outW}x${outH}${clip !== undefined ? ", clipped" : ""})`);
|
|
225488
225985
|
}
|
|
225489
225986
|
return done((cb) => cb());
|
|
@@ -225498,7 +225995,7 @@ var render = async (args) => {
|
|
|
225498
225995
|
return done((cb) => cb());
|
|
225499
225996
|
}
|
|
225500
225997
|
if (out) {
|
|
225501
|
-
|
|
225998
|
+
writeFileSync27(resolve19(out), result.text);
|
|
225502
225999
|
note(`Wrote ${out} (${result.text.length} bytes)`);
|
|
225503
226000
|
return done((cb) => cb());
|
|
225504
226001
|
}
|
|
@@ -225513,8 +226010,8 @@ init_api();
|
|
|
225513
226010
|
init_config();
|
|
225514
226011
|
init_manifest();
|
|
225515
226012
|
init_util();
|
|
225516
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
225517
|
-
import { join as
|
|
226013
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync36, readFileSync as readFileSync33, writeFileSync as writeFileSync28 } from "node:fs";
|
|
226014
|
+
import { join as join38 } from "node:path";
|
|
225518
226015
|
var ENV_VAR = "LECODES_PUSH_KEY";
|
|
225519
226016
|
var context = () => {
|
|
225520
226017
|
const root = findProjectRoot(process.cwd());
|
|
@@ -225658,11 +226155,11 @@ var pnKeysList = async (ctx) => {
|
|
|
225658
226155
|
};
|
|
225659
226156
|
var writeEnv = (root, key) => {
|
|
225660
226157
|
const touched = [];
|
|
225661
|
-
const envPath =
|
|
226158
|
+
const envPath = join38(root, ".env");
|
|
225662
226159
|
const line = `${ENV_VAR}=${key}`;
|
|
225663
|
-
const existing =
|
|
226160
|
+
const existing = existsSync36(envPath) ? readFileSync33(envPath, "utf8") : "";
|
|
225664
226161
|
if (new RegExp(`^${ENV_VAR}=`, "m").test(existing)) {
|
|
225665
|
-
|
|
226162
|
+
writeFileSync28(envPath, existing.replace(new RegExp(`^${ENV_VAR}=.*$`, "m"), line));
|
|
225666
226163
|
} else {
|
|
225667
226164
|
const separator = !existing || existing.endsWith(`
|
|
225668
226165
|
`) ? "" : `
|
|
@@ -225671,8 +226168,8 @@ var writeEnv = (root, key) => {
|
|
|
225671
226168
|
`);
|
|
225672
226169
|
}
|
|
225673
226170
|
touched.push(".env");
|
|
225674
|
-
const ignorePath =
|
|
225675
|
-
const ignore =
|
|
226171
|
+
const ignorePath = join38(root, ".gitignore");
|
|
226172
|
+
const ignore = existsSync36(ignorePath) ? readFileSync33(ignorePath, "utf8") : "";
|
|
225676
226173
|
if (!ignore.split(`
|
|
225677
226174
|
`).some((l2) => l2.trim() === ".env")) {
|
|
225678
226175
|
const separator = !ignore || ignore.endsWith(`
|
|
@@ -225771,8 +226268,8 @@ var pn = async (args) => {
|
|
|
225771
226268
|
init_api();
|
|
225772
226269
|
init_config();
|
|
225773
226270
|
init_manifest();
|
|
225774
|
-
import { existsSync as
|
|
225775
|
-
import { dirname as
|
|
226271
|
+
import { existsSync as existsSync37, mkdirSync as mkdirSync26, rmSync as rmSync11, writeFileSync as writeFileSync29 } from "node:fs";
|
|
226272
|
+
import { dirname as dirname18, join as join39 } from "node:path";
|
|
225776
226273
|
init_types4();
|
|
225777
226274
|
init_localFiles();
|
|
225778
226275
|
init_util();
|
|
@@ -225795,8 +226292,8 @@ var pull = async (args) => {
|
|
|
225795
226292
|
const remotePaths = new Set([...buildPaths(project.assets).entries()].filter(([id]) => project.assets.find((a2) => a2.id === id)?.type !== "folder").map(([, path2]) => path2));
|
|
225796
226293
|
for (const path2 of Object.keys(manifest.files)) {
|
|
225797
226294
|
if (!remotePaths.has(path2)) {
|
|
225798
|
-
const abs =
|
|
225799
|
-
if (
|
|
226295
|
+
const abs = join39(root, ...path2.replace(/^\//, "").split("/"));
|
|
226296
|
+
if (existsSync37(abs))
|
|
225800
226297
|
rmSync11(abs);
|
|
225801
226298
|
}
|
|
225802
226299
|
}
|
|
@@ -225834,18 +226331,18 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
|
|
|
225834
226331
|
const files = { ...manifest.files };
|
|
225835
226332
|
for (const asset of targets) {
|
|
225836
226333
|
const key = pathById.get(asset.id);
|
|
225837
|
-
const abs =
|
|
225838
|
-
|
|
226334
|
+
const abs = join39(root, ...key.replace(/^\//, "").split("/"));
|
|
226335
|
+
mkdirSync26(dirname18(abs), { recursive: true });
|
|
225839
226336
|
let sha;
|
|
225840
226337
|
if (asset.type === "file") {
|
|
225841
226338
|
if (!asset.file?.src)
|
|
225842
226339
|
continue;
|
|
225843
226340
|
const bytes = await downloadBlob(apiUrl, asset.file.src);
|
|
225844
|
-
|
|
226341
|
+
writeFileSync29(abs, bytes);
|
|
225845
226342
|
sha = sha256(bytes);
|
|
225846
226343
|
} else {
|
|
225847
226344
|
const text = asset.text ?? "";
|
|
225848
|
-
|
|
226345
|
+
writeFileSync29(abs, text);
|
|
225849
226346
|
sha = sha256(text);
|
|
225850
226347
|
}
|
|
225851
226348
|
files[key] = { id: asset.id, type: kindOf3(asset.type), sha };
|
|
@@ -225858,8 +226355,8 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
|
|
|
225858
226355
|
|
|
225859
226356
|
// src/commands/scene.ts
|
|
225860
226357
|
init_manifest();
|
|
225861
|
-
import { existsSync as
|
|
225862
|
-
import { basename as
|
|
226358
|
+
import { existsSync as existsSync39, readFileSync as readFileSync35, readdirSync as readdirSync17, statSync as statSync14, writeFileSync as writeFileSync31 } from "node:fs";
|
|
226359
|
+
import { basename as basename19, dirname as dirname19, join as join41, resolve as resolve20 } from "node:path";
|
|
225863
226360
|
|
|
225864
226361
|
// src/compile/sceneCompile.ts
|
|
225865
226362
|
init_localFiles();
|
|
@@ -225868,18 +226365,18 @@ init_distRoot();
|
|
|
225868
226365
|
init_util();
|
|
225869
226366
|
init_projectCompile();
|
|
225870
226367
|
import { createHash as createHash8 } from "node:crypto";
|
|
225871
|
-
import { existsSync as
|
|
225872
|
-
import { join as
|
|
226368
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync27, readFileSync as readFileSync34, writeFileSync as writeFileSync30 } from "node:fs";
|
|
226369
|
+
import { join as join40 } from "node:path";
|
|
225873
226370
|
var TEXT_EXT = /\.(ts|js|json|svg|txt|md)$/i;
|
|
225874
226371
|
var rel = (key) => key.replace(/^\//, "");
|
|
225875
226372
|
var resolveHarness = (loadSceneHarness2) => {
|
|
225876
|
-
const inRepo =
|
|
226373
|
+
const inRepo = join40(distRoot, "..", "projects", "src", "scene-editor");
|
|
225877
226374
|
try {
|
|
225878
|
-
return loadSceneHarness2(
|
|
226375
|
+
return loadSceneHarness2(existsSync38(inRepo) ? inRepo : undefined);
|
|
225879
226376
|
} catch {
|
|
225880
|
-
const vendored =
|
|
225881
|
-
if (
|
|
225882
|
-
return JSON.parse(
|
|
226377
|
+
const vendored = join40(distRoot, "runtime", "scene-harness.json");
|
|
226378
|
+
if (existsSync38(vendored))
|
|
226379
|
+
return JSON.parse(readFileSync34(vendored, "utf8"));
|
|
225883
226380
|
throw new CliError("scene-editor harness sources not found — reinstall the CLI or set LECODES_SCENE_HARNESS_DIR.");
|
|
225884
226381
|
}
|
|
225885
226382
|
};
|
|
@@ -225891,9 +226388,9 @@ var compileSceneEditor = async (opts) => {
|
|
|
225891
226388
|
for (const file of scan2.files) {
|
|
225892
226389
|
const path2 = "./" + rel(file.path);
|
|
225893
226390
|
if (TEXT_EXT.test(path2)) {
|
|
225894
|
-
entries.push({ path: path2, type: "text", text:
|
|
226391
|
+
entries.push({ path: path2, type: "text", text: readFileSync34(file.absPath, "utf8") });
|
|
225895
226392
|
} else if (/\.mat$/i.test(path2)) {
|
|
225896
|
-
entries.push({ path: path2, type: "shader", text:
|
|
226393
|
+
entries.push({ path: path2, type: "shader", text: readFileSync34(file.absPath, "utf8"), absPath: file.absPath });
|
|
225897
226394
|
} else {
|
|
225898
226395
|
entries.push({ path: path2, type: "resource", fileSrc: "/project/" + rel(file.path) });
|
|
225899
226396
|
}
|
|
@@ -225909,10 +226406,10 @@ var compileSceneEditor = async (opts) => {
|
|
|
225909
226406
|
continue;
|
|
225910
226407
|
const hash = createHash8("sha256").update(e2.text ?? "").digest("hex").slice(0, 16);
|
|
225911
226408
|
const dir = shadersCacheDir(root);
|
|
225912
|
-
|
|
226409
|
+
mkdirSync27(dir, { recursive: true });
|
|
225913
226410
|
const liteName = `${hash}_webgl-lite.filamat`;
|
|
225914
|
-
if (!
|
|
225915
|
-
|
|
226411
|
+
if (!existsSync38(join40(dir, liteName)))
|
|
226412
|
+
writeFileSync30(join40(dir, liteName), JSON.stringify(buildLiteMaterialArtifact2(e2.text ?? "")));
|
|
225916
226413
|
const src = `/project/.lecodes/shaders/${hash}_opengl.filamat`;
|
|
225917
226414
|
e2.shaders = ["opengl", "webgl-lite"].map((platform) => ({ platform, src }));
|
|
225918
226415
|
}
|
|
@@ -225942,13 +226439,14 @@ var compileSceneEditor = async (opts) => {
|
|
|
225942
226439
|
// src/commands/scene.ts
|
|
225943
226440
|
init_browserAuth();
|
|
225944
226441
|
init_util();
|
|
226442
|
+
init_peerInstall();
|
|
225945
226443
|
var DEFAULT_PORT3 = 4488;
|
|
225946
226444
|
var findAppRoot = (start) => {
|
|
225947
226445
|
let dir = start;
|
|
225948
226446
|
while (true) {
|
|
225949
|
-
if (
|
|
226447
|
+
if (existsSync39(join41(dir, "app.json")))
|
|
225950
226448
|
return dir;
|
|
225951
|
-
const parent =
|
|
226449
|
+
const parent = dirname19(dir);
|
|
225952
226450
|
if (parent === dir)
|
|
225953
226451
|
return null;
|
|
225954
226452
|
dir = parent;
|
|
@@ -225972,26 +226470,26 @@ var getContext2 = (args) => {
|
|
|
225972
226470
|
manifest = null;
|
|
225973
226471
|
const pathArg = args._[0];
|
|
225974
226472
|
if (pathArg) {
|
|
225975
|
-
const abs =
|
|
225976
|
-
if (
|
|
226473
|
+
const abs = resolve20(process.cwd(), pathArg);
|
|
226474
|
+
if (existsSync39(abs) && statSync14(abs).isDirectory())
|
|
225977
226475
|
root = abs;
|
|
225978
|
-
else if (abs.endsWith(".scene.ts") &&
|
|
225979
|
-
root = findAppRoot(
|
|
226476
|
+
else if (abs.endsWith(".scene.ts") && existsSync39(abs))
|
|
226477
|
+
root = findAppRoot(dirname19(abs)) ?? dirname19(abs);
|
|
225980
226478
|
else
|
|
225981
226479
|
throw new CliError(`No such folder or .scene.ts: ${pathArg}`);
|
|
225982
226480
|
} else {
|
|
225983
226481
|
root = findAppRoot(root) ?? root;
|
|
225984
226482
|
}
|
|
225985
226483
|
}
|
|
225986
|
-
return { root, manifest, name: manifest?.name ??
|
|
226484
|
+
return { root, manifest, name: manifest?.name ?? basename19(root) };
|
|
225987
226485
|
};
|
|
225988
226486
|
var writeMcpConfig2 = (root, port) => {
|
|
225989
|
-
const path2 =
|
|
226487
|
+
const path2 = join41(root, ".mcp.json");
|
|
225990
226488
|
const entry = { type: "http", url: `http://localhost:${port}/mcp` };
|
|
225991
226489
|
let config = {};
|
|
225992
|
-
if (
|
|
226490
|
+
if (existsSync39(path2)) {
|
|
225993
226491
|
try {
|
|
225994
|
-
config = JSON.parse(
|
|
226492
|
+
config = JSON.parse(readFileSync35(path2, "utf8"));
|
|
225995
226493
|
} catch {
|
|
225996
226494
|
warnErr(".mcp.json exists but isn't valid JSON — left it untouched. Add the lecodes-scene server by hand.");
|
|
225997
226495
|
return;
|
|
@@ -226004,16 +226502,16 @@ var writeMcpConfig2 = (root, port) => {
|
|
|
226004
226502
|
return;
|
|
226005
226503
|
}
|
|
226006
226504
|
config.mcpServers["lecodes-scene"] = entry;
|
|
226007
|
-
|
|
226505
|
+
writeFileSync31(path2, JSON.stringify(config, null, 2) + `
|
|
226008
226506
|
`);
|
|
226009
226507
|
note(`${existing ? "updated" : "created"} .mcp.json (lecodes-scene MCP → ${entry.url})`);
|
|
226010
226508
|
};
|
|
226011
226509
|
var countScenes = (dir) => {
|
|
226012
226510
|
let n2 = 0;
|
|
226013
|
-
for (const name of
|
|
226511
|
+
for (const name of readdirSync17(dir)) {
|
|
226014
226512
|
if (name === "node_modules" || name.startsWith("."))
|
|
226015
226513
|
continue;
|
|
226016
|
-
const full =
|
|
226514
|
+
const full = join41(dir, name);
|
|
226017
226515
|
if (statSync14(full).isDirectory())
|
|
226018
226516
|
n2 += countScenes(full);
|
|
226019
226517
|
else if (name.endsWith(".scene.ts"))
|
|
@@ -226023,12 +226521,7 @@ var countScenes = (dir) => {
|
|
|
226023
226521
|
};
|
|
226024
226522
|
var scene = async (args) => {
|
|
226025
226523
|
const ctx = getContext2(args);
|
|
226026
|
-
|
|
226027
|
-
try {
|
|
226028
|
-
sceneServer = await import("lecodes-3d-editor/server");
|
|
226029
|
-
} catch {
|
|
226030
|
-
throw new CliError("The scene editor isn't installed. Run: npm install -g lecodes-3d-editor");
|
|
226031
|
-
}
|
|
226524
|
+
const sceneServer = await loadPeer("lecodes-3d-editor", "server", { for: "lecodes scene" });
|
|
226032
226525
|
const port = Number(flagStr(args, "port")) || DEFAULT_PORT3;
|
|
226033
226526
|
if (countScenes(ctx.root) === 0) {
|
|
226034
226527
|
warnErr(`No .scene.ts file under ${ctx.root} yet — add one and the editor will pick it up.`);
|
|
@@ -226056,11 +226549,12 @@ var scene = async (args) => {
|
|
|
226056
226549
|
};
|
|
226057
226550
|
|
|
226058
226551
|
// src/commands/thumbs.ts
|
|
226059
|
-
|
|
226552
|
+
init_peerInstall();
|
|
226553
|
+
import { createRequire as createRequire7 } from "node:module";
|
|
226060
226554
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
226061
|
-
import { existsSync as
|
|
226555
|
+
import { existsSync as existsSync40, mkdtempSync as mkdtempSync6, readdirSync as readdirSync18, rmSync as rmSync12, statSync as statSync15 } from "node:fs";
|
|
226062
226556
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
226063
|
-
import { extname, join as
|
|
226557
|
+
import { extname, join as join42, relative as relative9, resolve as resolve21 } from "node:path";
|
|
226064
226558
|
init_util();
|
|
226065
226559
|
var DEFAULT_SIZE = 256;
|
|
226066
226560
|
var IMAGE_EXT = /\.(png|jpe?g|webp|bmp|gif|tiff?|ktx2)$/i;
|
|
@@ -226071,13 +226565,13 @@ var findKtx = () => {
|
|
|
226071
226565
|
process.env.LECODES_KTX,
|
|
226072
226566
|
process.env.KTX_CLI,
|
|
226073
226567
|
process.env.KTX_TOKTX?.replace(/toktx(\.exe)?$/i, exe),
|
|
226074
|
-
process.platform === "win32" ?
|
|
226075
|
-
process.platform === "win32" ?
|
|
226568
|
+
process.platform === "win32" ? join42(process.env.LOCALAPPDATA ?? "", "Programs", "KTX-Software", "bin", exe) : null,
|
|
226569
|
+
process.platform === "win32" ? join42(process.env.ProgramFiles ?? "", "KTX-Software", "bin", exe) : null,
|
|
226076
226570
|
"/usr/local/bin/ktx",
|
|
226077
226571
|
"/opt/homebrew/bin/ktx"
|
|
226078
226572
|
].filter((p2) => !!p2);
|
|
226079
226573
|
for (const path2 of candidates) {
|
|
226080
|
-
if (
|
|
226574
|
+
if (existsSync40(path2))
|
|
226081
226575
|
return path2;
|
|
226082
226576
|
}
|
|
226083
226577
|
try {
|
|
@@ -226089,8 +226583,8 @@ var findKtx = () => {
|
|
|
226089
226583
|
};
|
|
226090
226584
|
var sharpResizer = () => {
|
|
226091
226585
|
try {
|
|
226092
|
-
const req =
|
|
226093
|
-
const sharp =
|
|
226586
|
+
const req = createRequire7(import.meta.url);
|
|
226587
|
+
const sharp = createRequire7(req.resolve("lecodes-assets/cli"))("sharp");
|
|
226094
226588
|
return async (file, size) => sharp(file).resize({ width: size, height: size, fit: "inside", withoutEnlargement: true }).png().toBuffer();
|
|
226095
226589
|
} catch {
|
|
226096
226590
|
return null;
|
|
@@ -226104,21 +226598,21 @@ var jimpResizer = () => async (file, size) => {
|
|
|
226104
226598
|
return await img.getBuffer("image/png");
|
|
226105
226599
|
};
|
|
226106
226600
|
var ktxToPng = (ktx, file, out) => {
|
|
226107
|
-
const
|
|
226601
|
+
const run3 = (args) => execFileSync7(ktx, args, { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
|
|
226108
226602
|
try {
|
|
226109
|
-
|
|
226603
|
+
run3(["extract", "--transcode", "rgba8", "--level", "0", file, out]);
|
|
226110
226604
|
} catch (e2) {
|
|
226111
|
-
|
|
226112
|
-
if (!
|
|
226605
|
+
run3(["extract", "--level", "0", file, out]);
|
|
226606
|
+
if (!existsSync40(out))
|
|
226113
226607
|
throw e2;
|
|
226114
226608
|
}
|
|
226115
226609
|
};
|
|
226116
226610
|
var collect = (dir) => {
|
|
226117
226611
|
const out = [];
|
|
226118
|
-
for (const name of
|
|
226612
|
+
for (const name of readdirSync18(dir)) {
|
|
226119
226613
|
if (name === "node_modules" || name.startsWith("."))
|
|
226120
226614
|
continue;
|
|
226121
|
-
const full =
|
|
226615
|
+
const full = join42(dir, name);
|
|
226122
226616
|
if (statSync15(full).isDirectory())
|
|
226123
226617
|
out.push(...collect(full));
|
|
226124
226618
|
else if (IMAGE_EXT.test(name))
|
|
@@ -226128,20 +226622,15 @@ var collect = (dir) => {
|
|
|
226128
226622
|
};
|
|
226129
226623
|
var thumbs = async (args) => {
|
|
226130
226624
|
const root = projectRootOf(process.cwd());
|
|
226131
|
-
|
|
226132
|
-
try {
|
|
226133
|
-
sceneServer = await import("lecodes-3d-editor/server");
|
|
226134
|
-
} catch {
|
|
226135
|
-
throw new CliError("The scene editor isn't installed (it owns the preview cache). Run: npm install -g lecodes-3d-editor");
|
|
226136
|
-
}
|
|
226625
|
+
const sceneServer = await loadPeer("lecodes-3d-editor", "server", { for: "lecodes thumbs" });
|
|
226137
226626
|
const size = Number(flagStr(args, "size")) || DEFAULT_SIZE;
|
|
226138
226627
|
const force = flagBool(args, "force");
|
|
226139
226628
|
let files = [];
|
|
226140
226629
|
const rendered = [];
|
|
226141
226630
|
if (args._.length > 0) {
|
|
226142
226631
|
for (const arg of args._) {
|
|
226143
|
-
const abs =
|
|
226144
|
-
if (!
|
|
226632
|
+
const abs = resolve21(process.cwd(), arg);
|
|
226633
|
+
if (!existsSync40(abs))
|
|
226145
226634
|
throw new CliError(`No such file or folder: ${arg}`);
|
|
226146
226635
|
if (statSync15(abs).isDirectory())
|
|
226147
226636
|
files.push(...collect(abs));
|
|
@@ -226163,12 +226652,12 @@ var thumbs = async (args) => {
|
|
|
226163
226652
|
const ktx = files.some((f3) => f3.toLowerCase().endsWith(".ktx2")) ? findKtx() : null;
|
|
226164
226653
|
const sharp = sharpResizer();
|
|
226165
226654
|
const resize = sharp ?? jimpResizer();
|
|
226166
|
-
const tmp = mkdtempSync6(
|
|
226655
|
+
const tmp = mkdtempSync6(join42(tmpdir6(), "lecodes-thumbs-"));
|
|
226167
226656
|
let made = 0, fresh = 0;
|
|
226168
226657
|
const skipped = [];
|
|
226169
226658
|
try {
|
|
226170
226659
|
for (const [i2, file] of files.entries()) {
|
|
226171
|
-
const asset = "./" + toKey(
|
|
226660
|
+
const asset = "./" + toKey(relative9(root, file));
|
|
226172
226661
|
progress(i2, files.length, asset);
|
|
226173
226662
|
const hash = sceneServer.hashAsset(root, asset);
|
|
226174
226663
|
if (!hash)
|
|
@@ -226189,7 +226678,7 @@ var thumbs = async (args) => {
|
|
|
226189
226678
|
skipped.push(`${asset} (KTX2 needs KTX-Software's \`ktx\` — set LECODES_KTX or put it on PATH)`);
|
|
226190
226679
|
continue;
|
|
226191
226680
|
}
|
|
226192
|
-
source =
|
|
226681
|
+
source = join42(tmp, `${i2}.png`);
|
|
226193
226682
|
ktxToPng(ktx, file, source);
|
|
226194
226683
|
}
|
|
226195
226684
|
sceneServer.writePreviewFile(root, asset, await resize(source, size));
|
|
@@ -226209,16 +226698,13 @@ var thumbs = async (args) => {
|
|
|
226209
226698
|
if (rendered.length > 0) {
|
|
226210
226699
|
warnErr(`Models, materials and prefabs are RENDERED, not decoded — the scene editor does those: ${rendered.join(", ")}`);
|
|
226211
226700
|
}
|
|
226212
|
-
note(`Cache: ${
|
|
226701
|
+
note(`Cache: ${join42(root, ".lecodes", "preview")} (re-runs skip everything unchanged)`);
|
|
226213
226702
|
};
|
|
226214
226703
|
|
|
226215
|
-
// src/commands/assets.ts
|
|
226216
|
-
init_util();
|
|
226217
|
-
|
|
226218
226704
|
// src/cmgenTool.ts
|
|
226219
|
-
import { existsSync as
|
|
226220
|
-
import { homedir as
|
|
226221
|
-
import { join as
|
|
226705
|
+
import { existsSync as existsSync41, readdirSync as readdirSync19 } from "node:fs";
|
|
226706
|
+
import { homedir as homedir9 } from "node:os";
|
|
226707
|
+
import { join as join43, resolve as resolve22 } from "node:path";
|
|
226222
226708
|
init_util();
|
|
226223
226709
|
var exeName2 = () => process.platform === "win32" ? "cmgen.exe" : "cmgen";
|
|
226224
226710
|
var platformKey3 = () => {
|
|
@@ -226228,10 +226714,10 @@ var platformKey3 = () => {
|
|
|
226228
226714
|
return "linux-x64";
|
|
226229
226715
|
throw new CliError(process.platform === "darwin" ? "cmgen isn't published for macOS yet — build filament's host tools locally and set LECODES_CMGEN." : `cmgen isn't published for ${process.platform}.`);
|
|
226230
226716
|
};
|
|
226231
|
-
var cacheRoot3 = () =>
|
|
226717
|
+
var cacheRoot3 = () => join43(homedir9(), ".lecodes", "cmgen");
|
|
226232
226718
|
var findCmgenIn = (dir) => {
|
|
226233
|
-
for (const entry of
|
|
226234
|
-
const p2 =
|
|
226719
|
+
for (const entry of readdirSync19(dir, { withFileTypes: true })) {
|
|
226720
|
+
const p2 = join43(dir, entry.name);
|
|
226235
226721
|
if (entry.isFile() && entry.name === exeName2())
|
|
226236
226722
|
return p2;
|
|
226237
226723
|
if (entry.isDirectory()) {
|
|
@@ -226244,18 +226730,21 @@ var findCmgenIn = (dir) => {
|
|
|
226244
226730
|
};
|
|
226245
226731
|
var installedCmgenVersions = () => {
|
|
226246
226732
|
try {
|
|
226247
|
-
return
|
|
226733
|
+
return readdirSync19(cacheRoot3(), { withFileTypes: true }).filter((e2) => e2.isDirectory() && e2.name.startsWith("cmgen-")).map((e2) => e2.name.slice("cmgen-".length)).filter((v) => findCmgenIn(join43(cacheRoot3(), `cmgen-${v}`)) !== null).sort(semverNewer);
|
|
226248
226734
|
} catch {
|
|
226249
226735
|
return [];
|
|
226250
226736
|
}
|
|
226251
226737
|
};
|
|
226252
226738
|
var fetchCmgenManifest = () => fetchReleaseProduct("cmgen", "cmgen (environment tool)");
|
|
226253
|
-
var
|
|
226739
|
+
var pickCmgen = (versions) => {
|
|
226254
226740
|
const key = platformKey3();
|
|
226255
|
-
const
|
|
226256
|
-
if (!
|
|
226257
|
-
throw new CliError(`cmgen
|
|
226741
|
+
const hit = newestForPlatform(versions, key);
|
|
226742
|
+
if (!hit) {
|
|
226743
|
+
throw new CliError(`cmgen isn't published for ${key} — build filament's host tools locally and point ` + `LECODES_CMGEN at the binary.`);
|
|
226258
226744
|
}
|
|
226745
|
+
return hit;
|
|
226746
|
+
};
|
|
226747
|
+
var downloadCmgen = async (version, file, dest) => {
|
|
226259
226748
|
note(`Downloading the environment tool (cmgen ${version}, ${(file.size / 1024 / 1024).toFixed(1)} MB)…`);
|
|
226260
226749
|
await downloadReleaseArchive(file, {
|
|
226261
226750
|
cacheRoot: cacheRoot3(),
|
|
@@ -226266,20 +226755,21 @@ var downloadCmgen = async (version, versions, dest) => {
|
|
|
226266
226755
|
var resolveCmgenExeCached = () => {
|
|
226267
226756
|
const override = process.env.LECODES_CMGEN;
|
|
226268
226757
|
if (override) {
|
|
226269
|
-
if (!
|
|
226758
|
+
if (!existsSync41(override))
|
|
226270
226759
|
throw new CliError(`LECODES_CMGEN points at "${override}" which doesn't exist.`);
|
|
226271
|
-
return
|
|
226760
|
+
return resolve22(override);
|
|
226272
226761
|
}
|
|
226273
226762
|
const cached = installedCmgenVersions()[0];
|
|
226274
|
-
return cached ? findCmgenIn(
|
|
226763
|
+
return cached ? findCmgenIn(join43(cacheRoot3(), `cmgen-${cached}`)) : null;
|
|
226275
226764
|
};
|
|
226276
226765
|
var resolveCmgenExe = async () => {
|
|
226277
226766
|
const cached = resolveCmgenExeCached();
|
|
226278
226767
|
if (cached)
|
|
226279
226768
|
return cached;
|
|
226280
|
-
const {
|
|
226281
|
-
const
|
|
226282
|
-
|
|
226769
|
+
const { versions } = await fetchCmgenManifest();
|
|
226770
|
+
const { version, file } = pickCmgen(versions);
|
|
226771
|
+
const dir = join43(cacheRoot3(), `cmgen-${version}`);
|
|
226772
|
+
await downloadCmgen(version, file, dir);
|
|
226283
226773
|
const exe = findCmgenIn(dir);
|
|
226284
226774
|
if (!exe)
|
|
226285
226775
|
throw new CliError(`cmgen cache at ${dir} is broken — delete the folder and retry.`);
|
|
@@ -226287,13 +226777,9 @@ var resolveCmgenExe = async () => {
|
|
|
226287
226777
|
};
|
|
226288
226778
|
|
|
226289
226779
|
// src/commands/assets.ts
|
|
226780
|
+
init_peerInstall();
|
|
226290
226781
|
var assets = async (args) => {
|
|
226291
|
-
|
|
226292
|
-
try {
|
|
226293
|
-
mod = await import("lecodes-assets/cli");
|
|
226294
|
-
} catch {
|
|
226295
|
-
throw new CliError("The asset pipeline isn't installed. Run: npm install -g lecodes-assets");
|
|
226296
|
-
}
|
|
226782
|
+
const mod = await loadPeer("lecodes-assets", "cli", { for: "lecodes assets" });
|
|
226297
226783
|
const raw = args._raw ?? args._;
|
|
226298
226784
|
if (raw[0] === "sky" && !process.env.LECODES_CMGEN) {
|
|
226299
226785
|
process.env.LECODES_CMGEN = await resolveCmgenExe();
|
|
@@ -226336,8 +226822,8 @@ var status3 = async (_args) => {
|
|
|
226336
226822
|
};
|
|
226337
226823
|
|
|
226338
226824
|
// src/commands/test.ts
|
|
226339
|
-
import { existsSync as
|
|
226340
|
-
import { dirname as
|
|
226825
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync28, readFileSync as readFileSync36, readdirSync as readdirSync20 } from "node:fs";
|
|
226826
|
+
import { dirname as dirname20, resolve as resolve23 } from "node:path";
|
|
226341
226827
|
init_manifest();
|
|
226342
226828
|
init_util();
|
|
226343
226829
|
var deriveDesignFlows = (meta, entryScreen) => {
|
|
@@ -226400,7 +226886,7 @@ var slug3 = (s2) => s2.replace(/[^a-zA-Z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").
|
|
|
226400
226886
|
var readFlowFile = (abs, displayName) => {
|
|
226401
226887
|
let raw;
|
|
226402
226888
|
try {
|
|
226403
|
-
raw = JSON.parse(
|
|
226889
|
+
raw = JSON.parse(readFileSync36(abs, "utf8"));
|
|
226404
226890
|
} catch (e2) {
|
|
226405
226891
|
throw new CliError(`Can't read "${displayName}": ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
226406
226892
|
}
|
|
@@ -226414,7 +226900,7 @@ var readFlowFile = (abs, displayName) => {
|
|
|
226414
226900
|
name: file.name ?? displayName,
|
|
226415
226901
|
kind: "file",
|
|
226416
226902
|
steps: file.steps,
|
|
226417
|
-
fixtures: resolveFixtures(file.fixtures,
|
|
226903
|
+
fixtures: resolveFixtures(file.fixtures, dirname20(abs)),
|
|
226418
226904
|
width: file.width,
|
|
226419
226905
|
height: file.height,
|
|
226420
226906
|
settle: file.settle,
|
|
@@ -226455,40 +226941,40 @@ var test = async (args) => {
|
|
|
226455
226941
|
if (!Number.isFinite(nativeFixedDt) || nativeFixedDt < 0 || nativeFixedDt > 1000)
|
|
226456
226942
|
throw new CliError(`--fixed-dt wants a number of ms (0 = wall clock, max 1000), got "${fixedDtFlag}".`);
|
|
226457
226943
|
const projectViewport = desktopDefaultViewport(root);
|
|
226458
|
-
const testsDir =
|
|
226459
|
-
const designMetaPath =
|
|
226944
|
+
const testsDir = resolve23(root, flagStr(args, "dir") ?? "tests");
|
|
226945
|
+
const designMetaPath = resolve23(root, flagStr(args, "design-dir") ?? "design", "meta.json");
|
|
226460
226946
|
const asJson = flagBool(args, "json");
|
|
226461
226947
|
const out = asJson ? logErr : log;
|
|
226462
226948
|
const cases = [];
|
|
226463
226949
|
if (args._.length > 0) {
|
|
226464
226950
|
for (const given of args._) {
|
|
226465
|
-
const candidates = [
|
|
226466
|
-
const abs = candidates.find((p2) =>
|
|
226951
|
+
const candidates = [resolve23(given), resolve23(testsDir, given), resolve23(testsDir, `${given}.flow.json`)];
|
|
226952
|
+
const abs = candidates.find((p2) => existsSync42(p2));
|
|
226467
226953
|
if (!abs)
|
|
226468
226954
|
throw new CliError(`No such test: "${given}" (looked in ${testsDir}).`);
|
|
226469
226955
|
cases.push(readFlowFile(abs, given));
|
|
226470
226956
|
}
|
|
226471
|
-
} else if (
|
|
226472
|
-
for (const f3 of
|
|
226473
|
-
cases.push(readFlowFile(
|
|
226957
|
+
} else if (existsSync42(testsDir)) {
|
|
226958
|
+
for (const f3 of readdirSync20(testsDir).filter((f4) => f4.endsWith(".flow.json")).sort()) {
|
|
226959
|
+
cases.push(readFlowFile(resolve23(testsDir, f3), f3));
|
|
226474
226960
|
}
|
|
226475
226961
|
}
|
|
226476
226962
|
const fixturesFlag = flagStr(args, "fixtures");
|
|
226477
|
-
const defaultPack =
|
|
226478
|
-
const flowFixtures = fixturesFlag !== undefined ? resolveFixtures(fixturesFlag, ".") :
|
|
226963
|
+
const defaultPack = resolve23(testsDir, "fixtures.json");
|
|
226964
|
+
const flowFixtures = fixturesFlag !== undefined ? resolveFixtures(fixturesFlag, ".") : existsSync42(defaultPack) ? resolveFixtures("fixtures.json", testsDir) : undefined;
|
|
226479
226965
|
const flowsOnly = flagBool(args, "flows");
|
|
226480
226966
|
const wantFlows = !flagBool(args, "no-flows") && args._.length === 0;
|
|
226481
226967
|
if (flowsOnly)
|
|
226482
226968
|
cases.length = 0;
|
|
226483
226969
|
let skipped = [];
|
|
226484
226970
|
let entryScreen = null;
|
|
226485
|
-
if (desktop2 && wantFlows &&
|
|
226971
|
+
if (desktop2 && wantFlows && existsSync42(designMetaPath)) {
|
|
226486
226972
|
note("Design-derived flows skipped with --desktop (they tap by name; run `lecodes test` headless for them).");
|
|
226487
226973
|
}
|
|
226488
|
-
if (!desktop2 && (wantFlows || flowsOnly) &&
|
|
226974
|
+
if (!desktop2 && (wantFlows || flowsOnly) && existsSync42(designMetaPath)) {
|
|
226489
226975
|
let meta;
|
|
226490
226976
|
try {
|
|
226491
|
-
meta = JSON.parse(
|
|
226977
|
+
meta = JSON.parse(readFileSync36(designMetaPath, "utf8"));
|
|
226492
226978
|
} catch (e2) {
|
|
226493
226979
|
throw new CliError(`Can't read ${designMetaPath}: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
226494
226980
|
}
|
|
@@ -226520,16 +227006,16 @@ var test = async (args) => {
|
|
|
226520
227006
|
}, timeoutMs);
|
|
226521
227007
|
killer.unref?.();
|
|
226522
227008
|
const onConsole = flagBool(args, "logs") ? (level, text) => logErr(`${c.dim(`[${level}]`)} ${text}`) : undefined;
|
|
226523
|
-
const artifactsRoot =
|
|
227009
|
+
const artifactsRoot = resolve23(root, flagStr(args, "artifacts") ?? resolve23(testsDir, ".artifacts"));
|
|
226524
227010
|
const results = [];
|
|
226525
227011
|
const runNativeCase = async (kase) => {
|
|
226526
|
-
const caseDir =
|
|
227012
|
+
const caseDir = resolve23(artifactsRoot, slug3(kase.name));
|
|
226527
227013
|
const plan = translateScenario(kase.steps, { outDir: caseDir, headroomFrames: nativeFrames });
|
|
226528
227014
|
for (const st of plan.steps)
|
|
226529
227015
|
if (st.screenshot)
|
|
226530
|
-
|
|
226531
|
-
|
|
226532
|
-
const finalPng =
|
|
227016
|
+
mkdirSync28(dirname20(st.screenshot), { recursive: true });
|
|
227017
|
+
mkdirSync28(caseDir, { recursive: true });
|
|
227018
|
+
const finalPng = resolve23(caseDir, "final.png");
|
|
226533
227019
|
const { output } = await runDesktopRender(native.js, native.resources, {
|
|
226534
227020
|
renderer: desktopRenderer,
|
|
226535
227021
|
pluginDirs: desktopPluginLibraries(root).dirs,
|
|
@@ -226582,14 +227068,14 @@ var test = async (args) => {
|
|
|
226582
227068
|
onConsole
|
|
226583
227069
|
});
|
|
226584
227070
|
try {
|
|
226585
|
-
const io = fileScenarioIO(
|
|
227071
|
+
const io = fileScenarioIO(resolve23(artifactsRoot, slug3(kase.name)));
|
|
226586
227072
|
const outcome = await renderer.runScenario(session, steps, io);
|
|
226587
227073
|
result = {
|
|
226588
227074
|
name: kase.name,
|
|
226589
227075
|
kind: kase.kind,
|
|
226590
227076
|
ok: outcome.ok,
|
|
226591
227077
|
steps: outcome.steps.map((s2) => ({ desc: s2.desc, ok: s2.ok, error: s2.error })),
|
|
226592
|
-
artifacts: outcome.failureArtifacts.map((p2) =>
|
|
227078
|
+
artifacts: outcome.failureArtifacts.map((p2) => resolve23(artifactsRoot, slug3(kase.name), p2))
|
|
226593
227079
|
};
|
|
226594
227080
|
} finally {
|
|
226595
227081
|
session.dispose();
|
|
@@ -226652,41 +227138,14 @@ var test = async (args) => {
|
|
|
226652
227138
|
};
|
|
226653
227139
|
|
|
226654
227140
|
// src/commands/update.ts
|
|
226655
|
-
|
|
226656
|
-
import { existsSync as existsSync40, readdirSync as readdirSync20 } from "node:fs";
|
|
226657
|
-
import { homedir as homedir9 } from "node:os";
|
|
226658
|
-
import { dirname as dirname21, join as join41, resolve as resolve23 } from "node:path";
|
|
227141
|
+
init_package();
|
|
226659
227142
|
init_util();
|
|
226660
|
-
|
|
226661
|
-
|
|
226662
|
-
import {
|
|
226663
|
-
import {
|
|
226664
|
-
import {
|
|
226665
|
-
|
|
226666
|
-
["lecodes-renderer", "lecodes-renderer/headless"],
|
|
226667
|
-
["lecodes-design", "lecodes-design/server"],
|
|
226668
|
-
["lecodes-3d-editor", "lecodes-3d-editor/server"],
|
|
226669
|
-
["lecodes-assets", "lecodes-assets/cli"]
|
|
226670
|
-
];
|
|
226671
|
-
var peerVersion = (name, probe) => {
|
|
226672
|
-
try {
|
|
226673
|
-
const require2 = createRequire7(import.meta.url);
|
|
226674
|
-
let dir = dirname20(require2.resolve(probe));
|
|
226675
|
-
for (let depth = 0;depth < 16; depth++) {
|
|
226676
|
-
const manifest = join40(dir, "package.json");
|
|
226677
|
-
if (existsSync39(manifest)) {
|
|
226678
|
-
const parsed = JSON.parse(readFileSync34(manifest, "utf8"));
|
|
226679
|
-
if (parsed.name === name)
|
|
226680
|
-
return parsed.version ?? "unknown";
|
|
226681
|
-
}
|
|
226682
|
-
const parent = dirname20(dir);
|
|
226683
|
-
if (parent === dir)
|
|
226684
|
-
break;
|
|
226685
|
-
dir = parent;
|
|
226686
|
-
}
|
|
226687
|
-
} catch {}
|
|
226688
|
-
return null;
|
|
226689
|
-
};
|
|
227143
|
+
init_peers();
|
|
227144
|
+
init_peerInstall();
|
|
227145
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
227146
|
+
import { existsSync as existsSync43, readdirSync as readdirSync21 } from "node:fs";
|
|
227147
|
+
import { homedir as homedir10 } from "node:os";
|
|
227148
|
+
import { dirname as dirname21, join as join44, resolve as resolve25 } from "node:path";
|
|
226690
227149
|
|
|
226691
227150
|
// src/commands/types.ts
|
|
226692
227151
|
init_api();
|
|
@@ -226694,11 +227153,11 @@ init_config();
|
|
|
226694
227153
|
init_manifest();
|
|
226695
227154
|
init_types4();
|
|
226696
227155
|
init_util();
|
|
226697
|
-
import { resolve as
|
|
227156
|
+
import { resolve as resolve24 } from "node:path";
|
|
226698
227157
|
var label = (bundle) => `${Object.keys(bundle).length} file(s)`;
|
|
226699
227158
|
var types4 = async (args) => {
|
|
226700
227159
|
const dirArg = args._[0];
|
|
226701
|
-
const root = dirArg ?
|
|
227160
|
+
const root = dirArg ? resolve24(dirArg) : (() => {
|
|
226702
227161
|
try {
|
|
226703
227162
|
return findProjectRoot(process.cwd());
|
|
226704
227163
|
} catch {
|
|
@@ -226758,10 +227217,10 @@ var types4 = async (args) => {
|
|
|
226758
227217
|
|
|
226759
227218
|
// src/commands/update.ts
|
|
226760
227219
|
var findProjectDir = (start) => {
|
|
226761
|
-
const home =
|
|
226762
|
-
let dir =
|
|
227220
|
+
const home = resolve25(homedir10());
|
|
227221
|
+
let dir = resolve25(start);
|
|
226763
227222
|
while (true) {
|
|
226764
|
-
if (dir !== home && (
|
|
227223
|
+
if (dir !== home && (existsSync43(join44(dir, "app.json")) || existsSync43(join44(dir, ".lecodes", "manifest.json")) || existsSync43(join44(dir, ".lecodes", "types"))))
|
|
226765
227224
|
return dir;
|
|
226766
227225
|
const parent = dirname21(dir);
|
|
226767
227226
|
if (parent === dir)
|
|
@@ -226791,12 +227250,12 @@ var semverNewer2 = (a2, b) => {
|
|
|
226791
227250
|
return 0;
|
|
226792
227251
|
};
|
|
226793
227252
|
var FROM_SOURCE = /[\\/]src[\\/]commands[\\/]/.test(import.meta.url);
|
|
226794
|
-
var STANDALONE =
|
|
227253
|
+
var STANDALONE = existsSync43(join44(dirname21(process.execPath), "runtime")) && existsSync43(join44(dirname21(process.execPath), process.platform === "win32" ? "chisel.exe" : "chisel"));
|
|
226795
227254
|
var update = async (args) => {
|
|
226796
227255
|
const steps = [];
|
|
226797
227256
|
const heading = (name, what) => log(`
|
|
226798
227257
|
${c.bold(name)} ${c.dim(what)}`);
|
|
226799
|
-
const
|
|
227258
|
+
const run3 = async (name, what, fn) => {
|
|
226800
227259
|
heading(name, what);
|
|
226801
227260
|
try {
|
|
226802
227261
|
const r2 = await fn();
|
|
@@ -226811,77 +227270,88 @@ ${c.bold(name)} ${c.dim(what)}`);
|
|
|
226811
227270
|
}
|
|
226812
227271
|
};
|
|
226813
227272
|
const root = findProjectDir(process.cwd());
|
|
226814
|
-
await
|
|
227273
|
+
await run3("desktop", "LeCodes Desktop host (desktop run / render --desktop)", async () => {
|
|
226815
227274
|
if (process.env.LECODES_DESKTOP_EXE)
|
|
226816
227275
|
return ["skipped", `LECODES_DESKTOP_EXE=${process.env.LECODES_DESKTOP_EXE} (a local build; \`lecodes desktop update\` refreshes the cache anyway)`];
|
|
226817
227276
|
await updateDesktop();
|
|
226818
227277
|
return "ok";
|
|
226819
227278
|
});
|
|
226820
|
-
await
|
|
227279
|
+
await run3("matc", "Filament material compiler for local shader compiles", async () => {
|
|
226821
227280
|
if (process.env.LECODES_MATC)
|
|
226822
227281
|
return ["skipped", `LECODES_MATC=${process.env.LECODES_MATC} (a local matc)`];
|
|
226823
227282
|
await updateMatc();
|
|
226824
227283
|
return "ok";
|
|
226825
227284
|
});
|
|
226826
|
-
await
|
|
227285
|
+
await run3("shaders", "recompile the project's .mat shaders with the current matc", async () => {
|
|
226827
227286
|
if (!root)
|
|
226828
227287
|
return ["skipped", "not inside a project"];
|
|
226829
227288
|
await shaders({ _: ["compile"], flags: {} });
|
|
226830
227289
|
return "ok";
|
|
226831
227290
|
});
|
|
226832
|
-
await
|
|
227291
|
+
await run3("types", flagBool(args, "local") ? "IDE types from the SDK bundled in this CLI" : "IDE types from the server", async () => {
|
|
226833
227292
|
if (!root)
|
|
226834
227293
|
return ["skipped", "not inside a project"];
|
|
226835
227294
|
await types4({ _: [], flags: flagBool(args, "local") ? { local: true } : {} });
|
|
226836
227295
|
return "ok";
|
|
226837
227296
|
});
|
|
226838
|
-
await
|
|
227297
|
+
await run3("shells", "ios/ + android/ pins → newest SDK release + plugin tags, then re-sync", async () => {
|
|
226839
227298
|
if (!root)
|
|
226840
227299
|
return ["skipped", "not inside a project"];
|
|
226841
227300
|
if (flagBool(args, "no-shells"))
|
|
226842
227301
|
return ["skipped", "--no-shells"];
|
|
226843
|
-
const present = ["ios", "android"].filter((d) =>
|
|
227302
|
+
const present = ["ios", "android"].filter((d) => existsSync43(join44(root, d)) && readdirSync21(join44(root, d)).length > 0);
|
|
226844
227303
|
if (present.length === 0)
|
|
226845
227304
|
return ["skipped", "no ios/ or android/ shell in this project (`lecodes app init ios|android`)"];
|
|
226846
227305
|
await app({ _: ["update"], flags: {} });
|
|
226847
227306
|
return "ok";
|
|
226848
227307
|
});
|
|
226849
|
-
await
|
|
227308
|
+
await run3("cli", "lecodes-cli + its optional peers (global npm installs)", async () => {
|
|
226850
227309
|
if (flagBool(args, "no-cli"))
|
|
226851
227310
|
return ["skipped", "--no-cli"];
|
|
226852
227311
|
if (FROM_SOURCE)
|
|
226853
227312
|
return ["skipped", "running from the repo checkout — the CLI and its peers are the repo's own"];
|
|
226854
227313
|
const rows = [];
|
|
226855
227314
|
for (const [name, probe] of OPTIONAL_PEERS) {
|
|
227315
|
+
const where = peerSource(name, probe);
|
|
226856
227316
|
const installed = peerVersion(name, probe);
|
|
226857
|
-
|
|
227317
|
+
const latest = !installed ? null : where === "prefix" && name === "lecodes-renderer" ? package_default.version : await npmLatest(name);
|
|
227318
|
+
rows.push({ name, installed, latest, where });
|
|
226858
227319
|
}
|
|
226859
|
-
rows.push({ name: "lecodes-cli", installed: package_default.version, latest: await npmLatest("lecodes-cli") });
|
|
226860
|
-
if (rows.every((
|
|
227320
|
+
rows.push({ name: "lecodes-cli", installed: package_default.version, latest: await npmLatest("lecodes-cli"), where: "global" });
|
|
227321
|
+
if (rows.every((r2) => r2.latest === null))
|
|
226861
227322
|
return ["skipped", "npm unreachable"];
|
|
226862
|
-
const behind = rows.filter((
|
|
226863
|
-
for (const
|
|
226864
|
-
const state = !
|
|
226865
|
-
log(" " +
|
|
227323
|
+
const behind = rows.filter((r2) => r2.installed && r2.latest && semverNewer2(r2.latest, r2.installed) > 0);
|
|
227324
|
+
for (const r2 of rows) {
|
|
227325
|
+
const state = !r2.installed ? c.dim("not installed (optional — installed on first use)") : !r2.latest ? c.dim("(npm: unknown)") : behind.includes(r2) ? c.yellow("→ " + r2.latest) : c.dim("(up to date)");
|
|
227326
|
+
log(" " + r2.name.padEnd(18) + " " + (r2.installed ?? "").padEnd(8) + " " + (r2.where === "prefix" ? c.dim("peers ") : "") + state);
|
|
226866
227327
|
}
|
|
226867
227328
|
if (behind.length === 0)
|
|
226868
227329
|
return "ok";
|
|
226869
|
-
const
|
|
226870
|
-
|
|
226871
|
-
|
|
226872
|
-
|
|
227330
|
+
const prefixBehind = behind.filter((r2) => r2.where === "prefix");
|
|
227331
|
+
const globalSpecs = behind.filter((r2) => r2.where !== "prefix").map((r2) => r2.name + "@latest");
|
|
227332
|
+
if (flagBool(args, "check")) {
|
|
227333
|
+
return ["updated", behind.length + " behind — run: lecodes update" + (globalSpecs.length ? " (global: npm install -g " + globalSpecs.join(" ") + ")" : "")];
|
|
227334
|
+
}
|
|
227335
|
+
const done = [];
|
|
227336
|
+
if (prefixBehind.length) {
|
|
227337
|
+
await installPeers(prefixBehind.map((r2) => r2.name), "lecodes update", { yes: true });
|
|
227338
|
+
done.push(...prefixBehind.map((r2) => r2.name));
|
|
227339
|
+
}
|
|
227340
|
+
if (STANDALONE && globalSpecs.some((s2) => s2.startsWith("lecodes-cli@"))) {
|
|
226873
227341
|
note("this is the standalone build — the CLI updates by re-installing the release archive, not through npm; peers still go through npm");
|
|
226874
227342
|
}
|
|
226875
|
-
const toInstall = STANDALONE ?
|
|
226876
|
-
if (toInstall.length
|
|
227343
|
+
const toInstall = STANDALONE ? globalSpecs.filter((s2) => !s2.startsWith("lecodes-cli@")) : globalSpecs;
|
|
227344
|
+
if (toInstall.length) {
|
|
227345
|
+
log(" " + c.dim("$") + " npm install -g " + toInstall.join(" "));
|
|
227346
|
+
const r2 = spawnSync9("npm", ["install", "-g", ...toInstall], { stdio: "inherit", shell: true });
|
|
227347
|
+
if (r2.status !== 0)
|
|
227348
|
+
throw new CliError("npm install -g exited with " + (r2.status ?? "a signal"));
|
|
227349
|
+
done.push(...toInstall.map((s2) => s2.replace("@latest", "")));
|
|
227350
|
+
}
|
|
227351
|
+
if (done.length === 0)
|
|
226877
227352
|
return ["skipped", "only the standalone CLI itself is behind — download the release"];
|
|
226878
|
-
|
|
226879
|
-
|
|
226880
|
-
if (r2.status !== 0)
|
|
226881
|
-
throw new CliError("npm install -g exited with " + (r2.status ?? "a signal"));
|
|
226882
|
-
const names3 = toInstall.map((s2) => s2.replace("@latest", "")).join(", ");
|
|
226883
|
-
const cliToo = toInstall.some((s2) => s2.startsWith("lecodes-cli@"));
|
|
226884
|
-
return ["updated", names3 + " → latest" + (cliToo ? " (the new CLI runs from your next command)" : "")];
|
|
227353
|
+
const cliToo = done.includes("lecodes-cli");
|
|
227354
|
+
return ["updated", done.join(", ") + " → latest" + (cliToo ? " (the new CLI runs from your next command)" : "")];
|
|
226885
227355
|
});
|
|
226886
227356
|
log(`
|
|
226887
227357
|
${c.bold("Summary")}`);
|
|
@@ -226895,25 +227365,25 @@ ${c.bold("Summary")}`);
|
|
|
226895
227365
|
};
|
|
226896
227366
|
|
|
226897
227367
|
// src/index.ts
|
|
226898
|
-
import { existsSync as
|
|
226899
|
-
import { delimiter as delimiter3, dirname as dirname24, join as
|
|
227368
|
+
import { existsSync as existsSync46, realpathSync as realpathSync2 } from "node:fs";
|
|
227369
|
+
import { delimiter as delimiter3, dirname as dirname24, join as join47 } from "node:path";
|
|
226900
227370
|
|
|
226901
227371
|
// src/commands/install.ts
|
|
226902
227372
|
init_util();
|
|
226903
227373
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
226904
|
-
import { appendFileSync as appendFileSync3, chmodSync as chmodSync2, cpSync as cpSync5, existsSync as
|
|
226905
|
-
import { homedir as
|
|
226906
|
-
import { basename as
|
|
227374
|
+
import { appendFileSync as appendFileSync3, chmodSync as chmodSync2, cpSync as cpSync5, existsSync as existsSync44, mkdirSync as mkdirSync29, readFileSync as readFileSync37, realpathSync, rmSync as rmSync13, symlinkSync as symlinkSync2 } from "node:fs";
|
|
227375
|
+
import { homedir as homedir11 } from "node:os";
|
|
227376
|
+
import { basename as basename20, delimiter as delimiter2, dirname as dirname22, join as join45, resolve as resolve26, sep as sep2 } from "node:path";
|
|
226907
227377
|
var isWin = process.platform === "win32";
|
|
226908
|
-
var HOME =
|
|
227378
|
+
var HOME = homedir11();
|
|
226909
227379
|
var safeRealpath = (p2) => {
|
|
226910
227380
|
try {
|
|
226911
227381
|
return realpathSync(p2);
|
|
226912
227382
|
} catch {
|
|
226913
|
-
return
|
|
227383
|
+
return resolve26(p2);
|
|
226914
227384
|
}
|
|
226915
227385
|
};
|
|
226916
|
-
var
|
|
227386
|
+
var onPath2 = (dir) => {
|
|
226917
227387
|
const want = safeRealpath(dir);
|
|
226918
227388
|
return (process.env.PATH ?? "").split(delimiter2).some((p2) => {
|
|
226919
227389
|
if (!p2)
|
|
@@ -226927,24 +227397,24 @@ var onPath = (dir) => {
|
|
|
226927
227397
|
};
|
|
226928
227398
|
var install = async (args) => {
|
|
226929
227399
|
const bundle = dirname22(process.execPath);
|
|
226930
|
-
const exeName3 =
|
|
227400
|
+
const exeName3 = basename20(process.execPath);
|
|
226931
227401
|
const chiselName = isWin ? "chisel.exe" : "chisel";
|
|
226932
|
-
if (!
|
|
227402
|
+
if (!existsSync44(join45(bundle, "runtime")) || !existsSync44(join45(bundle, chiselName))) {
|
|
226933
227403
|
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.");
|
|
226934
227404
|
}
|
|
226935
|
-
const
|
|
227405
|
+
const companions2 = [exeName3, chiselName, "runtime", "README.txt"].filter((f3) => existsSync44(join45(bundle, f3)));
|
|
226936
227406
|
if (isWin)
|
|
226937
|
-
return installWindows(args, bundle,
|
|
226938
|
-
return installUnix(args, bundle,
|
|
227407
|
+
return installWindows(args, bundle, companions2);
|
|
227408
|
+
return installUnix(args, bundle, companions2);
|
|
226939
227409
|
};
|
|
226940
|
-
var copyBundle = (bundle, target,
|
|
226941
|
-
|
|
227410
|
+
var copyBundle = (bundle, target, companions2) => {
|
|
227411
|
+
mkdirSync29(target, { recursive: true });
|
|
226942
227412
|
if (safeRealpath(target) === safeRealpath(bundle))
|
|
226943
227413
|
return;
|
|
226944
|
-
for (const f3 of
|
|
226945
|
-
const dest =
|
|
227414
|
+
for (const f3 of companions2) {
|
|
227415
|
+
const dest = join45(target, f3);
|
|
226946
227416
|
rmSync13(dest, { recursive: true, force: true });
|
|
226947
|
-
cpSync5(
|
|
227417
|
+
cpSync5(join45(bundle, f3), dest, { recursive: true });
|
|
226948
227418
|
}
|
|
226949
227419
|
};
|
|
226950
227420
|
var permError = (e2, dir, system) => {
|
|
@@ -226954,43 +227424,43 @@ var permError = (e2, dir, system) => {
|
|
|
226954
227424
|
}
|
|
226955
227425
|
return new CliError(`Install failed: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
226956
227426
|
};
|
|
226957
|
-
var installUnix = (args, bundle,
|
|
227427
|
+
var installUnix = (args, bundle, companions2) => {
|
|
226958
227428
|
const system = flagBool(args, "system");
|
|
226959
|
-
const libDir =
|
|
226960
|
-
const binDir =
|
|
226961
|
-
const link2 =
|
|
227429
|
+
const libDir = resolve26(flagStr(args, "dir") ?? (system ? "/usr/local/lib/lecodes-cli" : join45(HOME, ".local", "lib", "lecodes-cli")));
|
|
227430
|
+
const binDir = resolve26(flagStr(args, "bin") ?? (system ? "/usr/local/bin" : join45(HOME, ".local", "bin")));
|
|
227431
|
+
const link2 = join45(binDir, "lecodes");
|
|
226962
227432
|
try {
|
|
226963
|
-
copyBundle(bundle, libDir,
|
|
226964
|
-
chmodSync2(
|
|
226965
|
-
chmodSync2(
|
|
227433
|
+
copyBundle(bundle, libDir, companions2);
|
|
227434
|
+
chmodSync2(join45(libDir, "lecodes"), 493);
|
|
227435
|
+
chmodSync2(join45(libDir, "chisel"), 493);
|
|
226966
227436
|
} catch (e2) {
|
|
226967
227437
|
throw permError(e2, libDir, system);
|
|
226968
227438
|
}
|
|
226969
227439
|
try {
|
|
226970
|
-
|
|
227440
|
+
mkdirSync29(binDir, { recursive: true });
|
|
226971
227441
|
rmSync13(link2, { force: true });
|
|
226972
|
-
symlinkSync2(
|
|
227442
|
+
symlinkSync2(join45(libDir, "lecodes"), link2);
|
|
226973
227443
|
} catch (e2) {
|
|
226974
227444
|
throw permError(e2, binDir, system);
|
|
226975
227445
|
}
|
|
226976
227446
|
success(`Installed to ${c.bold(libDir)}`);
|
|
226977
|
-
log(` ${c.dim(link2 + " → " +
|
|
227447
|
+
log(` ${c.dim(link2 + " → " + join45(libDir, "lecodes"))}`);
|
|
226978
227448
|
finishPathUnix(args, binDir);
|
|
226979
227449
|
};
|
|
226980
227450
|
var shellRc = () => {
|
|
226981
|
-
const shell =
|
|
227451
|
+
const shell = basename20(process.env.SHELL ?? "");
|
|
226982
227452
|
if (shell === "zsh")
|
|
226983
|
-
return { file:
|
|
227453
|
+
return { file: join45(HOME, ".zshrc"), name: "~/.zshrc" };
|
|
226984
227454
|
if (shell === "bash")
|
|
226985
|
-
return { file:
|
|
226986
|
-
return { file:
|
|
227455
|
+
return { file: join45(HOME, ".bashrc"), name: "~/.bashrc" };
|
|
227456
|
+
return { file: join45(HOME, ".profile"), name: "~/.profile" };
|
|
226987
227457
|
};
|
|
226988
227458
|
var PATH_MARKER = "# Added by `lecodes install`";
|
|
226989
227459
|
var appendPathToShellRc = (binDir) => {
|
|
226990
227460
|
const { file, name } = shellRc();
|
|
226991
227461
|
const line = `export PATH="${binDir}:$PATH"`;
|
|
226992
227462
|
try {
|
|
226993
|
-
const existing =
|
|
227463
|
+
const existing = existsSync44(file) ? readFileSync37(file, "utf8") : "";
|
|
226994
227464
|
if (existing.includes(line))
|
|
226995
227465
|
return name;
|
|
226996
227466
|
appendFileSync3(file, `${existing && !existing.endsWith(`
|
|
@@ -227005,7 +227475,7 @@ ${line}
|
|
|
227005
227475
|
}
|
|
227006
227476
|
};
|
|
227007
227477
|
var finishPathUnix = (args, binDir) => {
|
|
227008
|
-
if (
|
|
227478
|
+
if (onPath2(binDir)) {
|
|
227009
227479
|
log(` Run ${c.bold("lecodes --version")} in a new shell to verify.`);
|
|
227010
227480
|
return;
|
|
227011
227481
|
}
|
|
@@ -227027,15 +227497,15 @@ var finishPathUnix = (args, binDir) => {
|
|
|
227027
227497
|
log(` • let install do it: re-run with ${c.bold("--modify-path")}`);
|
|
227028
227498
|
log(` • or install system-wide (no PATH edit): ${c.bold("sudo lecodes install --system")}`);
|
|
227029
227499
|
};
|
|
227030
|
-
var installWindows = (args, bundle,
|
|
227031
|
-
const target =
|
|
227500
|
+
var installWindows = (args, bundle, companions2) => {
|
|
227501
|
+
const target = resolve26(flagStr(args, "dir") ?? join45(process.env.LOCALAPPDATA ?? join45(HOME, "AppData", "Local"), "Programs", "lecodes-cli"));
|
|
227032
227502
|
try {
|
|
227033
|
-
copyBundle(bundle, target,
|
|
227503
|
+
copyBundle(bundle, target, companions2);
|
|
227034
227504
|
} catch (e2) {
|
|
227035
227505
|
throw new CliError(`Install failed writing to ${target}: ${e2 instanceof Error ? e2.message : String(e2)}`);
|
|
227036
227506
|
}
|
|
227037
227507
|
success(`Installed to ${c.bold(target)}`);
|
|
227038
|
-
if (
|
|
227508
|
+
if (onPath2(target)) {
|
|
227039
227509
|
log(` Already on PATH — run ${c.bold("lecodes --version")} in a new terminal.`);
|
|
227040
227510
|
return;
|
|
227041
227511
|
}
|
|
@@ -227067,9 +227537,9 @@ var addToUserPathWindows = (dir) => {
|
|
|
227067
227537
|
init_util();
|
|
227068
227538
|
|
|
227069
227539
|
// src/projectEnv.ts
|
|
227070
|
-
import { existsSync as
|
|
227071
|
-
import { homedir as
|
|
227072
|
-
import { dirname as dirname23, join as
|
|
227540
|
+
import { existsSync as existsSync45, readFileSync as readFileSync38 } from "node:fs";
|
|
227541
|
+
import { homedir as homedir12 } from "node:os";
|
|
227542
|
+
import { dirname as dirname23, join as join46, resolve as resolve27 } from "node:path";
|
|
227073
227543
|
var KEY_RE = /^(LECODES|CREATOR)_[A-Z0-9_]+$/;
|
|
227074
227544
|
var parseDotEnv = (text) => {
|
|
227075
227545
|
const out = {};
|
|
@@ -227104,17 +227574,20 @@ var applyDotEnv = (parsed, dir, env) => {
|
|
|
227104
227574
|
continue;
|
|
227105
227575
|
let value = raw;
|
|
227106
227576
|
if (value.startsWith("./") || value.startsWith("../"))
|
|
227107
|
-
value =
|
|
227577
|
+
value = resolve27(dir, value);
|
|
227108
227578
|
else if (value === "~" || value.startsWith("~/"))
|
|
227109
|
-
value =
|
|
227579
|
+
value = join46(homedir12(), value.slice(1));
|
|
227110
227580
|
out[key] = value;
|
|
227111
227581
|
}
|
|
227112
227582
|
return out;
|
|
227113
227583
|
};
|
|
227114
227584
|
var findEnvRoot = (start) => {
|
|
227115
|
-
let dir =
|
|
227585
|
+
let dir = resolve27(start);
|
|
227586
|
+
const home = resolve27(homedir12());
|
|
227116
227587
|
while (true) {
|
|
227117
|
-
if (
|
|
227588
|
+
if (dir === home)
|
|
227589
|
+
return null;
|
|
227590
|
+
if (existsSync45(join46(dir, "app.json")) || existsSync45(join46(dir, ".lecodes")))
|
|
227118
227591
|
return dir;
|
|
227119
227592
|
const parent = dirname23(dir);
|
|
227120
227593
|
if (parent === dir)
|
|
@@ -227127,10 +227600,10 @@ var loadProjectEnv = (cwd = process.cwd()) => {
|
|
|
227127
227600
|
const root = findEnvRoot(cwd);
|
|
227128
227601
|
if (!root)
|
|
227129
227602
|
return [];
|
|
227130
|
-
const file =
|
|
227131
|
-
if (!
|
|
227603
|
+
const file = join46(root, ".env");
|
|
227604
|
+
if (!existsSync45(file))
|
|
227132
227605
|
return [];
|
|
227133
|
-
const added = applyDotEnv(parseDotEnv(
|
|
227606
|
+
const added = applyDotEnv(parseDotEnv(readFileSync38(file, "utf8")), root, process.env);
|
|
227134
227607
|
for (const [k, v] of Object.entries(added))
|
|
227135
227608
|
process.env[k] = v;
|
|
227136
227609
|
return Object.keys(added);
|
|
@@ -227140,10 +227613,11 @@ var loadProjectEnv = (cwd = process.cwd()) => {
|
|
|
227140
227613
|
};
|
|
227141
227614
|
|
|
227142
227615
|
// src/index.ts
|
|
227616
|
+
init_peers();
|
|
227143
227617
|
var STANDALONE2 = (() => {
|
|
227144
227618
|
const dir = dirname24(process.execPath);
|
|
227145
227619
|
const chisel = process.platform === "win32" ? "chisel.exe" : "chisel";
|
|
227146
|
-
return
|
|
227620
|
+
return existsSync46(join47(dir, "runtime")) && existsSync46(join47(dir, chisel));
|
|
227147
227621
|
})();
|
|
227148
227622
|
var INSTALLED = STANDALONE2 && (() => {
|
|
227149
227623
|
const exe = process.platform === "win32" ? "lecodes.exe" : "lecodes";
|
|
@@ -227158,8 +227632,8 @@ var INSTALLED = STANDALONE2 && (() => {
|
|
|
227158
227632
|
if (!d)
|
|
227159
227633
|
return false;
|
|
227160
227634
|
try {
|
|
227161
|
-
const cand =
|
|
227162
|
-
return
|
|
227635
|
+
const cand = join47(d, exe);
|
|
227636
|
+
return existsSync46(cand) && realpathSync2(cand) === self2;
|
|
227163
227637
|
} catch {
|
|
227164
227638
|
return false;
|
|
227165
227639
|
}
|
|
@@ -227227,6 +227701,14 @@ ${c.bold("Commands:")}
|
|
|
227227
227701
|
--host <ip> LAN address to advertise (default: auto-detect)
|
|
227228
227702
|
--entry <path> override the entrypoint
|
|
227229
227703
|
--no-qr don't render the QR code
|
|
227704
|
+
--android open the dev url in the LeCodes app on the phone
|
|
227705
|
+
plugged in over adb (adb reverse: no Wi-Fi/LAN
|
|
227706
|
+
address needed). --serial <id> picks a device,
|
|
227707
|
+
--apk <file> installs first, --package <name>
|
|
227708
|
+
(default com.letary.lecodes). Device logs stream
|
|
227709
|
+
here. --time <s> --png <file>: bounded check —
|
|
227710
|
+
screenshot after <s> seconds, force-stop, exit
|
|
227711
|
+
(exit 1 on a crash). Ctrl+C force-stops the app.
|
|
227230
227712
|
--web also open the app in the browser (Web Lite host:
|
|
227231
227713
|
same bundle + hot reload, camera/map plugins
|
|
227232
227714
|
have web twins; the /web/ URL is served always)
|
|
@@ -227422,12 +227904,15 @@ ${c.bold("Commands:")}
|
|
|
227422
227904
|
--cell <m> (0.25) --cell-height <m> (0.1) --tile <cells> (48)
|
|
227423
227905
|
--obj also write <n>.navmesh.obj to inspect
|
|
227424
227906
|
--frames <n> load headroom (900) --logs host output
|
|
227425
|
-
shaders [compile|update]
|
|
227907
|
+
shaders [compile|update|new] The project's custom shaders (.mat) and their local compile:
|
|
227426
227908
|
no args lists the shaders + their compile state; compile
|
|
227427
227909
|
compiles them all into .lecodes/shaders (the same pass
|
|
227428
227910
|
'lecodes dev' runs on the fly — via Filament's matc,
|
|
227429
227911
|
downloaded on first use to ~/.lecodes/matc); update
|
|
227430
|
-
downloads the newest matc (run after an engine upgrade)
|
|
227912
|
+
downloads the newest matc (run after an engine upgrade);
|
|
227913
|
+
new <name> [--ref lit] [--dir d] [--force] writes a fresh
|
|
227914
|
+
.mat + .material.ts: the starter, or a copy of the engine's
|
|
227915
|
+
own lit / unlit / particles / … source to start from
|
|
227431
227916
|
scene [path] LeCodes Scene Editor — visually edit a .scene.ts (tree, inspector,
|
|
227432
227917
|
gizmo) on local files (needs the optional 'lecodes-3d-editor' package;
|
|
227433
227918
|
works inside a cloned project or standalone in any folder)
|
|
@@ -227542,12 +228027,13 @@ LECODES_DESKTOP_EXE, LECODES_MATC, LECODES_NOTARY_PROFILE, LECODES_MACOS_SIGN_ID
|
|
|
227542
228027
|
var commands = { init: init2, login, create, clone: clone3, link, status: status3, diff: diff2, pull, push, compile: compile2, preview: previewLegacy, render, test, design, scene, thumbs, dev, app, pn, desktop, shaders, assets, lightmap, navmesh, update };
|
|
227543
228028
|
if (STANDALONE2)
|
|
227544
228029
|
commands.install = install;
|
|
227545
|
-
var
|
|
228030
|
+
var readVersion2 = () => package_default.version ?? "unknown";
|
|
227546
228031
|
var printVersion = () => {
|
|
227547
|
-
const rows = [["lecodes-cli",
|
|
228032
|
+
const rows = [["lecodes-cli", readVersion2()]];
|
|
227548
228033
|
for (const [name, probe] of OPTIONAL_PEERS) {
|
|
227549
228034
|
const version = peerVersion(name, probe);
|
|
227550
|
-
|
|
228035
|
+
const where = peerSource(name, probe);
|
|
228036
|
+
rows.push([name, version ? where === "prefix" ? `${version} ${c.dim("(~/.lecodes/peers)")}` : version : c.dim("not installed — installed on first use")]);
|
|
227551
228037
|
}
|
|
227552
228038
|
const desktopVersion = selectedDesktopVersion();
|
|
227553
228039
|
rows.push(["lecodes-desktop", installedDesktopVersions().includes(desktopVersion) ? desktopVersion : `${desktopVersion} ${c.dim("(not downloaded — fetched on first `render --desktop`)")}`]);
|
|
@@ -227561,6 +228047,8 @@ var main = async () => {
|
|
|
227561
228047
|
const argv = process.argv.slice(2);
|
|
227562
228048
|
const command = argv[0];
|
|
227563
228049
|
loadProjectEnv();
|
|
228050
|
+
if (argv.includes("--yes") || argv.includes("-y"))
|
|
228051
|
+
process.env.LECODES_YES = "1";
|
|
227564
228052
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
227565
228053
|
log(HELP);
|
|
227566
228054
|
return;
|
|
@@ -227569,14 +228057,14 @@ var main = async () => {
|
|
|
227569
228057
|
printVersion();
|
|
227570
228058
|
return;
|
|
227571
228059
|
}
|
|
227572
|
-
const
|
|
227573
|
-
if (!
|
|
228060
|
+
const run3 = commands[command];
|
|
228061
|
+
if (!run3) {
|
|
227574
228062
|
log(c.red(`Unknown command: ${command}`));
|
|
227575
228063
|
log(HELP);
|
|
227576
228064
|
process.exitCode = 1;
|
|
227577
228065
|
return;
|
|
227578
228066
|
}
|
|
227579
|
-
await
|
|
228067
|
+
await run3(Object.assign(parseArgs(argv.slice(1)), { _raw: argv.slice(1) }));
|
|
227580
228068
|
};
|
|
227581
228069
|
main().catch((e2) => {
|
|
227582
228070
|
if (e2 instanceof CliError)
|