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,339 @@
|
|
|
1
|
+
import { execFileSync, spawn } from "node:child_process"
|
|
2
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs"
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path"
|
|
4
|
+
import { buildBundle } from "./compile"
|
|
5
|
+
import { resolveDesktopExe, selectedDesktopVersion } from "../desktopRenderer"
|
|
6
|
+
import { CliError, c, flagBool, flagStr, log, note, success, warn, type Args } from "../util"
|
|
7
|
+
import { assertRendererSupportsBundle, desktopPluginLibraries, projectRoot, readJson, resolveDesktopRenderer, writeJson, type AppConfig } from "./appShared"
|
|
8
|
+
import { loadIconMaster } from "./appIcon"
|
|
9
|
+
import { buildMacApp, macHostProvidedNames } from "./appDesktopMac"
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* `lecodes app init|build desktop` — the desktop arm of the native-app commands
|
|
13
|
+
* (docs/desktop-target-plan.md phase 3). Unlike iOS/Android there is no user-side toolchain:
|
|
14
|
+
* `build` stages the verified cached host + the compiled bundle + its assets into one portable
|
|
15
|
+
* folder that runs on a double-click. On macOS the same command produces a signed (and, with a
|
|
16
|
+
* notarytool profile, notarized) .app instead — the staging half is shared, the bundle half lives
|
|
17
|
+
* in appDesktopMac.ts.
|
|
18
|
+
*
|
|
19
|
+
* The staged folder is SELF-DESCRIBING: a pruned app.json (name + desktop.window) ships next to
|
|
20
|
+
* app.js, and the host (≥1.3.0) reads it for title/size/min-size/resizable/fullscreen — no
|
|
21
|
+
* launcher, no envs, works the same on Windows and Linux. Field-proven ancestor: the quiz
|
|
22
|
+
* kiosk's tools/pack-desktop.mjs (its BUG-REPORT.md drove the host fixes this relies on).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const mb = (bytes: number) => `${(bytes / 1024 / 1024).toFixed(1)} MB`
|
|
26
|
+
|
|
27
|
+
const dirSize = (dir: string): number =>
|
|
28
|
+
readdirSync(dir, { withFileTypes: true }).reduce((sum, e) => {
|
|
29
|
+
const p = join(dir, e.name)
|
|
30
|
+
return sum + (e.isDirectory() ? dirSize(p) : statSync(p).size)
|
|
31
|
+
}, 0)
|
|
32
|
+
|
|
33
|
+
/* --------------------------------------- init ---------------------------------------- */
|
|
34
|
+
|
|
35
|
+
/** `lecodes app init desktop` — declare the desktop target in app.json (mirrors
|
|
36
|
+
* `lecodes init --desktop` for existing projects). Purely additive; re-running keeps edits. */
|
|
37
|
+
export const initDesktop = async (args: Args) => {
|
|
38
|
+
const root = projectRoot()
|
|
39
|
+
const existing = readJson<AppConfig>(join(root, "app.json")) ?? {}
|
|
40
|
+
const name = flagStr(args, "name") ?? existing.name ?? basename(root)
|
|
41
|
+
const window = {
|
|
42
|
+
width: 1280, height: 800, minWidth: 640, minHeight: 480,
|
|
43
|
+
...existing.desktop?.window,
|
|
44
|
+
...(flagBool(args, "fullscreen") ? { fullscreen: true } : {}),
|
|
45
|
+
}
|
|
46
|
+
const config: AppConfig = { ...existing, name, desktop: { ...existing.desktop, window } }
|
|
47
|
+
writeJson(join(root, "app.json"), config)
|
|
48
|
+
log(`${c.green("written")} app.json (desktop.window ${window.width}×${window.height}${window.fullscreen ? ", fullscreen" : ""})`)
|
|
49
|
+
log(`Next: ${c.bold("lecodes dev --desktop")} to develop, ${c.bold("lecodes app build desktop")} to package.`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* --------------------------------------- build --------------------------------------- */
|
|
53
|
+
|
|
54
|
+
/** Windows/file-system-clean name; non-ASCII is fine (the host is UTF-8-clean since 1.3.0). */
|
|
55
|
+
const cleanFileName = (name: string) =>
|
|
56
|
+
name.replace(/[<>:"/\\|?*\u0000-\u001f]/g, "").replace(/[. ]+$/, "").trim() || "app"
|
|
57
|
+
|
|
58
|
+
/** Where a built folder keeps the two kinds of file. Both are a HOST contract (see onFetchLocal and
|
|
59
|
+
* the runtime-assets probe in desktop/src/main.cpp) — change them on both sides or not at all. */
|
|
60
|
+
const ASSETS_DIR = "assets" // the project's own files
|
|
61
|
+
const RUNTIME_DIR = "runtime" // the host's engine files (initial.js, built-in materials, IBL)
|
|
62
|
+
|
|
63
|
+
export const buildDesktop = async (args: Args) => {
|
|
64
|
+
const root = projectRoot()
|
|
65
|
+
const config = readJson<AppConfig>(join(root, "app.json")) ?? {}
|
|
66
|
+
const name = (config.name ?? basename(root)).trim() || "app"
|
|
67
|
+
const slug = flagStr(args, "slug") ?? cleanFileName(name)
|
|
68
|
+
const out = resolve(root, flagStr(args, "o", "out") ?? join(root, "dist", slug))
|
|
69
|
+
|
|
70
|
+
// Only a previous build output (or an empty/absent dir) may be replaced — never wipe a
|
|
71
|
+
// directory this command didn't produce.
|
|
72
|
+
if (existsSync(out)) {
|
|
73
|
+
const entries = readdirSync(out)
|
|
74
|
+
const looksLikeBuild = entries.length === 0 || entries.includes("app.js") || entries.some((e) => e.endsWith(".app"))
|
|
75
|
+
if (!looksLikeBuild) {
|
|
76
|
+
throw new CliError(`${out} exists and doesn't look like a previous desktop build — pick another -o or clear it yourself.`)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Per-target entry (app.json desktop.entry); an explicit --entry wins.
|
|
81
|
+
if (config.desktop?.entry && flagStr(args, "entry") === undefined) args.flags.entry = config.desktop.entry
|
|
82
|
+
|
|
83
|
+
// ONE renderer decision: it selects the host binary staged into the folder AND the single
|
|
84
|
+
// .filamat variant compiled beside the bundle. Two independent reads of the config is how a
|
|
85
|
+
// build ships a Vulkan exe next to an OpenGL shader (a Filament abort at the first material).
|
|
86
|
+
const renderer = resolveDesktopRenderer(root, args)
|
|
87
|
+
|
|
88
|
+
const mac = process.platform === "darwin"
|
|
89
|
+
note(`Building "${name}" for ${mac ? "macOS" : "desktop"}${renderer === "vulkan" ? " (Vulkan)" : ""}…`)
|
|
90
|
+
const { js, resources } = await buildBundle(
|
|
91
|
+
args,
|
|
92
|
+
{ remotePatterns: config.resources?.remote ?? [] },
|
|
93
|
+
{ localShaders: true, desktopRenderer: renderer },
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
// The Vulkan host has no 2D engine — refuse now rather than shipping a folder that opens a blank
|
|
97
|
+
// window (see assertRendererSupportsBundle).
|
|
98
|
+
assertRendererSupportsBundle(renderer, js)
|
|
99
|
+
|
|
100
|
+
// Only what the bundle actually references ships — the compile inventory lists EVERY project
|
|
101
|
+
// resource (design/ references included), which would multiply the build size. asset() compiles
|
|
102
|
+
// to fetchLocal("<basename>"), so the bundle itself is the reference list.
|
|
103
|
+
const wanted = new Set([...js.matchAll(/fetchLocal\("([^"]+)"\)/g)].map((m) => m[1]))
|
|
104
|
+
// Compiled shader artifacts always ship: their name is built at runtime ("<hash>_" +
|
|
105
|
+
// _creator.backend + ".filamat" — one bundle serves every native backend), so no literal for
|
|
106
|
+
// the scan above to find. They only exist here when a shader was actually compiled locally.
|
|
107
|
+
const shipped = resources.filter((r) => !r.remote && (r.shader || wanted.has(r.name)))
|
|
108
|
+
|
|
109
|
+
// Duplicate basenames alias silently in a built app (asset() collapses to fetchLocal(basename),
|
|
110
|
+
// quiz BUG-REPORT §4) — a hard error for every referenced name, not a warning.
|
|
111
|
+
const byName = new Map<string, Set<string>>()
|
|
112
|
+
for (const r of resources) {
|
|
113
|
+
if (r.remote || r.shader || !wanted.has(r.name)) continue
|
|
114
|
+
const set = byName.get(r.name) ?? new Set<string>()
|
|
115
|
+
set.add(r.path)
|
|
116
|
+
byName.set(r.name, set)
|
|
117
|
+
}
|
|
118
|
+
const collisions = [...byName.entries()].filter(([, paths]) => paths.size > 1)
|
|
119
|
+
if (collisions.length > 0) {
|
|
120
|
+
throw new CliError(
|
|
121
|
+
"Asset file names must be unique across the whole project — the bundle refers to assets " +
|
|
122
|
+
"by BASENAME, so same-named files silently replace each other in the built app:\n" +
|
|
123
|
+
collisions.map(([n, paths]) => ` ${n}\n` + [...paths].map((p) => ` ${p}`).join("\n")).join("\n"))
|
|
124
|
+
}
|
|
125
|
+
// The host: same verified cache `render --desktop` uses; LECODES_DESKTOP_EXE overrides
|
|
126
|
+
// (points builds at a locally built host).
|
|
127
|
+
const exe = await resolveDesktopExe(renderer)
|
|
128
|
+
const hostDir = dirname(exe)
|
|
129
|
+
|
|
130
|
+
// A referenced name with no local file would 404 at runtime — fail here instead. The host
|
|
131
|
+
// supplies two classes of name itself, so neither counts as missing: the files in its assets/
|
|
132
|
+
// dir (the built-in materials `Material.lit`/`unlit`/… fetch — onFetchLocal falls back there
|
|
133
|
+
// after the app.js dir), and the engine-synthesized preloads. Same split the iOS/Android
|
|
134
|
+
// shells make with sdkProvided + IMPLICIT_PRELOADS.
|
|
135
|
+
const IMPLICIT_PRELOADS = new Set(["uberarchive", "capture"])
|
|
136
|
+
const hostProvided = mac ? macHostProvidedNames(exe) : new Set<string>()
|
|
137
|
+
if (!mac) {
|
|
138
|
+
try {
|
|
139
|
+
for (const f of readdirSync(join(hostDir, "assets"))) hostProvided.add(f)
|
|
140
|
+
} catch { /* checked properly below (assets/initial.js) */ }
|
|
141
|
+
}
|
|
142
|
+
const shippedNames = new Set(shipped.map((r) => r.name))
|
|
143
|
+
const missing = [...wanted].filter((n) => !shippedNames.has(n) && !hostProvided.has(n) && !IMPLICIT_PRELOADS.has(n))
|
|
144
|
+
if (missing.length > 0) {
|
|
145
|
+
throw new CliError(`The bundle references assets with no local file: ${missing.join(", ")}`)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// macOS: one signed .app rather than a folder — see appDesktopMac.ts.
|
|
149
|
+
if (mac) return buildMacApp({ root, config, name, slug, out, js, shipped, hostExe: exe, args })
|
|
150
|
+
if (flagBool(args, "dmg")) warn("--dmg is a macOS format — ignored on this platform.")
|
|
151
|
+
|
|
152
|
+
const version = process.env.LECODES_DESKTOP_EXE ? "local build" : selectedDesktopVersion()
|
|
153
|
+
|
|
154
|
+
rmSync(out, { recursive: true, force: true })
|
|
155
|
+
mkdirSync(out, { recursive: true })
|
|
156
|
+
|
|
157
|
+
// 1) Host files — a WHITELIST (exe + runtime DLLs + its assets/fonts dirs), so a
|
|
158
|
+
// LECODES_DESKTOP_EXE override pointing into a build tree doesn't stage the whole ninja
|
|
159
|
+
// output, and .pdb/README never ship. The exe is renamed to the app — the host is
|
|
160
|
+
// name-agnostic and auto-runs the app.js next to itself.
|
|
161
|
+
//
|
|
162
|
+
// The host's own assets land in `runtime/`, NOT `assets/`: that name belongs to the project's
|
|
163
|
+
// files (step 2), and one shared folder would let a project `logo.png` overwrite the host's
|
|
164
|
+
// splash — or a project `initial.js` stop the app booting at all. The host (≥1.5.0) looks for
|
|
165
|
+
// `runtime/initial.js` first and falls back to `assets/`, so a downloaded older host cache
|
|
166
|
+
// still stages correctly.
|
|
167
|
+
for (const entry of readdirSync(hostDir, { withFileTypes: true })) {
|
|
168
|
+
if (entry.isFile() && entry.name.toLowerCase().endsWith(".dll")) {
|
|
169
|
+
cpSync(join(hostDir, entry.name), join(out, entry.name))
|
|
170
|
+
} else if (entry.isDirectory() && entry.name === "assets") {
|
|
171
|
+
cpSync(join(hostDir, entry.name), join(out, RUNTIME_DIR), { recursive: true })
|
|
172
|
+
} else if (entry.isDirectory() && entry.name === "fonts") {
|
|
173
|
+
cpSync(join(hostDir, entry.name), join(out, entry.name), { recursive: true })
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!existsSync(join(out, RUNTIME_DIR, "initial.js"))) {
|
|
177
|
+
throw new CliError(`The host at ${hostDir} has no assets/initial.js next to it — a staged build can't boot. ` +
|
|
178
|
+
`(LECODES_DESKTOP_EXE must point at an exe with its runtime assets/ beside it.)`)
|
|
179
|
+
}
|
|
180
|
+
const exeName = process.platform === "win32" ? `${cleanFileName(name)}.exe` : cleanFileName(name)
|
|
181
|
+
cpSync(exe, join(out, exeName))
|
|
182
|
+
note(` host: LeCodes Desktop ${version}`)
|
|
183
|
+
|
|
184
|
+
// 2) The bundle next to the exe (the host auto-runs it), its assets in `assets/` beside it —
|
|
185
|
+
// fetchLocal resolves by BASENAME and searches `<app>/assets/` before the flat dir, so the
|
|
186
|
+
// shipped folder stays readable instead of scattering the whole asset library in the root.
|
|
187
|
+
writeFileSync(join(out, "app.js"), js)
|
|
188
|
+
let assetBytes = 0, assetCount = 0
|
|
189
|
+
if (shipped.length > 0) mkdirSync(join(out, ASSETS_DIR), { recursive: true })
|
|
190
|
+
for (const r of shipped) {
|
|
191
|
+
if (!r.absPath || !existsSync(r.absPath) || !statSync(r.absPath).isFile()) {
|
|
192
|
+
warn(`asset "${r.name}" (${r.path}) has no local file — skipped`)
|
|
193
|
+
continue
|
|
194
|
+
}
|
|
195
|
+
cpSync(r.absPath, join(out, ASSETS_DIR, r.name))
|
|
196
|
+
assetBytes += statSync(r.absPath).size
|
|
197
|
+
assetCount++
|
|
198
|
+
}
|
|
199
|
+
note(` assets: ${assetCount} files, ${mb(assetBytes)} → ${ASSETS_DIR}/`)
|
|
200
|
+
|
|
201
|
+
// 3) Pruned app.json — the host (≥1.3.0) reads name + desktop.window next to app.js, so the
|
|
202
|
+
// folder carries its own window config (title/size/min/resizable/fullscreen).
|
|
203
|
+
writeJson(join(out, "app.json"), { name, ...(config.desktop ? { desktop: { window: config.desktop.window } } : {}) })
|
|
204
|
+
|
|
205
|
+
// 4) Window/taskbar icon (host ≥1.3.0 loads app.ico next to the exe at runtime).
|
|
206
|
+
if (config.icon) {
|
|
207
|
+
await writeIco(root, config, join(out, "app.ico"))
|
|
208
|
+
note(" app.ico rendered from " + config.icon)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 5) Native plugins (packages/desktop/plugin-sdk/lecodes_plugin.h): the project's plugins/ dir +
|
|
212
|
+
// every declared plugin's desktop library → plugins/ next to the exe, where a host WITH the plugin
|
|
213
|
+
// loader (added after 1.4.0) scans at startup; an older host ignores the folder.
|
|
214
|
+
// A declared library that's missing is a build error (strict).
|
|
215
|
+
{
|
|
216
|
+
const { files } = desktopPluginLibraries(root, { strict: true })
|
|
217
|
+
if (files.length > 0) {
|
|
218
|
+
mkdirSync(join(out, "plugins"), { recursive: true })
|
|
219
|
+
for (const f of files) cpSync(f, join(out, "plugins", basename(f)))
|
|
220
|
+
note(` plugins: ${files.map((f) => basename(f)).join(", ")} → plugins/`)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 6) A short README (BOM so double-click-Notepad shows it right).
|
|
225
|
+
writeFileSync(join(out, "README.txt"), "\uFEFF" + readmeText(name, exeName, version))
|
|
226
|
+
|
|
227
|
+
success(`Portable build: ${out} (${mb(dirSize(out))})`)
|
|
228
|
+
|
|
229
|
+
if (flagBool(args, "probe")) await probeBuild(out, exeName, config, args)
|
|
230
|
+
if (flagBool(args, "zip")) zipBuild(out)
|
|
231
|
+
if (!flagBool(args, "probe")) note(`Tip: --probe renders the staged folder headless to verify exactly what ships.`)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** app.ico from the app.json icon master: PNG-compressed frames (fine on the Win10+ the
|
|
235
|
+
* distribution requires) at the sizes Windows asks for — no image tooling needed. */
|
|
236
|
+
const writeIco = async (root: string, config: AppConfig, dest: string) => {
|
|
237
|
+
const { image } = await loadIconMaster(root, config)
|
|
238
|
+
const sizes = [16, 24, 32, 48, 64, 256]
|
|
239
|
+
const pngs: Buffer[] = []
|
|
240
|
+
for (const s of sizes) {
|
|
241
|
+
const frame = image.clone()
|
|
242
|
+
frame.resize({ w: s, h: s })
|
|
243
|
+
pngs.push(Buffer.from(await frame.getBuffer("image/png")))
|
|
244
|
+
}
|
|
245
|
+
const header = Buffer.alloc(6 + sizes.length * 16)
|
|
246
|
+
header.writeUInt16LE(0, 0) // reserved
|
|
247
|
+
header.writeUInt16LE(1, 2) // type: icon
|
|
248
|
+
header.writeUInt16LE(sizes.length, 4)
|
|
249
|
+
let offset = header.length
|
|
250
|
+
sizes.forEach((s, i) => {
|
|
251
|
+
const e = 6 + i * 16
|
|
252
|
+
header.writeUInt8(s === 256 ? 0 : s, e) // width (0 encodes 256)
|
|
253
|
+
header.writeUInt8(s === 256 ? 0 : s, e + 1) // height
|
|
254
|
+
header.writeUInt16LE(1, e + 4) // color planes
|
|
255
|
+
header.writeUInt16LE(32, e + 6) // bits per pixel
|
|
256
|
+
header.writeUInt32LE(pngs[i].length, e + 8)
|
|
257
|
+
header.writeUInt32LE(offset, e + 12)
|
|
258
|
+
offset += pngs[i].length
|
|
259
|
+
})
|
|
260
|
+
writeFileSync(dest, Buffer.concat([header, ...pngs]))
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** `--probe`: run the STAGED folder hidden and dump a PNG next to it — verifies exactly what
|
|
264
|
+
* ships (the staged host, its assets, the bundle), not the dev environment. */
|
|
265
|
+
const probeBuild = async (out: string, exeName: string, config: AppConfig, args: Args) => {
|
|
266
|
+
const win = config.desktop?.window
|
|
267
|
+
const frames = Number(flagStr(args, "frames")) || 90
|
|
268
|
+
const shot = join(dirname(out), `${basename(out)}-probe.png`)
|
|
269
|
+
note(`Probe: hidden render of the staged folder (${frames} frames)…`)
|
|
270
|
+
await new Promise<void>((done, fail) => {
|
|
271
|
+
const child = spawn(join(out, exeName), [], {
|
|
272
|
+
cwd: out,
|
|
273
|
+
env: {
|
|
274
|
+
...process.env,
|
|
275
|
+
CREATOR_HIDDEN: "1",
|
|
276
|
+
CREATOR_MAX_FRAMES: String(frames),
|
|
277
|
+
CREATOR_DUMP_PNG: shot,
|
|
278
|
+
// Explicit size keeps the probe meaningful on pre-1.3.0 hosts too (no sibling-config read).
|
|
279
|
+
CREATOR_WINDOW_SIZE: `${win?.width ?? 1280}x${win?.height ?? 720}`,
|
|
280
|
+
},
|
|
281
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
282
|
+
})
|
|
283
|
+
const tail: string[] = []
|
|
284
|
+
const sink = (d: unknown) => { tail.push(String(d)); while (tail.length > 60) tail.shift() }
|
|
285
|
+
child.stdout.on("data", sink)
|
|
286
|
+
child.stderr.on("data", sink)
|
|
287
|
+
const killer = setTimeout(() => {
|
|
288
|
+
child.kill()
|
|
289
|
+
fail(new CliError("The probe timed out after 120s — raise --frames headroom or check the app."))
|
|
290
|
+
}, 120_000)
|
|
291
|
+
child.on("error", (e) => { clearTimeout(killer); fail(new CliError(`Probe couldn't run: ${e.message}`)) })
|
|
292
|
+
child.on("exit", (code) => {
|
|
293
|
+
clearTimeout(killer)
|
|
294
|
+
if (code === 0 && existsSync(shot)) { success(`Probe: ${shot}`); done() }
|
|
295
|
+
else {
|
|
296
|
+
const detail = tail.length > 0 ? `\n${tail.join("").trimEnd()}` : ""
|
|
297
|
+
fail(new CliError(`Probe failed (exit ${code}).${detail}`))
|
|
298
|
+
}
|
|
299
|
+
})
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** `--zip`: <out>.zip with UTF-8 names + forward-slash entries. `Compress-Archive` writes
|
|
304
|
+
* backslash entry paths and `tar -a` OEM-encodes non-ASCII names — both corrupt on
|
|
305
|
+
* macOS/Linux unpackers, so on Windows the entries are written by hand. */
|
|
306
|
+
const zipBuild = (out: string) => {
|
|
307
|
+
const zip = `${out}.zip`
|
|
308
|
+
rmSync(zip, { force: true })
|
|
309
|
+
note("Zipping…")
|
|
310
|
+
if (process.platform === "win32") {
|
|
311
|
+
const q = (s: string) => s.replaceAll("'", "''")
|
|
312
|
+
execFileSync("powershell", ["-NoProfile", "-Command", [
|
|
313
|
+
"Add-Type -AssemblyName System.IO.Compression.FileSystem;",
|
|
314
|
+
`$src='${q(out)}'; $base=Split-Path $src -Parent;`,
|
|
315
|
+
`$zip=[System.IO.Compression.ZipFile]::Open('${q(zip)}','Create');`,
|
|
316
|
+
"Get-ChildItem $src -Recurse -File | ForEach-Object {",
|
|
317
|
+
" $rel=$_.FullName.Substring($base.Length+1).Replace('\\','/');",
|
|
318
|
+
" [void][System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip,$_.FullName,$rel,'Optimal') };",
|
|
319
|
+
"$zip.Dispose()",
|
|
320
|
+
].join(" ")], { stdio: ["ignore", "ignore", "pipe"] })
|
|
321
|
+
} else {
|
|
322
|
+
execFileSync("zip", ["-rq", zip, basename(out)], { cwd: dirname(out) })
|
|
323
|
+
}
|
|
324
|
+
success(`Zip: ${zip} (${mb(statSync(zip).size)})`)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const readmeText = (name: string, exeName: string, version: string) => `${name}
|
|
328
|
+
${"=".repeat(Math.max(name.length, 4))}
|
|
329
|
+
|
|
330
|
+
A LeCodes desktop app. Nothing to install: unpack the WHOLE folder anywhere and run ${exeName}.
|
|
331
|
+
Keep the files together: assets/ holds the app's own content, runtime/ the engine it needs.
|
|
332
|
+
|
|
333
|
+
On first run Windows may show a SmartScreen prompt (any unsigned app gets one):
|
|
334
|
+
choose "More info", then "Run anyway".
|
|
335
|
+
|
|
336
|
+
Requires 64-bit Windows 10/11.
|
|
337
|
+
|
|
338
|
+
Built with LeCodes Desktop ${version}.
|
|
339
|
+
`
|