manabrew-relay-protocol 5.9.0

Relay wire protocol for manabrew multiplayer
Documentation
//! Generates every TypeScript wire type via ts-rs, from this crate and from
//! `manabrew-protocol` below it. It lives here because this crate sits at the
//! top of the protocol dependency graph and can name both sides:
//!
//!   cargo run -p manabrew-relay-protocol --bin gen-protocol -- <out-dir>
//!
//! Output is not formatted (ts-rs emits single-line types); run the consumer's
//! own formatter afterwards if desired.
use std::fs;
use std::path::{Path, PathBuf};

use manabrew_protocol::deck_dto::Deck;
use manabrew_protocol::display::DisplayEvent;
use manabrew_protocol::prompts::{PromptInput, PromptOutput};
use manabrew_protocol::transport::{
    AgentPrompt, ClientToServerMessage, DirectiveInput, ProtocolError, StateUpdate,
};
use manabrew_relay_protocol::{ResumeRoomRequest, PROTOCOL_VERSION};
use ts_rs::TS;

const HEADER: &str =
    "// AUTO-GENERATED by `cargo run -p manabrew-relay-protocol --bin gen-protocol`. Do not edit.\n";
fn ts_modules(dir: &Path) -> Vec<String> {
    let mut names = Vec::new();
    if let Ok(entries) = fs::read_dir(dir) {
        for entry in entries.flatten() {
            let path = entry.path();
            if path.extension().is_some_and(|e| e == "ts") {
                if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
                    if stem != "index" {
                        names.push(stem.to_string());
                    }
                }
            }
        }
    }
    names.sort();
    names
}

fn main() {
    let out = PathBuf::from(
        std::env::args()
            .nth(1)
            .unwrap_or_else(|| "bindings".to_string()),
    );

    let _ = fs::remove_dir_all(&out);
    fs::create_dir_all(&out).expect("create output directory");

    // A handful of roots cover the whole graph transitively; ts-rs dedups types
    // into their `export_to` files within this process.
    PromptInput::export_all_to(&out).expect("export PromptInput");
    PromptOutput::export_all_to(&out).expect("export PromptOutput");
    AgentPrompt::export_all_to(&out).expect("export AgentPrompt");
    StateUpdate::export_all_to(&out).expect("export StateUpdate");
    DirectiveInput::export_all_to(&out).expect("export DirectiveInput");
    ClientToServerMessage::export_all_to(&out).expect("export ClientToServerMessage");
    ProtocolError::export_all_to(&out).expect("export ProtocolError");
    DisplayEvent::export_all_to(&out).expect("export DisplayEvent");
    Deck::export_all_to(&out).expect("export Deck");
    ResumeRoomRequest::export_all_to(&out).expect("export ResumeRoomRequest");

    let prompts_dir = out.join("prompts");
    let mut prompts_index = String::from(HEADER);
    for module in ts_modules(&prompts_dir) {
        prompts_index.push_str(&format!("export * from \"./{module}\";\n"));
    }
    prompts_index.push_str("\nimport type { PromptInput } from \"./promptInput\";\n");
    prompts_index.push_str("export type PromptType = PromptInput[\"type\"];\n");
    fs::write(prompts_dir.join("index.ts"), prompts_index).expect("write prompts/index.ts");

    let transport_dir = out.join("transport");
    fs::write(transport_dir.join("envelope.ts"), ENVELOPE).expect("write transport/envelope.ts");
    fs::write(
        transport_dir.join("index.ts"),
        format!("{HEADER}export * from \"./messages\";\nexport * from \"./envelope\";\n"),
    )
    .expect("write transport/index.ts");

    // `VERSION` is the npm package's own version, so it must come from
    // `manabrew-protocol` (which `publish-protocol.yml` publishes the package
    // at), NOT from `CARGO_PKG_VERSION` of this generator crate. Those are
    // different crates on independent version lines, and using this one made
    // `VERSION` disagree with the installed package version (#777).
    fs::write(
        out.join("version.ts"),
        format!(
            "{HEADER}export const VERSION = \"{}\";\nexport const RELAY_PROTOCOL_VERSION = \"{}\";\nexport const PROTOCOL_VERSION = {PROTOCOL_VERSION};\n",
            manabrew_protocol::VERSION,
            env!("CARGO_PKG_VERSION"),
        ),
    )
    .expect("write version.ts");

    fs::write(
        out.join("index.ts"),
        format!(
            "{HEADER}export * from \"./prompts\";\nexport * from \"./transport\";\nexport type * from \"./display\";\nexport type * from \"./game\";\nexport type * from \"./deck\";\nexport type * from \"./lobby\";\nexport * from \"./version\";\n"
        ),
    )
    .expect("write index.ts");
}

/// The prompt-request envelope and distributive `Prompt` union — TS generics
/// ts-rs cannot express, emitted verbatim so the generated tree is complete.
const ENVELOPE: &str = r#"// AUTO-GENERATED by `cargo run -p manabrew-relay-protocol --bin gen-protocol`. Do not edit.
import type { CardDto } from "../game";
import type { PromptInput } from "../prompts";

export interface PromptRequestEnvelope {
  promptId?: string;
  decidingPlayerId?: string;
  sourceCard?: CardDto;
}

export type PromptRequest<TInput extends { type: string }> = PromptRequestEnvelope & {
  input: TInput;
};

type DistributeRequest<TInput extends { type: string }> = TInput extends unknown
  ? PromptRequest<TInput>
  : never;

export type Prompt = DistributeRequest<PromptInput>;
"#;