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.
Files changed (107) hide show
  1. package/README.md +56 -57
  2. package/dist/index.js +5920 -5022
  3. package/package.json +9 -7
  4. package/runtime/materials/decal-relief.mat +12 -1
  5. package/runtime/materials/decal.mat +17 -3
  6. package/runtime/materials/lightmap-baked-lite.mat +6 -1
  7. package/runtime/materials/lightmap-baked.mat +6 -1
  8. package/runtime/materials/lightmap.mat +3 -0
  9. package/runtime/materials/particles-quad.mat +17 -9
  10. package/runtime/materials/particles.mat +17 -9
  11. package/runtime/scene-harness.json +1 -1
  12. package/runtime/sdk-types.json +1 -1
  13. package/runtime/web/assets/createViewerLite-DHqTFhWB.js +866 -0
  14. package/runtime/web/assets/{index-BTB5KV9x.js → index-Q06c6oHx.js} +1 -1
  15. package/runtime/web/embed.js +2 -2
  16. package/src/api.ts +1 -302
  17. package/src/cli/args.ts +37 -0
  18. package/src/cli/command.ts +113 -0
  19. package/src/cli/errors.ts +13 -0
  20. package/src/cli/help.ts +133 -0
  21. package/src/cli/index.ts +7 -0
  22. package/src/cli/output.ts +102 -0
  23. package/src/cli/run.ts +106 -0
  24. package/src/commands/{appAndroid.ts → app/android.ts} +451 -42
  25. package/src/commands/{appDesktop.ts → app/desktop.ts} +63 -32
  26. package/src/commands/{appDesktopMac.ts → app/desktopMac.ts} +24 -18
  27. package/src/commands/{appIcon.ts → app/icon.ts} +2 -2
  28. package/src/commands/{app.ts → app/index.ts} +397 -289
  29. package/src/commands/{appShared.ts → app/shared.ts} +30 -39
  30. package/src/commands/{appTemplatesAndroid.ts → app/templates/android.ts} +62 -28
  31. package/src/commands/{appTemplatesGradlew.ts → app/templates/gradlew.ts} +1 -1
  32. package/src/commands/{appTemplates.ts → app/templates/ios.ts} +1 -1
  33. package/src/commands/assets.ts +23 -21
  34. package/src/commands/clone.ts +48 -51
  35. package/src/commands/compile.ts +62 -88
  36. package/src/commands/create.ts +46 -47
  37. package/src/commands/design/comments.ts +361 -0
  38. package/src/commands/design/context.ts +101 -0
  39. package/src/commands/design/index.ts +391 -0
  40. package/src/commands/design/snapshot.ts +134 -0
  41. package/src/commands/{designTemplates.ts → design/templates.ts} +4 -3
  42. package/src/commands/desktop.ts +90 -82
  43. package/src/commands/dev.ts +201 -176
  44. package/src/commands/diff.ts +39 -51
  45. package/src/commands/index.ts +60 -0
  46. package/src/commands/{init.ts → init/index.ts} +199 -191
  47. package/src/commands/install.ts +80 -67
  48. package/src/commands/lightmap.ts +203 -171
  49. package/src/commands/link.ts +87 -94
  50. package/src/commands/login.ts +25 -23
  51. package/src/commands/navmesh.ts +168 -138
  52. package/src/commands/pn.ts +201 -244
  53. package/src/commands/pull.ts +49 -56
  54. package/src/commands/push.ts +48 -54
  55. package/src/commands/render.ts +158 -99
  56. package/src/commands/scene.ts +58 -55
  57. package/src/commands/{shaders.ts → shaders/index.ts} +61 -42
  58. package/src/commands/{shadersNew.ts → shaders/new.ts} +2 -2
  59. package/src/commands/shared.ts +79 -0
  60. package/src/commands/status.ts +22 -26
  61. package/src/commands/test.ts +398 -371
  62. package/src/commands/thumbs.ts +185 -175
  63. package/src/commands/update/index.ts +226 -0
  64. package/src/commands/{types.ts → update/types.ts} +31 -22
  65. package/src/compile/collect.ts +5 -5
  66. package/src/compile/collectLocal.ts +3 -3
  67. package/src/compile/designCompile.ts +3 -3
  68. package/src/compile/headlessBundle.ts +133 -129
  69. package/src/compile/projectCompile.ts +2 -2
  70. package/src/compile/sceneCompile.ts +10 -8
  71. package/src/compile/screenEntry.ts +123 -127
  72. package/src/compile/shaders.ts +3 -3
  73. package/src/dev/androidDev.ts +1 -1
  74. package/src/dev/devServer.ts +3 -3
  75. package/src/dev/webRunner.ts +1 -1
  76. package/src/{cmgenTool.ts → hosts/cmgenTool.ts} +1 -1
  77. package/src/{desktopRenderer.ts → hosts/desktopRenderer.ts} +6 -5
  78. package/src/{desktopScript.ts → hosts/desktopScript.ts} +1 -1
  79. package/src/{distRoot.ts → hosts/distRoot.ts} +9 -5
  80. package/src/{matcTool.ts → hosts/matcTool.ts} +1 -1
  81. package/src/{peerInstall.ts → hosts/peerInstall.ts} +2 -2
  82. package/src/{releases.ts → hosts/releases.ts} +1 -1
  83. package/src/index.ts +32 -480
  84. package/src/platform/api.ts +302 -0
  85. package/src/{browserAuth.ts → platform/browserAuth.ts} +1 -1
  86. package/src/{config.ts → platform/config.ts} +1 -1
  87. package/src/{serverDiff.ts → platform/serverDiff.ts} +3 -3
  88. package/src/{projectEnv.ts → project/env.ts} +33 -2
  89. package/src/{localFiles.ts → project/localFiles.ts} +1 -1
  90. package/src/{manifest.ts → project/manifest.ts} +1 -1
  91. package/src/{project.ts → project/materialize.ts} +2 -2
  92. package/src/project/paths.ts +20 -0
  93. package/src/{textDiff.ts → project/textDiff.ts} +1 -1
  94. package/src/{types.ts → project/types.ts} +0 -0
  95. package/runtime/web/assets/createViewerLite-C_bkKXLg.js +0 -852
  96. package/src/commands/design.ts +0 -846
  97. package/src/commands/update.ts +0 -211
  98. package/src/util.ts +0 -146
  99. /package/src/commands/{projectTemplates.ts → init/templates.ts} +0 -0
  100. /package/src/{lecodes-3d-editor.d.ts → declarations/lecodes-3d-editor.d.ts} +0 -0
  101. /package/src/{lecodes-assets.d.ts → declarations/lecodes-assets.d.ts} +0 -0
  102. /package/src/{lecodes-design.d.ts → declarations/lecodes-design.d.ts} +0 -0
  103. /package/src/{lecodes-renderer.d.ts → declarations/lecodes-renderer.d.ts} +0 -0
  104. /package/src/{qrcode-terminal.d.ts → declarations/qrcode-terminal.d.ts} +0 -0
  105. /package/src/{peers.ts → hosts/peers.ts} +0 -0
  106. /package/src/{designMeta.ts → project/designMeta.ts} +0 -0
  107. /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
+ }