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
package/src/commands/test.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs"
|
|
2
2
|
import { dirname, resolve } from "node:path"
|
|
3
3
|
import { compileHeadlessBundle } from "../compile/headlessBundle"
|
|
4
|
-
import { desktopDefaultViewport, desktopPluginLibraries, resolveDesktopRenderer } from "./
|
|
5
|
-
import { fileScenarioIO, requireRenderer, resolveFixtures, resolveSafeAreaChoice } from "./render"
|
|
4
|
+
import { desktopDefaultViewport, desktopPluginLibraries, resolveDesktopRenderer } from "./app/shared"
|
|
5
|
+
import { desktopRenderFlags, fileScenarioIO, requireRenderer, resolveFixtures, resolveSafeAreaChoice, viewportFlags } from "./render"
|
|
6
6
|
import { buildBundle } from "./compile"
|
|
7
|
-
import { findProjectRoot } from "../manifest"
|
|
8
|
-
import { runDesktopRender } from "../desktopRenderer"
|
|
9
|
-
import { evaluateDesktopRun, translateScenario } from "../desktopScript"
|
|
10
|
-
import { CliError,
|
|
7
|
+
import { findProjectRoot } from "../project/manifest"
|
|
8
|
+
import { runDesktopRender } from "../hosts/desktopRenderer"
|
|
9
|
+
import { evaluateDesktopRun, translateScenario } from "../hosts/desktopScript"
|
|
10
|
+
import { CliError, bool, c, defineCommand, log, logErr, note, num, str, warnErr, type FlagValues } from "../cli"
|
|
11
|
+
import { bundleFlags, bundleOptions } from "./shared"
|
|
11
12
|
|
|
12
13
|
/*
|
|
13
|
-
* `lecodes test` — run the project's headless flow tests (needs the optional `lecodes-
|
|
14
|
+
* `lecodes test` — run the project's headless flow tests (needs the optional `lecodes-headless`
|
|
14
15
|
* package). Two sources, both executed as scenario step lists against a fresh session each:
|
|
15
16
|
*
|
|
16
17
|
* 1. `tests/*.flow.json` — hand-written scenarios (same schema as `lecodes render --script`:
|
|
@@ -26,6 +27,11 @@ import { CliError, c, flagBool, flagStr, log, logErr, note, warnErr, type Args }
|
|
|
26
27
|
*
|
|
27
28
|
* A failing step stops its flow and dumps the failing frame (JSON + PNG when @napi-rs/canvas is
|
|
28
29
|
* installed) under tests/.artifacts/<flow>/ so a red test explains itself without a rerun.
|
|
30
|
+
*
|
|
31
|
+
* `--desktop` runs the file scenarios through the NATIVE host (desktopScript.ts — targets by name or
|
|
32
|
+
* coordinates, expect.log + expect.paint checked, the semantic expects reported skipped). The
|
|
33
|
+
* design-derived flows need `expect.screen` and `fill`, which the host can't answer, so they stay
|
|
34
|
+
* headless-only. The viewport / timing and --desktop flags are `lecodes render`'s own groups.
|
|
29
35
|
*/
|
|
30
36
|
|
|
31
37
|
type DesignEdge = { from: string, to: string, label?: string, activator?: string }
|
|
@@ -37,7 +43,7 @@ type FlowCase = {
|
|
|
37
43
|
steps: unknown
|
|
38
44
|
/** Canned fetch responses for this case's session. File scenarios: their envelope `fixtures`
|
|
39
45
|
* (inline or a path). Design flows: the project pack (tests/fixtures.json / --fixtures). */
|
|
40
|
-
fixtures?: import("lecodes-
|
|
46
|
+
fixtures?: import("lecodes-headless/headless").Fixtures
|
|
41
47
|
/** Per-file envelope overrides (file scenarios only). */
|
|
42
48
|
width?: number
|
|
43
49
|
height?: number
|
|
@@ -131,19 +137,47 @@ const readFlowFile = (abs: string, displayName: string): FlowCase => {
|
|
|
131
137
|
}
|
|
132
138
|
}
|
|
133
139
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
+
const testFlags = {
|
|
141
|
+
flows: bool("run ONLY the design-derived flows"),
|
|
142
|
+
"no-flows": bool("run ONLY the tests/*.flow.json files"),
|
|
143
|
+
fixtures: str("fixture pack for the design flows (overrides the tests/fixtures.json default)", { value: "<file>" }),
|
|
144
|
+
json: bool("machine-readable report on stdout"),
|
|
145
|
+
dir: str("tests folder", { value: "<folder>", default: "tests" }),
|
|
146
|
+
"design-dir": str("design folder", { value: "<dir>", default: "design" }),
|
|
147
|
+
artifacts: str("failure-artifact folder override (default <tests>/.artifacts)", { value: "<dir>" }),
|
|
148
|
+
timeout: num("hard cap on the whole run (default 20000 + 10000 per case; --desktop 60000 + 60000 per case)", { value: "<ms>" }),
|
|
149
|
+
...viewportFlags,
|
|
150
|
+
...bundleFlags,
|
|
151
|
+
...desktopRenderFlags,
|
|
152
|
+
desktop: bool("run the file scenarios through the NATIVE host: the same steps, targets and expects as headless (expect.audio and the json output excepted); design flows stay headless. --frames / --fixed-dt as in render --desktop"),
|
|
153
|
+
}
|
|
154
|
+
// (`sim` comes with desktopRenderFlags: the file scenarios through the WINDOWLESS host — no GPU, no
|
|
155
|
+
// pixels, expect.log only; the same coordinate-only contract as --desktop minus screenshots.)
|
|
156
|
+
|
|
157
|
+
type TestFlags = FlagValues<typeof testFlags>
|
|
158
|
+
|
|
159
|
+
export default defineCommand({
|
|
160
|
+
name: "test",
|
|
161
|
+
summary: "Run the flow tests (tests/*.flow.json + design-derived flows)",
|
|
162
|
+
usage: "[name…]",
|
|
163
|
+
description: "Headless flow tests: tests/*.flow.json scenarios, plus flows derived from the design graph (design/meta.json activator edges — checks the built app against the designed navigation; needs named elements + screen roots). Failing steps dump the frame (JSON + PNG) under tests/.artifacts/<flow>/.\n\nDesign flows read tests/fixtures.json automatically (canned fetch responses) so backend-gated transitions run headless; file scenarios declare their own via the \"fixtures\" envelope. [name…] runs only those files (a path, or a name under the tests folder).",
|
|
164
|
+
flags: testFlags,
|
|
165
|
+
examples: ["lecodes test", "lecodes test hero --logs", "lecodes test --flows --json", "lecodes test --desktop"],
|
|
166
|
+
run: ({ args, flags }) => runTests(args, flags),
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
const runTests = async (args: string[], flags: TestFlags): Promise<void> => {
|
|
170
|
+
// --sim is the native path on the windowless host: everything --desktop does, minus pixels.
|
|
171
|
+
const sim = flags.sim
|
|
172
|
+
const desktop = flags.desktop || sim
|
|
173
|
+
if (desktop && flags.flows) throw new CliError(`--flows needs the headless renderer (design flows read expect.screen and fill inputs) — drop --${sim ? "sim" : "desktop"}.`)
|
|
140
174
|
// --desktop: ONE renderer decision (host binary + the staged .filamat variant). The root is
|
|
141
175
|
// recomputed here because `root` below comes from the headless bundle, which --desktop skips.
|
|
142
176
|
let desktopRoot: string
|
|
143
177
|
try { desktopRoot = findProjectRoot(process.cwd()) } catch { desktopRoot = process.cwd() }
|
|
144
|
-
const desktopRenderer = desktop ? resolveDesktopRenderer(desktopRoot,
|
|
145
|
-
const native = desktop ? await buildBundle(
|
|
146
|
-
const headless = desktop ? null : await compileHeadlessBundle(
|
|
178
|
+
const desktopRenderer = desktop ? resolveDesktopRenderer(desktopRoot, flags.renderer) : undefined
|
|
179
|
+
const native = desktop ? await buildBundle(bundleOptions(flags), { localAssets: true, desktopRenderer }) : null
|
|
180
|
+
const headless = desktop ? null : await compileHeadlessBundle({ entry: flags.entry, publicUrl: flags["public-url"], remoteAssets: flags["remote-assets"], waitNetwork: flags["wait-network"] })
|
|
147
181
|
const warnings = headless?.warnings ?? []
|
|
148
182
|
const useLocalAssets = headless?.useLocalAssets ?? false
|
|
149
183
|
const js = headless?.js ?? ""
|
|
@@ -151,22 +185,21 @@ export const test = async (args: Args) => {
|
|
|
151
185
|
if (headless) root = headless.root
|
|
152
186
|
else { try { root = findProjectRoot(process.cwd()) } catch { root = process.cwd() } }
|
|
153
187
|
const renderer = desktop ? null : await requireRenderer()
|
|
154
|
-
const nativeFrames =
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
if (!Number.isFinite(nativeFixedDt) || nativeFixedDt < 0 || nativeFixedDt > 1000) throw new CliError(`--fixed-dt wants a number of ms (0 = wall clock, max 1000), got "${fixedDtFlag}".`)
|
|
188
|
+
const nativeFrames = flags.frames || 60
|
|
189
|
+
const nativeFixedDt = flags["fixed-dt"] ?? 16
|
|
190
|
+
if (nativeFixedDt < 0 || nativeFixedDt > 1000) throw new CliError(`--fixed-dt wants a number of ms (0 = wall clock, max 1000), got "${nativeFixedDt}".`)
|
|
158
191
|
// Desktop-only projects (app.json desktop block, no ios/android) run flows desktop-sized.
|
|
159
192
|
const projectViewport = desktopDefaultViewport(root)
|
|
160
193
|
|
|
161
|
-
const testsDir = resolve(root,
|
|
162
|
-
const designMetaPath = resolve(root,
|
|
163
|
-
const asJson =
|
|
194
|
+
const testsDir = resolve(root, flags.dir)
|
|
195
|
+
const designMetaPath = resolve(root, flags["design-dir"], "meta.json")
|
|
196
|
+
const asJson = flags.json
|
|
164
197
|
const out = asJson ? logErr : log // --json owns stdout; the human report moves to stderr
|
|
165
198
|
|
|
166
199
|
// 1. File scenarios: the given paths, or every tests/*.flow.json.
|
|
167
200
|
const cases: FlowCase[] = []
|
|
168
|
-
if (args.
|
|
169
|
-
for (const given of args
|
|
201
|
+
if (args.length > 0) {
|
|
202
|
+
for (const given of args) {
|
|
170
203
|
const candidates = [resolve(given), resolve(testsDir, given), resolve(testsDir, `${given}.flow.json`)]
|
|
171
204
|
const abs = candidates.find((p) => existsSync(p))
|
|
172
205
|
if (!abs) throw new CliError(`No such test: "${given}" (looked in ${testsDir}).`)
|
|
@@ -182,20 +215,19 @@ export const test = async (args: Args) => {
|
|
|
182
215
|
// usually backend-gated). `tests/fixtures.json` is picked up automatically; --fixtures overrides
|
|
183
216
|
// the path. File scenarios do NOT inherit it — they declare their own via the envelope, so a
|
|
184
217
|
// deliberately-offline scenario (error states) stays offline.
|
|
185
|
-
const fixturesFlag = flagStr(args, "fixtures")
|
|
186
218
|
const defaultPack = resolve(testsDir, "fixtures.json")
|
|
187
|
-
const flowFixtures =
|
|
188
|
-
? resolveFixtures(
|
|
219
|
+
const flowFixtures = flags.fixtures !== undefined
|
|
220
|
+
? resolveFixtures(flags.fixtures, ".")
|
|
189
221
|
: existsSync(defaultPack) ? resolveFixtures("fixtures.json", testsDir) : undefined
|
|
190
222
|
|
|
191
223
|
// 2. Design-graph flows (opt out with --no-flows; --flows runs ONLY these).
|
|
192
|
-
const flowsOnly =
|
|
193
|
-
const wantFlows = !
|
|
224
|
+
const flowsOnly = flags.flows
|
|
225
|
+
const wantFlows = !flags["no-flows"] && args.length === 0
|
|
194
226
|
if (flowsOnly) cases.length = 0
|
|
195
227
|
let skipped: SkippedFlow[] = []
|
|
196
228
|
let entryScreen: string | null = null
|
|
197
229
|
if (desktop && wantFlows && existsSync(designMetaPath)) {
|
|
198
|
-
note("Design-derived flows skipped with --desktop (they
|
|
230
|
+
note("Design-derived flows skipped with --desktop (they read expect.screen and fill inputs; run `lecodes test` headless for them).")
|
|
199
231
|
}
|
|
200
232
|
if (!desktop && (wantFlows || flowsOnly) && existsSync(designMetaPath)) {
|
|
201
233
|
let meta: DesignMeta
|
|
@@ -211,7 +243,7 @@ export const test = async (args: Args) => {
|
|
|
211
243
|
entryScreen = probe.screenName()
|
|
212
244
|
probe.dispose()
|
|
213
245
|
if (entryScreen === null) {
|
|
214
|
-
warnErr("Design flows skipped: the app's start screen root has no `name`. Name each screen root with its design screen id (e.g. UIScreen(
|
|
246
|
+
warnErr("Design flows skipped: the app's start screen root has no `name`. Name each screen root with its design screen id (e.g. UIScreen(...).named(\"login\")).")
|
|
215
247
|
} else {
|
|
216
248
|
const derived = deriveDesignFlows(meta, entryScreen)
|
|
217
249
|
cases.push(...derived.flows.map((f) => ({ ...f, fixtures: flowFixtures })))
|
|
@@ -229,17 +261,17 @@ export const test = async (args: Args) => {
|
|
|
229
261
|
|
|
230
262
|
// Hard timeout across the whole run (each case is bounded by settles, but a pathological app can
|
|
231
263
|
// still spin) — generous per-case allowance, overridable.
|
|
232
|
-
const timeoutMs =
|
|
264
|
+
const timeoutMs = flags.timeout || (desktop ? 60000 + cases.length * 60000 : 20000 + cases.length * 10000)
|
|
233
265
|
const killer = setTimeout(() => {
|
|
234
266
|
warnErr(`Test run timed out after ${timeoutMs}ms (raise --timeout or lower --settle).`)
|
|
235
267
|
process.exit(1)
|
|
236
268
|
}, timeoutMs)
|
|
237
269
|
killer.unref?.()
|
|
238
270
|
|
|
239
|
-
const onConsole =
|
|
271
|
+
const onConsole = flags.logs
|
|
240
272
|
? (level: string, text: string) => logErr(`${c.dim(`[${level}]`)} ${text}`)
|
|
241
273
|
: undefined
|
|
242
|
-
const artifactsRoot = resolve(root,
|
|
274
|
+
const artifactsRoot = resolve(root, flags.artifacts ?? resolve(testsDir, ".artifacts"))
|
|
243
275
|
|
|
244
276
|
type CaseResult = {
|
|
245
277
|
name: string
|
|
@@ -251,23 +283,38 @@ export const test = async (args: Args) => {
|
|
|
251
283
|
}
|
|
252
284
|
const results: CaseResult[] = []
|
|
253
285
|
|
|
286
|
+
const reportCase = (result: CaseResult): void => {
|
|
287
|
+
const skippedCount = result.steps.reduce((n, s) => n + (s.skipped?.length ?? 0), 0)
|
|
288
|
+
if (result.ok) {
|
|
289
|
+
out(`${c.green("✓")} ${result.name}${skippedCount > 0 ? c.dim(` (${skippedCount} semantic check${skippedCount === 1 ? "" : "s"} skipped on --desktop)`) : ""}`)
|
|
290
|
+
} else {
|
|
291
|
+
out(`${c.red("✗")} ${result.name}`)
|
|
292
|
+
if (result.error) out(` ${c.red(result.error)}`)
|
|
293
|
+
for (const s of result.steps) {
|
|
294
|
+
out(s.ok ? c.dim(` ✓ ${s.desc}`) : ` ${c.red(`✗ ${s.desc} — ${s.error}`)}`)
|
|
295
|
+
for (const sk of s.skipped ?? []) out(c.dim(` – skipped: ${sk}`))
|
|
296
|
+
}
|
|
297
|
+
if (result.artifacts.length > 0) out(c.dim(` failing frame: ${result.artifacts.join(", ")}`))
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
254
301
|
// Native case runner: each case is its own host process (fresh state, like a fresh session);
|
|
255
302
|
// per-step screenshots + the final frame land under the case's artifacts folder.
|
|
256
303
|
const runNativeCase = async (kase: FlowCase): Promise<CaseResult> => {
|
|
257
304
|
const caseDir = resolve(artifactsRoot, slug(kase.name))
|
|
258
|
-
const plan = translateScenario(kase.steps, { outDir: caseDir, headroomFrames: nativeFrames })
|
|
305
|
+
const plan = translateScenario(kase.steps, { outDir: caseDir, headroomFrames: nativeFrames, sim })
|
|
259
306
|
for (const st of plan.steps) if (st.screenshot) mkdirSync(dirname(st.screenshot), { recursive: true })
|
|
260
307
|
mkdirSync(caseDir, { recursive: true })
|
|
261
308
|
const finalPng = resolve(caseDir, "final.png")
|
|
262
309
|
const { output } = await runDesktopRender(native!.js, native!.resources, {
|
|
263
|
-
renderer: desktopRenderer,
|
|
310
|
+
renderer: desktopRenderer, host: sim ? "server" : "desktop",
|
|
264
311
|
pluginDirs: desktopPluginLibraries(root).dirs,
|
|
265
|
-
width:
|
|
266
|
-
height:
|
|
267
|
-
frames: plan.frameCap, outPng: finalPng, logs:
|
|
312
|
+
width: flags.width || kase.width || projectViewport?.width || 390,
|
|
313
|
+
height: flags.height || kase.height || projectViewport?.height || 844,
|
|
314
|
+
frames: plan.frameCap, outPng: finalPng, logs: flags.logs,
|
|
268
315
|
timeoutMs: 60000 + plan.steps.length * 5000, script: plan.text, fixedDtMs: nativeFixedDt,
|
|
269
316
|
})
|
|
270
|
-
const r = evaluateDesktopRun(plan, output)
|
|
317
|
+
const r = evaluateDesktopRun(plan, output, (path) => existsSync(path) ? readFileSync(path, "utf8") : null)
|
|
271
318
|
if (!r.loaded) throw new CliError("This desktop host doesn't support scripted input (CREATOR_SCRIPT). Update it: lecodes desktop update (or point LECODES_DESKTOP_EXE at a newer build).")
|
|
272
319
|
return {
|
|
273
320
|
name: kase.name, kind: kase.kind, ok: r.ok,
|
|
@@ -278,7 +325,6 @@ export const test = async (args: Args) => {
|
|
|
278
325
|
}
|
|
279
326
|
|
|
280
327
|
for (const kase of cases) {
|
|
281
|
-
const flagSettle = flagStr(args, "settle")
|
|
282
328
|
let result: CaseResult
|
|
283
329
|
if (desktop) {
|
|
284
330
|
try {
|
|
@@ -295,13 +341,13 @@ export const test = async (args: Args) => {
|
|
|
295
341
|
const steps = renderer!.parseScenarioSteps(kase.steps)
|
|
296
342
|
// Every case runs in a FRESH session — no state bleeds between flows.
|
|
297
343
|
const session = await renderer!.createSession(js, {
|
|
298
|
-
width:
|
|
299
|
-
height:
|
|
300
|
-
settleMs:
|
|
301
|
-
waitNetwork:
|
|
344
|
+
width: flags.width || kase.width || projectViewport?.width || 390,
|
|
345
|
+
height: flags.height || kase.height || projectViewport?.height || 844,
|
|
346
|
+
settleMs: flags.settle ?? kase.settle,
|
|
347
|
+
waitNetwork: flags["wait-network"],
|
|
302
348
|
localAssets: useLocalAssets,
|
|
303
349
|
fixtures: kase.fixtures,
|
|
304
|
-
...resolveSafeAreaChoice(
|
|
350
|
+
...resolveSafeAreaChoice(flags["safe-area"] ?? kase.safeArea, flags.device ?? kase.device),
|
|
305
351
|
onConsole,
|
|
306
352
|
})
|
|
307
353
|
try {
|
|
@@ -323,21 +369,6 @@ export const test = async (args: Args) => {
|
|
|
323
369
|
reportCase(result)
|
|
324
370
|
}
|
|
325
371
|
|
|
326
|
-
function reportCase(result: CaseResult): void {
|
|
327
|
-
const skippedCount = result.steps.reduce((n, s) => n + (s.skipped?.length ?? 0), 0)
|
|
328
|
-
if (result.ok) {
|
|
329
|
-
out(`${c.green("✓")} ${result.name}${skippedCount > 0 ? c.dim(` (${skippedCount} semantic check${skippedCount === 1 ? "" : "s"} skipped on --desktop)`) : ""}`)
|
|
330
|
-
} else {
|
|
331
|
-
out(`${c.red("✗")} ${result.name}`)
|
|
332
|
-
if (result.error) out(` ${c.red(result.error)}`)
|
|
333
|
-
for (const s of result.steps) {
|
|
334
|
-
out(s.ok ? c.dim(` ✓ ${s.desc}`) : ` ${c.red(`✗ ${s.desc} — ${s.error}`)}`)
|
|
335
|
-
for (const sk of s.skipped ?? []) out(c.dim(` – skipped: ${sk}`))
|
|
336
|
-
}
|
|
337
|
-
if (result.artifacts.length > 0) out(c.dim(` failing frame: ${result.artifacts.join(", ")}`))
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
372
|
for (const w of warnings) warnErr(w)
|
|
342
373
|
const passed = results.filter((r) => r.ok).length
|
|
343
374
|
const failed = results.length - passed
|