> ## Documentation Index
> Fetch the complete documentation index at: https://openturn.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# @openturn/manifest

> Deployment manifest schema and HTML generator, shared by the build tool and the cloud shell.

Worker-safe leaf. Owns the shape of `manifest.json` emitted by `@openturn/deploy` and read back by the cloud shell. Both sides depend on this package so the schema stays a single source of truth.

## Install

```bash theme={null}
bun add @openturn/manifest
```

## Types

### `OpenturnDeploymentManifest`

The on-disk manifest for a deployed game:

```ts theme={null}
interface OpenturnDeploymentManifest {
  deploymentID?: string;
  projectID?: string;
  runtime: "local" | "multiplayer";
  gameName: string;
  entry: string;
  styles: readonly string[];
  assets: readonly string[];
  build: { at: string; openturn: Record<string, string> };
  multiplayer?: OpenturnMultiplayerManifest;
  inspector?: OpenturnInspectorPolicy;
  shellControls?: OpenturnShellControlsConfig;
}
```

`deploymentID` / `projectID` are optional because the build-time manifest may not know them yet; the cloud control plane fills them in when the manifest is promoted to a deployment record.

### `OpenturnInspectorPolicy`

```ts theme={null}
interface OpenturnInspectorPolicy {
  mode: "always" | "dev-only" | "never";
  allowedRoles?: readonly ("owner" | "player" | "spectator")[];
}
```

Controls who can open the in-shell inspector for this deployment. Omit to fall back to the cloud default. `allowedRoles` further restricts access within the chosen mode.

### `OpenturnMultiplayerManifest`

```ts theme={null}
interface OpenturnMultiplayerManifest {
  gameKey: string;
  deploymentVersion: string;
  schemaVersion: string;
  /** Maximal player roster. `players.length === maxPlayers` is invariant. */
  players: readonly string[];
  minPlayers: number;
  maxPlayers: number;
  serverBundleDigest: string;
  /** Catalog of bots the deployed game offers in its lobby (read by the cloud DO to populate the per-seat bot picker). */
  availableBots?: readonly OpenturnAvailableBot[];
}
```

The lobby seats up to `targetCapacity` players in `[minPlayers, maxPlayers]`; the running game sees `match.players` filtered to the seated subset.

### `OpenturnAvailableBot`

```ts theme={null}
interface OpenturnAvailableBot {
  botID: string;
  label: string;
  description?: string;
  difficulty?: "easy" | "medium" | "hard" | "expert";
}
```

Populated at build time from `game.bots` (see [`@openturn/lobby`](/docs/reference/lobby)). The cloud Durable Object reads this list to build `LobbyEnv.knownBots`, which the lobby UI uses to render the per-seat bot picker.

### `OpenturnShellControlsConfig`

```ts theme={null}
type OpenturnShellControlsConfig = {
  readonly [K in OpenturnShellControl]?: boolean;
};
```

Per-control opt-in/out for shell chrome. `undefined` means "default-on when the host adapter supports it"; set `false` to hide a control even if the adapter implements it. Used by the bridge's [`isShellControlEnabled`](/docs/reference/bridge#isshellcontrolenabledadapter-id).

### Shell control IDs

```ts theme={null}
const SHELL_CONTROL_IDS = [
  "save",
  "load",
  "reset",
  "returnToLobby",
  "copyInvite",
  "publicRooms",
  "visibilityToggle",
] as const;

type OpenturnShellControl = (typeof SHELL_CONTROL_IDS)[number];
```

Canonical list of shell controls a host may render. Listed once here so the manifest schema, the bridge registry ([`SHELL_CONTROLS` in `@openturn/bridge`](/docs/reference/bridge#shell-controls)), and the runtime gating helper all derive from the same source. To add a new control: add the id here, then map it to its adapter method / label / placement in `@openturn/bridge`'s `SHELL_CONTROLS`. The `satisfies` constraint in bridge fails to compile until the registry is updated.

### Scalar type aliases

The string-literal unions used inside the manifest types — exported separately so consumers can narrow on them or build their own UIs around the same vocabulary.

```ts theme={null}
type OpenturnDeploymentRuntime = "local" | "multiplayer";
type OpenturnInspectorMode = "always" | "dev-only" | "never";
type OpenturnInspectorRole = "owner" | "player" | "spectator";
```

## Validation

### `parseDeploymentManifest(value)`

Zod-parses unknown JSON into an `OpenturnDeploymentManifest`, or throws. Multiplayer deployments must include a `multiplayer` block; local deployments must omit it.

```ts theme={null}
import { parseDeploymentManifest } from "@openturn/manifest";

try {
  const manifest = parseDeploymentManifest(await res.json());
  if (manifest.runtime === "multiplayer") {
    // manifest.multiplayer is guaranteed present
    connectToRoom(manifest.multiplayer.gameKey);
  }
} catch (err) {
  // The thrown error carries a zod-shaped `issues` array.
  console.error("invalid deployment manifest", err);
}
```

### `OpenturnDeploymentManifestSchema` / `OpenturnMultiplayerManifestSchema` / `OpenturnShellControlsConfigSchema` / `OpenturnAvailableBotSchema` / `OpenturnInspectorPolicySchema`

The underlying Zod schemas if you need `.safeParse` or composition — for example, wrapping the manifest inside your own control-plane response envelope:

```ts theme={null}
import { z } from "zod";
import { OpenturnDeploymentManifestSchema } from "@openturn/manifest";

const DeploymentResponseSchema = z.object({
  deployment: OpenturnDeploymentManifestSchema,
  createdAt: z.string().datetime(),
});
```

## Lifecycle

The manifest schema is the single source of truth shared across three boundaries:

1. **Build time.** `@openturn/deploy` emits `manifest.json` beside the built assets. `deploymentID` and `projectID` are typically absent at this stage; the cloud control plane fills them in when it promotes the build to a deployment record.
2. **Serve time.** The cloud shell reads the manifest and passes it to `createDeploymentHTML` to generate the iframe's `index.html`. The `multiplayer` block (if present) tells the shell to issue a room token instead of rendering a local session.
3. **Dispatch time.** The `serverBundleDigest` is how the cloud pins the game-worker bundle in Cloudflare. The `schemaVersion` is what gates profile migrations and determines whether an older stored snapshot can be replayed on the current deployment.

Because the schema is shared, breaking it in one package breaks the whole pipeline. Add new fields as optional when possible, or bump the consumer versions in lockstep.

## HTML generation

### `createDeploymentHTML({ manifest, assetBaseURL? })`

Produces the static `index.html` that bootstraps the game bundle. Pass `assetBaseURL` when serving the HTML and assets from different origins (e.g. cloud serves from a CDN prefix); omit it when `index.html` lives beside its assets.

```ts theme={null}
import { createDeploymentHTML } from "@openturn/manifest";
const html = createDeploymentHTML({ manifest, assetBaseURL: "https://cdn.example/deployments/abc" });
```

### `normalizeAssetPath(asset)`

Strip a leading `./` or `/` from an asset path. Used internally when resolving manifest entries against an `assetBaseURL`; exported for tooling that needs to match the same normalization rule the HTML generator uses.

## See also

* [Reference: deploy](/docs/reference/deploy) for the build tool that writes manifests.
* [Reference: bridge](/docs/reference/bridge) for the wire contract used once the deployed iframe is live.
