lecodes-cli 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/index.js +1069 -396
- package/package.json +14 -13
- package/runtime/scene-harness.json +1 -1
- package/runtime/sdk-types.json +1 -1
- package/runtime/web/assets/Roboto-Variable-DHm9jpd-.woff2 +0 -0
- package/runtime/web/assets/__vite-browser-external-BIHI7g3E.js +1 -0
- package/runtime/web/assets/basis-C64VHDVD.js +1 -0
- package/runtime/web/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
- package/runtime/web/assets/createViewerLite-BvHobVhp.js +852 -0
- package/runtime/web/assets/draco-BiISTFcR.js +118 -0
- package/runtime/web/assets/draco_decoder-DsQ12WqX.wasm +0 -0
- package/runtime/web/assets/index-CfNCCBjN.js +2 -0
- package/runtime/web/assets/mapViewImpl-B2JcES8l.js +810 -0
- package/runtime/web/assets/maplibre-gl-worker-CJfwIrte.js +8 -0
- package/runtime/web/assets/neutral_ibl128-D-lzdkLN.ktx +0 -0
- package/runtime/web/assets/worker-Caf-yYEI.js +2 -0
- package/runtime/web/embed.html +32 -0
- package/runtime/web/embed.js +171 -0
- package/src/api.ts +302 -0
- package/src/browserAuth.ts +87 -0
- package/src/cmgenTool.ts +104 -0
- package/src/commands/app.ts +896 -0
- package/src/commands/appAndroid.ts +603 -0
- package/src/commands/appDesktop.ts +339 -0
- package/src/commands/appDesktopMac.ts +470 -0
- package/src/commands/appIcon.ts +187 -0
- package/src/commands/appShared.ts +542 -0
- package/src/commands/appTemplates.ts +907 -0
- package/src/commands/appTemplatesAndroid.ts +599 -0
- package/src/commands/appTemplatesGradlew.ts +9 -0
- package/src/commands/assets.ts +28 -0
- package/src/commands/clone.ts +60 -0
- package/src/commands/compile.ts +253 -0
- package/src/commands/create.ts +59 -0
- package/src/commands/design.ts +863 -0
- package/src/commands/designTemplates.ts +11 -0
- package/src/commands/desktop.ts +127 -0
- package/src/commands/dev.ts +236 -0
- package/src/commands/diff.ts +59 -0
- package/src/commands/init.ts +191 -0
- package/src/commands/install.ts +198 -0
- package/src/commands/lightmap.ts +290 -0
- package/src/commands/link.ts +147 -0
- package/src/commands/login.ts +24 -0
- package/src/commands/navmesh.ts +225 -0
- package/src/commands/pn.ts +305 -0
- package/src/commands/projectTemplates.ts +130 -0
- package/src/commands/pull.ts +109 -0
- package/src/commands/push.ts +173 -0
- package/src/commands/render.ts +416 -0
- package/src/commands/scene.ts +148 -0
- package/src/commands/shaders.ts +157 -0
- package/src/commands/status.ts +29 -0
- package/src/commands/test.ts +371 -0
- package/src/commands/thumbs.ts +178 -0
- package/src/commands/types.ts +87 -0
- package/src/commands/update.ts +190 -0
- package/src/compile/assetIcons.ts +214 -0
- package/src/compile/collect.ts +46 -0
- package/src/compile/collectLocal.ts +37 -0
- package/src/compile/designCompile.ts +104 -0
- package/src/compile/fonts.ts +156 -0
- package/src/compile/headlessBundle.ts +129 -0
- package/src/compile/nativeStack.ts +21 -0
- package/src/compile/projectCompile.ts +109 -0
- package/src/compile/sceneCompile.ts +113 -0
- package/src/compile/screenEntry.ts +127 -0
- package/src/compile/shaders.ts +245 -0
- package/src/config.ts +42 -0
- package/src/designMeta.ts +35 -0
- package/src/desktopRenderer.ts +545 -0
- package/src/desktopScript.ts +276 -0
- package/src/dev/clientTemplates.ts +160 -0
- package/src/dev/devServer.ts +333 -0
- package/src/dev/webRunner.ts +22 -0
- package/src/dev/wsServer.ts +144 -0
- package/src/distRoot.ts +20 -0
- package/src/ignore.ts +163 -0
- package/src/index.ts +494 -0
- package/src/lecodes-3d-editor.d.ts +41 -0
- package/src/lecodes-assets.d.ts +7 -0
- package/src/lecodes-design.d.ts +191 -0
- package/src/lecodes-renderer.d.ts +131 -0
- package/src/localFiles.ts +144 -0
- package/src/manifest.ts +46 -0
- package/src/matcTool.ts +137 -0
- package/src/peers.ts +40 -0
- package/src/project.ts +61 -0
- package/src/projectEnv.ts +94 -0
- package/src/qrcode-terminal.d.ts +9 -0
- package/src/releases.ts +125 -0
- package/src/serverDiff.ts +78 -0
- package/src/textDiff.ts +103 -0
- package/src/types.ts +0 -0
- package/src/util.ts +146 -0
- package/runtime/sdk/animate/animate.ts +0 -238
- package/runtime/sdk/animate/bezier.ts +0 -138
- package/runtime/sdk/animate/easings.ts +0 -126
- package/runtime/sdk/canvas/Canvas.ts +0 -305
- package/runtime/sdk/compile/aspectMacro.ts +0 -86
- package/runtime/sdk/compile/assetIconMacro.ts +0 -384
- package/runtime/sdk/compile/assetMacro.ts +0 -146
- package/runtime/sdk/compile/assetName.ts +0 -50
- package/runtime/sdk/compile/bundler.ts +0 -287
- package/runtime/sdk/compile/compileProject.ts +0 -129
- package/runtime/sdk/compile/detectEntry.ts +0 -128
- package/runtime/sdk/compile/fontMacro.ts +0 -459
- package/runtime/sdk/compile/fontRegistry.ts +0 -78
- package/runtime/sdk/compile/header.ts +0 -67
- package/runtime/sdk/compile/index.ts +0 -100
- package/runtime/sdk/compile/libraryImports.ts +0 -52
- package/runtime/sdk/compile/liteMaterial.ts +0 -247
- package/runtime/sdk/compile/sceneEditor.ts +0 -88
- package/runtime/sdk/compile/serverSplit.ts +0 -233
- package/runtime/sdk/compile/serverTypes.ts +0 -227
- package/runtime/sdk/compile/sfnt.ts +0 -98
- package/runtime/sdk/compile/shaderSchema.ts +0 -202
- package/runtime/sdk/compile/shaderTargets.ts +0 -81
- package/runtime/sdk/compile/sourcemap.ts +0 -25
- package/runtime/sdk/core/Aspect.ts +0 -568
- package/runtime/sdk/core/InspectorUI.ts +0 -212
- package/runtime/sdk/core/StateMachine.ts +0 -308
- package/runtime/sdk/core/color.ts +0 -66
- package/runtime/sdk/core/compWrite.ts +0 -42
- package/runtime/sdk/core/events.ts +0 -38
- package/runtime/sdk/core/fields.ts +0 -120
- package/runtime/sdk/core/registry.ts +0 -23
- package/runtime/sdk/core/signals.ts +0 -277
- package/runtime/sdk/core/time.ts +0 -84
- package/runtime/sdk/g2/Camera2D.ts +0 -40
- package/runtime/sdk/g2/CharacterController2D.ts +0 -276
- package/runtime/sdk/g2/Node2D.ts +0 -267
- package/runtime/sdk/g2/OneWay2D.ts +0 -66
- package/runtime/sdk/g2/Physics2D.ts +0 -346
- package/runtime/sdk/g2/Scene2D.ts +0 -209
- package/runtime/sdk/g2/Shape2D.ts +0 -259
- package/runtime/sdk/g2/Sprite.ts +0 -89
- package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
- package/runtime/sdk/g2/SpriteSheet.ts +0 -166
- package/runtime/sdk/g2/Texture2D.ts +0 -47
- package/runtime/sdk/g2/Tilemap.ts +0 -41
- package/runtime/sdk/g2/Tileset.ts +0 -71
- package/runtime/sdk/g2/Trigger2D.ts +0 -77
- package/runtime/sdk/g2/autotile.ts +0 -433
- package/runtime/sdk/g2/cells.ts +0 -91
- package/runtime/sdk/g2/defineScene2d.ts +0 -381
- package/runtime/sdk/g2/groups2d.ts +0 -106
- package/runtime/sdk/g2/loop.ts +0 -50
- package/runtime/sdk/g2/scenarios2d.ts +0 -69
- package/runtime/sdk/g2/touch.ts +0 -83
- package/runtime/sdk/gl/Camera.ts +0 -160
- package/runtime/sdk/gl/CameraPlace.ts +0 -52
- package/runtime/sdk/gl/CharacterController.ts +0 -238
- package/runtime/sdk/gl/Gearbox.ts +0 -212
- package/runtime/sdk/gl/Geometry.ts +0 -279
- package/runtime/sdk/gl/IK.ts +0 -193
- package/runtime/sdk/gl/InstancedMesh.ts +0 -132
- package/runtime/sdk/gl/Light.ts +0 -107
- package/runtime/sdk/gl/Lightmap.ts +0 -256
- package/runtime/sdk/gl/Material.ts +0 -260
- package/runtime/sdk/gl/Mesh.ts +0 -83
- package/runtime/sdk/gl/Model.ts +0 -124
- package/runtime/sdk/gl/NavAgent.ts +0 -337
- package/runtime/sdk/gl/NavMesh.ts +0 -397
- package/runtime/sdk/gl/Node.ts +0 -350
- package/runtime/sdk/gl/Noise.ts +0 -30
- package/runtime/sdk/gl/Particles.ts +0 -676
- package/runtime/sdk/gl/Physics.ts +0 -222
- package/runtime/sdk/gl/Plane.ts +0 -53
- package/runtime/sdk/gl/Ragdoll.ts +0 -270
- package/runtime/sdk/gl/Ray.ts +0 -16
- package/runtime/sdk/gl/Scene.ts +0 -486
- package/runtime/sdk/gl/Shape.ts +0 -409
- package/runtime/sdk/gl/Terrain.ts +0 -1092
- package/runtime/sdk/gl/Texture.ts +0 -63
- package/runtime/sdk/gl/Trigger.ts +0 -45
- package/runtime/sdk/gl/Vehicle.ts +0 -473
- package/runtime/sdk/gl/Wheel.ts +0 -240
- package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
- package/runtime/sdk/gl/animation/Animator.ts +0 -120
- package/runtime/sdk/gl/animation/Layer.ts +0 -31
- package/runtime/sdk/gl/animation/Loop.ts +0 -30
- package/runtime/sdk/gl/animation/Playback.ts +0 -44
- package/runtime/sdk/gl/animation/core.ts +0 -336
- package/runtime/sdk/gl/controls.ts +0 -95
- package/runtime/sdk/gl/physicsEvents.ts +0 -20
- package/runtime/sdk/gl/scenarios.ts +0 -291
- package/runtime/sdk/gl/state.ts +0 -6
- package/runtime/sdk/gl/terrainMesh.ts +0 -219
- package/runtime/sdk/gl/touch.ts +0 -68
- package/runtime/sdk/inject.ts +0 -197
- package/runtime/sdk/math/Mathf.ts +0 -118
- package/runtime/sdk/math/mat4.ts +0 -278
- package/runtime/sdk/math/quat.ts +0 -232
- package/runtime/sdk/math/vec.ts +0 -255
- package/runtime/sdk/net/codec.ts +0 -119
- package/runtime/sdk/net/core.ts +0 -384
- package/runtime/sdk/net/index.ts +0 -181
- package/runtime/sdk/net/replication.ts +0 -622
- package/runtime/sdk/plugins/camera.ts +0 -81
- package/runtime/sdk/plugins/geolocation.ts +0 -123
- package/runtime/sdk/plugins/oauth.ts +0 -61
- package/runtime/sdk/plugins/permission.ts +0 -7
- package/runtime/sdk/plugins/push.ts +0 -132
- package/runtime/sdk/plugins/qr.ts +0 -73
- package/runtime/sdk/plugins/service.ts +0 -47
- package/runtime/sdk/runtime/app.ts +0 -101
- package/runtime/sdk/runtime/appEvents.ts +0 -54
- package/runtime/sdk/runtime/channel.ts +0 -61
- package/runtime/sdk/runtime/clipboard.ts +0 -20
- package/runtime/sdk/runtime/datetime.ts +0 -329
- package/runtime/sdk/runtime/device.ts +0 -293
- package/runtime/sdk/runtime/fetch.ts +0 -77
- package/runtime/sdk/runtime/files.ts +0 -23
- package/runtime/sdk/runtime/input.ts +0 -175
- package/runtime/sdk/runtime/media.ts +0 -111
- package/runtime/sdk/runtime/misc.ts +0 -16
- package/runtime/sdk/runtime/net.ts +0 -36
- package/runtime/sdk/runtime/rpc.ts +0 -218
- package/runtime/sdk/runtime/service.ts +0 -83
- package/runtime/sdk/runtime/share.ts +0 -9
- package/runtime/sdk/runtime/storage.ts +0 -13
- package/runtime/sdk/runtime/touch.ts +0 -76
- package/runtime/sdk/scene/defineScene.ts +0 -1353
- package/runtime/sdk/scene/editorPlugins.ts +0 -110
- package/runtime/sdk/scene/gizmos.ts +0 -148
- package/runtime/sdk/scene/grammar.ts +0 -120
- package/runtime/sdk/scene/material.ts +0 -188
- package/runtime/sdk/server/auth/appConfig.ts +0 -12
- package/runtime/sdk/server/auth/global.ts +0 -80
- package/runtime/sdk/server/auth/host.ts +0 -318
- package/runtime/sdk/server/auth/models.ts +0 -83
- package/runtime/sdk/server/auth/types.ts +0 -50
- package/runtime/sdk/server/channel.ts +0 -56
- package/runtime/sdk/server/context.ts +0 -36
- package/runtime/sdk/server/db/defineDb.ts +0 -237
- package/runtime/sdk/server/db/fields.ts +0 -132
- package/runtime/sdk/server/db/httpTransport.ts +0 -93
- package/runtime/sdk/server/db/index.ts +0 -7
- package/runtime/sdk/server/db/marci/query.ts +0 -412
- package/runtime/sdk/server/db/types.ts +0 -202
- package/runtime/sdk/server/errors.ts +0 -12
- package/runtime/sdk/server/host.ts +0 -74
- package/runtime/sdk/server/inject.ts +0 -13
- package/runtime/sdk/server/runtime.ts +0 -133
- package/runtime/sdk/server/validate.ts +0 -87
- package/runtime/sdk/ui/NativeView.ts +0 -142
- package/runtime/sdk/ui/UI.ts +0 -39
- package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
- package/runtime/sdk/ui/UIButton.ts +0 -101
- package/runtime/sdk/ui/UIContainer.ts +0 -60
- package/runtime/sdk/ui/UIImage.ts +0 -83
- package/runtime/sdk/ui/UIInput.ts +0 -185
- package/runtime/sdk/ui/UIModal.ts +0 -139
- package/runtime/sdk/ui/UINode.ts +0 -830
- package/runtime/sdk/ui/UIPager.ts +0 -362
- package/runtime/sdk/ui/UIPopover.ts +0 -100
- package/runtime/sdk/ui/UIScreen.ts +0 -123
- package/runtime/sdk/ui/UIScrollable.ts +0 -87
- package/runtime/sdk/ui/UISpacer.ts +0 -14
- package/runtime/sdk/ui/UITabs.ts +0 -236
- package/runtime/sdk/ui/UIText.ts +0 -51
- package/runtime/sdk/ui/UIVideo.ts +0 -88
- package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
- package/runtime/sdk/ui/UIWidget.ts +0 -127
- package/runtime/sdk/ui/fonts.ts +0 -13
- package/runtime/sdk/ui/presentable.ts +0 -117
- package/runtime/sdk/ui/router.ts +0 -132
- package/runtime/sdk/ui/theme.ts +0 -84
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs"
|
|
2
|
+
import { basename } from "node:path"
|
|
3
|
+
import type { Project } from "../api"
|
|
4
|
+
import type { Manifest } from "../manifest"
|
|
5
|
+
import { classifyKind, scanLocal } from "../localFiles"
|
|
6
|
+
import { loadIgnore } from "../ignore"
|
|
7
|
+
import { buildPaths } from "../project"
|
|
8
|
+
import type { CompileEntry } from "./projectCompile"
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* Builds the compile input from the working tree + the project's server metadata: code/.svg/scene
|
|
12
|
+
* content comes from local files (fresh); binary & shader URLs come from the server (matched by
|
|
13
|
+
* path). Local-only / locally-changed binaries and shaders are reported as warnings. Shared by
|
|
14
|
+
* `preview` and `render`. Untracked files matching .lecodesignore are skipped — same contract as
|
|
15
|
+
* `status`/`push` (tracked files are always in), so a generated `ios/` shell never leaks in here.
|
|
16
|
+
*/
|
|
17
|
+
export const collectEntries = (
|
|
18
|
+
root: string, manifest: Manifest, project: Project,
|
|
19
|
+
opts: { /** Warn about resources with no server URL yet (default true; shell compiles bundle them instead). */ warnMissingUrl?: boolean } = {},
|
|
20
|
+
): { entries: CompileEntry[], warnings: string[] } => {
|
|
21
|
+
const pathById = buildPaths(project.assets)
|
|
22
|
+
const serverByPath = new Map<string, { fileSrc?: string, shaders?: { platform: string, src: string }[] }>()
|
|
23
|
+
for (const asset of project.assets) {
|
|
24
|
+
if (asset.type === "folder") continue
|
|
25
|
+
serverByPath.set(pathById.get(asset.id)!, { fileSrc: asset.file?.src ?? undefined, shaders: asset.shaders ?? [] })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const entries: CompileEntry[] = []
|
|
29
|
+
const warnings: string[] = []
|
|
30
|
+
const scan = scanLocal(root, { ignore: loadIgnore(root), tracked: new Set(Object.keys(manifest.files)) })
|
|
31
|
+
for (const file of scan.files) {
|
|
32
|
+
const type = manifest.files[file.path]?.type ?? classifyKind(basename(file.path))
|
|
33
|
+
if (type === "resource") {
|
|
34
|
+
const src = serverByPath.get(file.path)?.fileSrc
|
|
35
|
+
if (!src && (opts.warnMissingUrl ?? true)) warnings.push(`No server URL for ${file.path} — push it so its URL resolves.`)
|
|
36
|
+
entries.push({ path: file.path, type, fileSrc: src, absPath: file.absPath })
|
|
37
|
+
} else if (type === "shader") {
|
|
38
|
+
const shaders = serverByPath.get(file.path)?.shaders
|
|
39
|
+
if (!shaders?.length) warnings.push(`No compiled shader for ${file.path} — push it to compile the shader.`)
|
|
40
|
+
entries.push({ path: file.path, type, shaders })
|
|
41
|
+
} else {
|
|
42
|
+
entries.push({ path: file.path, type, text: readFileSync(file.absPath, "utf8") })
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { entries, warnings }
|
|
46
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs"
|
|
2
|
+
import { basename } from "node:path"
|
|
3
|
+
import type { Manifest } from "../manifest"
|
|
4
|
+
import { classifyKind, scanLocal } from "../localFiles"
|
|
5
|
+
import { loadIgnore } from "../ignore"
|
|
6
|
+
import type { CompileEntry } from "./projectCompile"
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Builds the compile input for `lecodes dev` from the working tree ALONE — no server round-trip,
|
|
10
|
+
* no login. Binary resources compile to dev-server URLs (`<assetBase>/<path>`, served straight
|
|
11
|
+
* from the working tree), so the bundle works fully offline. Shaders carry their absPath so a
|
|
12
|
+
* caller that compiles them locally (the dev server's applyLocalShaders pass, via matc) can fill
|
|
13
|
+
* in the artifacts; callers that don't pass `localShaders` keep the old degradation — an empty
|
|
14
|
+
* artifact list + a push-once warning (same as `collectEntries` reports for unpushed shaders).
|
|
15
|
+
*/
|
|
16
|
+
export const collectLocalEntries = (
|
|
17
|
+
root: string, manifest: Manifest | null, assetBase: string,
|
|
18
|
+
opts: { localShaders?: boolean } = {},
|
|
19
|
+
): { entries: CompileEntry[], warnings: string[] } => {
|
|
20
|
+
const entries: CompileEntry[] = []
|
|
21
|
+
const warnings: string[] = []
|
|
22
|
+
const scan = scanLocal(root, { ignore: loadIgnore(root), tracked: new Set(Object.keys(manifest?.files ?? {})) })
|
|
23
|
+
for (const file of scan.files) {
|
|
24
|
+
const type = manifest?.files[file.path]?.type ?? classifyKind(basename(file.path))
|
|
25
|
+
if (type === "resource") {
|
|
26
|
+
entries.push({ path: file.path, type, fileSrc: assetBase + encodeURI(file.path), absPath: file.absPath })
|
|
27
|
+
} else if (type === "shader") {
|
|
28
|
+
if (!opts.localShaders) {
|
|
29
|
+
warnings.push(`${file.path}: shaders compile on the server — push the project once for shaders to work in dev mode.`)
|
|
30
|
+
}
|
|
31
|
+
entries.push({ path: file.path, type, shaders: [], absPath: file.absPath })
|
|
32
|
+
} else {
|
|
33
|
+
entries.push({ path: file.path, type, text: readFileSync(file.absPath, "utf8") })
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { entries, warnings }
|
|
37
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs"
|
|
2
|
+
import { basename } from "node:path"
|
|
3
|
+
import type { Manifest } from "../manifest"
|
|
4
|
+
import { classifyKind, scanLocal } from "../localFiles"
|
|
5
|
+
import { loadIgnore } from "../ignore"
|
|
6
|
+
import { compileProject, type CompileEntry } from "./projectCompile"
|
|
7
|
+
import { applyAssetIcons } from "./assetIcons"
|
|
8
|
+
import { applyProjectFonts } from "./fonts"
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* Per-screen compile for `lecodes design`. A design screen file default-exports either an
|
|
12
|
+
* already-computed UIScreen (a value) or a `(state) => UIScreen` function (screen states); the
|
|
13
|
+
* virtual entry imports it, invokes the function form for the requested state, and opens the result.
|
|
14
|
+
* Bundled through the normal project pipeline. Fully local — no server round-trip:
|
|
15
|
+
* assets "server" → resource URLs are `/project/<path>` (the design dev server serves them)
|
|
16
|
+
* assets "disk" → `lecodesfile://<abs-path>` (the headless renderer reads them off disk)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export type DesignAssetMode = "server" | "disk"
|
|
20
|
+
|
|
21
|
+
// Must match lecodes-renderer's LOCAL_ASSET_SCHEME (headless/index.ts) — same as `lecodes render`.
|
|
22
|
+
const LOCAL_ASSET_SCHEME = "lecodesfile://"
|
|
23
|
+
|
|
24
|
+
export const compileDesignScreen = async (opts: {
|
|
25
|
+
root: string
|
|
26
|
+
/** Null in standalone mode (no cloned project): kinds come from filenames alone. */
|
|
27
|
+
manifest: Manifest | null
|
|
28
|
+
/** Project display name (compile metadata). */
|
|
29
|
+
name: string
|
|
30
|
+
/** Absolute path of the design folder — where `assetIcon()` icons are vendored (assets/icons/,
|
|
31
|
+
* icons.lock.json, ICON-NOTICES.md). */
|
|
32
|
+
designDir: string
|
|
33
|
+
/** The design folder as a project key ("/design"). */
|
|
34
|
+
designKey: string
|
|
35
|
+
/** Screen id = filename without .ts ("login"). */
|
|
36
|
+
screenId: string
|
|
37
|
+
/** State to render for a function-of-state screen. Omit for the canonical state (the export's
|
|
38
|
+
* param default). Ignored by a value-export screen. */
|
|
39
|
+
state?: string
|
|
40
|
+
assets: DesignAssetMode
|
|
41
|
+
}): Promise<string> => {
|
|
42
|
+
const { root, manifest, designDir, designKey, screenId, state } = opts
|
|
43
|
+
|
|
44
|
+
const entries: CompileEntry[] = []
|
|
45
|
+
const scan = scanLocal(root, { ignore: loadIgnore(root), tracked: new Set(Object.keys(manifest?.files ?? {})) })
|
|
46
|
+
for (const file of scan.files) {
|
|
47
|
+
const type = manifest?.files[file.path]?.type ?? classifyKind(basename(file.path))
|
|
48
|
+
if (type === "resource") {
|
|
49
|
+
entries.push({
|
|
50
|
+
path: file.path,
|
|
51
|
+
type,
|
|
52
|
+
fileSrc: opts.assets === "disk" ? encodeURIComponent(file.absPath) : encodeURI(file.path),
|
|
53
|
+
absPath: file.absPath,
|
|
54
|
+
})
|
|
55
|
+
} else if (type === "shader") {
|
|
56
|
+
entries.push({ path: file.path, type, shaders: [] }) // UI-only render — no shader artifacts needed
|
|
57
|
+
} else {
|
|
58
|
+
entries.push({ path: file.path, type, text: readFileSync(file.absPath, "utf8") })
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// The virtual entry lives in the design folder so `./screens/…` and the screen's own `../shared/…`
|
|
63
|
+
// imports resolve naturally. It never touches disk.
|
|
64
|
+
// A function-of-state export is invoked with the chosen state (canonical = a bare call, using the
|
|
65
|
+
// param's default); a value export is used as-is. Either way we .open() the resulting screen.
|
|
66
|
+
const invoke = state !== undefined ? `screen(${JSON.stringify(state)})` : "screen()"
|
|
67
|
+
const entryPath = `${designKey}/.screen-entry.ts`
|
|
68
|
+
entries.push({
|
|
69
|
+
path: entryPath,
|
|
70
|
+
type: "text",
|
|
71
|
+
text: `import screen from "./screens/${screenId}"\n`
|
|
72
|
+
+ `const __screen = typeof screen === "function" ? ${invoke} : screen\n`
|
|
73
|
+
+ `__screen.open()\n`,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// Resolve any `assetIcon('pack:name')` calls: vendor the SVGs into the design folder and rewrite
|
|
77
|
+
// each call to an inline SvgSource(...) so the bundle renders offline (no network at render time).
|
|
78
|
+
await applyAssetIcons(entries, designDir)
|
|
79
|
+
|
|
80
|
+
// Resolve `font(...)` calls the same way: registry faces vendor into the design folder
|
|
81
|
+
// (assets/fonts/ + fonts.lock.json) and each call becomes its family name plus a generated
|
|
82
|
+
// registration module — offline after the first compile, board and headless render alike.
|
|
83
|
+
await applyProjectFonts(entries, {
|
|
84
|
+
mode: "vendor",
|
|
85
|
+
root,
|
|
86
|
+
vendorDir: designDir,
|
|
87
|
+
vendorPrefix: designKey,
|
|
88
|
+
makeResourceEntry: (path, absPath) => ({
|
|
89
|
+
path,
|
|
90
|
+
type: "resource",
|
|
91
|
+
fileSrc: opts.assets === "disk" ? encodeURIComponent(absPath) : encodeURI(path),
|
|
92
|
+
absPath,
|
|
93
|
+
}),
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
return compileProject({
|
|
97
|
+
entries,
|
|
98
|
+
name: opts.name,
|
|
99
|
+
publicUrl: opts.assets === "disk" ? LOCAL_ASSET_SCHEME : "/project",
|
|
100
|
+
entryOverride: entryPath,
|
|
101
|
+
header: false,
|
|
102
|
+
format: "iife",
|
|
103
|
+
})
|
|
104
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
3
|
+
import { dirname, join } from "node:path"
|
|
4
|
+
import { applyFontMacro, buildFontsHeaderValue, fontRegistryBase, type FontFaceRef } from "lecodes-sdk/compile"
|
|
5
|
+
import type { CompileEntry } from "./projectCompile"
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* The CLI side of the `font()` compile macro (docs/font-system-plan.md): the sdk core scans and
|
|
9
|
+
* rewrites; this wrapper decides where face FILES come from per compile mode and keeps the vendor
|
|
10
|
+
* bookkeeping (fonts.lock.json + FONT-NOTICES.md, mirroring assetIcons.ts):
|
|
11
|
+
*
|
|
12
|
+
* cdn — server-backed compiles: registry faces stay remote (baked CDN URLs), only
|
|
13
|
+
* project-file fonts touch entries. No disk writes.
|
|
14
|
+
* vendor — local/dev/render/design compiles: registry faces are downloaded once into
|
|
15
|
+
* <vendorDir>/assets/fonts/ (offline after that) and pushed as resource entries, so
|
|
16
|
+
* they ride the normal asset pipeline (dev URLs / `id:`+fetchLocal / lecodesfile://).
|
|
17
|
+
*
|
|
18
|
+
* Returns the `// fonts:` header value (boot faces a host prefetches + holds first paint for),
|
|
19
|
+
* or undefined when the project uses no fonts.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
type LicenseEntry = { spdx: string, title: string, url: string, attribution: string | null, modifiedNotice: boolean }
|
|
23
|
+
type LockFace = { id: string, family: string, weight: number, italic: boolean, hash: string, spdx: string, path: string }
|
|
24
|
+
type Lock = { registry: string, faces: Record<string, LockFace>, families: Record<string, LicenseEntry> }
|
|
25
|
+
|
|
26
|
+
export type ProjectFontsOptions = {
|
|
27
|
+
mode: "cdn" | "vendor"
|
|
28
|
+
/** Project root (abs) — fallback for reading project-font files without an absPath. */
|
|
29
|
+
root: string
|
|
30
|
+
/** vendor: where assets/fonts/, fonts.lock.json and FONT-NOTICES.md live (abs). */
|
|
31
|
+
vendorDir?: string
|
|
32
|
+
/** vendor: leading-slash entries prefix for vendorDir ("" = project root, "/design" = a design folder). */
|
|
33
|
+
vendorPrefix?: string
|
|
34
|
+
/** vendor: shape a resource entry for a vendored face the caller's way (fileSrc convention). */
|
|
35
|
+
makeResourceEntry?: (path: string, absPath: string) => CompileEntry
|
|
36
|
+
/** Registry override (URL or a local directory with the registry layout). */
|
|
37
|
+
registry?: string
|
|
38
|
+
/** Absolute-URL prefix of the compile (URL modes) — used for the header's entry-face URLs. */
|
|
39
|
+
publicUrl?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const applyProjectFonts = async (entries: CompileEntry[], opts: ProjectFontsOptions): Promise<string | undefined> => {
|
|
43
|
+
const registry = (opts.registry ?? fontRegistryBase()).replace(/\/+$/, "")
|
|
44
|
+
const byPath = () => new Map(entries.map((e) => [e.path.replace(/^\//, ""), e]))
|
|
45
|
+
|
|
46
|
+
const faces = await applyFontMacro(entries, {
|
|
47
|
+
mode: opts.mode,
|
|
48
|
+
registryBase: registry,
|
|
49
|
+
readBytes: (entryPath) => {
|
|
50
|
+
const entry = byPath().get(entryPath)
|
|
51
|
+
const abs = entry?.absPath ?? join(opts.root, entryPath)
|
|
52
|
+
try { return new Uint8Array(readFileSync(abs)) } catch { return null }
|
|
53
|
+
},
|
|
54
|
+
vendorFace: opts.mode === "vendor"
|
|
55
|
+
? async (face) => {
|
|
56
|
+
const vendorDir = opts.vendorDir ?? opts.root
|
|
57
|
+
const rel = `assets/fonts/${face.fileName}`
|
|
58
|
+
const abs = join(vendorDir, rel)
|
|
59
|
+
const entryPath = `${opts.vendorPrefix ?? ""}/${rel}`
|
|
60
|
+
if (!existsSync(abs)) await downloadFace(registry, vendorDir, face, rel, abs)
|
|
61
|
+
if (!byPath().has(entryPath.replace(/^\//, ""))) {
|
|
62
|
+
entries.push(opts.makeResourceEntry?.(entryPath, abs) ?? { path: entryPath, type: "resource", absPath: abs })
|
|
63
|
+
}
|
|
64
|
+
return entryPath
|
|
65
|
+
}
|
|
66
|
+
: undefined,
|
|
67
|
+
})
|
|
68
|
+
if (faces.length === 0) return undefined
|
|
69
|
+
|
|
70
|
+
return buildFontsHeaderValue(faces, (path) => {
|
|
71
|
+
if (!opts.publicUrl || !/^https?:/i.test(opts.publicUrl)) return null
|
|
72
|
+
const fileSrc = byPath().get(path.replace(/^\//, ""))?.fileSrc
|
|
73
|
+
return fileSrc ? opts.publicUrl + fileSrc : null
|
|
74
|
+
}) || undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---- vendoring (registry → assets/fonts + lock + notices; mirrors assetIcons.ts) ----------------
|
|
78
|
+
|
|
79
|
+
const isHttp = (s: string) => /^https?:/i.test(s)
|
|
80
|
+
const sha12 = (b: Uint8Array) => createHash("sha256").update(b).digest("hex").slice(0, 12)
|
|
81
|
+
const registryHome = (registry: string) => registry.replace(/\/v\d+$/, "")
|
|
82
|
+
|
|
83
|
+
const readRegistry = async (registry: string, rel: string): Promise<Uint8Array | null> => {
|
|
84
|
+
if (isHttp(registry)) {
|
|
85
|
+
const res = await fetch(`${registry}/${rel}`)
|
|
86
|
+
return res.ok ? new Uint8Array(await res.arrayBuffer()) : null
|
|
87
|
+
}
|
|
88
|
+
try { return new Uint8Array(readFileSync(join(registry, rel))) } catch { return null }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const readLock = (path: string): Lock | null => {
|
|
92
|
+
try {
|
|
93
|
+
const lock = JSON.parse(readFileSync(path, "utf8")) as Lock
|
|
94
|
+
if (lock && typeof lock === "object" && lock.faces && lock.families) return lock
|
|
95
|
+
} catch { /* absent or corrupt → start fresh */ }
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Stable serialization: keys sorted, no timestamp — same inputs, byte-identical output. */
|
|
100
|
+
const serializeLock = (lock: Lock): string => {
|
|
101
|
+
const sortKeys = <T>(rec: Record<string, T>): Record<string, T> =>
|
|
102
|
+
Object.fromEntries(Object.keys(rec).sort().map((k) => [k, rec[k]]))
|
|
103
|
+
return JSON.stringify({ registry: lock.registry, faces: sortKeys(lock.faces), families: sortKeys(lock.families) }, null, 2) + "\n"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const renderNotices = (lock: Lock): string => {
|
|
107
|
+
const ids = [...new Set(Object.values(lock.faces).map((f) => f.id))].sort()
|
|
108
|
+
const lines = [
|
|
109
|
+
"# Font attribution & notices",
|
|
110
|
+
"",
|
|
111
|
+
"Fonts vendored from the font registry via `font(...)`. Keep this file in your repo / product.",
|
|
112
|
+
"",
|
|
113
|
+
]
|
|
114
|
+
for (const id of ids) {
|
|
115
|
+
const l = lock.families[id]
|
|
116
|
+
if (!l) continue
|
|
117
|
+
let note = `- **${l.title || id}** — ${l.spdx}${l.url ? ` — ${l.url}` : ""}`
|
|
118
|
+
if (l.attribution) note += `\n - **Attribution required:** ${l.attribution}`
|
|
119
|
+
lines.push(note)
|
|
120
|
+
}
|
|
121
|
+
lines.push("")
|
|
122
|
+
return lines.join("\n")
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const writeIfChanged = (path: string, text: string) => {
|
|
126
|
+
try { if (readFileSync(path, "utf8") === text) return } catch { /* absent → write */ }
|
|
127
|
+
writeFileSync(path, text)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const downloadFace = async (
|
|
131
|
+
registry: string, vendorDir: string,
|
|
132
|
+
face: { id: string, family: string, weight: number, italic: boolean, fileName: string },
|
|
133
|
+
rel: string, abs: string,
|
|
134
|
+
): Promise<void> => {
|
|
135
|
+
const licRaw = await readRegistry(registry, "licenses.json")
|
|
136
|
+
if (!licRaw) throw new Error(`font: can't reach the font registry (${registry}). Check your connection, or set LECODES_FONT_REGISTRY.`)
|
|
137
|
+
const licenses = JSON.parse(new TextDecoder().decode(licRaw)) as Record<string, LicenseEntry>
|
|
138
|
+
const lic = licenses[face.id]
|
|
139
|
+
if (!lic) throw new Error(`font("${face.id}"): the registry has no license entry for this family — it may not be published yet. Browse ${registryHome(registry)}.`)
|
|
140
|
+
|
|
141
|
+
const bytes = await readRegistry(registry, `${face.id}/${face.fileName}`)
|
|
142
|
+
if (!bytes) throw new Error(`font("${face.id}"): face "${face.fileName}" not found in the registry (${registryHome(registry)}).`)
|
|
143
|
+
|
|
144
|
+
mkdirSync(dirname(abs), { recursive: true })
|
|
145
|
+
writeFileSync(abs, bytes)
|
|
146
|
+
|
|
147
|
+
const lockPath = join(vendorDir, "fonts.lock.json")
|
|
148
|
+
const lock: Lock = readLock(lockPath) ?? { registry, faces: {}, families: {} }
|
|
149
|
+
lock.registry = registry
|
|
150
|
+
lock.faces[`${face.id}@${face.weight}${face.italic ? "i" : ""}`] =
|
|
151
|
+
{ id: face.id, family: face.family, weight: face.weight, italic: face.italic, hash: sha12(bytes), spdx: lic.spdx, path: rel }
|
|
152
|
+
lock.families[face.id] = lic
|
|
153
|
+
writeIfChanged(lockPath, serializeLock(lock))
|
|
154
|
+
// Attribution is fail-closed: notices always reflect the vendored families (write throws → build fails).
|
|
155
|
+
writeIfChanged(join(vendorDir, "FONT-NOTICES.md"), renderNotices(lock))
|
|
156
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { getProject } from "../api"
|
|
2
|
+
import { loadConfig, requireApiUrl, requireToken } from "../config"
|
|
3
|
+
import { findProjectRoot, readManifest, type Manifest } from "../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 { flagBool, flagStr, note, type Args } from "../util"
|
|
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
|
+
/** Compile the local project for a headless run (honors --entry / --public-url / --remote-assets). */
|
|
40
|
+
export const compileHeadlessBundle = async (args: Args, opts: { screen?: ScreenTarget } = {}): Promise<HeadlessBundle> => {
|
|
41
|
+
let root: string
|
|
42
|
+
let manifest: Manifest | null
|
|
43
|
+
try {
|
|
44
|
+
root = findProjectRoot(process.cwd())
|
|
45
|
+
manifest = readManifest(root)
|
|
46
|
+
} catch {
|
|
47
|
+
root = process.cwd()
|
|
48
|
+
manifest = null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const publicUrlFlag = flagStr(args, "public-url")
|
|
52
|
+
// A local (server-less) project has no remote asset URLs — it always reads from disk.
|
|
53
|
+
const useLocalAssets = manifest ? (!publicUrlFlag && !flagBool(args, "remote-assets")) : true
|
|
54
|
+
|
|
55
|
+
let entries
|
|
56
|
+
let warnings: string[]
|
|
57
|
+
let name: string
|
|
58
|
+
let publicUrl: string
|
|
59
|
+
|
|
60
|
+
if (manifest) {
|
|
61
|
+
const config = loadConfig()
|
|
62
|
+
const apiUrl = requireApiUrl(config)
|
|
63
|
+
const token = requireToken(config)
|
|
64
|
+
|
|
65
|
+
note("Resolving project assets…")
|
|
66
|
+
const project = await getProject(apiUrl, token, manifest.uuid)
|
|
67
|
+
;({ entries, warnings } = collectEntries(root, manifest, project))
|
|
68
|
+
name = manifest.name
|
|
69
|
+
publicUrl = useLocalAssets ? LOCAL_ASSET_SCHEME : (publicUrlFlag ?? apiUrl)
|
|
70
|
+
// In local mode the "push it so its URL resolves" notices are moot (we read from disk).
|
|
71
|
+
if (useLocalAssets) warnings = warnings.filter((w) => !w.includes("No server URL"))
|
|
72
|
+
} else {
|
|
73
|
+
;({ entries, warnings } = collectLocalEntries(root, null, ""))
|
|
74
|
+
name = root.split(/[\\/]/).pop() ?? "project"
|
|
75
|
+
publicUrl = LOCAL_ASSET_SCHEME
|
|
76
|
+
if (flagBool(args, "remote-assets") || publicUrlFlag) {
|
|
77
|
+
warnings.push("This is a local project (no server) — assets always load from the working tree.")
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Local assets (default): resolve each resource (asset('./x.png')) from the working tree instead of
|
|
82
|
+
// the server, so unpushed / edited images show up with no --wait-network. Each resource's URL becomes
|
|
83
|
+
// `lecodesfile://<abs-path>`, which the renderer reads off disk.
|
|
84
|
+
const compileEntries = useLocalAssets
|
|
85
|
+
? entries.map((e) => (e.type === "resource" && e.absPath)
|
|
86
|
+
? { ...e, fileSrc: encodeURIComponent(e.absPath) }
|
|
87
|
+
: e)
|
|
88
|
+
: [...entries]
|
|
89
|
+
// Rendering one screen: the generated entry that imports it and calls .open() (see screenEntry.ts).
|
|
90
|
+
if (opts.screen) compileEntries.push(opts.screen.entry)
|
|
91
|
+
|
|
92
|
+
// font() macro: vendor registry faces into assets/fonts/ so headless renders read them off disk
|
|
93
|
+
// (lecodesfile://); with --remote-assets they stay CDN URLs like every other remote resource.
|
|
94
|
+
await applyProjectFonts(compileEntries, useLocalAssets
|
|
95
|
+
? {
|
|
96
|
+
mode: "vendor", root, vendorDir: root, vendorPrefix: "",
|
|
97
|
+
makeResourceEntry: (path, absPath) => ({ path, type: "resource", fileSrc: encodeURIComponent(absPath), absPath }),
|
|
98
|
+
}
|
|
99
|
+
: { mode: "cdn", root })
|
|
100
|
+
|
|
101
|
+
// assetIcon() macro: resolve from the user-level cache (~/.lecodes/cache/icons) and inline, so
|
|
102
|
+
// headless renders stay deterministic and offline after the first fetch (like local assets).
|
|
103
|
+
await applyAppAssetIcons(compileEntries)
|
|
104
|
+
|
|
105
|
+
note("Compiling…")
|
|
106
|
+
const entryFlag = opts.screen?.entryPath ?? flagStr(args, "entry")
|
|
107
|
+
if (!entryFlag) {
|
|
108
|
+
const ambiguous = ambiguousEntryWarning(compileEntries)
|
|
109
|
+
if (ambiguous) warnings.push(ambiguous)
|
|
110
|
+
}
|
|
111
|
+
const js = await compileProject({
|
|
112
|
+
entries: compileEntries,
|
|
113
|
+
name,
|
|
114
|
+
publicUrl,
|
|
115
|
+
entryOverride: entryFlag,
|
|
116
|
+
header: false,
|
|
117
|
+
format: "iife",
|
|
118
|
+
})
|
|
119
|
+
if (js.includes("_creator.createScene") || js.includes("_creator.createEntity")) {
|
|
120
|
+
warnings.push("3D project: the scene graph is captured as `scene3d` in the JSON output; 3D isn't rasterized into PNGs.")
|
|
121
|
+
}
|
|
122
|
+
// With --remote-assets, 2D textures come from the network; without --wait-network they resolve empty
|
|
123
|
+
// and sprites render as flat placeholders. Local assets (default) load from disk, so no hint needed.
|
|
124
|
+
if (js.includes("_creator2d") && !useLocalAssets && !flagBool(args, "wait-network")) {
|
|
125
|
+
warnings.push("2D scene detected: sprite textures load over the network — add --wait-network to include them (else sprites render as flat placeholders).")
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { js, root, warnings, useLocalAssets }
|
|
129
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { extractInlineSourceMap, mapNamedFileStack } from "lecodes-viewer-lite/src/sourcemapError"
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* De-minify native (QuickJS) stack traces in a desktop host's output.
|
|
5
|
+
*
|
|
6
|
+
* The host evaluates the compiled bundle as one file, so every frame it prints reads
|
|
7
|
+
* `at e$ (module.js:5:51631)` — a minified column offset nobody can act on. The bundle carries its
|
|
8
|
+
* map inline (`//# lecodesMap=`, see sdk/compile/sourcemap.ts) and the CLI holds the very string it
|
|
9
|
+
* staged, so it can rewrite those frames into `main.ts:92:23` on the way through. The map consumer
|
|
10
|
+
* lives in viewer-lite (the preview needs the same decoding for its `new Function` frames).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** quickjs names the bundle "module.js" when a project world is `run()` into a fresh context
|
|
14
|
+
* (creatorUtilsHandler) — "main.js" is the host's own boot eval, whose frames aren't ours. */
|
|
15
|
+
const BUNDLE_FILENAME = "module.js"
|
|
16
|
+
|
|
17
|
+
/** A rewriter for one bundle's output, or `null` when it carries no map (nothing to do). */
|
|
18
|
+
export const makeStackDemangler = (bundleJs: string): ((chunk: string) => string) | null => {
|
|
19
|
+
const map = extractInlineSourceMap(bundleJs)
|
|
20
|
+
return map ? (chunk: string) => mapNamedFileStack(chunk, map, BUNDLE_FILENAME) : null
|
|
21
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { existsSync } from "node:fs"
|
|
2
|
+
import { createRequire } from "node:module"
|
|
3
|
+
import { dirname, join } from "node:path"
|
|
4
|
+
import type { FileKind } from "../manifest"
|
|
5
|
+
import { distRoot } from "../distRoot"
|
|
6
|
+
import { compileProject as sdkCompileProject, detectEntry, scanModulesChisel, ENTRY_CONVENTION } from "lecodes-sdk/compile"
|
|
7
|
+
|
|
8
|
+
// Every project compiles through the SDK's one import-free compiler, which bundles with chisel (the
|
|
9
|
+
// Rust method-granular bundler, @letary/chisel) — the only bundler. The SDK resolves the chisel
|
|
10
|
+
// binary itself; here we just hand it an explicit path when the @letary/chisel package is present,
|
|
11
|
+
// and point it at the SDK *source* tree chisel compiles from:
|
|
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);
|
|
14
|
+
// 2. else the copy vendored beside the standalone binary (runtime/sdk/, scripts/vendor-runtime.ts
|
|
15
|
+
// --sdk) — the binary has no node_modules.
|
|
16
|
+
const resolveSdkInject = (): string | null => {
|
|
17
|
+
try { return createRequire(import.meta.url).resolve("lecodes-sdk/inject") } catch { /* no package: standalone */ }
|
|
18
|
+
const vendored = join(distRoot, "runtime", "sdk", "inject.ts")
|
|
19
|
+
return existsSync(vendored) ? vendored : null
|
|
20
|
+
}
|
|
21
|
+
const sdkInject = resolveSdkInject()
|
|
22
|
+
if (sdkInject) process.env.LECODES_SDK_INJECT = sdkInject
|
|
23
|
+
|
|
24
|
+
/** Absolute path to the chisel binary: env override → the @letary/chisel npm package → a chisel
|
|
25
|
+
* binary shipped next to the executable (standalone binary distribution). Null when none found. */
|
|
26
|
+
const resolveChiselBin = (): string | null => {
|
|
27
|
+
if (process.env.LECODES_CHISEL_BIN) return process.env.LECODES_CHISEL_BIN
|
|
28
|
+
try {
|
|
29
|
+
const fromPkg = createRequire(import.meta.url)("@letary/chisel").binaryPath()
|
|
30
|
+
if (fromPkg) return fromPkg
|
|
31
|
+
} catch {
|
|
32
|
+
// @letary/chisel (or its platform binary) not installed — try the exe-adjacent copy
|
|
33
|
+
}
|
|
34
|
+
const local = join(dirname(process.execPath), process.platform === "win32" ? "chisel.exe" : "chisel")
|
|
35
|
+
return existsSync(local) ? local : null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const chiselBin = resolveChiselBin()
|
|
39
|
+
if (chiselBin) process.env.LECODES_CHISEL_BIN = chiselBin
|
|
40
|
+
|
|
41
|
+
export type CompileEntry = {
|
|
42
|
+
path: string
|
|
43
|
+
type: FileKind
|
|
44
|
+
/** Source text for code / .svg / scene files. */
|
|
45
|
+
text?: string
|
|
46
|
+
/** Server src for a binary resource (e.g. "/uploads/x.png"); prefixed with publicUrl. */
|
|
47
|
+
fileSrc?: string
|
|
48
|
+
/** Under `localAssets`: keep THIS resource in the URL form anyway (app.json resources.remote). */
|
|
49
|
+
remote?: boolean
|
|
50
|
+
/** Absolute path of the local file (resources + shaders) — `render` resolves assets from disk
|
|
51
|
+
* with it; the dev server's local shader compile (compile/shaders.ts) reads .mat sources. */
|
|
52
|
+
absPath?: string
|
|
53
|
+
/** Compiled-shader artifacts (platform + src); the first drives the per-backend URL. */
|
|
54
|
+
shaders?: { platform: string, src: string }[]
|
|
55
|
+
/** `shaders` are LOCAL files staged next to the bundle — the module compiles to a fetchLocal
|
|
56
|
+
* lookup instead of a URL (set by compile/shaders.ts in `local` mode). */
|
|
57
|
+
shadersLocal?: boolean
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type CompileOptions = {
|
|
61
|
+
entries: CompileEntry[]
|
|
62
|
+
name: string
|
|
63
|
+
publicUrl: string
|
|
64
|
+
entryOverride?: string
|
|
65
|
+
header?: boolean
|
|
66
|
+
/** `// fonts:` header payload (from applyProjectFonts — the font() macro pass). */
|
|
67
|
+
fontsHeader?: string
|
|
68
|
+
/** `// icons:` header payload (from applyAppAssetIcons — the assetIcon() macro pass). */
|
|
69
|
+
iconsHeader?: string
|
|
70
|
+
/** Output format: "esm" (default, shipped) or "iife" (self-running, for the local renderer). */
|
|
71
|
+
format?: "esm" | "iife"
|
|
72
|
+
/** Resolve `asset()` resources via the host's `fetchLocal` (local file → systemId) instead of a
|
|
73
|
+
* `publicUrl` URL — for a host with no asset server (e.g. the desktop native runtime). */
|
|
74
|
+
localAssets?: boolean
|
|
75
|
+
/** Override minification (default on). Scene-editor bundles pass `false`: mangling erases class
|
|
76
|
+
* names, which the inspector matches aspect schemas by (describeAspect().className). */
|
|
77
|
+
minify?: boolean
|
|
78
|
+
/** Generate + inline a source map (default true). `false` gives a smaller bundle whose runtime
|
|
79
|
+
* errors report generated positions only. */
|
|
80
|
+
sourcemap?: boolean
|
|
81
|
+
/** Editor compile: the `EDITOR` global compiles to `true` and `*.editor.ts` plugin files are
|
|
82
|
+
* bundled in (scene-editor bundles). Default false — production compiles skip them. */
|
|
83
|
+
editor?: boolean
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** A warning when auto-detection has several import-graph roots and no main.ts to settle it —
|
|
87
|
+
* the pick still works, but the developer should make the entry explicit. Null when unambiguous.
|
|
88
|
+
* Callers skip this when an explicit --entry / entryOverride is in play. */
|
|
89
|
+
export const ambiguousEntryWarning = (entries: { path: string, type?: string, text?: string }[]): string | null => {
|
|
90
|
+
const { entry, roots } = detectEntry(entries, scanModulesChisel)
|
|
91
|
+
if (!entry || roots.length < 2 || roots.includes(ENTRY_CONVENTION)) return null
|
|
92
|
+
return `Ambiguous entrypoint: nothing imports ${roots.join(", ")} — picked ${entry}. Rename your entry to main.ts (or pass --entry).`
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const compileProject = (opts: CompileOptions): Promise<string> =>
|
|
96
|
+
sdkCompileProject({
|
|
97
|
+
entries: opts.entries.map((e) => ({ path: e.path, type: e.type as any, text: e.text, fileSrc: e.fileSrc, remote: e.remote, shaders: e.shaders, shadersLocal: e.shadersLocal })),
|
|
98
|
+
name: opts.name,
|
|
99
|
+
publicUrl: opts.publicUrl,
|
|
100
|
+
entryOverride: opts.entryOverride,
|
|
101
|
+
header: opts.header,
|
|
102
|
+
fontsHeader: opts.fontsHeader,
|
|
103
|
+
iconsHeader: opts.iconsHeader,
|
|
104
|
+
format: opts.format,
|
|
105
|
+
localAssets: opts.localAssets,
|
|
106
|
+
minify: opts.minify,
|
|
107
|
+
sourcemap: opts.sourcemap,
|
|
108
|
+
editor: opts.editor,
|
|
109
|
+
})
|