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.
Files changed (104) hide show
  1. package/dist/index.js +6211 -3649
  2. package/package.json +12 -8
  3. package/runtime/materials/lightmap.mat +115 -139
  4. package/runtime/materials/particles-quad.mat +6 -2
  5. package/runtime/materials/terrain-lightmap.mat +123 -0
  6. package/runtime/materials/terrain.mat +36 -55
  7. package/runtime/sdk-types.json +1 -1
  8. package/runtime/web/VERSION.json +5 -0
  9. package/runtime/web/assets/camera-5XOM8eHl.js +1 -0
  10. package/runtime/web/assets/creator-2d-D9WKYM6h.js +1 -0
  11. package/runtime/web/assets/creator-2d-ukvEmNEp.wasm +0 -0
  12. package/runtime/web/assets/creator-full-BBXzQlhq.js +1 -0
  13. package/runtime/web/assets/creator-full-D2dSDdDj.wasm +0 -0
  14. package/runtime/web/assets/creator-full.js-BYswGsqR.symbols +8124 -0
  15. package/runtime/web/assets/creator-ui-DIc92ion.wasm +0 -0
  16. package/runtime/web/assets/creator-ui-_1Z_AX2M.js +1 -0
  17. package/runtime/web/assets/geolocation-BjTKN9_P.js +1 -0
  18. package/runtime/web/assets/inspect-C7x78PNs.js +2 -0
  19. package/runtime/web/assets/map-CyBxOSV0.js +1 -0
  20. package/runtime/web/assets/map-XzoOFt0E.js +2 -0
  21. package/runtime/web/assets/mapImpl-bxvQ2h1Z.js +823 -0
  22. package/runtime/web/assets/maplibre-gl-worker-B7LXXUkR.js +8 -0
  23. package/runtime/web/assets/materials-uri4FvPE.bin +0 -0
  24. package/runtime/web/assets/materials-world-CQWt8TAm.bin +0 -0
  25. package/runtime/web/assets/qr-scanner-CK_cYoJR.js +1 -0
  26. package/runtime/web/assets/uberarchive-DLH-kBhM.bin +0 -0
  27. package/runtime/web/index.html +36 -0
  28. package/runtime/web/player.js +21 -0
  29. package/runtime/web-page/index.html +41 -0
  30. package/runtime/web-page/page.css +1 -0
  31. package/runtime/web-page/page.js +31 -0
  32. package/src/cli/help.ts +25 -0
  33. package/src/cli/index.ts +7 -7
  34. package/src/commands/app/android.ts +3 -1
  35. package/src/commands/app/desktop.ts +1 -1
  36. package/src/commands/app/desktopMac.ts +10 -9
  37. package/src/commands/app/icon.ts +9 -6
  38. package/src/commands/app/index.ts +3 -1
  39. package/src/commands/app/shared.ts +13 -5
  40. package/src/commands/app/templates/android.ts +10 -11
  41. package/src/commands/app/templates/ios.ts +1 -1
  42. package/src/commands/assets.ts +1 -1
  43. package/src/commands/compile.ts +15 -2
  44. package/src/commands/design/index.ts +4 -4
  45. package/src/commands/design/snapshot.ts +3 -3
  46. package/src/commands/desktop.ts +5 -1
  47. package/src/commands/dev.ts +82 -29
  48. package/src/commands/index.ts +64 -60
  49. package/src/commands/init/index.ts +11 -15
  50. package/src/commands/init/templates.ts +64 -21
  51. package/src/commands/lightmap.ts +459 -200
  52. package/src/commands/navmesh.ts +2 -2
  53. package/src/commands/plugin.ts +46 -0
  54. package/src/commands/render.ts +22 -16
  55. package/src/commands/scene.ts +6 -1
  56. package/src/commands/serve.ts +58 -0
  57. package/src/commands/shaders/new.ts +1 -1
  58. package/src/commands/test.ts +402 -398
  59. package/src/commands/update/index.ts +3 -3
  60. package/src/compile/designCompile.ts +1 -1
  61. package/src/compile/headlessBundle.ts +133 -133
  62. package/src/compile/nativeStack.ts +1 -1
  63. package/src/compile/projectCompile.ts +10 -2
  64. package/src/compile/sceneCompile.ts +8 -22
  65. package/src/declarations/lecodes-3d-editor.d.ts +4 -0
  66. package/src/declarations/lecodes-assets.d.ts +1 -1
  67. package/src/declarations/{lecodes-renderer.d.ts → lecodes-headless.d.ts} +3 -3
  68. package/src/dev/clientTemplates.ts +18 -14
  69. package/src/dev/devServer.ts +75 -38
  70. package/src/dev/iosDev.ts +180 -0
  71. package/src/dev/webRunner.ts +82 -14
  72. package/src/hosts/cmgenTool.ts +1 -1
  73. package/src/hosts/desktopRenderer.ts +98 -27
  74. package/src/hosts/desktopScript.ts +150 -35
  75. package/src/hosts/distRoot.ts +1 -1
  76. package/src/hosts/matcTool.ts +1 -1
  77. package/src/hosts/peerInstall.ts +5 -5
  78. package/src/hosts/peers.ts +2 -2
  79. package/src/index.ts +4 -1
  80. package/src/plugin/contract.ts +438 -0
  81. package/src/plugin/emitKotlin.ts +357 -0
  82. package/src/plugin/emitSdk.ts +259 -0
  83. package/src/plugin/emitSwift.ts +336 -0
  84. package/src/plugin/emitWeb.ts +383 -0
  85. package/src/plugin/generate.ts +157 -0
  86. package/src/plugin/lines.ts +28 -0
  87. package/src/plugin/lower.ts +231 -0
  88. package/src/plugin/manifest.ts +192 -0
  89. package/src/project/ignore.ts +164 -163
  90. package/runtime/materials/lightmap-baked-lite.mat +0 -180
  91. package/runtime/materials/lightmap-baked.mat +0 -181
  92. package/runtime/scene-harness.json +0 -1
  93. package/runtime/web/assets/__vite-browser-external-BIHI7g3E.js +0 -1
  94. package/runtime/web/assets/basis-C64VHDVD.js +0 -1
  95. package/runtime/web/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
  96. package/runtime/web/assets/createViewerLite-DHqTFhWB.js +0 -866
  97. package/runtime/web/assets/draco-BiISTFcR.js +0 -118
  98. package/runtime/web/assets/draco_decoder-DsQ12WqX.wasm +0 -0
  99. package/runtime/web/assets/index-Q06c6oHx.js +0 -2
  100. package/runtime/web/assets/mapViewImpl-B2JcES8l.js +0 -810
  101. package/runtime/web/assets/maplibre-gl-worker-CJfwIrte.js +0 -8
  102. package/runtime/web/assets/worker-Caf-yYEI.js +0 -2
  103. package/runtime/web/embed.html +0 -32
  104. 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-renderer, lecodes-design,
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-renderer, 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.",
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-renderer" ? pkg.version
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-renderer's LOCAL_ASSET_SCHEME (headless/index.ts) — same as `lecodes render`.
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-renderer'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
+ 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-viewer-lite/src/sourcemapError"
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 packages/sdk (so an SDK edit is picked up with no re-vendoring);
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 { distRoot } from "../hosts/distRoot"
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 (scene-editor/src/server/inProcess.ts) field-for-field so every host emits the same
18
- * bundle — only the file source (disk vs. the folder) and the harness source differ.
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 { loadSceneHarness, sceneEditorEntries, sceneImportsOf, SCENE_ENTRY_PATH } = await import("lecodes-sdk/compile")
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, resolveHarness(loadSceneHarness), paths, sceneImportsOf(sceneEntry.text ?? "", scene, paths)))
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 packages/lecodes-assets/src/cli.ts.
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-renderer` package's node headless entry (lazy-loaded by the
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-renderer/headless" {
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 `_creatorUtils.fetch` responses (URL pattern → { status?, json?, text? }); they beat
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 `_creatorUtils` / `_creator` bridge — no SDK — so it
11
- * can ride along any compiled project without duplicating the injected runtime. Each
12
- * `_creatorUtils.run()` world therefore carries its own copy: the running world holds the live
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 { _creatorUtils.websocketSend(wsId, text) } catch (e) {} }
52
+ if (wsId !== null) { try { S.send(wsId, text) } catch (e) {} }
48
53
  return
49
54
  }
50
55
  try {
51
- _creatorUtils.fetch(ORIGIN + BASE + "/log", { method: "POST", body: text, headers: { "content-type": "application/json" } },
52
- function (id) { try { _creatorUtils.disposeFetch(id) } catch (e) {} }, 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
- _creatorUtils.fetch(ORIGIN + BASE + "/bundle.js?v=" + v + "&t=" + TRANSPORT, {}, function (id, status) {
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 = _creatorUtils.fetchSlice(id, 0, 300) } catch (e) {}
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
- _creatorUtils.run(id, function (err) {
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 = _creatorUtils.websocketOpen(url, function (channel, data) {
117
+ wsId = S.open(url, function (channel, data) {
113
118
  if (channel === "open") {
114
- try { _creatorUtils.websocketSend(wsId, JSON.stringify({ type: "hello", v: V })) } catch (e) {}
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
- _creatorUtils.fetch(ORIGIN + BASE + "/version", {}, function (id, status) {
132
+ F.fetch(ORIGIN + BASE + "/version", {}, function (body, status) {
128
133
  var v = null
129
- try { v = _creatorUtils.fetchToJson(id).v } catch (e) {}
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) })