lecodes-cli 1.0.0 → 2.0.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 +6211 -3649
- package/package.json +12 -8
- package/runtime/materials/lightmap.mat +115 -139
- package/runtime/materials/particles-quad.mat +6 -2
- package/runtime/materials/terrain-lightmap.mat +123 -0
- package/runtime/materials/terrain.mat +36 -55
- package/runtime/sdk-types.json +1 -1
- package/runtime/web/VERSION.json +5 -0
- package/runtime/web/assets/camera-5XOM8eHl.js +1 -0
- package/runtime/web/assets/creator-2d-D9WKYM6h.js +1 -0
- package/runtime/web/assets/creator-2d-ukvEmNEp.wasm +0 -0
- package/runtime/web/assets/creator-full-BBXzQlhq.js +1 -0
- package/runtime/web/assets/creator-full-D2dSDdDj.wasm +0 -0
- package/runtime/web/assets/creator-full.js-BYswGsqR.symbols +8124 -0
- package/runtime/web/assets/creator-ui-DIc92ion.wasm +0 -0
- package/runtime/web/assets/creator-ui-_1Z_AX2M.js +1 -0
- package/runtime/web/assets/geolocation-BjTKN9_P.js +1 -0
- package/runtime/web/assets/inspect-C7x78PNs.js +2 -0
- package/runtime/web/assets/map-CyBxOSV0.js +1 -0
- package/runtime/web/assets/map-XzoOFt0E.js +2 -0
- package/runtime/web/assets/mapImpl-bxvQ2h1Z.js +823 -0
- package/runtime/web/assets/maplibre-gl-worker-B7LXXUkR.js +8 -0
- package/runtime/web/assets/materials-uri4FvPE.bin +0 -0
- package/runtime/web/assets/materials-world-CQWt8TAm.bin +0 -0
- package/runtime/web/assets/qr-scanner-CK_cYoJR.js +1 -0
- package/runtime/web/assets/uberarchive-DLH-kBhM.bin +0 -0
- package/runtime/web/index.html +36 -0
- package/runtime/web/player.js +21 -0
- package/runtime/web-page/index.html +41 -0
- package/runtime/web-page/page.css +1 -0
- package/runtime/web-page/page.js +31 -0
- package/src/cli/help.ts +25 -0
- package/src/cli/index.ts +7 -7
- package/src/commands/app/android.ts +3 -1
- package/src/commands/app/desktop.ts +1 -1
- package/src/commands/app/desktopMac.ts +10 -9
- package/src/commands/app/icon.ts +9 -6
- package/src/commands/app/index.ts +3 -1
- package/src/commands/app/shared.ts +13 -5
- package/src/commands/app/templates/android.ts +10 -11
- package/src/commands/app/templates/ios.ts +1 -1
- package/src/commands/assets.ts +1 -1
- package/src/commands/compile.ts +15 -2
- package/src/commands/design/index.ts +4 -4
- package/src/commands/design/snapshot.ts +3 -3
- package/src/commands/desktop.ts +5 -1
- package/src/commands/dev.ts +82 -29
- package/src/commands/index.ts +64 -60
- package/src/commands/init/index.ts +11 -15
- package/src/commands/init/templates.ts +64 -21
- package/src/commands/lightmap.ts +459 -200
- package/src/commands/navmesh.ts +2 -2
- package/src/commands/plugin.ts +46 -0
- package/src/commands/render.ts +22 -16
- package/src/commands/scene.ts +6 -1
- package/src/commands/serve.ts +58 -0
- package/src/commands/shaders/new.ts +1 -1
- package/src/commands/test.ts +402 -398
- package/src/commands/update/index.ts +3 -3
- package/src/compile/designCompile.ts +1 -1
- package/src/compile/headlessBundle.ts +133 -133
- package/src/compile/nativeStack.ts +1 -1
- package/src/compile/projectCompile.ts +10 -2
- package/src/compile/sceneCompile.ts +8 -22
- package/src/declarations/lecodes-3d-editor.d.ts +4 -0
- package/src/declarations/lecodes-assets.d.ts +1 -1
- package/src/declarations/{lecodes-renderer.d.ts → lecodes-headless.d.ts} +3 -3
- package/src/dev/clientTemplates.ts +18 -14
- package/src/dev/devServer.ts +75 -38
- package/src/dev/iosDev.ts +180 -0
- package/src/dev/webRunner.ts +82 -14
- package/src/hosts/cmgenTool.ts +1 -1
- package/src/hosts/desktopRenderer.ts +98 -27
- package/src/hosts/desktopScript.ts +150 -35
- package/src/hosts/distRoot.ts +1 -1
- package/src/hosts/matcTool.ts +1 -1
- package/src/hosts/peerInstall.ts +5 -5
- package/src/hosts/peers.ts +2 -2
- package/src/index.ts +4 -1
- package/src/plugin/contract.ts +438 -0
- package/src/plugin/emitKotlin.ts +357 -0
- package/src/plugin/emitSdk.ts +259 -0
- package/src/plugin/emitSwift.ts +336 -0
- package/src/plugin/emitWeb.ts +383 -0
- package/src/plugin/generate.ts +157 -0
- package/src/plugin/lines.ts +28 -0
- package/src/plugin/lower.ts +231 -0
- package/src/plugin/manifest.ts +192 -0
- package/src/project/ignore.ts +164 -163
- package/runtime/materials/lightmap-baked-lite.mat +0 -180
- package/runtime/materials/lightmap-baked.mat +0 -181
- package/runtime/scene-harness.json +0 -1
- package/runtime/web/assets/__vite-browser-external-BIHI7g3E.js +0 -1
- package/runtime/web/assets/basis-C64VHDVD.js +0 -1
- package/runtime/web/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
- package/runtime/web/assets/createViewerLite-DHqTFhWB.js +0 -866
- package/runtime/web/assets/draco-BiISTFcR.js +0 -118
- package/runtime/web/assets/draco_decoder-DsQ12WqX.wasm +0 -0
- package/runtime/web/assets/index-Q06c6oHx.js +0 -2
- package/runtime/web/assets/mapViewImpl-B2JcES8l.js +0 -810
- package/runtime/web/assets/maplibre-gl-worker-CJfwIrte.js +0 -8
- package/runtime/web/assets/worker-Caf-yYEI.js +0 -2
- package/runtime/web/embed.html +0 -32
- package/runtime/web/embed.js +0 -171
|
@@ -21,7 +21,7 @@ import app from "../app"
|
|
|
21
21
|
* 3. shaders recompile the project's .mat files with that matc (= `shaders compile`)
|
|
22
22
|
* 4. types the IDE types (.lecodes/types + tsconfig) from the server (types.ts)
|
|
23
23
|
* 5. shells ios/ + android/ pins → newest SDK release + plugin tags, re-sync (= `app update`)
|
|
24
|
-
* 6. cli the npm installs — every installed optional peer (lecodes-
|
|
24
|
+
* 6. cli the npm installs — every installed optional peer (lecodes-headless, lecodes-design,
|
|
25
25
|
* lecodes-3d-editor, lecodes-assets, @napi-rs/canvas) and the CLI itself that is behind:
|
|
26
26
|
* peers in ~/.lecodes/peers (first-use installs, peerInstall.ts) are re-installed there
|
|
27
27
|
* — the renderer at THIS CLI's version, the rest at npm's latest in range; global ones
|
|
@@ -92,7 +92,7 @@ const STANDALONE = existsSync(join(dirname(process.execPath), "runtime")) &&
|
|
|
92
92
|
export default defineCommand({
|
|
93
93
|
name: "update",
|
|
94
94
|
summary: "Move everything the project depends on to its newest published state",
|
|
95
|
-
description: "In dependency order: the desktop host (`desktop update`), matc (`shaders update`), the project's shader compiles, the IDE types (.lecodes/types/ + tsconfig.json, from the server — the former `lecodes types`), the ios/android shell pins (`app update`), then the GLOBAL npm installs: every installed optional peer (lecodes-
|
|
95
|
+
description: "In dependency order: the desktop host (`desktop update`), matc (`shaders update`), the project's shader compiles, the IDE types (.lecodes/types/ + tsconfig.json, from the server — the former `lecodes types`), the ios/android shell pins (`app update`), then the GLOBAL npm installs: every installed optional peer (lecodes-headless, lecodes-design, lecodes-3d-editor, lecodes-assets) and this CLI that is behind npm's latest gets `npm install -g`.\n\nPinned tools are skipped, not overridden: LECODES_DESKTOP_EXE / LECODES_MATC mean a local build. Keeps going when a step fails; the summary says which.",
|
|
96
96
|
flags: {
|
|
97
97
|
local: bool("types from the SDK bundled in this CLI"),
|
|
98
98
|
"no-shells": bool("leave ios/android pins alone (no re-sync)"),
|
|
@@ -174,7 +174,7 @@ export default defineCommand({
|
|
|
174
174
|
const where = peerSource(name, probe)
|
|
175
175
|
const installed = peerVersion(name, probe)
|
|
176
176
|
const latest = !installed ? null
|
|
177
|
-
: where === "prefix" && name === "lecodes-
|
|
177
|
+
: where === "prefix" && name === "lecodes-headless" ? pkg.version
|
|
178
178
|
: await npmLatest(name)
|
|
179
179
|
rows.push({ name, installed, latest, where })
|
|
180
180
|
}
|
|
@@ -18,7 +18,7 @@ import { applyProjectFonts } from "./fonts"
|
|
|
18
18
|
|
|
19
19
|
export type DesignAssetMode = "server" | "disk"
|
|
20
20
|
|
|
21
|
-
// Must match lecodes-
|
|
21
|
+
// Must match lecodes-headless's LOCAL_ASSET_SCHEME (headless/index.ts) — same as `lecodes render`.
|
|
22
22
|
const LOCAL_ASSET_SCHEME = "lecodesfile://"
|
|
23
23
|
|
|
24
24
|
export const compileDesignScreen = async (opts: {
|
|
@@ -1,133 +1,133 @@
|
|
|
1
|
-
import { getProject } from "../platform/api"
|
|
2
|
-
import { loadConfig, requireApiUrl, requireToken } from "../platform/config"
|
|
3
|
-
import { findProjectRoot, readManifest, type Manifest } from "../project/manifest"
|
|
4
|
-
import { collectEntries } from "./collect"
|
|
5
|
-
import { collectLocalEntries } from "./collectLocal"
|
|
6
|
-
import { ambiguousEntryWarning, compileProject } from "./projectCompile"
|
|
7
|
-
import { applyProjectFonts } from "./fonts"
|
|
8
|
-
import { applyAppAssetIcons } from "./assetIcons"
|
|
9
|
-
import type { ScreenTarget } from "./screenEntry"
|
|
10
|
-
import { note } from "../cli"
|
|
11
|
-
|
|
12
|
-
/*
|
|
13
|
-
* Compile the project into the iife bundle the headless renderer runs — shared by `lecodes render`
|
|
14
|
-
* and `lecodes test`. Two modes, picked automatically (same split as `lecodes compile`):
|
|
15
|
-
* - Server-backed project (.lecodes/manifest.json): entries resolve from local files + the
|
|
16
|
-
* project's server metadata (needs login).
|
|
17
|
-
* - Local project (from `lecodes init` — no manifest): compiles straight from the working tree,
|
|
18
|
-
* no login.
|
|
19
|
-
* In both, resource assets default to LOCAL disk reads: each asset('./x.png') URL becomes
|
|
20
|
-
* `lecodesfile://<abs-path>` which the renderer reads off disk — unpushed/edited files render with
|
|
21
|
-
* no network. `--remote-assets` / an explicit `--public-url` switch to server URLs instead
|
|
22
|
-
* (server-backed projects only; then data needs --wait-network).
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
// URL scheme emitted for a local project's resources so the renderer reads them off disk. Must match
|
|
26
|
-
// lecodes-
|
|
27
|
-
export const LOCAL_ASSET_SCHEME = "lecodesfile://"
|
|
28
|
-
|
|
29
|
-
export type HeadlessBundle = {
|
|
30
|
-
js: string
|
|
31
|
-
/** The project root the bundle was compiled from. */
|
|
32
|
-
root: string
|
|
33
|
-
/** Non-fatal compile-time notices (missing URLs, engine hints) — print via warnErr. */
|
|
34
|
-
warnings: string[]
|
|
35
|
-
/** Whether asset URLs resolve from the local disk (`lecodesfile://`). */
|
|
36
|
-
useLocalAssets: boolean
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** What a headless compile honours: `--entry` / `--public-url` / `--remote-assets` / `--wait-network`,
|
|
40
|
-
* and the screen module a `render <file>` named. */
|
|
41
|
-
export type HeadlessOptions = { entry?: string, publicUrl?: string, remoteAssets?: boolean, waitNetwork?: boolean, screen?: ScreenTarget }
|
|
42
|
-
|
|
43
|
-
/** Compile the local project for a headless run. */
|
|
44
|
-
export const compileHeadlessBundle = async (opts: HeadlessOptions = {}): Promise<HeadlessBundle> => {
|
|
45
|
-
let root: string
|
|
46
|
-
let manifest: Manifest | null
|
|
47
|
-
try {
|
|
48
|
-
root = findProjectRoot(process.cwd())
|
|
49
|
-
manifest = readManifest(root)
|
|
50
|
-
} catch {
|
|
51
|
-
root = process.cwd()
|
|
52
|
-
manifest = null
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const publicUrlFlag = opts.publicUrl
|
|
56
|
-
// A local (server-less) project has no remote asset URLs — it always reads from disk.
|
|
57
|
-
const useLocalAssets = manifest ? (!publicUrlFlag && !opts.remoteAssets) : true
|
|
58
|
-
|
|
59
|
-
let entries
|
|
60
|
-
let warnings: string[]
|
|
61
|
-
let name: string
|
|
62
|
-
let publicUrl: string
|
|
63
|
-
|
|
64
|
-
if (manifest) {
|
|
65
|
-
const config = loadConfig()
|
|
66
|
-
const apiUrl = requireApiUrl(config)
|
|
67
|
-
const token = requireToken(config)
|
|
68
|
-
|
|
69
|
-
note("Resolving project assets…")
|
|
70
|
-
const project = await getProject(apiUrl, token, manifest.uuid)
|
|
71
|
-
;({ entries, warnings } = collectEntries(root, manifest, project))
|
|
72
|
-
name = manifest.name
|
|
73
|
-
publicUrl = useLocalAssets ? LOCAL_ASSET_SCHEME : (publicUrlFlag ?? apiUrl)
|
|
74
|
-
// In local mode the "push it so its URL resolves" notices are moot (we read from disk).
|
|
75
|
-
if (useLocalAssets) warnings = warnings.filter((w) => !w.includes("No server URL"))
|
|
76
|
-
} else {
|
|
77
|
-
;({ entries, warnings } = collectLocalEntries(root, null, ""))
|
|
78
|
-
name = root.split(/[\\/]/).pop() ?? "project"
|
|
79
|
-
publicUrl = LOCAL_ASSET_SCHEME
|
|
80
|
-
if (opts.remoteAssets || publicUrlFlag) {
|
|
81
|
-
warnings.push("This is a local project (no server) — assets always load from the working tree.")
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Local assets (default): resolve each resource (asset('./x.png')) from the working tree instead of
|
|
86
|
-
// the server, so unpushed / edited images show up with no --wait-network. Each resource's URL becomes
|
|
87
|
-
// `lecodesfile://<abs-path>`, which the renderer reads off disk.
|
|
88
|
-
const compileEntries = useLocalAssets
|
|
89
|
-
? entries.map((e) => (e.type === "resource" && e.absPath)
|
|
90
|
-
? { ...e, fileSrc: encodeURIComponent(e.absPath) }
|
|
91
|
-
: e)
|
|
92
|
-
: [...entries]
|
|
93
|
-
// Rendering one screen: the generated entry that imports it and calls .open() (see screenEntry.ts).
|
|
94
|
-
if (opts.screen) compileEntries.push(opts.screen.entry)
|
|
95
|
-
|
|
96
|
-
// font() macro: vendor registry faces into assets/fonts/ so headless renders read them off disk
|
|
97
|
-
// (lecodesfile://); with --remote-assets they stay CDN URLs like every other remote resource.
|
|
98
|
-
await applyProjectFonts(compileEntries, useLocalAssets
|
|
99
|
-
? {
|
|
100
|
-
mode: "vendor", root, vendorDir: root, vendorPrefix: "",
|
|
101
|
-
makeResourceEntry: (path, absPath) => ({ path, type: "resource", fileSrc: encodeURIComponent(absPath), absPath }),
|
|
102
|
-
}
|
|
103
|
-
: { mode: "cdn", root })
|
|
104
|
-
|
|
105
|
-
// assetIcon() macro: resolve from the user-level cache (~/.lecodes/cache/icons) and inline, so
|
|
106
|
-
// headless renders stay deterministic and offline after the first fetch (like local assets).
|
|
107
|
-
await applyAppAssetIcons(compileEntries)
|
|
108
|
-
|
|
109
|
-
note("Compiling…")
|
|
110
|
-
const entryFlag = opts.screen?.entryPath ?? opts.entry
|
|
111
|
-
if (!entryFlag) {
|
|
112
|
-
const ambiguous = ambiguousEntryWarning(compileEntries)
|
|
113
|
-
if (ambiguous) warnings.push(ambiguous)
|
|
114
|
-
}
|
|
115
|
-
const js = await compileProject({
|
|
116
|
-
entries: compileEntries,
|
|
117
|
-
name,
|
|
118
|
-
publicUrl,
|
|
119
|
-
entryOverride: entryFlag,
|
|
120
|
-
header: false,
|
|
121
|
-
format: "iife",
|
|
122
|
-
})
|
|
123
|
-
if (js.includes("_creator.createScene") || js.includes("_creator.createEntity")) {
|
|
124
|
-
warnings.push("3D project: the scene graph is captured as `scene3d` in the JSON output; 3D isn't rasterized into PNGs.")
|
|
125
|
-
}
|
|
126
|
-
// With --remote-assets, 2D textures come from the network; without --wait-network they resolve empty
|
|
127
|
-
// and sprites render as flat placeholders. Local assets (default) load from disk, so no hint needed.
|
|
128
|
-
if (js.includes("_creator2d") && !useLocalAssets && !opts.waitNetwork) {
|
|
129
|
-
warnings.push("2D scene detected: sprite textures load over the network — add --wait-network to include them (else sprites render as flat placeholders).")
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return { js, root, warnings, useLocalAssets }
|
|
133
|
-
}
|
|
1
|
+
import { getProject } from "../platform/api"
|
|
2
|
+
import { loadConfig, requireApiUrl, requireToken } from "../platform/config"
|
|
3
|
+
import { findProjectRoot, readManifest, type Manifest } from "../project/manifest"
|
|
4
|
+
import { collectEntries } from "./collect"
|
|
5
|
+
import { collectLocalEntries } from "./collectLocal"
|
|
6
|
+
import { ambiguousEntryWarning, compileProject } from "./projectCompile"
|
|
7
|
+
import { applyProjectFonts } from "./fonts"
|
|
8
|
+
import { applyAppAssetIcons } from "./assetIcons"
|
|
9
|
+
import type { ScreenTarget } from "./screenEntry"
|
|
10
|
+
import { note } from "../cli"
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* Compile the project into the iife bundle the headless renderer runs — shared by `lecodes render`
|
|
14
|
+
* and `lecodes test`. Two modes, picked automatically (same split as `lecodes compile`):
|
|
15
|
+
* - Server-backed project (.lecodes/manifest.json): entries resolve from local files + the
|
|
16
|
+
* project's server metadata (needs login).
|
|
17
|
+
* - Local project (from `lecodes init` — no manifest): compiles straight from the working tree,
|
|
18
|
+
* no login.
|
|
19
|
+
* In both, resource assets default to LOCAL disk reads: each asset('./x.png') URL becomes
|
|
20
|
+
* `lecodesfile://<abs-path>` which the renderer reads off disk — unpushed/edited files render with
|
|
21
|
+
* no network. `--remote-assets` / an explicit `--public-url` switch to server URLs instead
|
|
22
|
+
* (server-backed projects only; then data needs --wait-network).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// URL scheme emitted for a local project's resources so the renderer reads them off disk. Must match
|
|
26
|
+
// lecodes-headless's LOCAL_ASSET_SCHEME (headless/runProject.ts).
|
|
27
|
+
export const LOCAL_ASSET_SCHEME = "lecodesfile://"
|
|
28
|
+
|
|
29
|
+
export type HeadlessBundle = {
|
|
30
|
+
js: string
|
|
31
|
+
/** The project root the bundle was compiled from. */
|
|
32
|
+
root: string
|
|
33
|
+
/** Non-fatal compile-time notices (missing URLs, engine hints) — print via warnErr. */
|
|
34
|
+
warnings: string[]
|
|
35
|
+
/** Whether asset URLs resolve from the local disk (`lecodesfile://`). */
|
|
36
|
+
useLocalAssets: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** What a headless compile honours: `--entry` / `--public-url` / `--remote-assets` / `--wait-network`,
|
|
40
|
+
* and the screen module a `render <file>` named. */
|
|
41
|
+
export type HeadlessOptions = { entry?: string, publicUrl?: string, remoteAssets?: boolean, waitNetwork?: boolean, screen?: ScreenTarget }
|
|
42
|
+
|
|
43
|
+
/** Compile the local project for a headless run. */
|
|
44
|
+
export const compileHeadlessBundle = async (opts: HeadlessOptions = {}): Promise<HeadlessBundle> => {
|
|
45
|
+
let root: string
|
|
46
|
+
let manifest: Manifest | null
|
|
47
|
+
try {
|
|
48
|
+
root = findProjectRoot(process.cwd())
|
|
49
|
+
manifest = readManifest(root)
|
|
50
|
+
} catch {
|
|
51
|
+
root = process.cwd()
|
|
52
|
+
manifest = null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const publicUrlFlag = opts.publicUrl
|
|
56
|
+
// A local (server-less) project has no remote asset URLs — it always reads from disk.
|
|
57
|
+
const useLocalAssets = manifest ? (!publicUrlFlag && !opts.remoteAssets) : true
|
|
58
|
+
|
|
59
|
+
let entries
|
|
60
|
+
let warnings: string[]
|
|
61
|
+
let name: string
|
|
62
|
+
let publicUrl: string
|
|
63
|
+
|
|
64
|
+
if (manifest) {
|
|
65
|
+
const config = loadConfig()
|
|
66
|
+
const apiUrl = requireApiUrl(config)
|
|
67
|
+
const token = requireToken(config)
|
|
68
|
+
|
|
69
|
+
note("Resolving project assets…")
|
|
70
|
+
const project = await getProject(apiUrl, token, manifest.uuid)
|
|
71
|
+
;({ entries, warnings } = collectEntries(root, manifest, project))
|
|
72
|
+
name = manifest.name
|
|
73
|
+
publicUrl = useLocalAssets ? LOCAL_ASSET_SCHEME : (publicUrlFlag ?? apiUrl)
|
|
74
|
+
// In local mode the "push it so its URL resolves" notices are moot (we read from disk).
|
|
75
|
+
if (useLocalAssets) warnings = warnings.filter((w) => !w.includes("No server URL"))
|
|
76
|
+
} else {
|
|
77
|
+
;({ entries, warnings } = collectLocalEntries(root, null, ""))
|
|
78
|
+
name = root.split(/[\\/]/).pop() ?? "project"
|
|
79
|
+
publicUrl = LOCAL_ASSET_SCHEME
|
|
80
|
+
if (opts.remoteAssets || publicUrlFlag) {
|
|
81
|
+
warnings.push("This is a local project (no server) — assets always load from the working tree.")
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Local assets (default): resolve each resource (asset('./x.png')) from the working tree instead of
|
|
86
|
+
// the server, so unpushed / edited images show up with no --wait-network. Each resource's URL becomes
|
|
87
|
+
// `lecodesfile://<abs-path>`, which the renderer reads off disk.
|
|
88
|
+
const compileEntries = useLocalAssets
|
|
89
|
+
? entries.map((e) => (e.type === "resource" && e.absPath)
|
|
90
|
+
? { ...e, fileSrc: encodeURIComponent(e.absPath) }
|
|
91
|
+
: e)
|
|
92
|
+
: [...entries]
|
|
93
|
+
// Rendering one screen: the generated entry that imports it and calls .open() (see screenEntry.ts).
|
|
94
|
+
if (opts.screen) compileEntries.push(opts.screen.entry)
|
|
95
|
+
|
|
96
|
+
// font() macro: vendor registry faces into assets/fonts/ so headless renders read them off disk
|
|
97
|
+
// (lecodesfile://); with --remote-assets they stay CDN URLs like every other remote resource.
|
|
98
|
+
await applyProjectFonts(compileEntries, useLocalAssets
|
|
99
|
+
? {
|
|
100
|
+
mode: "vendor", root, vendorDir: root, vendorPrefix: "",
|
|
101
|
+
makeResourceEntry: (path, absPath) => ({ path, type: "resource", fileSrc: encodeURIComponent(absPath), absPath }),
|
|
102
|
+
}
|
|
103
|
+
: { mode: "cdn", root })
|
|
104
|
+
|
|
105
|
+
// assetIcon() macro: resolve from the user-level cache (~/.lecodes/cache/icons) and inline, so
|
|
106
|
+
// headless renders stay deterministic and offline after the first fetch (like local assets).
|
|
107
|
+
await applyAppAssetIcons(compileEntries)
|
|
108
|
+
|
|
109
|
+
note("Compiling…")
|
|
110
|
+
const entryFlag = opts.screen?.entryPath ?? opts.entry
|
|
111
|
+
if (!entryFlag) {
|
|
112
|
+
const ambiguous = ambiguousEntryWarning(compileEntries)
|
|
113
|
+
if (ambiguous) warnings.push(ambiguous)
|
|
114
|
+
}
|
|
115
|
+
const js = await compileProject({
|
|
116
|
+
entries: compileEntries,
|
|
117
|
+
name,
|
|
118
|
+
publicUrl,
|
|
119
|
+
entryOverride: entryFlag,
|
|
120
|
+
header: false,
|
|
121
|
+
format: "iife",
|
|
122
|
+
})
|
|
123
|
+
if (js.includes("_creator.createScene") || js.includes("_creator.createEntity")) {
|
|
124
|
+
warnings.push("3D project: the scene graph is captured as `scene3d` in the JSON output; 3D isn't rasterized into PNGs.")
|
|
125
|
+
}
|
|
126
|
+
// With --remote-assets, 2D textures come from the network; without --wait-network they resolve empty
|
|
127
|
+
// and sprites render as flat placeholders. Local assets (default) load from disk, so no hint needed.
|
|
128
|
+
if (js.includes("_creator2d") && !useLocalAssets && !opts.waitNetwork) {
|
|
129
|
+
warnings.push("2D scene detected: sprite textures load over the network — add --wait-network to include them (else sprites render as flat placeholders).")
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { js, root, warnings, useLocalAssets }
|
|
133
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { extractInlineSourceMap, mapNamedFileStack } from "lecodes-
|
|
1
|
+
import { extractInlineSourceMap, mapNamedFileStack } from "lecodes-web-shared/src/sourcemapError"
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
4
|
* De-minify native (QuickJS) stack traces in a desktop host's output.
|
|
@@ -3,14 +3,14 @@ import { createRequire } from "node:module"
|
|
|
3
3
|
import { dirname, join } from "node:path"
|
|
4
4
|
import type { FileKind } from "../project/manifest"
|
|
5
5
|
import { distRoot } from "../hosts/distRoot"
|
|
6
|
-
import { compileProject as sdkCompileProject, detectEntry, scanModulesChisel, ENTRY_CONVENTION } from "lecodes-sdk/compile"
|
|
6
|
+
import { compileProject as sdkCompileProject, detectEntry, scanModulesChisel, localAssetNames, ENTRY_CONVENTION } from "lecodes-sdk/compile"
|
|
7
7
|
|
|
8
8
|
// Every project compiles through the SDK's one import-free compiler, which bundles with chisel (the
|
|
9
9
|
// Rust method-granular bundler, @letary/chisel) — the only bundler. The SDK resolves the chisel
|
|
10
10
|
// binary itself; here we just hand it an explicit path when the @letary/chisel package is present,
|
|
11
11
|
// and point it at the SDK *source* tree chisel compiles from:
|
|
12
12
|
// 1. the `lecodes-sdk` package when it resolves — the npm install's dependency, or in the core
|
|
13
|
-
// workspace the live
|
|
13
|
+
// workspace the live sdk (so an SDK edit is picked up with no re-vendoring);
|
|
14
14
|
// 2. else the copy vendored beside the standalone binary (runtime/sdk/, scripts/vendor-runtime.ts
|
|
15
15
|
// --sdk) — the binary has no node_modules.
|
|
16
16
|
const resolveSdkInject = (): string | null => {
|
|
@@ -81,6 +81,9 @@ export type CompileOptions = {
|
|
|
81
81
|
/** Editor compile: the `EDITOR` global compiles to `true` and `*.editor.ts` plugin files are
|
|
82
82
|
* bundled in (scene-editor bundles). Default false — production compiles skip them. */
|
|
83
83
|
editor?: boolean
|
|
84
|
+
/** What the compile has to say that is not an error (a plugin the app uses and app.json does not
|
|
85
|
+
* list). */
|
|
86
|
+
onWarning?: (message: string) => void
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
/** A warning when auto-detection has several import-graph roots and no main.ts to settle it —
|
|
@@ -106,4 +109,9 @@ export const compileProject = (opts: CompileOptions): Promise<string> =>
|
|
|
106
109
|
minify: opts.minify,
|
|
107
110
|
sourcemap: opts.sourcemap,
|
|
108
111
|
editor: opts.editor,
|
|
112
|
+
onWarning: opts.onWarning,
|
|
109
113
|
})
|
|
114
|
+
|
|
115
|
+
/** Re-exported so the CALLERS that stage files next to the bundle name them the way the bundle asks
|
|
116
|
+
* for them (see localAssetNames in the SDK compile — two packs shipping SM_Sign_01.glb). */
|
|
117
|
+
export { localAssetNames }
|
|
@@ -4,8 +4,7 @@ import { join } from "node:path"
|
|
|
4
4
|
import type { Manifest } from "../project/manifest"
|
|
5
5
|
import { scanLocal } from "../project/localFiles"
|
|
6
6
|
import { loadIgnore } from "../project/ignore"
|
|
7
|
-
import {
|
|
8
|
-
import { CliError, warnErr } from "../cli"
|
|
7
|
+
import { warnErr } from "../cli"
|
|
9
8
|
import { compileProject, type CompileEntry } from "./projectCompile"
|
|
10
9
|
import { applyLocalShaders, shadersCacheDir } from "./shaders"
|
|
11
10
|
|
|
@@ -14,8 +13,9 @@ import { applyLocalShaders, shadersCacheDir } from "./shaders"
|
|
|
14
13
|
* point (the platform backend and the standalone playground are the other two). Reads the project
|
|
15
14
|
* off disk, composes the edit-mode bundle with the SDK's shared `sceneEditorEntries`, and drives it
|
|
16
15
|
* through the CLI's chisel + vendored-SDK pipeline. Deliberately mirrors the playground's in-process
|
|
17
|
-
* closure (
|
|
18
|
-
* bundle — only the file source
|
|
16
|
+
* closure (lecodes-3d-editor's src/server/inProcess.ts) field-for-field so every host emits the same
|
|
17
|
+
* bundle — only the file source differs. The harness is the editor's: the caller hands in
|
|
18
|
+
* `sceneHarness()` of the editor package it serves the UI from, so the two are one version.
|
|
19
19
|
*
|
|
20
20
|
* publicUrl "" + resource fileSrc "/project/<rel>" → the scene server's /project/* mount.
|
|
21
21
|
* minify:false — mangling erases class names the inspector matches aspect schemas by.
|
|
@@ -29,22 +29,6 @@ const TEXT_EXT = /\.(ts|js|json|svg|txt|md)$/i
|
|
|
29
29
|
/** Strip scanLocal's leading-slash key convention ("/city.scene.ts" → "city.scene.ts"). */
|
|
30
30
|
const rel = (key: string) => key.replace(/^\//, "")
|
|
31
31
|
|
|
32
|
-
/**
|
|
33
|
-
* Resolve the scene-editor harness sources (packages/projects/src/scene-editor):
|
|
34
|
-
* LECODES_SCENE_HARNESS_DIR env → the in-repo dir (monorepo dev), both via the SDK helper, else
|
|
35
|
-
* the JSON snapshot vendored into the CLI at publish time (runtime/scene-harness.json).
|
|
36
|
-
*/
|
|
37
|
-
const resolveHarness = (loadSceneHarness: (dir?: string) => Record<string, string>): Record<string, string> => {
|
|
38
|
-
const inRepo = join(distRoot, "..", "projects", "src", "scene-editor")
|
|
39
|
-
try {
|
|
40
|
-
return loadSceneHarness(existsSync(inRepo) ? inRepo : undefined)
|
|
41
|
-
} catch {
|
|
42
|
-
const vendored = join(distRoot, "runtime", "scene-harness.json")
|
|
43
|
-
if (existsSync(vendored)) return JSON.parse(readFileSync(vendored, "utf8")) as Record<string, string>
|
|
44
|
-
throw new CliError("scene-editor harness sources not found — reinstall the CLI or set LECODES_SCENE_HARNESS_DIR.")
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
32
|
export const compileSceneEditor = async (opts: {
|
|
49
33
|
root: string
|
|
50
34
|
/** Null in standalone mode (a bare folder, no cloned project). */
|
|
@@ -53,9 +37,11 @@ export const compileSceneEditor = async (opts: {
|
|
|
53
37
|
name: string
|
|
54
38
|
/** Project path of the scene file as the editor sends it ("./city.scene.ts"). */
|
|
55
39
|
scenePath: string
|
|
40
|
+
/** The editor's harness, module name → source (`sceneHarness()` of lecodes-3d-editor/server). */
|
|
41
|
+
harness: Record<string, string>
|
|
56
42
|
}): Promise<{ js: string | null, error?: string }> => {
|
|
57
43
|
const { root, scenePath } = opts
|
|
58
|
-
const {
|
|
44
|
+
const { sceneEditorEntries, sceneImportsOf, SCENE_ENTRY_PATH } = await import("lecodes-sdk/compile")
|
|
59
45
|
|
|
60
46
|
const entries: CompileEntry[] = []
|
|
61
47
|
const scan = scanLocal(root, { ignore: loadIgnore(root), tracked: new Set(Object.keys(opts.manifest?.files ?? {})) })
|
|
@@ -101,7 +87,7 @@ export const compileSceneEditor = async (opts: {
|
|
|
101
87
|
return { js: null, error: `Scene file not found: ${scene}` }
|
|
102
88
|
}
|
|
103
89
|
const paths = entries.map((e) => e.path)
|
|
104
|
-
entries.push(...sceneEditorEntries(scene,
|
|
90
|
+
entries.push(...sceneEditorEntries(scene, opts.harness, paths, sceneImportsOf(sceneEntry.text ?? "", scene, paths)))
|
|
105
91
|
|
|
106
92
|
try {
|
|
107
93
|
const js = await compileProject({
|
|
@@ -24,6 +24,10 @@ declare module "lecodes-3d-editor/server" {
|
|
|
24
24
|
|
|
25
25
|
export function startSceneServer(options: SceneServerOptions): Promise<SceneServer>
|
|
26
26
|
|
|
27
|
+
/** The editor's harness — its half that runs in the scene's world — as module name → source:
|
|
28
|
+
* what the compile closure hands to the SDK's `sceneEditorEntries`. */
|
|
29
|
+
export function sceneHarness(): Record<string, string>
|
|
30
|
+
|
|
27
31
|
/* The asset-preview cache (server/previewCache.ts) — `lecodes thumbs` writes into the same
|
|
28
32
|
* `.lecodes/preview/<asset>.<hash>.png` layout the editor reads. */
|
|
29
33
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Types for the optional `lecodes-assets` package's CLI entry (lazy-loaded by the `assets` command).
|
|
2
2
|
// The real package is installed separately; this lets the dynamic import typecheck without it
|
|
3
|
-
// present. Keep in sync with
|
|
3
|
+
// present. Keep in sync with tools/assets/src/cli.ts.
|
|
4
4
|
declare module "lecodes-assets/cli" {
|
|
5
5
|
/** Runs `convert | probe | doctor` with the given argv (sub-command first). Resolves to the exit code. */
|
|
6
6
|
export function runAssetsCli(argv: string[]): Promise<number>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// Types for the optional `lecodes-
|
|
1
|
+
// Types for the optional `lecodes-headless` package's node headless entry (lazy-loaded by the
|
|
2
2
|
// `render` command). The real package is installed separately; this lets the dynamic import
|
|
3
3
|
// typecheck without it present.
|
|
4
|
-
declare module "lecodes-
|
|
4
|
+
declare module "lecodes-headless/headless" {
|
|
5
5
|
export type RenderOptions = {
|
|
6
6
|
width?: number
|
|
7
7
|
height?: number
|
|
@@ -21,7 +21,7 @@ declare module "lecodes-renderer/headless" {
|
|
|
21
21
|
safeArea?: [number, number, number, number]
|
|
22
22
|
/** Receive the project's console.* output (unset → swallowed). */
|
|
23
23
|
onConsole?: (level: string, text: string) => void
|
|
24
|
-
/** Canned `
|
|
24
|
+
/** Canned `_creatorFetch.fetch` responses (URL pattern → { status?, json?, text? }); they beat
|
|
25
25
|
* every other fetch path, including waitNetwork. */
|
|
26
26
|
fixtures?: Fixtures
|
|
27
27
|
}
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* header says `// gl: disable`, so the app launcher creates no engine for it.
|
|
8
8
|
*
|
|
9
9
|
* - DEV_PRELUDE_JS — plain JS appended VERBATIM to every bundle the dev server serves (dev.js and
|
|
10
|
-
* bundle.js alike). It only touches the raw `
|
|
11
|
-
* can ride along any compiled project without duplicating the
|
|
12
|
-
* `
|
|
10
|
+
* bundle.js alike). It only touches the raw `_creatorFetch` / `_creatorSocket` / `_creatorApp` /
|
|
11
|
+
* `_creator` bridge — no SDK — so it can ride along any compiled project without duplicating the
|
|
12
|
+
* injected runtime. Each `_creatorApp.run()` world therefore carries its own copy: the running world holds the live
|
|
13
13
|
* connection; when it loads the next bundle, the NEW world's copy connects and the native
|
|
14
14
|
* per-world socket teardown closes the old one. On eval error, run()'s rollback keeps the
|
|
15
15
|
* current world (and its connection) alive, so the reject callback here reports + keeps serving.
|
|
@@ -33,6 +33,11 @@ UIScreen([
|
|
|
33
33
|
|
|
34
34
|
export const DEV_PRELUDE_JS = `
|
|
35
35
|
;(function () {
|
|
36
|
+
// The raw bridges (sdk/src/bridges): fetch's onComplete hands an OWNED handle ({ id }) whose
|
|
37
|
+
// body is freed when the handle is collected — the readers / run take the NUMBER (handle.id).
|
|
38
|
+
var F = _creatorFetch
|
|
39
|
+
var S = _creatorSocket
|
|
40
|
+
var A = _creatorApp
|
|
36
41
|
var ORIGIN = "__DEV_ORIGIN__"
|
|
37
42
|
var BASE = "__DEV_BASE__"
|
|
38
43
|
var TRANSPORT = "__DEV_TRANSPORT__"
|
|
@@ -44,12 +49,12 @@ export const DEV_PRELUDE_JS = `
|
|
|
44
49
|
function post(obj) {
|
|
45
50
|
var text = JSON.stringify(obj)
|
|
46
51
|
if (TRANSPORT === "ws") {
|
|
47
|
-
if (wsId !== null) { try {
|
|
52
|
+
if (wsId !== null) { try { S.send(wsId, text) } catch (e) {} }
|
|
48
53
|
return
|
|
49
54
|
}
|
|
50
55
|
try {
|
|
51
|
-
|
|
52
|
-
function (
|
|
56
|
+
F.fetch(ORIGIN + BASE + "/log", { method: "POST", body: text, headers: { "content-type": "application/json" } },
|
|
57
|
+
function () {}, function () {})
|
|
53
58
|
} catch (e) {}
|
|
54
59
|
}
|
|
55
60
|
|
|
@@ -80,16 +85,16 @@ export const DEV_PRELUDE_JS = `
|
|
|
80
85
|
function load(v) {
|
|
81
86
|
if (busy) { queued = v; return }
|
|
82
87
|
busy = true
|
|
83
|
-
|
|
88
|
+
F.fetch(ORIGIN + BASE + "/bundle.js?v=" + v + "&t=" + TRANSPORT, {}, function (body, status) {
|
|
84
89
|
if (status >= 400) { busy = false; report("bundle fetch: HTTP " + status); return }
|
|
85
90
|
// Same engine bringup the app launcher does for a fresh launch (see desktop initial.ts).
|
|
86
91
|
var head = ""
|
|
87
|
-
try { head =
|
|
92
|
+
try { head = F.slice(body.id, 0, 300) } catch (e) {}
|
|
88
93
|
var preloads = head.match(/\\/\\/ preload: (.+)/)
|
|
89
94
|
if (head.indexOf("// gl:") < 0) {
|
|
90
95
|
try { _creator.createEngine(preloads ? preloads[1].split(";") : []) } catch (e) { report("createEngine: " + String(e)) }
|
|
91
96
|
}
|
|
92
|
-
|
|
97
|
+
A.run(body.id, function (err) {
|
|
93
98
|
// Eval failed → native rollback kept THIS world (and its connection) alive. Keep serving.
|
|
94
99
|
busy = false
|
|
95
100
|
report(err)
|
|
@@ -109,9 +114,9 @@ export const DEV_PRELUDE_JS = `
|
|
|
109
114
|
var url = ORIGIN.replace("http", "ws") + BASE + "/ws"
|
|
110
115
|
var open = function () {
|
|
111
116
|
try {
|
|
112
|
-
wsId =
|
|
117
|
+
wsId = S.open(url, function (channel, data) {
|
|
113
118
|
if (channel === "open") {
|
|
114
|
-
try {
|
|
119
|
+
try { S.send(wsId, JSON.stringify({ type: "hello", v: V })) } catch (e) {}
|
|
115
120
|
} else if (channel === "message") {
|
|
116
121
|
onServer(data)
|
|
117
122
|
} else if (channel === "close" || channel === "error") {
|
|
@@ -124,10 +129,9 @@ export const DEV_PRELUDE_JS = `
|
|
|
124
129
|
open()
|
|
125
130
|
} else {
|
|
126
131
|
var poll = function () {
|
|
127
|
-
|
|
132
|
+
F.fetch(ORIGIN + BASE + "/version", {}, function (body, status) {
|
|
128
133
|
var v = null
|
|
129
|
-
try { v =
|
|
130
|
-
try { _creatorUtils.disposeFetch(id) } catch (e) {}
|
|
134
|
+
try { v = F.toJson(body.id).v } catch (e) {}
|
|
131
135
|
setTimeout(poll, 700)
|
|
132
136
|
if (status < 400 && typeof v === "number" && v !== V && !busy) load(v)
|
|
133
137
|
}, function () { setTimeout(poll, 1500) })
|