lecodes-cli 0.20.2 → 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 +5918 -5020
- package/package.json +7 -5
- 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/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-BMt7AnC5.js → index-Q06c6oHx.js} +1 -1
- package/runtime/web/embed.js +1 -1
- 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-Ct_PZdof.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
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createInterface } from "node:readline"
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Everything a command prints goes through here. Two channels, one rule:
|
|
5
|
+
* stdout the command's RESULT — what a pipe or `-o` would capture (JSON, JS, a listing)
|
|
6
|
+
* stderr diagnostics: progress, notes, warnings — never in the way of piped output
|
|
7
|
+
* `log` / `success` / `warn` are stdout (for commands whose output IS the conversation);
|
|
8
|
+
* `note` / `warnErr` / `logErr` are the stderr twins for commands that write a result to stdout.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const useColor = process.stdout.isTTY && process.env.NO_COLOR === undefined
|
|
12
|
+
const paint = (code: string, s: string): string => useColor ? `\x1b[${code}m${s}\x1b[0m` : s
|
|
13
|
+
|
|
14
|
+
/** ANSI colours (no-ops when not a TTY or NO_COLOR is set). */
|
|
15
|
+
export const c = {
|
|
16
|
+
bold: (s: string) => paint("1", s),
|
|
17
|
+
dim: (s: string) => paint("2", s),
|
|
18
|
+
red: (s: string) => paint("31", s),
|
|
19
|
+
green: (s: string) => paint("32", s),
|
|
20
|
+
yellow: (s: string) => paint("33", s),
|
|
21
|
+
cyan: (s: string) => paint("36", s),
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const log = (msg = ""): void => { process.stdout.write(msg + "\n") }
|
|
25
|
+
export const info = (msg: string): void => log(msg)
|
|
26
|
+
export const success = (msg: string): void => log(c.green("✓ ") + msg)
|
|
27
|
+
export const warn = (msg: string): void => log(c.yellow("! ") + msg)
|
|
28
|
+
|
|
29
|
+
export const logErr = (msg = ""): void => { process.stderr.write(msg + "\n") }
|
|
30
|
+
export const note = (msg: string): void => logErr(c.dim(msg))
|
|
31
|
+
export const warnErr = (msg: string): void => logErr(c.yellow("! ") + msg)
|
|
32
|
+
|
|
33
|
+
const ANSI = /\x1b\[[0-9;]*m/g
|
|
34
|
+
/** Printed width of a string — colour codes don't count. */
|
|
35
|
+
export const printedWidth = (s: string): number => s.replace(ANSI, "").length
|
|
36
|
+
|
|
37
|
+
/** Rows as aligned columns (the last column unpadded so colours don't drag trailing spaces). */
|
|
38
|
+
export const table = (rows: string[][], indent = " "): void => {
|
|
39
|
+
if (rows.length === 0) return
|
|
40
|
+
const widths = rows[0].map((_, i) => Math.max(...rows.map(r => printedWidth(r[i] ?? ""))))
|
|
41
|
+
for (const row of rows) {
|
|
42
|
+
const cells = row.map((cell, i) => i === row.length - 1 ? cell : cell + " ".repeat(Math.max(0, widths[i] - printedWidth(cell))))
|
|
43
|
+
log(indent + cells.join(" "))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A compact "3h ago" — absolute timestamps make tables unreadable at a glance. */
|
|
48
|
+
export const relativeTime = (iso: string | null | undefined): string => {
|
|
49
|
+
if (!iso) return c.dim("never")
|
|
50
|
+
const ms = Date.now() - new Date(iso).getTime()
|
|
51
|
+
if (!Number.isFinite(ms)) return c.dim("—")
|
|
52
|
+
const mins = Math.round(ms / 60_000)
|
|
53
|
+
if (mins < 1) return "just now"
|
|
54
|
+
if (mins < 60) return `${mins}m ago`
|
|
55
|
+
const hours = Math.round(mins / 60)
|
|
56
|
+
if (hours < 24) return `${hours}h ago`
|
|
57
|
+
return `${Math.round(hours / 24)}d ago`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Keep a label short, dropping characters from the FRONT (paths matter most at the end). */
|
|
61
|
+
const ellipsize = (s: string, max: number): string => s.length <= max ? s : "…" + s.slice(s.length - max + 1)
|
|
62
|
+
|
|
63
|
+
/** A single, in-place-updating progress line (TTY only; a no-op when piped). */
|
|
64
|
+
export const progress = (done: number, total: number, label = ""): void => {
|
|
65
|
+
if (!process.stdout.isTTY) return
|
|
66
|
+
const pct = total > 0 ? Math.round((done / total) * 100) : 100
|
|
67
|
+
process.stdout.clearLine(0)
|
|
68
|
+
process.stdout.cursorTo(0)
|
|
69
|
+
process.stdout.write(` ${c.cyan(`${pct}`.padStart(3) + "%")} ${c.dim(`(${done}/${total})`)} ${c.dim(ellipsize(label, 48))}`)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Finish a progress line with a newline so later output starts cleanly. */
|
|
73
|
+
export const endProgress = (): void => { if (process.stdout.isTTY) process.stdout.write("\n") }
|
|
74
|
+
|
|
75
|
+
export const prompt = (question: string, fallback?: string): Promise<string> =>
|
|
76
|
+
new Promise(resolve => {
|
|
77
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
78
|
+
const suffix = fallback ? c.dim(` (${fallback})`) : ""
|
|
79
|
+
rl.question(`${question}${suffix}: `, answer => {
|
|
80
|
+
rl.close()
|
|
81
|
+
resolve(answer.trim() || fallback || "")
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
/** Prompt without echoing the input (passwords, token paste). */
|
|
86
|
+
export const promptHidden = (question: string): Promise<string> =>
|
|
87
|
+
new Promise(resolve => {
|
|
88
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true })
|
|
89
|
+
rl.question(`${question}: `, answer => {
|
|
90
|
+
rl.close()
|
|
91
|
+
process.stdout.write("\n")
|
|
92
|
+
resolve(answer.trim())
|
|
93
|
+
})
|
|
94
|
+
// Mute everything written after the prompt line was emitted (the keystroke echoes).
|
|
95
|
+
;(rl as unknown as { _writeToOutput: (s: string) => void })._writeToOutput = () => {}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
export const confirm = async (question: string, fallback = false): Promise<boolean> => {
|
|
99
|
+
const answer = (await prompt(`${question} ${fallback ? "[Y/n]" : "[y/N]"}`)).toLowerCase()
|
|
100
|
+
if (!answer) return fallback
|
|
101
|
+
return answer === "y" || answer === "yes"
|
|
102
|
+
}
|
package/src/cli/run.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { parseArgv } from "./args"
|
|
2
|
+
import { readFlags, unknownFlags, type Command, type Context } from "./command"
|
|
3
|
+
import { CliError, UsageError } from "./errors"
|
|
4
|
+
import { commandHelp, globalHelp, type Group } from "./help"
|
|
5
|
+
import { c, log, logErr, warnErr } from "./output"
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* The runner: argv → the command node → typed flags → run. Global switches are handled here for
|
|
9
|
+
* every node: --help / -h / `help <cmd…>`, --version / -v, --yes / -y (pre-approves first-use
|
|
10
|
+
* installs of optional peers, see hosts/peerInstall.ts).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type Cli = {
|
|
14
|
+
version: string
|
|
15
|
+
groups: Group[]
|
|
16
|
+
/** Printed under the global help. */
|
|
17
|
+
footer?: string
|
|
18
|
+
/** `lecodes --version` — the caller prints whatever it wants there. */
|
|
19
|
+
printVersion: () => void
|
|
20
|
+
/** Runs before the command (the project's .env, for instance). */
|
|
21
|
+
before?: () => void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const all = (groups: Group[]): Command<any>[] => groups.flatMap(g => g.commands)
|
|
25
|
+
|
|
26
|
+
/** Levenshtein-closest command name for the "did you mean" line. */
|
|
27
|
+
const closest = (name: string, cmds: Command<any>[]): string | undefined => {
|
|
28
|
+
const dist = (a: string, b: string): number => {
|
|
29
|
+
const d = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)] as number[])
|
|
30
|
+
for (let j = 1; j <= b.length; j++) d[0][j] = j
|
|
31
|
+
for (let i = 1; i <= a.length; i++) for (let j = 1; j <= b.length; j++)
|
|
32
|
+
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1))
|
|
33
|
+
return d[a.length][b.length]
|
|
34
|
+
}
|
|
35
|
+
const best = cmds.filter(x => !x.hidden).map(x => [x.name, dist(name, x.name)] as const).sort((p, q) => p[1] - q[1])[0]
|
|
36
|
+
return best && best[1] <= Math.max(2, Math.floor(name.length / 3)) ? best[0] : undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const find = (cmds: Command<any>[] | undefined, name: string): Command<any> | undefined =>
|
|
40
|
+
cmds?.find(x => x.name === name || x.aliases?.includes(name))
|
|
41
|
+
|
|
42
|
+
const HELP_WORDS = new Set(["--help", "-h"])
|
|
43
|
+
|
|
44
|
+
export const runCli = async (cli: Cli, argv: string[]): Promise<void> => {
|
|
45
|
+
cli.before?.()
|
|
46
|
+
if (argv.includes("--yes") || argv.includes("-y")) process.env.LECODES_YES = "1"
|
|
47
|
+
|
|
48
|
+
const top = all(cli.groups)
|
|
49
|
+
const help = () => globalHelp(cli.version, cli.groups, cli.footer)
|
|
50
|
+
let words = argv
|
|
51
|
+
// `lecodes help …` = `lecodes … --help`
|
|
52
|
+
if (words[0] === "help") words = [...words.slice(1), "--help"]
|
|
53
|
+
if (words.length === 0 || (words.length === 1 && HELP_WORDS.has(words[0]))) { log(help()); return }
|
|
54
|
+
if (words[0] === "--version" || words[0] === "-v" || words[0] === "version") { cli.printVersion(); return }
|
|
55
|
+
|
|
56
|
+
// walk the tree: every leading word that names a subcommand descends
|
|
57
|
+
const path: string[] = []
|
|
58
|
+
let node = find(top, words[0])
|
|
59
|
+
if (!node) {
|
|
60
|
+
const hint = closest(words[0], top)
|
|
61
|
+
logErr(c.red(`Unknown command: ${words[0]}`) + (hint ? ` — did you mean ${c.bold(hint)}?` : ""))
|
|
62
|
+
logErr(c.dim("Run `lecodes --help` for the list."))
|
|
63
|
+
process.exitCode = 1
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
path.push(node.name)
|
|
67
|
+
let rest = words.slice(1)
|
|
68
|
+
while (rest.length && node.commands) {
|
|
69
|
+
const sub = find(node.commands, rest[0])
|
|
70
|
+
if (!sub) break
|
|
71
|
+
node = sub
|
|
72
|
+
path.push(sub.name)
|
|
73
|
+
rest = rest.slice(1)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (rest.some(w => HELP_WORDS.has(w)) && !node.passthrough) { log(commandHelp(path, node)); return }
|
|
77
|
+
if (!node.run) {
|
|
78
|
+
// a group called bare (or with a word that names no subcommand): its help is the answer
|
|
79
|
+
if (rest.length) logErr(c.red(`Unknown command: ${[...path, rest[0]].join(" ")}`))
|
|
80
|
+
log(commandHelp(path, node))
|
|
81
|
+
if (rest.length) process.exitCode = 1
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const parsed = parseArgv(rest)
|
|
86
|
+
const spec = node.flags ?? {}
|
|
87
|
+
if (!node.passthrough) {
|
|
88
|
+
const unknown = unknownFlags(spec, parsed.flags)
|
|
89
|
+
if (unknown.length) warnErr(`${unknown.map(f => `--${f}`).join(", ")}: not an option of \`lecodes ${path.join(" ")}\` (see --help)`)
|
|
90
|
+
}
|
|
91
|
+
const ctx: Context<any> = { args: parsed.positional, flags: readFlags(spec, parsed.flags, path), raw: parsed.flags, argv: rest, path }
|
|
92
|
+
try {
|
|
93
|
+
await node.run(ctx)
|
|
94
|
+
} catch (e) {
|
|
95
|
+
if (e instanceof UsageError) { logErr(c.red("✗ ") + e.message); logErr(""); logErr(commandHelp(path, node)) }
|
|
96
|
+
else throw e
|
|
97
|
+
process.exitCode = 1
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Top-level error reporting: a CliError is a message, anything else is unexpected. */
|
|
102
|
+
export const reportError = (e: unknown): void => {
|
|
103
|
+
if (e instanceof CliError) logErr(c.red("✗ ") + e.message)
|
|
104
|
+
else logErr(c.red("✗ Unexpected error: ") + (e instanceof Error ? (e.stack ?? e.message) : String(e)))
|
|
105
|
+
process.exitCode = 1
|
|
106
|
+
}
|