vercel_rpc_cli/
commands.rs1use std::fs;
2use std::path::{Path, PathBuf};
3
4use anyhow::{Context, Result};
5
6use crate::config::RpcConfig;
7use crate::model::Manifest;
8use crate::{codegen, parser};
9
10pub fn cmd_scan(config: &RpcConfig) -> Result<()> {
13 let manifest = parser::scan_directory(&config.input)?;
14
15 println!(
16 "Discovered {} procedure(s), {} struct(s), {} enum(s):\n",
17 manifest.procedures.len(),
18 manifest.structs.len(),
19 manifest.enums.len(),
20 );
21
22 for proc in &manifest.procedures {
23 let input_str = proc
24 .input
25 .as_ref()
26 .map(|t| t.to_string())
27 .unwrap_or_else(|| "()".to_string());
28 let output_str = proc
29 .output
30 .as_ref()
31 .map(|t| t.to_string())
32 .unwrap_or_else(|| "()".to_string());
33
34 println!(
35 " {:?} {} ({}) -> {} [{}]",
36 proc.kind,
37 proc.name,
38 input_str,
39 output_str,
40 proc.source_file.display(),
41 );
42 }
43
44 for s in &manifest.structs {
45 println!("\n struct {} {{", s.name);
46 for field in &s.fields {
47 println!(" {}: {},", field.name, field.ty);
48 }
49 println!(" }}");
50 }
51
52 for e in &manifest.enums {
53 let variants: Vec<&str> = e.variants.iter().map(|v| v.name.as_str()).collect();
54 println!("\n enum {} {{ {} }}", e.name, variants.join(", "));
55 }
56
57 println!("\n--- JSON manifest ---");
59 println!("{}", serde_json::to_string_pretty(&manifest)?);
60
61 Ok(())
62}
63
64pub fn cmd_generate(config: &RpcConfig) -> Result<()> {
67 let manifest = generate_all(config)?;
68
69 println!(
70 "Generated {} procedure(s), {} struct(s), {} enum(s)",
71 manifest.procedures.len(),
72 manifest.structs.len(),
73 manifest.enums.len(),
74 );
75 println!(" → {}", config.output.types.display());
76 println!(" → {}", config.output.client.display());
77 if let Some(path) = &config.output.svelte {
78 println!(" → {}", path.display());
79 }
80 if let Some(path) = &config.output.react {
81 println!(" → {}", path.display());
82 }
83 if let Some(path) = &config.output.vue {
84 println!(" → {}", path.display());
85 }
86 if let Some(path) = &config.output.solid {
87 println!(" → {}", path.display());
88 }
89
90 Ok(())
91}
92
93pub fn generate_all(config: &RpcConfig) -> Result<Manifest> {
97 let manifest = parser::scan_directory(&config.input)?;
98
99 let types_content = codegen::typescript::generate_types_file(
100 &manifest,
101 config.codegen.preserve_docs,
102 config.codegen.naming.fields,
103 );
104 write_file(&config.output.types, &types_content)?;
105
106 let client_content = codegen::client::generate_client_file(
107 &manifest,
108 &config.output.imports.types_specifier(),
109 config.codegen.preserve_docs,
110 );
111 write_file(&config.output.client, &client_content)?;
112
113 write_framework_files(config, &manifest)?;
114
115 Ok(manifest)
116}
117
118fn client_import_path(config: &RpcConfig) -> String {
120 let client_stem = config
121 .output
122 .client
123 .file_stem()
124 .unwrap_or_default()
125 .to_string_lossy();
126 format!("./{client_stem}{}", config.output.imports.extension)
127}
128
129type FrameworkEntry<'a> = (
131 Option<&'a PathBuf>,
132 fn(&Manifest, &str, &str, bool) -> String,
133);
134
135fn write_framework_files(config: &RpcConfig, manifest: &Manifest) -> Result<()> {
137 let client_import = client_import_path(config);
138 let types_specifier = config.output.imports.types_specifier();
139
140 let frameworks: [FrameworkEntry<'_>; 4] = [
141 (
142 config.output.svelte.as_ref(),
143 codegen::svelte::generate_svelte_file,
144 ),
145 (
146 config.output.react.as_ref(),
147 codegen::react::generate_react_file,
148 ),
149 (config.output.vue.as_ref(), codegen::vue::generate_vue_file),
150 (
151 config.output.solid.as_ref(),
152 codegen::solid::generate_solid_file,
153 ),
154 ];
155
156 for (path_opt, generator) in &frameworks {
157 if let Some(path) = path_opt {
158 let content = generator(
159 manifest,
160 &client_import,
161 &types_specifier,
162 config.codegen.preserve_docs,
163 );
164 if !content.is_empty() {
165 write_file(path, &content)?;
166 }
167 }
168 }
169
170 Ok(())
171}
172
173pub fn write_file(path: &Path, content: &str) -> Result<()> {
175 if let Some(parent) = path.parent() {
176 fs::create_dir_all(parent)
177 .with_context(|| format!("Failed to create directory {}", parent.display()))?;
178 }
179 fs::write(path, content).with_context(|| format!("Failed to write {}", path.display()))?;
180 Ok(())
181}
182
183pub fn bytecount(s: &str) -> String {
185 let bytes = s.len();
186 if bytes < 1024 {
187 format!("{bytes} bytes")
188 } else {
189 format!("{:.1} KB", bytes as f64 / 1024.0)
190 }
191}