lecodes-cli 0.20.2 → 2.0.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 +56 -57
- package/dist/index.js +11052 -7592
- package/package.json +14 -8
- package/runtime/materials/decal-relief.mat +12 -1
- package/runtime/materials/decal.mat +17 -3
- package/runtime/materials/lightmap.mat +117 -138
- package/runtime/materials/particles-quad.mat +6 -2
- package/runtime/materials/terrain-lightmap.mat +123 -0
- package/runtime/materials/terrain.mat +36 -55
- package/runtime/sdk-types.json +1 -1
- package/runtime/web/VERSION.json +5 -0
- package/runtime/web/assets/camera-5XOM8eHl.js +1 -0
- package/runtime/web/assets/creator-2d-D9WKYM6h.js +1 -0
- package/runtime/web/assets/creator-2d-ukvEmNEp.wasm +0 -0
- package/runtime/web/assets/creator-full-BBXzQlhq.js +1 -0
- package/runtime/web/assets/creator-full-D2dSDdDj.wasm +0 -0
- package/runtime/web/assets/creator-full.js-BYswGsqR.symbols +8124 -0
- package/runtime/web/assets/creator-ui-DIc92ion.wasm +0 -0
- package/runtime/web/assets/creator-ui-_1Z_AX2M.js +1 -0
- package/runtime/web/assets/geolocation-BjTKN9_P.js +1 -0
- package/runtime/web/assets/inspect-C7x78PNs.js +2 -0
- package/runtime/web/assets/map-CyBxOSV0.js +1 -0
- package/runtime/web/assets/map-XzoOFt0E.js +2 -0
- package/runtime/web/assets/mapImpl-bxvQ2h1Z.js +823 -0
- package/runtime/web/assets/maplibre-gl-worker-B7LXXUkR.js +8 -0
- package/runtime/web/assets/materials-uri4FvPE.bin +0 -0
- package/runtime/web/assets/materials-world-CQWt8TAm.bin +0 -0
- package/runtime/web/assets/qr-scanner-CK_cYoJR.js +1 -0
- package/runtime/web/assets/uberarchive-DLH-kBhM.bin +0 -0
- package/runtime/web/index.html +36 -0
- package/runtime/web/player.js +21 -0
- package/runtime/web-page/index.html +41 -0
- package/runtime/web-page/page.css +1 -0
- package/runtime/web-page/page.js +31 -0
- package/src/api.ts +1 -302
- package/src/cli/args.ts +37 -0
- package/src/cli/command.ts +113 -0
- package/src/cli/errors.ts +13 -0
- package/src/cli/help.ts +158 -0
- package/src/cli/index.ts +7 -0
- package/src/cli/output.ts +102 -0
- package/src/cli/run.ts +106 -0
- package/src/commands/{appAndroid.ts → app/android.ts} +454 -43
- package/src/commands/{appDesktop.ts → app/desktop.ts} +64 -33
- package/src/commands/{appDesktopMac.ts → app/desktopMac.ts} +34 -27
- package/src/commands/{appIcon.ts → app/icon.ts} +11 -8
- package/src/commands/{app.ts → app/index.ts} +400 -290
- package/src/commands/{appShared.ts → app/shared.ts} +43 -44
- package/src/commands/{appTemplatesAndroid.ts → app/templates/android.ts} +72 -39
- package/src/commands/{appTemplatesGradlew.ts → app/templates/gradlew.ts} +1 -1
- package/src/commands/{appTemplates.ts → app/templates/ios.ts} +2 -2
- package/src/commands/assets.ts +23 -21
- package/src/commands/clone.ts +48 -51
- package/src/commands/compile.ts +76 -89
- package/src/commands/create.ts +46 -47
- package/src/commands/design/comments.ts +361 -0
- package/src/commands/design/context.ts +101 -0
- package/src/commands/design/index.ts +391 -0
- package/src/commands/design/snapshot.ts +134 -0
- package/src/commands/{designTemplates.ts → design/templates.ts} +4 -3
- package/src/commands/desktop.ts +94 -82
- package/src/commands/dev.ts +260 -182
- package/src/commands/diff.ts +39 -51
- package/src/commands/index.ts +64 -0
- package/src/commands/{init.ts → init/index.ts} +195 -191
- package/src/commands/{projectTemplates.ts → init/templates.ts} +64 -21
- package/src/commands/install.ts +80 -67
- package/src/commands/lightmap.ts +543 -252
- package/src/commands/link.ts +87 -94
- package/src/commands/login.ts +25 -23
- package/src/commands/navmesh.ts +169 -139
- package/src/commands/plugin.ts +46 -0
- package/src/commands/pn.ts +201 -244
- package/src/commands/pull.ts +49 -56
- package/src/commands/push.ts +48 -54
- package/src/commands/render.ts +176 -111
- package/src/commands/scene.ts +63 -55
- package/src/commands/serve.ts +58 -0
- package/src/commands/{shaders.ts → shaders/index.ts} +61 -42
- package/src/commands/{shadersNew.ts → shaders/new.ts} +3 -3
- package/src/commands/shared.ts +79 -0
- package/src/commands/status.ts +22 -26
- package/src/commands/test.ts +94 -63
- package/src/commands/thumbs.ts +185 -175
- package/src/commands/update/index.ts +226 -0
- package/src/commands/{types.ts → update/types.ts} +31 -22
- package/src/compile/collect.ts +5 -5
- package/src/compile/collectLocal.ts +3 -3
- package/src/compile/designCompile.ts +4 -4
- package/src/compile/headlessBundle.ts +16 -12
- package/src/compile/nativeStack.ts +1 -1
- package/src/compile/projectCompile.ts +12 -4
- package/src/compile/sceneCompile.ts +14 -26
- package/src/compile/screenEntry.ts +123 -127
- package/src/compile/shaders.ts +3 -3
- package/src/{lecodes-3d-editor.d.ts → declarations/lecodes-3d-editor.d.ts} +4 -0
- package/src/{lecodes-assets.d.ts → declarations/lecodes-assets.d.ts} +1 -1
- package/src/{lecodes-renderer.d.ts → declarations/lecodes-headless.d.ts} +3 -3
- package/src/dev/androidDev.ts +1 -1
- package/src/dev/clientTemplates.ts +18 -14
- package/src/dev/devServer.ts +78 -41
- package/src/dev/iosDev.ts +180 -0
- package/src/dev/webRunner.ts +83 -15
- package/src/{cmgenTool.ts → hosts/cmgenTool.ts} +2 -2
- package/src/{desktopRenderer.ts → hosts/desktopRenderer.ts} +104 -32
- package/src/{desktopScript.ts → hosts/desktopScript.ts} +151 -36
- package/src/{distRoot.ts → hosts/distRoot.ts} +9 -5
- package/src/{matcTool.ts → hosts/matcTool.ts} +2 -2
- package/src/{peerInstall.ts → hosts/peerInstall.ts} +7 -7
- package/src/{peers.ts → hosts/peers.ts} +2 -2
- package/src/{releases.ts → hosts/releases.ts} +1 -1
- package/src/index.ts +35 -480
- package/src/platform/api.ts +302 -0
- package/src/{browserAuth.ts → platform/browserAuth.ts} +1 -1
- package/src/{config.ts → platform/config.ts} +1 -1
- package/src/{serverDiff.ts → platform/serverDiff.ts} +3 -3
- package/src/plugin/contract.ts +438 -0
- package/src/plugin/emitKotlin.ts +357 -0
- package/src/plugin/emitSdk.ts +259 -0
- package/src/plugin/emitSwift.ts +336 -0
- package/src/plugin/emitWeb.ts +383 -0
- package/src/plugin/generate.ts +157 -0
- package/src/plugin/lines.ts +28 -0
- package/src/plugin/lower.ts +231 -0
- package/src/plugin/manifest.ts +192 -0
- package/src/{projectEnv.ts → project/env.ts} +33 -2
- package/src/{ignore.ts → project/ignore.ts} +164 -163
- package/src/{localFiles.ts → project/localFiles.ts} +1 -1
- package/src/{manifest.ts → project/manifest.ts} +1 -1
- package/src/{project.ts → project/materialize.ts} +2 -2
- package/src/project/paths.ts +20 -0
- package/src/{textDiff.ts → project/textDiff.ts} +1 -1
- package/src/{types.ts → project/types.ts} +0 -0
- package/runtime/materials/lightmap-baked-lite.mat +0 -175
- package/runtime/materials/lightmap-baked.mat +0 -176
- package/runtime/scene-harness.json +0 -1
- package/runtime/web/assets/__vite-browser-external-BIHI7g3E.js +0 -1
- package/runtime/web/assets/basis-C64VHDVD.js +0 -1
- package/runtime/web/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
- package/runtime/web/assets/createViewerLite-Ct_PZdof.js +0 -852
- package/runtime/web/assets/draco-BiISTFcR.js +0 -118
- package/runtime/web/assets/draco_decoder-DsQ12WqX.wasm +0 -0
- package/runtime/web/assets/index-BMt7AnC5.js +0 -2
- package/runtime/web/assets/mapViewImpl-B2JcES8l.js +0 -810
- package/runtime/web/assets/maplibre-gl-worker-CJfwIrte.js +0 -8
- package/runtime/web/assets/worker-Caf-yYEI.js +0 -2
- package/runtime/web/embed.html +0 -32
- package/runtime/web/embed.js +0 -171
- package/src/commands/design.ts +0 -846
- package/src/commands/update.ts +0 -211
- package/src/util.ts +0 -146
- /package/src/{lecodes-design.d.ts → declarations/lecodes-design.d.ts} +0 -0
- /package/src/{qrcode-terminal.d.ts → declarations/qrcode-terminal.d.ts} +0 -0
- /package/src/{designMeta.ts → project/designMeta.ts} +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Group } from "../cli"
|
|
2
|
+
import app from "./app"
|
|
3
|
+
import assets from "./assets"
|
|
4
|
+
import clone from "./clone"
|
|
5
|
+
import compile from "./compile"
|
|
6
|
+
import create from "./create"
|
|
7
|
+
import design from "./design"
|
|
8
|
+
import desktop from "./desktop"
|
|
9
|
+
import dev from "./dev"
|
|
10
|
+
import diff from "./diff"
|
|
11
|
+
import init from "./init"
|
|
12
|
+
import install from "./install"
|
|
13
|
+
import lightmap from "./lightmap"
|
|
14
|
+
import link from "./link"
|
|
15
|
+
import login from "./login"
|
|
16
|
+
import navmesh from "./navmesh"
|
|
17
|
+
import plugin from "./plugin"
|
|
18
|
+
import pn from "./pn"
|
|
19
|
+
import pull from "./pull"
|
|
20
|
+
import push from "./push"
|
|
21
|
+
import render from "./render"
|
|
22
|
+
import scene from "./scene"
|
|
23
|
+
import serve from "./serve"
|
|
24
|
+
import shaders from "./shaders"
|
|
25
|
+
import status from "./status"
|
|
26
|
+
import test from "./test"
|
|
27
|
+
import thumbs from "./thumbs"
|
|
28
|
+
import update from "./update"
|
|
29
|
+
|
|
30
|
+
/*
|
|
31
|
+
* THE command list. Adding a command = one file with a `defineCommand` default export + one line
|
|
32
|
+
* here, in the group it belongs to (CONTRIBUTING.md). Order within a group is the help's order.
|
|
33
|
+
*/
|
|
34
|
+
export const commandGroups = (opts: { install: boolean }): Group[] => [
|
|
35
|
+
{ title: "Project", commands: [init, create, clone, link, status, diff, pull, push, login] },
|
|
36
|
+
{ title: "Run & test", commands: [
|
|
37
|
+
dev,
|
|
38
|
+
compile,
|
|
39
|
+
render,
|
|
40
|
+
test,
|
|
41
|
+
desktop,
|
|
42
|
+
serve,
|
|
43
|
+
] },
|
|
44
|
+
{ title: "Editors", commands: [
|
|
45
|
+
design,
|
|
46
|
+
scene,
|
|
47
|
+
thumbs,
|
|
48
|
+
] },
|
|
49
|
+
{ title: "Content", commands: [
|
|
50
|
+
assets,
|
|
51
|
+
shaders,
|
|
52
|
+
lightmap,
|
|
53
|
+
navmesh,
|
|
54
|
+
] },
|
|
55
|
+
{ title: "Native apps", commands: [
|
|
56
|
+
app,
|
|
57
|
+
plugin,
|
|
58
|
+
pn,
|
|
59
|
+
] },
|
|
60
|
+
{ title: "Maintenance", commands: [
|
|
61
|
+
update,
|
|
62
|
+
{ ...install, hidden: !opts.install },
|
|
63
|
+
] },
|
|
64
|
+
]
|
|
@@ -1,191 +1,195 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs"
|
|
2
|
-
import { basename, dirname, join, resolve } from "node:path"
|
|
3
|
-
import { LECODES_DIR } from "
|
|
4
|
-
import { materializeTypesLocal, writeTsconfig } from "
|
|
5
|
-
import { DEFAULT_IGNORE_FILE, ensureGitignore } from "
|
|
6
|
-
import { appClaudeMd, CLAUDE_SETTINGS, STARTER_FLOW } from "./
|
|
7
|
-
import { c,
|
|
8
|
-
|
|
9
|
-
/*
|
|
10
|
-
* `lecodes init [dir]` — scaffold a brand-new LOCAL project: no account, no login, no server.
|
|
11
|
-
*
|
|
12
|
-
* Writes a starter entry, a CLAUDE.md (the headless render/test workflow for AI assistants) with
|
|
13
|
-
* a passing starter flow test, the SDK's import-free type surface + a tsconfig (so VS Code gives
|
|
14
|
-
* full IntelliSense offline), and a `.lecodesignore`. The result runs immediately with `lecodes dev`
|
|
15
|
-
* (hot reload on a device) and compiles with `lecodes compile -o app.js` (a self-contained bundle
|
|
16
|
-
* for LeCodes Desktop). It's the offline counterpart to `lecodes create`, which makes a project on
|
|
17
|
-
* the server; a local project needs no manifest — `dev`/`compile` compile straight from the tree.
|
|
18
|
-
*
|
|
19
|
-
* Idempotent: existing files are kept (never clobbered), so it's safe to re-run or run in a folder
|
|
20
|
-
* that already has content. Only the generated tsconfig + `.lecodes/types/` are rewritten each run.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
const STARTER = `// Welcome to LeCodes — the whole SDK is available as globals, no imports needed.
|
|
24
|
-
// Run it live on a device with \`lecodes dev\`, or bundle it with \`lecodes compile -o app.js\`.
|
|
25
|
-
|
|
26
|
-
let count = 0
|
|
27
|
-
|
|
28
|
-
const counter = UIText({ color: "#9fb0d0", fontSize: 22, marginTop: 18 }
|
|
29
|
-
|
|
30
|
-
const button = UIButton(
|
|
31
|
-
{
|
|
32
|
-
backgroundColor: "#4f7cff",
|
|
33
|
-
borderRadius: 14,
|
|
34
|
-
paddingVertical: 16,
|
|
35
|
-
paddingHorizontal: 40,
|
|
36
|
-
marginTop: 24,
|
|
37
|
-
alignItems: "center",
|
|
38
|
-
justifyContent: "center",
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
)
|
|
42
|
-
.
|
|
43
|
-
|
|
44
|
-
count
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
\`\`\`
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
\`\`\`
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
\`\`\`
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
- \`
|
|
117
|
-
|
|
118
|
-
- \`
|
|
119
|
-
\`
|
|
120
|
-
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
write("
|
|
158
|
-
|
|
159
|
-
write("
|
|
160
|
-
//
|
|
161
|
-
write(".
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
log(` ${c.bold(cd + "lecodes dev")}
|
|
188
|
-
log(` ${c.bold(cd + "lecodes test")} run the flow tests headless (
|
|
189
|
-
log(` ${c.bold(cd + "lecodes
|
|
190
|
-
}
|
|
191
|
-
}
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs"
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path"
|
|
3
|
+
import { LECODES_DIR } from "../../project/manifest"
|
|
4
|
+
import { materializeTypesLocal, writeTsconfig } from "../../project/types"
|
|
5
|
+
import { DEFAULT_IGNORE_FILE, ensureGitignore } from "../../project/ignore"
|
|
6
|
+
import { appClaudeMd, CLAUDE_SETTINGS, STARTER_FLOW } from "./templates"
|
|
7
|
+
import { bool, c, defineCommand, log, note, success, warnErr } from "../../cli"
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* `lecodes init [dir]` — scaffold a brand-new LOCAL project: no account, no login, no server.
|
|
11
|
+
*
|
|
12
|
+
* Writes a starter entry, a CLAUDE.md (the headless render/test workflow for AI assistants) with
|
|
13
|
+
* a passing starter flow test, the SDK's import-free type surface + a tsconfig (so VS Code gives
|
|
14
|
+
* full IntelliSense offline), and a `.lecodesignore`. The result runs immediately with `lecodes dev`
|
|
15
|
+
* (hot reload on a device) and compiles with `lecodes compile -o app.js` (a self-contained bundle
|
|
16
|
+
* for LeCodes Desktop). It's the offline counterpart to `lecodes create`, which makes a project on
|
|
17
|
+
* the server; a local project needs no manifest — `dev`/`compile` compile straight from the tree.
|
|
18
|
+
*
|
|
19
|
+
* Idempotent: existing files are kept (never clobbered), so it's safe to re-run or run in a folder
|
|
20
|
+
* that already has content. Only the generated tsconfig + `.lecodes/types/` are rewritten each run.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const STARTER = `// Welcome to LeCodes — the whole SDK is available as globals, no imports needed.
|
|
24
|
+
// Run it live on a device with \`lecodes dev\`, or bundle it with \`lecodes compile -o app.js\`.
|
|
25
|
+
|
|
26
|
+
let count = 0
|
|
27
|
+
|
|
28
|
+
const counter = UIText("Taps: 0").style({ color: "#9fb0d0", fontSize: 22, marginTop: 18 })
|
|
29
|
+
|
|
30
|
+
const button = UIButton(UIText("Tap me").style({ color: "#ffffff", fontSize: 22 }))
|
|
31
|
+
.style({
|
|
32
|
+
backgroundColor: "#4f7cff",
|
|
33
|
+
borderRadius: 14,
|
|
34
|
+
paddingVertical: 16,
|
|
35
|
+
paddingHorizontal: 40,
|
|
36
|
+
marginTop: 24,
|
|
37
|
+
alignItems: "center",
|
|
38
|
+
justifyContent: "center",
|
|
39
|
+
$hovered: { backgroundColor: "#6b91ff" }, // the mouse over it (desktop); $pressed is the press
|
|
40
|
+
})
|
|
41
|
+
.named("tap") // names make elements targetable in tests/*.flow.json (see CLAUDE.md)
|
|
42
|
+
.onClick(() => {
|
|
43
|
+
count++
|
|
44
|
+
counter.text = "Taps: " + count
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const screen = UIScreen(
|
|
48
|
+
UIColumn(UIText("Hello, LeCodes").style({ color: "#ffffff", fontSize: 32 }), button, counter)
|
|
49
|
+
.style({ backgroundColor: "#2b3550", borderRadius: 24, padding: 40, alignItems: "center" }),
|
|
50
|
+
)
|
|
51
|
+
.style({ backgroundColor: "#1e2230", alignItems: "center", justifyContent: "center", padding: 40 })
|
|
52
|
+
.named("home") // the screen root's name is what tests' \`expect: { screen: … }\` reads
|
|
53
|
+
|
|
54
|
+
screen.open()
|
|
55
|
+
`
|
|
56
|
+
|
|
57
|
+
/** `--desktop`: declare the desktop target up front. Window sizes are logical units (the host
|
|
58
|
+
* scales for HiDPI); `dev --desktop` / `desktop run` / `render --desktop` all read this, and a
|
|
59
|
+
* desktop-only app.json makes headless render/test default to the window size too. */
|
|
60
|
+
const appJsonDesktop = (name: string) => JSON.stringify({
|
|
61
|
+
name,
|
|
62
|
+
desktop: {
|
|
63
|
+
window: { width: 1280, height: 800, minWidth: 640, minHeight: 480 },
|
|
64
|
+
},
|
|
65
|
+
}, null, 2) + "\n"
|
|
66
|
+
|
|
67
|
+
const readmeTemplate = (name: string) => `# ${name}
|
|
68
|
+
|
|
69
|
+
A LeCodes app. The whole SDK is available as globals (\`UIScreen\`, \`UIText\`, \`Scene2D\`, …) —
|
|
70
|
+
no imports needed. \`main.ts\` is the entry point.
|
|
71
|
+
|
|
72
|
+
## Run it
|
|
73
|
+
|
|
74
|
+
\`\`\`
|
|
75
|
+
lecodes dev # live on your phone: scan the QR with the LeCodes app, hot reload
|
|
76
|
+
lecodes compile -o app.js # bundle to a single self-contained JS file
|
|
77
|
+
\`\`\`
|
|
78
|
+
|
|
79
|
+
## Test it (no device needed)
|
|
80
|
+
|
|
81
|
+
\`\`\`
|
|
82
|
+
lecodes render --png shot.png # headless screenshot of the running app
|
|
83
|
+
lecodes test # run tests/*.flow.json — the starter ships with a passing one
|
|
84
|
+
\`\`\`
|
|
85
|
+
|
|
86
|
+
Scenario format + conventions: see [CLAUDE.md](./CLAUDE.md) (written for AI assistants, useful
|
|
87
|
+
for humans too).
|
|
88
|
+
|
|
89
|
+
## Put it on the platform
|
|
90
|
+
|
|
91
|
+
This project is local-only. To give it a home on le.codes (web editor, sharing, OTA updates):
|
|
92
|
+
|
|
93
|
+
\`\`\`
|
|
94
|
+
lecodes login # once per machine
|
|
95
|
+
lecodes link # create a project from this folder and connect it
|
|
96
|
+
lecodes push -m "initial" # upload it as the first checkpoint (--publish to go live)
|
|
97
|
+
\`\`\`
|
|
98
|
+
|
|
99
|
+
Already made the project in the web editor? \`lecodes link --project <uuid>\` adopts it — it
|
|
100
|
+
shows what your next push would change there before connecting anything.
|
|
101
|
+
|
|
102
|
+
## Go native
|
|
103
|
+
|
|
104
|
+
\`\`\`
|
|
105
|
+
lecodes app init ios # generate a native Xcode shell in ios/
|
|
106
|
+
lecodes app sync # embed the compiled bundle + plugins into it
|
|
107
|
+
lecodes app open # open it in Xcode, build & run
|
|
108
|
+
\`\`\`
|
|
109
|
+
|
|
110
|
+
## Layout
|
|
111
|
+
|
|
112
|
+
- \`main.ts\` — the app's entry point. Add more .ts files freely and import them from
|
|
113
|
+
\`main.ts\`; a file named \`main.ts\` is always the entry.
|
|
114
|
+
- \`tests/\` — headless flow tests (\`lecodes test\`); failing steps dump the frame to
|
|
115
|
+
\`tests/.artifacts/\`
|
|
116
|
+
- \`CLAUDE.md\` — guidance for AI assistants (the headless feedback loop + testability conventions)
|
|
117
|
+
- \`assets/\` — images/fonts the app references (created on demand)
|
|
118
|
+
- \`tsconfig.json\` + \`.lecodes/types/\` — generated IDE types (rewritten by the CLI; don't edit).
|
|
119
|
+
They come from the SDK bundled in the CLI; \`lecodes update\` refreshes them from the platform.
|
|
120
|
+
- \`.lecodes/\` is gitignored — per-checkout state (those types + \`manifest.json\`, this machine's
|
|
121
|
+
sync cursor once linked). After a fresh git clone: \`lecodes update\`, then
|
|
122
|
+
\`lecodes link --project <uuid>\` to re-attach.
|
|
123
|
+
- \`.lecodesignore\` — files a future \`lecodes push\` would skip
|
|
124
|
+
|
|
125
|
+
Full command list: \`lecodes --help\`.
|
|
126
|
+
`
|
|
127
|
+
|
|
128
|
+
export default defineCommand({
|
|
129
|
+
name: "init",
|
|
130
|
+
summary: "Scaffold a new LOCAL project (no login, no server)",
|
|
131
|
+
usage: "[dir]",
|
|
132
|
+
description: "Writes a starter main.ts, a CLAUDE.md + a passing flow test (the headless render / test loop), the IDE types + tsconfig, and a .lecodesignore. Run it with `lecodes dev` or `lecodes compile`. Idempotent: existing files are kept.",
|
|
133
|
+
flags: { desktop: bool("also write an app.json declaring the desktop target (window size; `dev --desktop`, `desktop run` and headless render / test pick it up)") },
|
|
134
|
+
examples: ["lecodes init myapp", "lecodes init --desktop"],
|
|
135
|
+
run: ({ args, flags }) => init(args[0], flags.desktop),
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
const init = (dir: string | undefined, forDesktop: boolean): void => {
|
|
139
|
+
const dirArg = (dir ?? ".").replace(/[/\\]+$/, "") || "."
|
|
140
|
+
const root = resolve(dirArg)
|
|
141
|
+
const name = basename(root)
|
|
142
|
+
|
|
143
|
+
mkdirSync(root, { recursive: true })
|
|
144
|
+
|
|
145
|
+
let created = 0
|
|
146
|
+
const write = (rel: string, content: string) => {
|
|
147
|
+
const abs = join(root, rel)
|
|
148
|
+
if (existsSync(abs)) { note(`kept ${rel}`); return }
|
|
149
|
+
mkdirSync(dirname(abs), { recursive: true })
|
|
150
|
+
writeFileSync(abs, content)
|
|
151
|
+
log(`${c.green("created")} ${rel}`)
|
|
152
|
+
created++
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Starter entry — a root file named main.ts is always the detected entrypoint.
|
|
156
|
+
write("main.ts", STARTER)
|
|
157
|
+
write("README.md", readmeTemplate(name))
|
|
158
|
+
// --desktop: an app.json declaring the desktop target (window config in logical units).
|
|
159
|
+
if (forDesktop) write("app.json", appJsonDesktop(name))
|
|
160
|
+
// AI-assistant guidance + a passing flow test: the headless loop is discoverable from day one.
|
|
161
|
+
write("CLAUDE.md", appClaudeMd(name))
|
|
162
|
+
write(".claude/settings.json", CLAUDE_SETTINGS)
|
|
163
|
+
write("tests/counter.flow.json", STARTER_FLOW)
|
|
164
|
+
// Keep local render/preview outputs (screenshot.png, shots/) out of any future push.
|
|
165
|
+
write(".lecodesignore", DEFAULT_IGNORE_FILE)
|
|
166
|
+
// .lecodes/ is per-checkout state (generated types + the push cursor) — keep it out of git.
|
|
167
|
+
const gi = ensureGitignore(root)
|
|
168
|
+
if (gi === "created") { log(`${c.green("created")} .gitignore`); created++ }
|
|
169
|
+
else if (gi === "updated") log(`${c.green("updated")} .gitignore ${c.dim("(+ .lecodes/)")}`)
|
|
170
|
+
else note("kept .gitignore")
|
|
171
|
+
|
|
172
|
+
// IDE types: the SDK's import-free global surface + a tsconfig, so VS Code resolves UIScreen,
|
|
173
|
+
// Scene2D, setLoop, … with no server. Regenerated every run to pick up CLI upgrades.
|
|
174
|
+
const wroteTypes = materializeTypesLocal(root)
|
|
175
|
+
writeTsconfig(root)
|
|
176
|
+
log(`${c.green("written")} tsconfig.json${wroteTypes ? ` + ${LECODES_DIR}/types/` : ""} (IDE types)`)
|
|
177
|
+
if (!wroteTypes) {
|
|
178
|
+
warnErr("Bundled SDK types not found — tsconfig written, but globals may show unresolved until the CLI is rebuilt with vendored types.")
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
log("")
|
|
182
|
+
success(created
|
|
183
|
+
? `Local project ${c.bold(name)} ready${dirArg === "." ? "" : ` in ${c.bold(dirArg)}/`}.`
|
|
184
|
+
: `Project ${c.bold(name)} already initialized.`)
|
|
185
|
+
const cd = dirArg === "." ? "" : `cd ${dirArg} && `
|
|
186
|
+
if (forDesktop) {
|
|
187
|
+
log(` ${c.bold(cd + "lecodes dev --desktop")} run it in a desktop window with hot reload`)
|
|
188
|
+
log(` ${c.bold(cd + "lecodes test")} run the flow tests headless (desktop-sized)`)
|
|
189
|
+
log(` ${c.bold(cd + "lecodes desktop run")} open the compiled app in the desktop host`)
|
|
190
|
+
} else {
|
|
191
|
+
log(` ${c.bold(cd + "lecodes dev")} run it on a device with hot reload`)
|
|
192
|
+
log(` ${c.bold(cd + "lecodes test")} run the flow tests headless (no device)`)
|
|
193
|
+
log(` ${c.bold(cd + "lecodes compile -o app.js")} bundle it for LeCodes Desktop`)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* has its own set — designTemplates.ts). CLAUDE.md is the AI-assistant counterpart of README.md:
|
|
3
3
|
* it teaches the headless feedback loop (render / --script / test) and the conventions that make
|
|
4
4
|
* an app testable, so an assistant opening the project drives the real app instead of guessing.
|
|
5
|
-
* Facts here mirror lecodes-
|
|
5
|
+
* Facts here mirror lecodes-headless's scenario engine — update both when the format changes. */
|
|
6
6
|
|
|
7
7
|
export const appClaudeMd = (name: string) => `# ${name} — LeCodes app
|
|
8
8
|
|
|
@@ -30,17 +30,22 @@ lecodes render --desktop --view iso --png iso.png # 3D: inspection camera fitt
|
|
|
30
30
|
# scene (iso|top|front|side|back [--fov N]) — or --camera x,y,z
|
|
31
31
|
# --look-at x,y,z; the app's camera is untouched. In scripts:
|
|
32
32
|
# { "view": "iso", "screenshot": "iso.png" } then { "view": "off" }
|
|
33
|
-
lecodes render --desktop --script <file> # the same scenario on the NATIVE host (
|
|
34
|
-
#
|
|
35
|
-
#
|
|
33
|
+
lecodes render --desktop --script <file> # the same scenario on the NATIVE host (real GPU, the
|
|
34
|
+
# real hit-test): the same steps, targets and expects as
|
|
35
|
+
# headless (only expect.audio / the json output are headless-only)
|
|
36
|
+
lecodes render --sim --script <file> # the WINDOWLESS native host (lecodes-server): the real
|
|
37
|
+
# engines, no GPU, no pixels, no UI hit-test — expect.log /
|
|
38
|
+
# expect.paint only; runs on CI and beside an open desktop window
|
|
36
39
|
lecodes test # all tests/*.flow.json + design-derived flows; run before "done"
|
|
37
|
-
lecodes test --desktop # the file flows on the NATIVE host (
|
|
38
|
-
|
|
40
|
+
lecodes test --desktop # the file flows on the NATIVE host (design flows stay headless)
|
|
41
|
+
lecodes test --sim # the windowless twin: coordinates + expect.log / expect.paint only
|
|
39
42
|
lecodes dev --android --time 12 --png phone.png # the REAL phone over adb (USB): launches the app on
|
|
40
43
|
# the dev url via adb reverse, streams device logs as [device:log],
|
|
41
44
|
# screenshots after 12 s, force-stops, exit 1 on a crash. --serial
|
|
42
45
|
# <id> with several devices, --apk <file> to install a build first.
|
|
43
46
|
# Without --time it runs until Ctrl+C (which stops the app).
|
|
47
|
+
lecodes dev --ios --time 12 --png sim.png # the same on a booted iOS simulator or a plugged-in iPhone
|
|
48
|
+
# (Mac + Xcode): --udid <id|name>, --app <LeCodesApp.app> to install.
|
|
44
49
|
\`\`\`
|
|
45
50
|
|
|
46
51
|
Iterate with \`--script\` scenarios and keep the useful ones as \`tests/<name>.flow.json\` — they
|
|
@@ -49,7 +54,8 @@ are the regression suite. A failing step dumps the failing frame (JSON + PNG) un
|
|
|
49
54
|
|
|
50
55
|
## Scenario format (tests/*.flow.json and --script files)
|
|
51
56
|
|
|
52
|
-
One action per step (\`tap\`, \`type\`+\`text\`, \`fill\`, \`scroll\` [+\`by\`], \`wait\`,
|
|
57
|
+
One action per step (\`tap\`, \`type\`+\`text\`, \`fill\`, \`scroll\` [+\`by\`], \`wait\`, \`hover\` (the
|
|
58
|
+
mouse over a node: \`$hovered\` + \`onMouseEnter\`; hovering elsewhere ends it), and for
|
|
53
59
|
games: \`frames\` (advance N fixed 1/60 steps, no sleeping), \`drag\`+\`to\` [+\`ms\`], \`hold\`+\`ms\`,
|
|
54
60
|
\`key\`+\`ms\` / \`keyDown\` / \`keyUp\` (KeyboardEvent.code: "ArrowRight", "KeyW", "Space" — mouse
|
|
55
61
|
buttons are keys too: "MouseLeft"/"MouseRight"), \`look: [dx, dy]\` [+\`ms\`] (mouse motion for
|
|
@@ -57,12 +63,29 @@ buttons are keys too: "MouseLeft"/"MouseRight"), \`look: [dx, dy]\` [+\`ms\`] (m
|
|
|
57
63
|
\`stick: { left: [x, y], right: [x, y], triggers: [l, r] }\` (values persist; \`index\` = pad),
|
|
58
64
|
\`gamepad: "connect" | "disconnect"\`, pad buttons as keys: \`keyDown: "GamepadSouth"\`), then an
|
|
59
65
|
optional \`expect\` (\`screen\`, \`node\`/\`notNode\`, \`text\`/\`notText\`,
|
|
60
|
-
\`value: { target, equals }\`, \`log\` — a \`console.log\` line contains it
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
\`value: { target, equals }\`, \`log\` — a \`console.log\` line contains it,
|
|
67
|
+
\`paint: { "<name>": { color: "#rrggbbaa", fontSize: 18, present: [...], absent: [...] } }\` — the
|
|
68
|
+
resolved style of a named node, \`audio: { played, playing }\`), then outputs
|
|
69
|
+
(\`screenshot\` [+\`clip\`: a node name or \`{ x, y, width, height }\`], \`json\`, \`dump\` — the
|
|
70
|
+
runtime's tree with every node's box + resolved paint, identical on every host). Targets resolve
|
|
71
|
+
by \`name\` → exact visible text → substring → input placeholder; a tap/drag on empty space reaches
|
|
63
72
|
the 2D scene (sprite \`click\`/\`touchstart\`, \`ev.track()\`). Envelope keys: \`name\`,
|
|
64
73
|
\`width\`/\`height\`, \`settle\`, \`fixtures\`, \`steps\`.
|
|
65
74
|
|
|
75
|
+
Three hosts run the same file, at a fixed 16 ms clock on all of them:
|
|
76
|
+
|
|
77
|
+
| host | flag | what it is | targets | expects |
|
|
78
|
+
|---|---|---|---|---|
|
|
79
|
+
| headless | (none) | Bun: the runtime + UI renderer + a recording 3D core (no GPU) | names or \`{ x, y }\` | all |
|
|
80
|
+
| sim | \`--sim\` | the windowless native server: real engines, no pixels, no UI | \`{ x, y }\`, straight to the scene | \`log\`, \`paint\` |
|
|
81
|
+
| desktop | \`--desktop\` | the hidden desktop window: pixels, GL materials, the real hit-test | names or \`{ x, y }\` | all but \`audio\` |
|
|
82
|
+
|
|
83
|
+
One flow file, the same verdict on headless and desktop: names resolve the same way (\`name\` →
|
|
84
|
+
exact text → substring → placeholder), every step runs (\`hover\`, \`type\`, \`fill\`, \`scroll\`,
|
|
85
|
+
\`clip\` included) and the semantic expects read the same tree. Only \`expect.audio\` and the
|
|
86
|
+
\`json\` output are the headless renderer's. On the desktop \`type\` taps the input and types into
|
|
87
|
+
it (appends); the sim has no UI, so it takes coordinates and \`expect.log\` / \`expect.paint\` only.
|
|
88
|
+
|
|
66
89
|
\`\`\`json
|
|
67
90
|
{ "steps": [
|
|
68
91
|
{ "expect": { "screen": "home", "text": "Taps: 0" } },
|
|
@@ -72,19 +95,21 @@ the 2D scene (sprite \`click\`/\`touchstart\`, \`ev.track()\`). Envelope keys: \
|
|
|
72
95
|
] }
|
|
73
96
|
\`\`\`
|
|
74
97
|
|
|
75
|
-
Game time is deterministic
|
|
76
|
-
advance in fixed 1/60 steps driven by \`wait\`/\`frames\`/\`drag\`/\`key\` —
|
|
77
|
-
so a flow that passes once passes every time. \`Input.key(code)\` reads the
|
|
78
|
-
\`Input.on('keydown' | 'keyup', e => e.code)\` fires for each
|
|
79
|
-
(events land before the next loop tick); \`stick\` values are what
|
|
80
|
-
Headless draws UI + the 2D scene (sprites, tilemaps, Box2D); 3D is
|
|
81
|
-
physics (bodies fall, characters collide, triggers fire, ragdolls
|
|
82
|
-
under it falls, like on device) — use \`--desktop\` for 3D pixels
|
|
83
|
-
|
|
98
|
+
Game time is deterministic on every host: the Aspect / System phases (\`updateBefore\` / \`update\`),
|
|
99
|
+
2D physics and animation advance in fixed 1/60 steps driven by \`wait\`/\`frames\`/\`drag\`/\`key\` —
|
|
100
|
+
never by a wall clock — so a flow that passes once passes every time. \`Input.key(code)\` reads the
|
|
101
|
+
keys a flow holds and \`Input.on('keydown' | 'keyup', e => e.code)\` fires for each
|
|
102
|
+
\`key\`/\`keyDown\`/\`keyUp\` step (events land before the next loop tick); \`stick\` values are what
|
|
103
|
+
\`Input.gamepad(0).axis()\` reads. Headless draws UI + the 2D scene (sprites, tilemaps, Box2D); 3D is
|
|
104
|
+
captured as JSON with REAL Jolt physics (bodies fall, characters collide, triggers fire, ragdolls
|
|
105
|
+
drop — a character with no ground under it falls, like on device) — use \`--desktop\` for 3D pixels,
|
|
106
|
+
\`--sim\` for engine behaviour on a box without a GPU. \`console.log\` + \`expect.log\` is the
|
|
107
|
+
assertion channel for game state that has no UI.
|
|
84
108
|
|
|
85
109
|
## Make the app testable
|
|
86
110
|
|
|
87
|
-
- Give interactive elements a \`name\`: \`UIButton(...).
|
|
111
|
+
- Give interactive elements a \`name\`: \`UIButton(...).named("login")\` (or \`el.name = "login"\`;
|
|
112
|
+
it is not a style key).
|
|
88
113
|
- Name every screen's ROOT node with the screen's id — \`expect.screen\` reads it.
|
|
89
114
|
- If the project has a \`design/\` folder, keep the design's screen ids and activator \`name\`s
|
|
90
115
|
in the app code: \`lecodes test\` then checks the built app against the designed navigation
|
|
@@ -94,6 +119,16 @@ channel for game state that has no UI.
|
|
|
94
119
|
match wins). \`lecodes test\` auto-loads it for design-derived flows; a scenario file opts in
|
|
95
120
|
with \`"fixtures": "fixtures.json"\` in its envelope.
|
|
96
121
|
- \`fill\` fills every empty input — use it before tapping a disabled-until-filled submit.
|
|
122
|
+
- Visual state is a \`$class\` in the style (\`$pressed\`, \`$hovered\`, \`$focused\`, your own
|
|
123
|
+
\`$selected\` set with \`el.class.selected = true\` or bound with \`.class({ selected: () => … })\`);
|
|
124
|
+
the interaction states read back as \`button.isPressed\` / \`button.isHovered\`.
|
|
125
|
+
A flow asserts the EFFECT through \`expect.paint\` on a named node (the resolved \`color\` /
|
|
126
|
+
\`backgroundColor\` from the dump), never the class name — that works headless and native alike.
|
|
127
|
+
- Hover is the mouse's: \`{ "hover": "login" }\`, a \`frames\` step, then \`expect.paint\` on the node's
|
|
128
|
+
resolved colour (\`$hovered\`) and \`expect.log\` from \`onMouseEnter\`; hover somewhere else to see
|
|
129
|
+
it end. Works headless and on \`--desktop\`; \`mouseTo: [x, y]\` is the same cursor by coordinates.
|
|
130
|
+
\`$focused\` is the desktop renderer's; a keyboard-driven highlight (your own \`$focus\` class moved
|
|
131
|
+
by arrow keys) is assertable everywhere: \`key\` steps, then \`expect.paint\` on the highlighted node.
|
|
97
132
|
|
|
98
133
|
## 3D scenes
|
|
99
134
|
|
|
@@ -108,7 +143,15 @@ channel for game state that has no UI.
|
|
|
108
143
|
\`lecodes update\` if it's missing, never commit \`.lecodes/\`). For semantics and examples,
|
|
109
144
|
fetch the docs — they are served as raw markdown for AI tools:
|
|
110
145
|
|
|
111
|
-
- https://le.codes/llms.txt — the docs map; fetch it first to find the right page
|
|
146
|
+
- https://le.codes/llms.txt — the docs map; fetch it first to find the right page. It opens with the
|
|
147
|
+
CLI: every \`lecodes\` command with what it is for, the agent page (running without a terminal,
|
|
148
|
+
\`--entry\`, login) and the editors that expose MCP tools — \`lecodes design\` (the screen board)
|
|
149
|
+
and \`lecodes scene\` (the 3D scene editor for \`.scene.ts\` files). \`lecodes <command> --help\`
|
|
150
|
+
is the authority on flags.
|
|
151
|
+
**Building a whole thing — a game, enemies, a crowd of characters, a native plugin? llms.txt opens
|
|
152
|
+
with a COOKBOOK: read the matching recipe BEFORE you design the structure.** It has the file
|
|
153
|
+
layout, a skeleton per part, how to verify, and the traps already paid for (start with
|
|
154
|
+
"Project architecture for a game" for any game bigger than one file).
|
|
112
155
|
- https://le.codes/en/docs/api/<area>/<page>.md — one reference page as raw markdown
|
|
113
156
|
(e.g. \`ui/styling.md\`, \`2d/scene2d.md\`, \`3d/scene.md\`, \`runtime/storage.md\`)
|
|
114
157
|
- https://le.codes/en/docs/guide/<page>.md — guide pages (project anatomy, CLI, tracks)
|