Skip to main content

vercel_rpc_cli/
commands.rs

1use std::fs;
2use std::path::Path;
3
4use anyhow::{Context, Result};
5
6use crate::config::RpcConfig;
7use crate::{codegen, parser};
8
9/// Scans the configured directory and prints discovered RPC procedures, structs,
10/// and enums to stdout, followed by a JSON manifest.
11pub fn cmd_scan(config: &RpcConfig) -> Result<()> {
12    let manifest = parser::scan_directory(&config.input)?;
13
14    println!(
15        "Discovered {} procedure(s), {} struct(s), {} enum(s):\n",
16        manifest.procedures.len(),
17        manifest.structs.len(),
18        manifest.enums.len(),
19    );
20
21    for proc in &manifest.procedures {
22        let input_str = proc
23            .input
24            .as_ref()
25            .map(|t| t.to_string())
26            .unwrap_or_else(|| "()".to_string());
27        let output_str = proc
28            .output
29            .as_ref()
30            .map(|t| t.to_string())
31            .unwrap_or_else(|| "()".to_string());
32
33        println!(
34            "  {:?} {} ({}) -> {}  [{}]",
35            proc.kind,
36            proc.name,
37            input_str,
38            output_str,
39            proc.source_file.display(),
40        );
41    }
42
43    for s in &manifest.structs {
44        println!("\n  struct {} {{", s.name);
45        for field in &s.fields {
46            println!("    {}: {},", field.name, field.ty);
47        }
48        println!("  }}");
49    }
50
51    for e in &manifest.enums {
52        let variants: Vec<&str> = e.variants.iter().map(|v| v.name.as_str()).collect();
53        println!("\n  enum {} {{ {} }}", e.name, variants.join(", "));
54    }
55
56    // Also output raw JSON for tooling consumption
57    println!("\n--- JSON manifest ---");
58    println!("{}", serde_json::to_string_pretty(&manifest)?);
59
60    Ok(())
61}
62
63/// Generates TypeScript type definitions and a typed RPC client from the
64/// configured directory, writing the results to the configured output paths.
65pub fn cmd_generate(config: &RpcConfig) -> Result<()> {
66    let manifest = parser::scan_directory(&config.input)?;
67
68    // Generate types file
69    let types_content = codegen::typescript::generate_types_file(
70        &manifest,
71        config.codegen.preserve_docs,
72        config.codegen.naming.fields,
73    );
74    write_file(&config.output.types, &types_content)?;
75    println!(
76        "Generated {} ({} procedure(s), {} struct(s), {} enum(s)) -> {}",
77        config.output.types.display(),
78        manifest.procedures.len(),
79        manifest.structs.len(),
80        manifest.enums.len(),
81        bytecount(&types_content),
82    );
83
84    // Generate client file
85    let client_content = codegen::client::generate_client_file(
86        &manifest,
87        &config.output.imports.types_specifier(),
88        config.codegen.preserve_docs,
89    );
90    write_file(&config.output.client, &client_content)?;
91    println!(
92        "Generated {} -> {}",
93        config.output.client.display(),
94        bytecount(&client_content),
95    );
96
97    Ok(())
98}
99
100/// Writes content to a file, creating parent directories as needed.
101pub fn write_file(path: &Path, content: &str) -> Result<()> {
102    if let Some(parent) = path.parent() {
103        fs::create_dir_all(parent)
104            .with_context(|| format!("Failed to create directory {}", parent.display()))?;
105    }
106    fs::write(path, content).with_context(|| format!("Failed to write {}", path.display()))?;
107    Ok(())
108}
109
110/// Formats byte count in a human-readable way.
111pub fn bytecount(s: &str) -> String {
112    let bytes = s.len();
113    if bytes < 1024 {
114        format!("{bytes} bytes")
115    } else {
116        format!("{:.1} KB", bytes as f64 / 1024.0)
117    }
118}