/** * One headless engine, ready to voxelize — the arrangement `bitmagic generate prop`, `generate * vehicle` and `assets add ` all need and none should own. * * Only the engine can voxelize, so each of those runs the project's own game in a headless Chrome * and talks to it over the forge transport. Getting there is five steps in a fixed order, and the * teardown has to run in the reverse order whatever happened: * * 0. build the project — vite serves `dist/`, and a `bitmagic upgrade` (or any edit) since the * last build otherwise runs the PREVIOUS engine, which is invisible until an engine fix that * was just vendored fails to take effect; * 1. reserve a port in 3000–3199 — the only origins the asset CDN allows, so the page can fetch * the project's assets and the GLB it is about to bake; * 2. spawn the project's vendored `vite` on it, and wait for it; * 3. start the upload proxy — the engine uploads the baked `.vxl` itself, to a presigned URL it * asks for at a hardcoded path with no credentials, and the proxy turns that into the * authenticated api-server route while keeping the token out of the page * (see `upload-proxy.ts`); * 4. launch Chrome on the page and wait for the game to load; * 5. run the caller's body against `host.transport`, then close the page (it may be mid-upload), * then the proxy it was uploading through, then the server underneath both. * * Three commands carrying three copies of this is how a fix to one of them fails to reach the * other two, so it lives here once. The two setup failures a creator can actually hit — no free * port, vite not coming up — are reported with the caller's own `retryHint`, because what is kept * and how to retry differs per command (a paid mesh, a paid vehicle design, an already-uploaded * file) and that sentence is the useful part of the message. */ import type { ChildProcess } from 'child_process'; import type { Environment } from '../config/environments.js'; import { ForgeBrowserHost } from './browser-host.js'; import { type ProjectContext } from '../project/context.js'; /** Voxelizing a dense mesh is minutes of work in the browser, not seconds. */ export declare const VOXELIZE_TIMEOUT_MS: number; export interface VoxelizeSessionOptions { context: ProjectContext; environment: Environment; token: string; log: (message: string) => void; /** * What cannot happen if the browser does not come up — "the mesh", "the vehicle", * `"tree.glb"` — completing "…so ${subject} cannot be voxelized." */ subject: string; /** Appended to a setup failure: what has been kept and the command that retries it. */ retryHint: string; } export interface VoxelizeSession { host: ForgeBrowserHost; /** The port the game is served on — handy for a unique transport `requestId`. */ port: number; } /** * Build the project to completion. Every alias resolves into dist/, so serving before the first * emit 404s every module — a failure that reads as "the engine is broken", not "not built" — and * serving a stale dist/ runs whatever engine was last compiled, not the one in `engine/`. * * `consequence` completes "Build failed, so …": what this command cannot do without a build. */ export declare function buildProject(context: ProjectContext, log: (message: string) => void, consequence: string, retryHint: string): void; export declare function withVoxelizeSession(options: VoxelizeSessionOptions, body: (session: VoxelizeSession) => Promise): Promise; /** * Run one teardown step, reporting a failure rather than propagating it. Cleanup must never mask * the error that caused it, and must never stop the steps that follow. */ export declare function closeQuietly(what: string, close: () => Promise | undefined, log: (message: string) => void): Promise; /** * Stop vite, then stop waiting for it. `unref` matters as much as the signal: the child was * spawned with a piped stderr, so its handle keeps the parent's event loop alive and a vite that * ignores SIGTERM left the CLI hanging after it had already printed its result. */ export declare function killQuietly(server: ChildProcess, log: (message: string) => void): void;