lecodes-cli 0.20.1 → 1.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 +5920 -5022
- package/package.json +9 -7
- package/runtime/materials/decal-relief.mat +12 -1
- package/runtime/materials/decal.mat +17 -3
- package/runtime/materials/lightmap-baked-lite.mat +6 -1
- package/runtime/materials/lightmap-baked.mat +6 -1
- package/runtime/materials/lightmap.mat +3 -0
- package/runtime/materials/particles-quad.mat +17 -9
- package/runtime/materials/particles.mat +17 -9
- package/runtime/scene-harness.json +1 -1
- package/runtime/sdk-types.json +1 -1
- package/runtime/web/assets/createViewerLite-DHqTFhWB.js +866 -0
- package/runtime/web/assets/{index-BTB5KV9x.js → index-Q06c6oHx.js} +1 -1
- package/runtime/web/embed.js +2 -2
- 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 +133 -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} +451 -42
- package/src/commands/{appDesktop.ts → app/desktop.ts} +63 -32
- package/src/commands/{appDesktopMac.ts → app/desktopMac.ts} +24 -18
- package/src/commands/{appIcon.ts → app/icon.ts} +2 -2
- package/src/commands/{app.ts → app/index.ts} +397 -289
- package/src/commands/{appShared.ts → app/shared.ts} +30 -39
- package/src/commands/{appTemplatesAndroid.ts → app/templates/android.ts} +62 -28
- package/src/commands/{appTemplatesGradlew.ts → app/templates/gradlew.ts} +1 -1
- package/src/commands/{appTemplates.ts → app/templates/ios.ts} +1 -1
- package/src/commands/assets.ts +23 -21
- package/src/commands/clone.ts +48 -51
- package/src/commands/compile.ts +62 -88
- 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 +90 -82
- package/src/commands/dev.ts +201 -176
- package/src/commands/diff.ts +39 -51
- package/src/commands/index.ts +60 -0
- package/src/commands/{init.ts → init/index.ts} +199 -191
- package/src/commands/install.ts +80 -67
- package/src/commands/lightmap.ts +203 -171
- package/src/commands/link.ts +87 -94
- package/src/commands/login.ts +25 -23
- package/src/commands/navmesh.ts +168 -138
- 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 +158 -99
- package/src/commands/scene.ts +58 -55
- package/src/commands/{shaders.ts → shaders/index.ts} +61 -42
- package/src/commands/{shadersNew.ts → shaders/new.ts} +2 -2
- package/src/commands/shared.ts +79 -0
- package/src/commands/status.ts +22 -26
- package/src/commands/test.ts +398 -371
- 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 +3 -3
- package/src/compile/headlessBundle.ts +133 -129
- package/src/compile/projectCompile.ts +2 -2
- package/src/compile/sceneCompile.ts +10 -8
- package/src/compile/screenEntry.ts +123 -127
- package/src/compile/shaders.ts +3 -3
- package/src/dev/androidDev.ts +1 -1
- package/src/dev/devServer.ts +3 -3
- package/src/dev/webRunner.ts +1 -1
- package/src/{cmgenTool.ts → hosts/cmgenTool.ts} +1 -1
- package/src/{desktopRenderer.ts → hosts/desktopRenderer.ts} +6 -5
- package/src/{desktopScript.ts → hosts/desktopScript.ts} +1 -1
- package/src/{distRoot.ts → hosts/distRoot.ts} +9 -5
- package/src/{matcTool.ts → hosts/matcTool.ts} +1 -1
- package/src/{peerInstall.ts → hosts/peerInstall.ts} +2 -2
- package/src/{releases.ts → hosts/releases.ts} +1 -1
- package/src/index.ts +32 -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/{projectEnv.ts → project/env.ts} +33 -2
- 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/web/assets/createViewerLite-C_bkKXLg.js +0 -852
- package/src/commands/design.ts +0 -846
- package/src/commands/update.ts +0 -211
- package/src/util.ts +0 -146
- /package/src/commands/{projectTemplates.ts → init/templates.ts} +0 -0
- /package/src/{lecodes-3d-editor.d.ts → declarations/lecodes-3d-editor.d.ts} +0 -0
- /package/src/{lecodes-assets.d.ts → declarations/lecodes-assets.d.ts} +0 -0
- /package/src/{lecodes-design.d.ts → declarations/lecodes-design.d.ts} +0 -0
- /package/src/{lecodes-renderer.d.ts → declarations/lecodes-renderer.d.ts} +0 -0
- /package/src/{qrcode-terminal.d.ts → declarations/qrcode-terminal.d.ts} +0 -0
- /package/src/{peers.ts → hosts/peers.ts} +0 -0
- /package/src/{designMeta.ts → project/designMeta.ts} +0 -0
- /package/src/{ignore.ts → project/ignore.ts} +0 -0
package/src/commands/scene.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs"
|
|
2
|
-
import { basename, dirname, join, resolve } from "node:path"
|
|
3
|
-
import {
|
|
2
|
+
import { basename, dirname, join, relative, resolve } from "node:path"
|
|
3
|
+
import { CliError, bool, c, defineCommand, log, note, num, warnErr } from "../cli"
|
|
4
4
|
import { compileSceneEditor } from "../compile/sceneCompile"
|
|
5
5
|
import { localShaderArtifact } from "../compile/shaders"
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { loadPeer } from "../hosts/peerInstall"
|
|
7
|
+
import { openBrowser } from "../platform/browserAuth"
|
|
8
|
+
import { findProjectRoot, readManifest, type Manifest } from "../project/manifest"
|
|
9
9
|
|
|
10
10
|
/*
|
|
11
|
-
* `lecodes scene [path]` — the 3D scene editor (lecodes-tools/packages/lecodes-3d-editor) hosted on a
|
|
12
|
-
*
|
|
11
|
+
* `lecodes scene [path]` — the 3D scene editor (lecodes-tools/packages/lecodes-3d-editor) hosted on a
|
|
12
|
+
* local project, the way `lecodes design` hosts the design board. Starts the editor's node:http server,
|
|
13
13
|
* injects the CLI's compile closure (chisel + the vendored SDK + a vendored harness), and opens the
|
|
14
14
|
* browser. `.scene.ts` files are edited visually (tree + inspector) and written back to disk;
|
|
15
15
|
* external (IDE) edits hot-reload over SSE.
|
|
@@ -25,6 +25,9 @@ type SceneContext = {
|
|
|
25
25
|
/** Null in standalone mode: the scene lives outside any cloned lecodes project. */
|
|
26
26
|
manifest: Manifest | null
|
|
27
27
|
name: string
|
|
28
|
+
/** The `.scene.ts` the positional arg named, as a project path ("./src/map.scene.ts") — the
|
|
29
|
+
* editor opens on it (`?scene=`) instead of the project's first scene file. */
|
|
30
|
+
scene: string | null
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
/** The nearest ancestor (inclusive) holding an `app.json` — a local project's root — or null. */
|
|
@@ -42,14 +45,10 @@ const findAppRoot = (start: string): string | null => {
|
|
|
42
45
|
* app.json, else the directory itself. `lecodes thumbs` resolves its root this way — its
|
|
43
46
|
* positional args are targets, not the root, so it can't go through getContext. */
|
|
44
47
|
export const projectRootOf = (dir: string): string => {
|
|
45
|
-
try {
|
|
46
|
-
return findProjectRoot(dir)
|
|
47
|
-
} catch {
|
|
48
|
-
return findAppRoot(dir) ?? dir
|
|
49
|
-
}
|
|
48
|
+
try { return findProjectRoot(dir) } catch { return findAppRoot(dir) ?? dir }
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
const getContext = (
|
|
51
|
+
const getContext = (pathArg: string | undefined): SceneContext => {
|
|
53
52
|
let root: string
|
|
54
53
|
let manifest: Manifest | null
|
|
55
54
|
try {
|
|
@@ -62,7 +61,6 @@ const getContext = (args: Args): SceneContext => {
|
|
|
62
61
|
// `app.json`; only without one does a scene path fall back to its containing dir.
|
|
63
62
|
root = process.cwd()
|
|
64
63
|
manifest = null
|
|
65
|
-
const pathArg = args._[0]
|
|
66
64
|
if (pathArg) {
|
|
67
65
|
const abs = resolve(process.cwd(), pathArg)
|
|
68
66
|
if (existsSync(abs) && statSync(abs).isDirectory()) root = abs
|
|
@@ -72,23 +70,25 @@ const getContext = (args: Args): SceneContext => {
|
|
|
72
70
|
root = findAppRoot(root) ?? root
|
|
73
71
|
}
|
|
74
72
|
}
|
|
75
|
-
|
|
73
|
+
// a `.scene.ts` arg (either mode) picks the file the editor opens on
|
|
74
|
+
let scene: string | null = null
|
|
75
|
+
if (pathArg?.endsWith(".scene.ts")) {
|
|
76
|
+
const abs = resolve(process.cwd(), pathArg)
|
|
77
|
+
const rel = relative(root, abs).replace(/\\/g, "/")
|
|
78
|
+
if (existsSync(abs) && !rel.startsWith("..")) scene = "./" + rel
|
|
79
|
+
}
|
|
80
|
+
return { root, manifest, name: manifest?.name ?? basename(root), scene }
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
/** Register the scene server as a project-scoped MCP server so Claude Code auto-discovers its
|
|
79
|
-
* scene_state / check_scene tools. Merges into an existing .mcp.json (never clobbers a broken one).
|
|
80
|
-
|
|
81
|
-
const writeMcpConfig = (root: string, port: number) => {
|
|
84
|
+
* scene_state / check_scene tools. Merges into an existing .mcp.json (never clobbers a broken one). */
|
|
85
|
+
const writeMcpConfig = (root: string, port: number): void => {
|
|
82
86
|
const path = join(root, ".mcp.json")
|
|
83
87
|
const entry = { type: "http", url: `http://localhost:${port}/mcp` }
|
|
84
88
|
let config: { mcpServers?: Record<string, unknown> } = {}
|
|
85
89
|
if (existsSync(path)) {
|
|
86
|
-
try {
|
|
87
|
-
|
|
88
|
-
} catch {
|
|
89
|
-
warnErr(".mcp.json exists but isn't valid JSON — left it untouched. Add the lecodes-scene server by hand.")
|
|
90
|
-
return
|
|
91
|
-
}
|
|
90
|
+
try { config = JSON.parse(readFileSync(path, "utf8")) }
|
|
91
|
+
catch { warnErr(".mcp.json exists but isn't valid JSON — left it untouched. Add the lecodes-scene server by hand."); return }
|
|
92
92
|
}
|
|
93
93
|
config.mcpServers ??= {}
|
|
94
94
|
const existing = config.mcpServers["lecodes-scene"] as { url?: string } | undefined
|
|
@@ -98,7 +98,7 @@ const writeMcpConfig = (root: string, port: number) => {
|
|
|
98
98
|
note(`${existing ? "updated" : "created"} .mcp.json (lecodes-scene MCP → ${entry.url})`)
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
/**
|
|
101
|
+
/** How many `.scene.ts` files sit under root (node_modules / dot-dirs skipped). */
|
|
102
102
|
const countScenes = (dir: string): number => {
|
|
103
103
|
let n = 0
|
|
104
104
|
for (const name of readdirSync(dir)) {
|
|
@@ -110,35 +110,38 @@ const countScenes = (dir: string): number => {
|
|
|
110
110
|
return n
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
export
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
export default defineCommand({
|
|
114
|
+
name: "scene",
|
|
115
|
+
summary: "The scene editor: edit a .scene.ts visually (tree, inspector, gizmo)",
|
|
116
|
+
usage: "[path]",
|
|
117
|
+
description: "Needs the optional lecodes-3d-editor package (installed on first use). Works inside a cloned project or standalone in any folder; `path` is a folder or a .scene.ts to host. Registers an MCP server in .mcp.json so Claude Code can call scene_state / check_scene.",
|
|
118
|
+
flags: {
|
|
119
|
+
port: num("editor-server port", { default: DEFAULT_PORT }),
|
|
120
|
+
"no-open": bool("don't open the browser"),
|
|
121
|
+
"no-mcp": bool("don't register the MCP server in .mcp.json"),
|
|
122
|
+
},
|
|
123
|
+
examples: ["lecodes scene", "lecodes scene src/forest.scene.ts --no-open"],
|
|
124
|
+
run: async ({ args, flags }) => {
|
|
125
|
+
const ctx = getContext(args[0])
|
|
126
|
+
const sceneServer = await loadPeer<typeof import("lecodes-3d-editor/server")>("lecodes-3d-editor", "server", { for: "lecodes scene" })
|
|
127
|
+
if (countScenes(ctx.root) === 0) warnErr(`No .scene.ts file under ${ctx.root} yet — add one and the editor will pick it up.`)
|
|
117
128
|
|
|
118
|
-
|
|
129
|
+
const server = await sceneServer.startSceneServer({
|
|
130
|
+
root: ctx.root,
|
|
131
|
+
port: flags.port,
|
|
132
|
+
log: note,
|
|
133
|
+
compile: (scenePath: string) => compileSceneEditor({ root: ctx.root, manifest: ctx.manifest, name: ctx.name, scenePath }),
|
|
134
|
+
// live material patches load a shader's compiled artifact (matc'd for the Rendered mode,
|
|
135
|
+
// the lite JSON otherwise) from the local shader cache
|
|
136
|
+
shaderArtifact: (shaderText, platform) => localShaderArtifact(ctx.root, shaderText, platform),
|
|
137
|
+
})
|
|
138
|
+
if (!flags["no-mcp"]) writeMcpConfig(ctx.root, flags.port)
|
|
119
139
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
compile: (scenePath: string) => compileSceneEditor({
|
|
129
|
-
root: ctx.root, manifest: ctx.manifest, name: ctx.name, scenePath,
|
|
130
|
-
}),
|
|
131
|
-
// live material patches load a shader's compiled artifact (matc'd for the Rendered mode,
|
|
132
|
-
// the lite JSON otherwise) from the local shader cache
|
|
133
|
-
shaderArtifact: (shaderText, platform) => localShaderArtifact(ctx.root, shaderText, platform),
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
// Let Claude Code reach the running server over MCP (scene_state / check_scene), unless opted out.
|
|
137
|
-
if (!flagBool(args, "no-mcp")) writeMcpConfig(ctx.root, port)
|
|
138
|
-
|
|
139
|
-
log(`${c.bold("LeCodes Scene Editor")} — ${ctx.name}`)
|
|
140
|
-
log(`Editor: ${c.bold(server.url)}`)
|
|
141
|
-
log(`MCP: ${c.bold(server.url + "/mcp")}`)
|
|
142
|
-
log(`Watching: ${ctx.root} (scenes hot-reload on save; Ctrl+C to stop)`)
|
|
143
|
-
if (!flagBool(args, "no-open")) openBrowser(server.url)
|
|
144
|
-
}
|
|
140
|
+
log(`${c.bold("LeCodes Scene Editor")} — ${ctx.name}`)
|
|
141
|
+
const editorUrl = ctx.scene ? `${server.url}/?scene=${encodeURIComponent(ctx.scene.replace(/^\.\//, ""))}` : server.url
|
|
142
|
+
log(`Editor: ${c.bold(editorUrl)}`)
|
|
143
|
+
log(`MCP: ${c.bold(server.url + "/mcp")}`)
|
|
144
|
+
log(`Watching: ${ctx.root} (scenes hot-reload on save; Ctrl+C to stop)`)
|
|
145
|
+
if (!flags["no-open"]) openBrowser(editorUrl)
|
|
146
|
+
},
|
|
147
|
+
})
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs"
|
|
2
2
|
import { relative } from "node:path"
|
|
3
|
-
import {
|
|
4
|
-
import { collectLocalEntries } from "../compile/collectLocal"
|
|
3
|
+
import { collectLocalEntries } from "../../compile/collectLocal"
|
|
5
4
|
import {
|
|
6
5
|
allShaderPlatforms, applyLocalShaders, matcIdentity, shaderCompiledPlatforms, shaderIsCompiled,
|
|
7
6
|
shadersCacheDir,
|
|
8
|
-
} from "
|
|
9
|
-
import { fetchMatcManifest, installedMatcVersions, resolveMatcExe, resolveMatcExeCached, updateMatc } from "
|
|
10
|
-
import { semverNewer } from "
|
|
11
|
-
import { CliError, c,
|
|
12
|
-
import {
|
|
7
|
+
} from "../../compile/shaders"
|
|
8
|
+
import { fetchMatcManifest, installedMatcVersions, resolveMatcExe, resolveMatcExeCached, updateMatc } from "../../hosts/matcTool"
|
|
9
|
+
import { semverNewer } from "../../hosts/releases"
|
|
10
|
+
import { CliError, UsageError, bool, c, defineCommand, log, note, str, warnErr } from "../../cli"
|
|
11
|
+
import { localProject } from "../shared"
|
|
12
|
+
import { engineMaterialNames, writeNewShader } from "./new"
|
|
13
13
|
import { parseShaderSchema, type AnyShaderPlatform } from "lecodes-sdk/compile"
|
|
14
14
|
|
|
15
15
|
/*
|
|
@@ -21,7 +21,7 @@ import { parseShaderSchema, type AnyShaderPlatform } from "lecodes-sdk/compile"
|
|
|
21
21
|
* update download the newest published matc (run after an engine upgrade)
|
|
22
22
|
* new <name> a fresh .mat + its .material.ts asset in the current dir (--dir elsewhere): the
|
|
23
23
|
* starter template, or `--ref lit|unlit|particles|…` = a copy of that engine source
|
|
24
|
-
* to start from (
|
|
24
|
+
* to start from (new.ts); --force overwrites
|
|
25
25
|
* matc (Filament's material compiler) caches in ~/.lecodes/matc/; LECODES_MATC overrides it.
|
|
26
26
|
*
|
|
27
27
|
* A shader compiles to one artifact per target: `opengl` + `metal` (mobile profile, Android/Apple),
|
|
@@ -34,20 +34,12 @@ import { parseShaderSchema, type AnyShaderPlatform } from "lecodes-sdk/compile"
|
|
|
34
34
|
|
|
35
35
|
/** The project's shader entries from the working tree (works with or without a server manifest). */
|
|
36
36
|
const collectShaders = () => {
|
|
37
|
-
|
|
38
|
-
let manifest: Manifest | null
|
|
39
|
-
try {
|
|
40
|
-
root = findProjectRoot(process.cwd())
|
|
41
|
-
manifest = readManifest(root)
|
|
42
|
-
} catch {
|
|
43
|
-
root = process.cwd()
|
|
44
|
-
manifest = null
|
|
45
|
-
}
|
|
37
|
+
const { root, manifest } = localProject()
|
|
46
38
|
const { entries } = collectLocalEntries(root, manifest, "", { localShaders: true })
|
|
47
39
|
return { root, entries, shaders: entries.filter((e) => e.type === "shader" && e.absPath) }
|
|
48
40
|
}
|
|
49
41
|
|
|
50
|
-
const status = async () => {
|
|
42
|
+
const status = async (): Promise<void> => {
|
|
51
43
|
const { root, shaders } = collectShaders()
|
|
52
44
|
|
|
53
45
|
const installed = installedMatcVersions()
|
|
@@ -104,8 +96,7 @@ const status = async () => {
|
|
|
104
96
|
|
|
105
97
|
/** Parse `--platform a,b` into a validated target list. A typo must fail loudly: silently compiling
|
|
106
98
|
* nothing would look like success and leave the cache short of what the caller asked for. */
|
|
107
|
-
const parsePlatforms = (
|
|
108
|
-
const raw = flagStr(args, "platform", "platforms")
|
|
99
|
+
const parsePlatforms = (raw: string | undefined): AnyShaderPlatform[] | undefined => {
|
|
109
100
|
if (raw === undefined) return undefined
|
|
110
101
|
const known = allShaderPlatforms()
|
|
111
102
|
const asked = raw.split(",").map((p) => p.trim()).filter(Boolean)
|
|
@@ -119,11 +110,10 @@ const parsePlatforms = (args: Args): AnyShaderPlatform[] | undefined => {
|
|
|
119
110
|
|
|
120
111
|
/** Compile every project shader into the local cache — what `lecodes dev` does lazily, run
|
|
121
112
|
* explicitly (e.g. to pre-warm before going offline, or to see all compile errors at once).
|
|
122
|
-
*
|
|
123
|
-
* a full one later add up rather than redoing each other's work. */
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
const { root, entries, shaders } = collectShaders()
|
|
113
|
+
* `platforms` narrows the targets; compiling is incremental per artifact, so a narrow run now and
|
|
114
|
+
* a full one later add up rather than redoing each other's work. `lecodes update` runs it too. */
|
|
115
|
+
export const compileShaders = async (platforms?: AnyShaderPlatform[]): Promise<void> => {
|
|
116
|
+
const { root, shaders } = collectShaders()
|
|
127
117
|
if (shaders.length === 0) {
|
|
128
118
|
note("No .mat shaders in this project.")
|
|
129
119
|
return
|
|
@@ -153,21 +143,50 @@ const compile = async (args: Args) => {
|
|
|
153
143
|
note(`${shaders.length} shader${shaders.length === 1 ? "" : "s"} compiled${scope} → ${shadersCacheDir(root)}`)
|
|
154
144
|
}
|
|
155
145
|
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
146
|
+
const compile = defineCommand({
|
|
147
|
+
name: "compile",
|
|
148
|
+
summary: "Compile every shader into .lecodes/shaders (the pass `lecodes dev` runs on the fly)",
|
|
149
|
+
description: "Downloads matc on first use (~/.lecodes/matc). Every target is compiled by default (opengl, metal, desktop, desktop-vulkan, webgl-lite) because one bundle serves every host; --platform narrows it when you know the target. Incremental per artifact.",
|
|
150
|
+
flags: {
|
|
151
|
+
platform: str("only these targets, comma-separated (opengl, metal, desktop, desktop-vulkan, webgl-lite)", { value: "<a,b>" }),
|
|
152
|
+
},
|
|
153
|
+
examples: ["lecodes shaders compile", "lecodes shaders compile --platform desktop"],
|
|
154
|
+
run: ({ flags }) => compileShaders(parsePlatforms(flags.platform)),
|
|
155
|
+
})
|
|
165
156
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
157
|
+
const update = defineCommand({
|
|
158
|
+
name: "update",
|
|
159
|
+
summary: "Download the newest published matc (run after an engine upgrade)",
|
|
160
|
+
run: () => updateMatc(),
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
const newShader = defineCommand({
|
|
164
|
+
name: "new",
|
|
165
|
+
summary: "Write a fresh .mat + .material.ts: the starter, or a copy of an engine material",
|
|
166
|
+
usage: "<name>",
|
|
167
|
+
description: "The starter is an unlit material with an annotated parameter of each kind. `--ref lit|unlit|particles|…` copies the engine's own source instead, renamed after the file — the way to get vertex colours, an emissive term or a second map onto an otherwise standard material without touching the engine. A <name>.material.ts asset (defineMaterial with the declared parameters) goes next to it.",
|
|
168
|
+
flags: {
|
|
169
|
+
ref: str("start from the engine's material of that name (lit, unlit, particles, …)", { value: "<name>" }),
|
|
170
|
+
dir: str("write into this folder instead of the current dir", { value: "<folder>" }),
|
|
171
|
+
force: bool("overwrite existing files"),
|
|
172
|
+
},
|
|
173
|
+
examples: ["lecodes shaders new hologram", "lecodes shaders new rim-light --ref lit --dir assets/shaders"],
|
|
174
|
+
run: ({ args, flags }) => {
|
|
175
|
+
const name = args[0]
|
|
176
|
+
if (!name) throw new UsageError(`Which shader? lecodes shaders new <name> [--ref ${engineMaterialNames().join("|") || "lit"}] [--dir <folder>] [--force]`)
|
|
177
|
+
const res = writeNewShader({ name, ref: flags.ref, dir: flags.dir, force: flags.force })
|
|
178
|
+
const rel = (p: string) => relative(process.cwd(), p)
|
|
179
|
+
log(`${rel(res.mat)} ${c.dim(res.ref ? `(from the engine's ${res.ref}.mat)` : "(the starter template)")}`)
|
|
180
|
+
log(`${rel(res.asset)} ${c.dim("(defineMaterial asset — a scene takes it as `material:`)")}`)
|
|
181
|
+
note(`next: edit it, then \`lecodes shaders compile\` (or just \`lecodes dev\`); in code: Material.load(asset('./${name.replace(/\.mat$/, "")}.mat'))`)
|
|
182
|
+
},
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
export default defineCommand({
|
|
186
|
+
name: "shaders",
|
|
187
|
+
summary: "The project's custom shaders (.mat): list, compile, update matc, new",
|
|
188
|
+
description: "Bare `lecodes shaders` lists the project's shaders with their compile state and the matc status. matc (Filament's material compiler) caches in ~/.lecodes/matc; LECODES_MATC points at a local build.",
|
|
189
|
+
commands: [compile, update, newShader],
|
|
190
|
+
examples: ["lecodes shaders", "lecodes shaders compile", "lecodes shaders new hologram --ref lit"],
|
|
191
|
+
run: () => status(),
|
|
192
|
+
})
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
13
13
|
import { basename, join, relative, resolve } from "node:path"
|
|
14
14
|
import { parseShaderSchema } from "lecodes-sdk/compile"
|
|
15
|
-
import { distRoot } from "
|
|
16
|
-
import { CliError } from "
|
|
15
|
+
import { distRoot } from "../../hosts/distRoot"
|
|
16
|
+
import { CliError } from "../../cli"
|
|
17
17
|
|
|
18
18
|
/** where the engine's .mat sources are, or null when this CLI carries none */
|
|
19
19
|
export const engineMaterialsDir = (): string | null => {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { basename } from "node:path"
|
|
2
|
+
import { bool, str, type FlagValues } from "../cli"
|
|
3
|
+
import { loadConfig, requireApiUrl, requireToken } from "../platform/config"
|
|
4
|
+
import { findProjectRoot, readManifest, type Manifest } from "../project/manifest"
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* What several commands share: the flag groups (spread into a command's `flags:`) and the
|
|
8
|
+
* "where am I / who am I" lookups every platform command starts with.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ---- flag groups -----------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
/** How the project is compiled — `compile`, `dev`, `render`, `test`, `desktop run`, `app sync` … */
|
|
14
|
+
export const bundleFlags = {
|
|
15
|
+
entry: str("compile from this entry instead of the detected one", { value: "<path>" }),
|
|
16
|
+
"public-url": str("resolve assets from this URL prefix (default: the API origin / the working tree)", { value: "<url>" }),
|
|
17
|
+
"remote-assets": bool("desktop hosts: load asset() resources from the server instead of the working tree"),
|
|
18
|
+
"no-header": bool("omit the metadata header"),
|
|
19
|
+
"no-minify": bool("keep the bundle readable (no compress / mangle)"),
|
|
20
|
+
"no-sourcemap": bool("don't generate / inline the source map"),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** `-o` for commands whose result is a file. */
|
|
24
|
+
export const outFlag = { out: str("write to this file instead of stdout", { alias: "o", value: "<file>" }) }
|
|
25
|
+
|
|
26
|
+
/** The compiler's view of the bundle flags (see compile.ts `buildBundle`). */
|
|
27
|
+
export type BundleOptions = {
|
|
28
|
+
entry?: string
|
|
29
|
+
publicUrl?: string
|
|
30
|
+
remoteAssets?: boolean
|
|
31
|
+
header?: boolean
|
|
32
|
+
minify?: boolean
|
|
33
|
+
sourcemap?: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const bundleOptions = (flags: FlagValues<typeof bundleFlags>): BundleOptions => ({
|
|
37
|
+
entry: flags.entry,
|
|
38
|
+
publicUrl: flags["public-url"],
|
|
39
|
+
remoteAssets: flags["remote-assets"],
|
|
40
|
+
header: !flags["no-header"],
|
|
41
|
+
// Both default on; `false` only when the flag is present, so the SDK's own defaults stand.
|
|
42
|
+
minify: flags["no-minify"] ? false : undefined,
|
|
43
|
+
sourcemap: flags["no-sourcemap"] ? false : undefined,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// ---- where / who -----------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
export type Session = { apiUrl: string, token: string }
|
|
49
|
+
|
|
50
|
+
/** The logged-in session (api origin + token) or a "run `lecodes login`" error. */
|
|
51
|
+
export const session = (): Session => {
|
|
52
|
+
const config = loadConfig()
|
|
53
|
+
return { apiUrl: requireApiUrl(config), token: requireToken(config) }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type LinkedProject = Session & { root: string, manifest: Manifest }
|
|
57
|
+
|
|
58
|
+
/** A server-backed checkout: the project root (found upward from the cwd), its manifest and the session. */
|
|
59
|
+
export const linkedProject = (): LinkedProject => {
|
|
60
|
+
const root = findProjectRoot(process.cwd())
|
|
61
|
+
return { root, manifest: readManifest(root), ...session() }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type LocalProject = { root: string, manifest: Manifest | null, name: string }
|
|
65
|
+
|
|
66
|
+
/** Any project: a linked checkout, else the current folder standing alone (no login needed). */
|
|
67
|
+
export const localProject = (): LocalProject => {
|
|
68
|
+
try {
|
|
69
|
+
const root = findProjectRoot(process.cwd())
|
|
70
|
+
const manifest = readManifest(root)
|
|
71
|
+
return { root, manifest, name: manifest.name }
|
|
72
|
+
} catch {
|
|
73
|
+
const root = process.cwd()
|
|
74
|
+
return { root, manifest: null, name: basename(root) }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** A folder name for a project: lower-case, dashes, never empty. */
|
|
79
|
+
export const slugOf = (name: string): string => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project"
|
package/src/commands/status.ts
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { diffLocal, hasChanges, scanProject } from "../localFiles"
|
|
3
|
-
import {
|
|
1
|
+
import { c, defineCommand, info, log } from "../cli"
|
|
2
|
+
import { diffLocal, hasChanges, scanProject } from "../project/localFiles"
|
|
3
|
+
import { linkedProject } from "./shared"
|
|
4
4
|
|
|
5
|
-
/* `lecodes status` —
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
/* `lecodes status` — local changes vs the last clone / pull / push. */
|
|
6
|
+
export default defineCommand({
|
|
7
|
+
name: "status",
|
|
8
|
+
summary: "Show local changes vs the last sync",
|
|
9
|
+
run: () => {
|
|
10
|
+
const { root, manifest } = linkedProject()
|
|
11
|
+
const scan = scanProject(root, manifest)
|
|
12
|
+
const diff = diffLocal(manifest, scan)
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
info(c.dim("Working tree clean — nothing to push.") + ignoredNote)
|
|
16
|
-
return
|
|
17
|
-
}
|
|
14
|
+
info(`Project ${c.bold(manifest.name)} (${manifest.uuid})`)
|
|
15
|
+
const ignoredNote = scan.ignored.length ? c.dim(` (${scan.ignored.length} ignored)`) : ""
|
|
16
|
+
if (!hasChanges(diff)) { info(c.dim("Working tree clean — nothing to push.") + ignoredNote); return }
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
if (scan.ignored.length) info(c.dim(` ${scan.ignored.length} ignored (.lecodesignore)`))
|
|
29
|
-
}
|
|
18
|
+
for (const f of diff.added) if (!diff.moves.has(f.path)) log(c.green(` added ${f.path}`))
|
|
19
|
+
for (const f of diff.modified) log(c.yellow(` modified ${f.path}`))
|
|
20
|
+
for (const [to, from] of diff.moves) log(c.cyan(` moved ${from} → ${to}`))
|
|
21
|
+
const movedFrom = new Set(diff.moves.values())
|
|
22
|
+
for (const path of diff.deleted) if (!movedFrom.has(path)) log(c.red(` deleted ${path}`))
|
|
23
|
+
if (scan.ignored.length) info(c.dim(` ${scan.ignored.length} ignored (.lecodesignore)`))
|
|
24
|
+
},
|
|
25
|
+
})
|