commit 2f542e0a48b6fafb35684db16416665367da6e32
parent 4550601711d55a03a7d8df0092bfd0a49ed27258
Author: triesap <tyson@radroots.org>
Date: Fri, 20 Feb 2026 23:34:20 +0000
build: add ts model export command
Diffstat:
3 files changed, 81 insertions(+), 2 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -5024,6 +5024,14 @@ dependencies = [
]
[[package]]
+name = "xtask"
+version = "0.1.0"
+dependencies = [
+ "serde",
+ "toml 0.8.23",
+]
+
+[[package]]
name = "yaml-rust2"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/crates/xtask/src/export_ts.rs b/crates/xtask/src/export_ts.rs
@@ -0,0 +1,52 @@
+#![forbid(unsafe_code)]
+
+use crate::contract;
+use std::fs;
+use std::path::{Path, PathBuf};
+
+fn to_package_dir(base: &Path, package_name: &str) -> PathBuf {
+ let stripped = package_name.strip_prefix("@radroots/").unwrap_or(package_name);
+ base.join(stripped)
+}
+
+fn copy_if_exists(src: &Path, dst: &Path) -> Result<bool, String> {
+ if !src.exists() {
+ return Ok(false);
+ }
+ if let Some(parent) = dst.parent() {
+ fs::create_dir_all(parent).map_err(|e| format!("create {}: {e}", parent.display()))?;
+ }
+ fs::copy(src, dst).map_err(|e| format!("copy {} -> {}: {e}", src.display(), dst.display()))?;
+ Ok(true)
+}
+
+pub fn export_ts_models(workspace_root: &Path, out_dir: &Path) -> Result<(), String> {
+ let bundle = contract::load_contract_bundle(workspace_root)?;
+ contract::validate_contract_bundle(&bundle)?;
+ let ts_export = bundle
+ .exports
+ .iter()
+ .find(|mapping| mapping.language.id == "ts")
+ .ok_or_else(|| "missing ts export mapping".to_string())?;
+ let source_root = workspace_root.join("target").join("ts-rs");
+ if !source_root.exists() {
+ return Err(format!("missing ts-rs source root {}", source_root.display()));
+ }
+ let ts_out_root = out_dir.join("ts").join("packages");
+ let mut copied = 0usize;
+ for (crate_name, package_name) in &ts_export.packages {
+ let crate_dir = crate_name.strip_prefix("radroots-").unwrap_or(crate_name);
+ let src = source_root.join(crate_dir).join("types.ts");
+ let dst = to_package_dir(&ts_out_root, package_name)
+ .join("src")
+ .join("generated")
+ .join("types.ts");
+ if copy_if_exists(&src, &dst)? {
+ copied += 1;
+ }
+ }
+ if copied == 0 {
+ return Err("no ts model files were exported".to_string());
+ }
+ Ok(())
+}
diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs
@@ -1,6 +1,7 @@
#![forbid(unsafe_code)]
mod contract;
+mod export_ts;
use std::env;
use std::path::{Path, PathBuf};
@@ -8,7 +9,7 @@ use std::process::ExitCode;
fn usage() {
eprintln!("usage:");
- eprintln!(" cargo xtask sdk export-ts [--out <dir>]");
+ eprintln!(" cargo xtask sdk export-ts-models [--out <dir>]");
eprintln!(" cargo xtask sdk validate");
}
@@ -23,6 +24,24 @@ fn workspace_root() -> Result<PathBuf, String> {
Ok(root.to_path_buf())
}
+fn parse_out_dir(args: &[String], workspace_root: &Path) -> Result<PathBuf, String> {
+ if args.is_empty() {
+ return Ok(workspace_root.join("target").join("sdk-export"));
+ }
+ if args.len() == 2 && args[0] == "--out" {
+ return Ok(PathBuf::from(&args[1]));
+ }
+ Err("invalid export args, expected --out <dir>".to_string())
+}
+
+fn export_ts_models(args: &[String]) -> Result<(), String> {
+ let root = workspace_root()?;
+ let out_dir = parse_out_dir(args, &root)?;
+ export_ts::export_ts_models(&root, &out_dir)?;
+ eprintln!("exported ts models to {}", out_dir.display());
+ Ok(())
+}
+
fn validate_contract() -> Result<(), String> {
let root = workspace_root()?;
let bundle = contract::load_contract_bundle(&root)?;
@@ -37,7 +56,7 @@ fn validate_contract() -> Result<(), String> {
fn run_sdk(args: &[String]) -> Result<(), String> {
match args.first().map(String::as_str) {
- Some("export-ts") => Ok(()),
+ Some("export-ts-models") => export_ts_models(&args[1..]),
Some("validate") => validate_contract(),
_ => Err("unknown sdk subcommand".to_string()),
}