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
|
@@ -1,54 +1,63 @@
|
|
|
1
1
|
import { resolve } from "node:path"
|
|
2
|
-
import { getTypes } from "
|
|
3
|
-
import { DEFAULT_API, loadConfig, normalizeApiUrl } from "
|
|
4
|
-
import { findProjectRoot, LECODES_DIR } from "
|
|
2
|
+
import { getTypes } from "../../platform/api"
|
|
3
|
+
import { DEFAULT_API, loadConfig, normalizeApiUrl } from "../../platform/config"
|
|
4
|
+
import { findProjectRoot, LECODES_DIR } from "../../project/manifest"
|
|
5
5
|
import {
|
|
6
6
|
bundleHash, readLocalTypeBundle, readProjectTypeBundle, writeTsconfig, writeTypeBundle,
|
|
7
7
|
type TypeBundle,
|
|
8
|
-
} from "
|
|
9
|
-
import { CliError, c,
|
|
8
|
+
} from "../../project/types"
|
|
9
|
+
import { CliError, c, log, note, success, warn } from "../../cli"
|
|
10
10
|
|
|
11
11
|
/*
|
|
12
|
-
* The types step of `lecodes update` (formerly `lecodes types [dir]`) — re-materialize the IDE type
|
|
12
|
+
* The types step of `lecodes update` (formerly `lecodes types [dir]`) — re-materialize the IDE type
|
|
13
|
+
* surface (<root>/.lecodes/types/ + tsconfig).
|
|
13
14
|
*
|
|
14
15
|
* Every project gets these types once, at scaffold time: `clone`/`create`/`pull` fetch them from
|
|
15
16
|
* GET /api/types (the deployed runtime's surface), while `init` and `design` — which run with no
|
|
16
17
|
* server and no login — write the bundle vendored into the CLI at publish time. Neither source
|
|
17
|
-
* refreshes on its own, so a local project's types age with the CLI binary that made it. This
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* refreshes on its own, so a local project's types age with the CLI binary that made it. This step
|
|
19
|
+
* is the refresh: server by default (newest SDK the platform actually runs), `local` for the CLI's
|
|
20
|
+
* own copy, `check` to only report drift (a CliError when stale).
|
|
20
21
|
*
|
|
21
22
|
* IDE-only: the compiler reads the SDK sources vendored in runtime/sdk, never .lecodes/types. When
|
|
22
|
-
* the two sources disagree the
|
|
23
|
+
* the two sources disagree the step says so — types promising APIs this CLI can't compile is
|
|
23
24
|
* exactly the confusing case worth naming out loud.
|
|
24
25
|
*/
|
|
25
26
|
|
|
27
|
+
export type RefreshTypesOptions = {
|
|
28
|
+
/** The project folder (default: the project root above the cwd, else the cwd). */
|
|
29
|
+
dir?: string
|
|
30
|
+
/** The bundle vendored in this CLI instead of the server's. */
|
|
31
|
+
local: boolean
|
|
32
|
+
/** Only report drift — write nothing, throw when stale. */
|
|
33
|
+
check: boolean
|
|
34
|
+
/** API origin for the server bundle (default: the configured one). */
|
|
35
|
+
api?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
26
38
|
const label = (bundle: TypeBundle) => `${Object.keys(bundle).length} file(s)`
|
|
27
39
|
|
|
28
|
-
export const
|
|
29
|
-
const
|
|
30
|
-
const root = dirArg ? resolve(dirArg) : (() => {
|
|
40
|
+
export const refreshTypes = async (opts: RefreshTypesOptions): Promise<void> => {
|
|
41
|
+
const root = opts.dir ? resolve(opts.dir) : (() => {
|
|
31
42
|
try { return findProjectRoot(process.cwd()) } catch { return process.cwd() }
|
|
32
43
|
})()
|
|
33
44
|
|
|
34
|
-
const useLocal = flagBool(args, "local")
|
|
35
|
-
const checkOnly = flagBool(args, "check")
|
|
36
45
|
const local = readLocalTypeBundle()
|
|
37
46
|
|
|
38
47
|
// ---- pick the source ----
|
|
39
48
|
let bundle: TypeBundle | null = null
|
|
40
49
|
let source = ""
|
|
41
|
-
if (
|
|
50
|
+
if (opts.local) {
|
|
42
51
|
if (!local) throw new CliError("This CLI ships no vendored type bundle — drop --local to fetch them from the server.")
|
|
43
52
|
bundle = local
|
|
44
53
|
source = "the CLI's bundled SDK"
|
|
45
54
|
} else {
|
|
46
|
-
const apiUrl = normalizeApiUrl(
|
|
55
|
+
const apiUrl = normalizeApiUrl(opts.api ?? loadConfig().apiUrl ?? DEFAULT_API)
|
|
47
56
|
try {
|
|
48
57
|
bundle = await getTypes(apiUrl)
|
|
49
58
|
source = apiUrl
|
|
50
59
|
} catch (e) {
|
|
51
|
-
if (!local ||
|
|
60
|
+
if (!local || opts.check) throw e
|
|
52
61
|
warn(`${(e as Error).message}`)
|
|
53
62
|
bundle = local
|
|
54
63
|
source = "the CLI's bundled SDK (server unreachable)"
|
|
@@ -56,10 +65,10 @@ export const types = async (args: Args) => {
|
|
|
56
65
|
}
|
|
57
66
|
log(`${c.green("source")} ${source} — ${label(bundle)}`)
|
|
58
67
|
|
|
59
|
-
// ---- report drift, write unless
|
|
68
|
+
// ---- report drift, write unless check ----
|
|
60
69
|
const onDisk = readProjectTypeBundle(root)
|
|
61
70
|
const same = bundleHash(onDisk) === bundleHash(bundle)
|
|
62
|
-
if (
|
|
71
|
+
if (opts.check) {
|
|
63
72
|
if (Object.keys(onDisk).length === 0) throw new CliError(`No types in ${LECODES_DIR}/types/ — run \`lecodes update\`.`)
|
|
64
73
|
if (!same) throw new CliError(`Types are out of date (${label(onDisk)} on disk) — run \`lecodes update\`.`)
|
|
65
74
|
success(`Types are up to date (${label(onDisk)}).`)
|
|
@@ -77,11 +86,11 @@ export const types = async (args: Args) => {
|
|
|
77
86
|
|
|
78
87
|
// The compiler uses the SDK vendored in this CLI, not these declarations — worth saying when the
|
|
79
88
|
// server's surface is a different one, since that gap shows up as "autocompletes but won't build".
|
|
80
|
-
if (!
|
|
89
|
+
if (!opts.local && local && bundleHash(local) !== bundleHash(bundle)) {
|
|
81
90
|
warn("The server's SDK surface differs from the one this CLI compiles with — if something autocompletes but fails to compile, update the CLI (`npm install -g lecodes-cli@latest`) or re-run with --local.")
|
|
82
91
|
}
|
|
83
92
|
|
|
84
93
|
log("")
|
|
85
94
|
success(stats.added + stats.updated + stats.removed > 0 ? "Types synced." : "Types already current.")
|
|
86
|
-
if (!
|
|
95
|
+
if (!opts.dir) note("Reload the TypeScript server in your editor to pick them up.")
|
|
87
96
|
}
|
package/src/compile/collect.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs"
|
|
2
2
|
import { basename } from "node:path"
|
|
3
|
-
import type { Project } from "../api"
|
|
4
|
-
import type { Manifest } from "../manifest"
|
|
5
|
-
import { classifyKind, scanLocal } from "../localFiles"
|
|
6
|
-
import { loadIgnore } from "../ignore"
|
|
7
|
-
import { buildPaths } from "../project"
|
|
3
|
+
import type { Project } from "../platform/api"
|
|
4
|
+
import type { Manifest } from "../project/manifest"
|
|
5
|
+
import { classifyKind, scanLocal } from "../project/localFiles"
|
|
6
|
+
import { loadIgnore } from "../project/ignore"
|
|
7
|
+
import { buildPaths } from "../project/materialize"
|
|
8
8
|
import type { CompileEntry } from "./projectCompile"
|
|
9
9
|
|
|
10
10
|
/*
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs"
|
|
2
2
|
import { basename } from "node:path"
|
|
3
|
-
import type { Manifest } from "../manifest"
|
|
4
|
-
import { classifyKind, scanLocal } from "../localFiles"
|
|
5
|
-
import { loadIgnore } from "../ignore"
|
|
3
|
+
import type { Manifest } from "../project/manifest"
|
|
4
|
+
import { classifyKind, scanLocal } from "../project/localFiles"
|
|
5
|
+
import { loadIgnore } from "../project/ignore"
|
|
6
6
|
import type { CompileEntry } from "./projectCompile"
|
|
7
7
|
|
|
8
8
|
/*
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs"
|
|
2
2
|
import { basename } from "node:path"
|
|
3
|
-
import type { Manifest } from "../manifest"
|
|
4
|
-
import { classifyKind, scanLocal } from "../localFiles"
|
|
5
|
-
import { loadIgnore } from "../ignore"
|
|
3
|
+
import type { Manifest } from "../project/manifest"
|
|
4
|
+
import { classifyKind, scanLocal } from "../project/localFiles"
|
|
5
|
+
import { loadIgnore } from "../project/ignore"
|
|
6
6
|
import { compileProject, type CompileEntry } from "./projectCompile"
|
|
7
7
|
import { applyAssetIcons } from "./assetIcons"
|
|
8
8
|
import { applyProjectFonts } from "./fonts"
|
|
@@ -18,7 +18,7 @@ import { applyProjectFonts } from "./fonts"
|
|
|
18
18
|
|
|
19
19
|
export type DesignAssetMode = "server" | "disk"
|
|
20
20
|
|
|
21
|
-
// Must match lecodes-
|
|
21
|
+
// Must match lecodes-headless's LOCAL_ASSET_SCHEME (headless/index.ts) — same as `lecodes render`.
|
|
22
22
|
const LOCAL_ASSET_SCHEME = "lecodesfile://"
|
|
23
23
|
|
|
24
24
|
export const compileDesignScreen = async (opts: {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { getProject } from "../api"
|
|
2
|
-
import { loadConfig, requireApiUrl, requireToken } from "../config"
|
|
3
|
-
import { findProjectRoot, readManifest, type Manifest } from "../manifest"
|
|
1
|
+
import { getProject } from "../platform/api"
|
|
2
|
+
import { loadConfig, requireApiUrl, requireToken } from "../platform/config"
|
|
3
|
+
import { findProjectRoot, readManifest, type Manifest } from "../project/manifest"
|
|
4
4
|
import { collectEntries } from "./collect"
|
|
5
5
|
import { collectLocalEntries } from "./collectLocal"
|
|
6
6
|
import { ambiguousEntryWarning, compileProject } from "./projectCompile"
|
|
7
7
|
import { applyProjectFonts } from "./fonts"
|
|
8
8
|
import { applyAppAssetIcons } from "./assetIcons"
|
|
9
9
|
import type { ScreenTarget } from "./screenEntry"
|
|
10
|
-
import {
|
|
10
|
+
import { note } from "../cli"
|
|
11
11
|
|
|
12
12
|
/*
|
|
13
13
|
* Compile the project into the iife bundle the headless renderer runs — shared by `lecodes render`
|
|
@@ -23,7 +23,7 @@ import { flagBool, flagStr, note, type Args } from "../util"
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
// URL scheme emitted for a local project's resources so the renderer reads them off disk. Must match
|
|
26
|
-
// lecodes-
|
|
26
|
+
// lecodes-headless's LOCAL_ASSET_SCHEME (headless/runProject.ts).
|
|
27
27
|
export const LOCAL_ASSET_SCHEME = "lecodesfile://"
|
|
28
28
|
|
|
29
29
|
export type HeadlessBundle = {
|
|
@@ -36,8 +36,12 @@ export type HeadlessBundle = {
|
|
|
36
36
|
useLocalAssets: boolean
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
/**
|
|
40
|
-
|
|
39
|
+
/** What a headless compile honours: `--entry` / `--public-url` / `--remote-assets` / `--wait-network`,
|
|
40
|
+
* and the screen module a `render <file>` named. */
|
|
41
|
+
export type HeadlessOptions = { entry?: string, publicUrl?: string, remoteAssets?: boolean, waitNetwork?: boolean, screen?: ScreenTarget }
|
|
42
|
+
|
|
43
|
+
/** Compile the local project for a headless run. */
|
|
44
|
+
export const compileHeadlessBundle = async (opts: HeadlessOptions = {}): Promise<HeadlessBundle> => {
|
|
41
45
|
let root: string
|
|
42
46
|
let manifest: Manifest | null
|
|
43
47
|
try {
|
|
@@ -48,9 +52,9 @@ export const compileHeadlessBundle = async (args: Args, opts: { screen?: ScreenT
|
|
|
48
52
|
manifest = null
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
const publicUrlFlag =
|
|
55
|
+
const publicUrlFlag = opts.publicUrl
|
|
52
56
|
// A local (server-less) project has no remote asset URLs — it always reads from disk.
|
|
53
|
-
const useLocalAssets = manifest ? (!publicUrlFlag && !
|
|
57
|
+
const useLocalAssets = manifest ? (!publicUrlFlag && !opts.remoteAssets) : true
|
|
54
58
|
|
|
55
59
|
let entries
|
|
56
60
|
let warnings: string[]
|
|
@@ -73,7 +77,7 @@ export const compileHeadlessBundle = async (args: Args, opts: { screen?: ScreenT
|
|
|
73
77
|
;({ entries, warnings } = collectLocalEntries(root, null, ""))
|
|
74
78
|
name = root.split(/[\\/]/).pop() ?? "project"
|
|
75
79
|
publicUrl = LOCAL_ASSET_SCHEME
|
|
76
|
-
if (
|
|
80
|
+
if (opts.remoteAssets || publicUrlFlag) {
|
|
77
81
|
warnings.push("This is a local project (no server) — assets always load from the working tree.")
|
|
78
82
|
}
|
|
79
83
|
}
|
|
@@ -103,7 +107,7 @@ export const compileHeadlessBundle = async (args: Args, opts: { screen?: ScreenT
|
|
|
103
107
|
await applyAppAssetIcons(compileEntries)
|
|
104
108
|
|
|
105
109
|
note("Compiling…")
|
|
106
|
-
const entryFlag = opts.screen?.entryPath ??
|
|
110
|
+
const entryFlag = opts.screen?.entryPath ?? opts.entry
|
|
107
111
|
if (!entryFlag) {
|
|
108
112
|
const ambiguous = ambiguousEntryWarning(compileEntries)
|
|
109
113
|
if (ambiguous) warnings.push(ambiguous)
|
|
@@ -121,7 +125,7 @@ export const compileHeadlessBundle = async (args: Args, opts: { screen?: ScreenT
|
|
|
121
125
|
}
|
|
122
126
|
// With --remote-assets, 2D textures come from the network; without --wait-network they resolve empty
|
|
123
127
|
// and sprites render as flat placeholders. Local assets (default) load from disk, so no hint needed.
|
|
124
|
-
if (js.includes("_creator2d") && !useLocalAssets && !
|
|
128
|
+
if (js.includes("_creator2d") && !useLocalAssets && !opts.waitNetwork) {
|
|
125
129
|
warnings.push("2D scene detected: sprite textures load over the network — add --wait-network to include them (else sprites render as flat placeholders).")
|
|
126
130
|
}
|
|
127
131
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { extractInlineSourceMap, mapNamedFileStack } from "lecodes-
|
|
1
|
+
import { extractInlineSourceMap, mapNamedFileStack } from "lecodes-web-shared/src/sourcemapError"
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
4
|
* De-minify native (QuickJS) stack traces in a desktop host's output.
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { existsSync } from "node:fs"
|
|
2
2
|
import { createRequire } from "node:module"
|
|
3
3
|
import { dirname, join } from "node:path"
|
|
4
|
-
import type { FileKind } from "../manifest"
|
|
5
|
-
import { distRoot } from "../distRoot"
|
|
6
|
-
import { compileProject as sdkCompileProject, detectEntry, scanModulesChisel, ENTRY_CONVENTION } from "lecodes-sdk/compile"
|
|
4
|
+
import type { FileKind } from "../project/manifest"
|
|
5
|
+
import { distRoot } from "../hosts/distRoot"
|
|
6
|
+
import { compileProject as sdkCompileProject, detectEntry, scanModulesChisel, localAssetNames, ENTRY_CONVENTION } from "lecodes-sdk/compile"
|
|
7
7
|
|
|
8
8
|
// Every project compiles through the SDK's one import-free compiler, which bundles with chisel (the
|
|
9
9
|
// Rust method-granular bundler, @letary/chisel) — the only bundler. The SDK resolves the chisel
|
|
10
10
|
// binary itself; here we just hand it an explicit path when the @letary/chisel package is present,
|
|
11
11
|
// and point it at the SDK *source* tree chisel compiles from:
|
|
12
12
|
// 1. the `lecodes-sdk` package when it resolves — the npm install's dependency, or in the core
|
|
13
|
-
// workspace the live
|
|
13
|
+
// workspace the live sdk (so an SDK edit is picked up with no re-vendoring);
|
|
14
14
|
// 2. else the copy vendored beside the standalone binary (runtime/sdk/, scripts/vendor-runtime.ts
|
|
15
15
|
// --sdk) — the binary has no node_modules.
|
|
16
16
|
const resolveSdkInject = (): string | null => {
|
|
@@ -81,6 +81,9 @@ export type CompileOptions = {
|
|
|
81
81
|
/** Editor compile: the `EDITOR` global compiles to `true` and `*.editor.ts` plugin files are
|
|
82
82
|
* bundled in (scene-editor bundles). Default false — production compiles skip them. */
|
|
83
83
|
editor?: boolean
|
|
84
|
+
/** What the compile has to say that is not an error (a plugin the app uses and app.json does not
|
|
85
|
+
* list). */
|
|
86
|
+
onWarning?: (message: string) => void
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
/** A warning when auto-detection has several import-graph roots and no main.ts to settle it —
|
|
@@ -106,4 +109,9 @@ export const compileProject = (opts: CompileOptions): Promise<string> =>
|
|
|
106
109
|
minify: opts.minify,
|
|
107
110
|
sourcemap: opts.sourcemap,
|
|
108
111
|
editor: opts.editor,
|
|
112
|
+
onWarning: opts.onWarning,
|
|
109
113
|
})
|
|
114
|
+
|
|
115
|
+
/** Re-exported so the CALLERS that stage files next to the bundle name them the way the bundle asks
|
|
116
|
+
* for them (see localAssetNames in the SDK compile — two packs shipping SM_Sign_01.glb). */
|
|
117
|
+
export { localAssetNames }
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto"
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
3
3
|
import { join } from "node:path"
|
|
4
|
-
import type { Manifest } from "../manifest"
|
|
5
|
-
import { scanLocal } from "../localFiles"
|
|
6
|
-
import { loadIgnore } from "../ignore"
|
|
7
|
-
import {
|
|
8
|
-
import { CliError, warnErr } from "../util"
|
|
4
|
+
import type { Manifest } from "../project/manifest"
|
|
5
|
+
import { scanLocal } from "../project/localFiles"
|
|
6
|
+
import { loadIgnore } from "../project/ignore"
|
|
7
|
+
import { warnErr } from "../cli"
|
|
9
8
|
import { compileProject, type CompileEntry } from "./projectCompile"
|
|
10
9
|
import { applyLocalShaders, shadersCacheDir } from "./shaders"
|
|
11
10
|
|
|
@@ -14,8 +13,9 @@ import { applyLocalShaders, shadersCacheDir } from "./shaders"
|
|
|
14
13
|
* point (the platform backend and the standalone playground are the other two). Reads the project
|
|
15
14
|
* off disk, composes the edit-mode bundle with the SDK's shared `sceneEditorEntries`, and drives it
|
|
16
15
|
* through the CLI's chisel + vendored-SDK pipeline. Deliberately mirrors the playground's in-process
|
|
17
|
-
* closure (
|
|
18
|
-
* bundle — only the file source
|
|
16
|
+
* closure (lecodes-3d-editor's src/server/inProcess.ts) field-for-field so every host emits the same
|
|
17
|
+
* bundle — only the file source differs. The harness is the editor's: the caller hands in
|
|
18
|
+
* `sceneHarness()` of the editor package it serves the UI from, so the two are one version.
|
|
19
19
|
*
|
|
20
20
|
* publicUrl "" + resource fileSrc "/project/<rel>" → the scene server's /project/* mount.
|
|
21
21
|
* minify:false — mangling erases class names the inspector matches aspect schemas by.
|
|
@@ -29,22 +29,6 @@ const TEXT_EXT = /\.(ts|js|json|svg|txt|md)$/i
|
|
|
29
29
|
/** Strip scanLocal's leading-slash key convention ("/city.scene.ts" → "city.scene.ts"). */
|
|
30
30
|
const rel = (key: string) => key.replace(/^\//, "")
|
|
31
31
|
|
|
32
|
-
/**
|
|
33
|
-
* Resolve the scene-editor harness sources (packages/projects/src/scene-editor):
|
|
34
|
-
* LECODES_SCENE_HARNESS_DIR env → the in-repo dir (monorepo dev), both via the SDK helper, else
|
|
35
|
-
* the JSON snapshot vendored into the CLI at publish time (runtime/scene-harness.json).
|
|
36
|
-
*/
|
|
37
|
-
const resolveHarness = (loadSceneHarness: (dir?: string) => Record<string, string>): Record<string, string> => {
|
|
38
|
-
const inRepo = join(distRoot, "..", "projects", "src", "scene-editor")
|
|
39
|
-
try {
|
|
40
|
-
return loadSceneHarness(existsSync(inRepo) ? inRepo : undefined)
|
|
41
|
-
} catch {
|
|
42
|
-
const vendored = join(distRoot, "runtime", "scene-harness.json")
|
|
43
|
-
if (existsSync(vendored)) return JSON.parse(readFileSync(vendored, "utf8")) as Record<string, string>
|
|
44
|
-
throw new CliError("scene-editor harness sources not found — reinstall the CLI or set LECODES_SCENE_HARNESS_DIR.")
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
32
|
export const compileSceneEditor = async (opts: {
|
|
49
33
|
root: string
|
|
50
34
|
/** Null in standalone mode (a bare folder, no cloned project). */
|
|
@@ -53,9 +37,11 @@ export const compileSceneEditor = async (opts: {
|
|
|
53
37
|
name: string
|
|
54
38
|
/** Project path of the scene file as the editor sends it ("./city.scene.ts"). */
|
|
55
39
|
scenePath: string
|
|
40
|
+
/** The editor's harness, module name → source (`sceneHarness()` of lecodes-3d-editor/server). */
|
|
41
|
+
harness: Record<string, string>
|
|
56
42
|
}): Promise<{ js: string | null, error?: string }> => {
|
|
57
43
|
const { root, scenePath } = opts
|
|
58
|
-
const {
|
|
44
|
+
const { sceneEditorEntries, sceneImportsOf, SCENE_ENTRY_PATH } = await import("lecodes-sdk/compile")
|
|
59
45
|
|
|
60
46
|
const entries: CompileEntry[] = []
|
|
61
47
|
const scan = scanLocal(root, { ignore: loadIgnore(root), tracked: new Set(Object.keys(opts.manifest?.files ?? {})) })
|
|
@@ -96,10 +82,12 @@ export const compileSceneEditor = async (opts: {
|
|
|
96
82
|
}
|
|
97
83
|
|
|
98
84
|
const scene = scenePath.startsWith("./") ? scenePath : "./" + rel(scenePath)
|
|
99
|
-
|
|
85
|
+
const sceneEntry = entries.find((e) => e.path === scene && e.type === "text")
|
|
86
|
+
if (!sceneEntry) {
|
|
100
87
|
return { js: null, error: `Scene file not found: ${scene}` }
|
|
101
88
|
}
|
|
102
|
-
|
|
89
|
+
const paths = entries.map((e) => e.path)
|
|
90
|
+
entries.push(...sceneEditorEntries(scene, opts.harness, paths, sceneImportsOf(sceneEntry.text ?? "", scene, paths)))
|
|
103
91
|
|
|
104
92
|
try {
|
|
105
93
|
const js = await compileProject({
|
|
@@ -1,127 +1,123 @@
|
|
|
1
|
-
import { existsSync, readFileSync, statSync } from "node:fs"
|
|
2
|
-
import { isAbsolute, join, relative, resolve } from "node:path"
|
|
3
|
-
import { screenFrame } from "../designMeta"
|
|
4
|
-
import { CliError
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
entry
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
frame
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
/** Whether the argument names a path at all (vs a bare screen id). */
|
|
43
|
-
const hasSeparator = (file: string) => file.includes("/") || file.includes("\\")
|
|
44
|
-
|
|
45
|
-
/** Find the screen file: a path (with or without extension), or a bare id resolved against the
|
|
46
|
-
* design folder's screens/ (`lecodes render login`, honoring --dir). */
|
|
47
|
-
const findScreenFile = (root: string, file: string, dirFlag: string | undefined): string => {
|
|
48
|
-
const candidates = [resolve(process.cwd(), file)]
|
|
49
|
-
if (!hasSeparator(file)) {
|
|
50
|
-
candidates.push(join(root, (dirFlag ?? DEFAULT_DESIGN_DIR).replace(/[/\\]+$/, ""), "screens", file))
|
|
51
|
-
}
|
|
52
|
-
for (const base of candidates) {
|
|
53
|
-
if (existsSync(base) && statSync(base).isFile()) return base
|
|
54
|
-
for (const ext of SCREEN_EXTS) if (existsSync(base + ext)) return base + ext
|
|
55
|
-
}
|
|
56
|
-
throw new CliError(
|
|
57
|
-
`No screen file at "${file}".` +
|
|
58
|
-
(hasSeparator(file) ? "" : ` Pass a path, or a screen id that exists in ${dirFlag ?? DEFAULT_DESIGN_DIR}/screens/.`))
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** The design folder a screen file belongs to ("<root>/design" for "/design/screens/login.ts"),
|
|
62
|
-
* or null when it isn't laid out as a design screen. */
|
|
63
|
-
const designDirOf = (root: string, key: string): { dir: string, id: string } | null => {
|
|
64
|
-
const m = /^\/(.*)screens\/([^/]+)\.[^./]+$/.exec(key)
|
|
65
|
-
if (!m) return null
|
|
66
|
-
const dir = join(root, m[1])
|
|
67
|
-
return existsSync(join(dir, "meta.json")) ? { dir, id: m[2] } : null
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Resolve `<file>` into the virtual entry that opens its default export.
|
|
72
|
-
* `state` picks the branch of a `(state) => UIScreen` screen (ignored by a value export).
|
|
73
|
-
*/
|
|
74
|
-
const resolveScreenTarget = (
|
|
75
|
-
root: string, file: string, opts: { state?: string, dir?: string } = {},
|
|
76
|
-
): ScreenTarget => {
|
|
77
|
-
const abs = findScreenFile(root, file, opts.dir)
|
|
78
|
-
const rel = relative(root, abs)
|
|
79
|
-
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
80
|
-
throw new CliError(
|
|
81
|
-
`"${file}" is outside the project (${root}) — a screen renders as part of its project, so its ` +
|
|
82
|
-
`imports and asset() files resolve from the same tree.`)
|
|
83
|
-
}
|
|
84
|
-
const key = toKey(rel)
|
|
85
|
-
|
|
86
|
-
// A file with no default export compiles to an empty render; say so here instead.
|
|
87
|
-
if (!/^\s*export\s+default\b/m.test(readFileSync(abs, "utf8"))) {
|
|
88
|
-
throw new CliError(
|
|
89
|
-
`${key.slice(1)} has no default export. A screen file ends with ` +
|
|
90
|
-
`\`export default UIScreen([...])\` — or \`export default (state) => UIScreen([...])\` for screen states.`)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Same wrapper the design board compiles (designCompile.ts): a function export is invoked with the
|
|
94
|
-
// chosen state (a bare call uses the parameter's default = the canonical state), a value is used
|
|
95
|
-
// as-is, and either way the screen is opened so the runtime has something to render.
|
|
96
|
-
const invoke = opts.state !== undefined ? `screen(${JSON.stringify(opts.state)})` : "screen()"
|
|
97
|
-
const spec = "." + key.replace(/\.[^./]+$/, "")
|
|
98
|
-
const design = designDirOf(root, key)
|
|
99
|
-
return {
|
|
100
|
-
entryPath: SCREEN_ENTRY_PATH,
|
|
101
|
-
entry: {
|
|
102
|
-
path: SCREEN_ENTRY_PATH,
|
|
103
|
-
type: "text",
|
|
104
|
-
text: `import screen from ${JSON.stringify(spec)}\n`
|
|
105
|
-
+ `const __screen = typeof screen === "function" ? ${invoke} : screen\n`
|
|
106
|
-
+ `__screen.open()\n`,
|
|
107
|
-
},
|
|
108
|
-
frame: design ? screenFrame(design.dir, design.id) : undefined,
|
|
109
|
-
label: (design ? design.id : key.slice(1)) + (opts.state !== undefined ? `@${opts.state}` : ""),
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* flag combinations that would fight over the entrypoint. */
|
|
115
|
-
export
|
|
116
|
-
|
|
117
|
-
if (file === undefined) {
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
throw new CliError("Use either a screen file or --entry, not both — each names the module to compile from.")
|
|
125
|
-
}
|
|
126
|
-
return resolveScreenTarget(root, file, { state: flagStr(args, "state"), dir: flagStr(args, "dir") })
|
|
127
|
-
}
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs"
|
|
2
|
+
import { isAbsolute, join, relative, resolve } from "node:path"
|
|
3
|
+
import { screenFrame } from "../project/designMeta"
|
|
4
|
+
import { CliError } from "../cli"
|
|
5
|
+
import { toKey } from "../project/paths"
|
|
6
|
+
import type { CompileEntry } from "./projectCompile"
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Rendering ONE screen module instead of the whole app — `lecodes render <file>` (and
|
|
10
|
+
* `--desktop`). A design screen (`design/screens/login.ts`) never boots itself: it default-exports
|
|
11
|
+
* a `UIScreen`, or a `(state) => UIScreen` function for its states, and the design board / snapshot
|
|
12
|
+
* supply the `.open()`. This builds the same virtual entry for the normal project pipeline, so any
|
|
13
|
+
* file written that way — inside design/ or not — renders with the full `lecodes render` flag set
|
|
14
|
+
* (--png/--clip/--script/--device/…) against the project's real imports and assets.
|
|
15
|
+
*
|
|
16
|
+
* Kept in step with compile/designCompile.ts, which does the same wrapping per screen for the board.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Extensions a screen module may carry (so `render design/screens/login` works too). */
|
|
20
|
+
const SCREEN_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs"]
|
|
21
|
+
|
|
22
|
+
/** Where the generated entry lives — the project root, so its import specifier is the screen's
|
|
23
|
+
* own project key ("./design/screens/login"). Leading dot: never a real project file. */
|
|
24
|
+
const SCREEN_ENTRY_PATH = "/.screen-entry.ts"
|
|
25
|
+
|
|
26
|
+
/** The design folder a bare screen id (`lecodes render login`) is looked up in. */
|
|
27
|
+
const DEFAULT_DESIGN_DIR = "design"
|
|
28
|
+
|
|
29
|
+
export type ScreenTarget = {
|
|
30
|
+
/** The virtual entry module — hand it to the compiler alongside the project's own entries. */
|
|
31
|
+
entry: CompileEntry
|
|
32
|
+
/** Path of that entry, for `entryOverride`. */
|
|
33
|
+
entryPath: string
|
|
34
|
+
/** The board frame from the design folder's meta.json, when this IS a design screen. */
|
|
35
|
+
frame?: [number, number]
|
|
36
|
+
/** How the screen is named in progress notes ("design/screens/login.ts", "login@empty"). */
|
|
37
|
+
label: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** `lecodes render <file|screen-id>` — the positional a screen render is asked for by. */
|
|
41
|
+
|
|
42
|
+
/** Whether the argument names a path at all (vs a bare screen id). */
|
|
43
|
+
const hasSeparator = (file: string) => file.includes("/") || file.includes("\\")
|
|
44
|
+
|
|
45
|
+
/** Find the screen file: a path (with or without extension), or a bare id resolved against the
|
|
46
|
+
* design folder's screens/ (`lecodes render login`, honoring --dir). */
|
|
47
|
+
const findScreenFile = (root: string, file: string, dirFlag: string | undefined): string => {
|
|
48
|
+
const candidates = [resolve(process.cwd(), file)]
|
|
49
|
+
if (!hasSeparator(file)) {
|
|
50
|
+
candidates.push(join(root, (dirFlag ?? DEFAULT_DESIGN_DIR).replace(/[/\\]+$/, ""), "screens", file))
|
|
51
|
+
}
|
|
52
|
+
for (const base of candidates) {
|
|
53
|
+
if (existsSync(base) && statSync(base).isFile()) return base
|
|
54
|
+
for (const ext of SCREEN_EXTS) if (existsSync(base + ext)) return base + ext
|
|
55
|
+
}
|
|
56
|
+
throw new CliError(
|
|
57
|
+
`No screen file at "${file}".` +
|
|
58
|
+
(hasSeparator(file) ? "" : ` Pass a path, or a screen id that exists in ${dirFlag ?? DEFAULT_DESIGN_DIR}/screens/.`))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The design folder a screen file belongs to ("<root>/design" for "/design/screens/login.ts"),
|
|
62
|
+
* or null when it isn't laid out as a design screen. */
|
|
63
|
+
const designDirOf = (root: string, key: string): { dir: string, id: string } | null => {
|
|
64
|
+
const m = /^\/(.*)screens\/([^/]+)\.[^./]+$/.exec(key)
|
|
65
|
+
if (!m) return null
|
|
66
|
+
const dir = join(root, m[1])
|
|
67
|
+
return existsSync(join(dir, "meta.json")) ? { dir, id: m[2] } : null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Resolve `<file>` into the virtual entry that opens its default export.
|
|
72
|
+
* `state` picks the branch of a `(state) => UIScreen` screen (ignored by a value export).
|
|
73
|
+
*/
|
|
74
|
+
const resolveScreenTarget = (
|
|
75
|
+
root: string, file: string, opts: { state?: string, dir?: string } = {},
|
|
76
|
+
): ScreenTarget => {
|
|
77
|
+
const abs = findScreenFile(root, file, opts.dir)
|
|
78
|
+
const rel = relative(root, abs)
|
|
79
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
80
|
+
throw new CliError(
|
|
81
|
+
`"${file}" is outside the project (${root}) — a screen renders as part of its project, so its ` +
|
|
82
|
+
`imports and asset() files resolve from the same tree.`)
|
|
83
|
+
}
|
|
84
|
+
const key = toKey(rel)
|
|
85
|
+
|
|
86
|
+
// A file with no default export compiles to an empty render; say so here instead.
|
|
87
|
+
if (!/^\s*export\s+default\b/m.test(readFileSync(abs, "utf8"))) {
|
|
88
|
+
throw new CliError(
|
|
89
|
+
`${key.slice(1)} has no default export. A screen file ends with ` +
|
|
90
|
+
`\`export default UIScreen([...])\` — or \`export default (state) => UIScreen([...])\` for screen states.`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Same wrapper the design board compiles (designCompile.ts): a function export is invoked with the
|
|
94
|
+
// chosen state (a bare call uses the parameter's default = the canonical state), a value is used
|
|
95
|
+
// as-is, and either way the screen is opened so the runtime has something to render.
|
|
96
|
+
const invoke = opts.state !== undefined ? `screen(${JSON.stringify(opts.state)})` : "screen()"
|
|
97
|
+
const spec = "." + key.replace(/\.[^./]+$/, "")
|
|
98
|
+
const design = designDirOf(root, key)
|
|
99
|
+
return {
|
|
100
|
+
entryPath: SCREEN_ENTRY_PATH,
|
|
101
|
+
entry: {
|
|
102
|
+
path: SCREEN_ENTRY_PATH,
|
|
103
|
+
type: "text",
|
|
104
|
+
text: `import screen from ${JSON.stringify(spec)}\n`
|
|
105
|
+
+ `const __screen = typeof screen === "function" ? ${invoke} : screen\n`
|
|
106
|
+
+ `__screen.open()\n`,
|
|
107
|
+
},
|
|
108
|
+
frame: design ? screenFrame(design.dir, design.id) : undefined,
|
|
109
|
+
label: (design ? design.id : key.slice(1)) + (opts.state !== undefined ? `@${opts.state}` : ""),
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The screen target named on the command line (`render <file>`), or undefined when no screen was
|
|
114
|
+
* named. Rejects the flag combinations that would fight over the entrypoint. */
|
|
115
|
+
export type ScreenArgs = { file?: string, state?: string, entry?: string, dir?: string }
|
|
116
|
+
export const screenTargetFrom = (root: string, a: ScreenArgs): ScreenTarget | undefined => {
|
|
117
|
+
if (a.file === undefined) {
|
|
118
|
+
if (a.state !== undefined) throw new CliError("--state picks a screen's state — name the screen file too: lecodes render <file> --state <name>")
|
|
119
|
+
return undefined
|
|
120
|
+
}
|
|
121
|
+
if (a.entry !== undefined) throw new CliError("Use either a screen file or --entry, not both — each names the module to compile from.")
|
|
122
|
+
return resolveScreenTarget(root, a.file, { state: a.state, dir: a.dir })
|
|
123
|
+
}
|