import { type PortLane } from './port-lane.js'; /** * The port window the platform's bucket CORS policy allowlists as an origin. * * A scaffolded project fetches its forged level (`.vwld`) and its GLBs from the portal CDN, so a * project served on localhost is making CROSS-ORIGIN requests for them. The bucket answers with * `Access-Control-Allow-Origin` only for the origins named in its CORS config — the portal and * creator hosts, plus `http://localhost:3000`–`3199` (roleverse-infra, * the portal-cdn terragrunt config per environment). Everything `bitmagic dev`, `verify`, `forge` * and `generate` bind sits inside it; anything that asks the OS for an ephemeral port * (`listen(0)` → 49xxx+) falls outside it. * * Nothing here picks a number, though. The window is split in half so a dev-line and a release-line * install cannot take each other's ports (port-lane.ts), and every function below scans the LANE it * is handed, never the whole window. The bounds here remain the outer limit: they are what an * explicit `--port` is checked against, because a creator naming a port is entitled to either half. * * The failure is quiet and easy to misread. The fetch is blocked, `WorldGenerator` falls back to * an empty voxel world, the level's terrain simply is not there, and the symptom is a player * falling through the floor forever — which reads as a broken forge, not a blocked request. The * forge's own browser hides it too, because it runs with `--disable-web-security` for the presigned * PUTs, so uploads and reads both succeed there while a normal browser fails. */ export declare const CORS_ALLOWED_PORT_MIN = 3000; export declare const CORS_ALLOWED_PORT_MAX = 3199; /** * A `--port`-style flag parsed and checked against the window above. * * Every port `bitmagic dev` binds goes through here, the game port for the reason documented above * and the shell port because the two are adjacent by default: a creator who moves one will move the * other, and being told the rule once, at the point of the mistake, beats discovering it as a * terrain-less world. (The shell itself fetches nothing from the CDN, so its own check is purely * that kindness.) * * Checked against the whole window, deliberately not against this install's lane. The lane governs * where a DEFAULT drifts to, which is a question about avoiding the other install; naming a port * outright is a statement that you know what is on the machine, and refusing it because the other * line nominally owns that half would make the flag a suggestion for no benefit. */ export declare function parseLocalPort(value: string | undefined, fallback: number, flag: string): number; /** * Whether this exact port is free right now. Same TOCTOU caveat as `reservePortInLane` below. * * A port counts as free only when BOTH loopback families are free. Vite binds `localhost`, which * Node resolves to `::1` on modern systems — so a running `bitmagic dev` holds only the IPv6 side * of its port. Probing 127.0.0.1 alone once called such a port "free": `verify` then bound its own * vite to the IPv4 side, `http://localhost:/` resolved to `::1`, and the run drove — and * graded — whichever OTHER project's dev server was already there, printing "Verification passed" * for a game it never loaded. A machine without IPv6 reports the family as unavailable rather * than the port as busy, and that must not mark every port taken. */ export declare function isPortFree(port: number): Promise; /** * A free port in this install's lane, or `null` when every port in the lane is taken. * * Returning `null` rather than silently falling back to an ephemeral port is deliberate: the * caller can then say what the consequence is, instead of the run proceeding to a level with no * terrain and no explanation. * * It never spills into the other lane either, for the same class of reason. A lane is a promise * that the other install's ports are still there when it starts; taking them because they happen to * be free right now would break that promise at exactly the moment two installs are busiest, and * quietly. A hundred held ports is a machine with a problem to report, not a lane to abandon. * * The scan starts at a pseudo-random offset so two concurrent runs do not both take the lane's * first port and race; `isPortFree` closing before the caller binds leaves a window either way, * which is why callers must handle a bind failure rather than trusting this. */ export declare function reservePortInLane(lane: PortLane, exclude?: ReadonlySet): Promise; /** * What to say when a lane is full — the one message that has to explain why the numbers it names * are narrower than the 3000-3199 everything else talks about. * * Without the second sentence this reads as a bug to anyone who knows the CDN allows two hundred * ports and has just been told a hundred of them are the limit. */ export declare function laneExhaustedMessage(lane: PortLane, remedy: string): string; /** * A free port in THIS install's lane, or a `CliError` carrying `remedy` when the lane is full. * * What every command that serves the project for a real browser to load actually wants: `verify` * and `forge` alike need a port inside the CDN's CORS window (see the file header) and have * nothing useful to do with a `null`. Bundling the lane lookup, the scan and the refusal here * keeps the three from drifting apart — a caller varies only what it tells the creator to do. * * Every port a project on this machine has been promised (project/dev-ports.ts) is skipped as * well, whether or not that project's `dev` is up right now. A verify sitting on 3021 while * project B's dev happened to be down would push B's next `dev` into a drift, which is exactly the * instability the registry ends. At most 27 of the lane's hundred ports, so the scan loses nothing. */ export declare function reserveLanePort(remedy: string, exclude?: ReadonlySet, deps?: { registryDir?: string; }): Promise; /** * The port to actually bind: `preferred` when it is free, otherwise another one in the window. * * Only ever called for a port the creator did NOT ask for by name. An explicit `--port` is a * request, and quietly serving somewhere else would make the flag a suggestion — those still fail * loudly. The default is different: nobody chose the lane's `gamePort`, it is just where we start * looking, and `bitmagic dev` dying because some unrelated process holds it is a bad trade now that * agents run it as a background task whose output nobody reads. * * Drifting is only safe because of two things that did not exist when these ports were fixed: * `.bitmagic/dev.json` records what was actually bound (so `bitmagic reload` still finds it), and * `dev` opens the browser itself (so nobody has to know the number). */ export declare function resolveDevPort(preferred: number, lane: PortLane, exclude?: ReadonlySet): Promise;