From 3fa748b11e2b77d9a61d3ab9f62a1a3fbaa2ef75 Mon Sep 17 00:00:00 2001 From: Gustavo Shigueo Date: Sat, 5 Apr 2025 16:09:04 -0300 Subject: [PATCH 01/15] Copy over changes from cli branch --- .github/workflows/test.yml | 8 +- .gitignore | 1 + Cargo.lock | 248 +++++++++++++++++++-- Cargo.toml | 2 +- cli/Cargo.toml | 10 + cli/src/args.rs | 48 ++++ cli/src/cargo.rs | 51 +++++ cli/src/main.rs | 107 +++++++++ cli/src/metadata.rs | 104 +++++++++ cli/src/path.rs | 64 ++++++ macros/Cargo.toml | 1 + macros/src/lib.rs | 5 +- macros/src/utils.rs | 2 +- ts-rs/Cargo.toml | 2 + ts-rs/src/export.rs | 23 +- ts-rs/tests/integration/path_bug.rs | 4 +- ts-rs/tests/integration/recursion_limit.rs | 2 + 17 files changed, 654 insertions(+), 28 deletions(-) create mode 100644 cli/Cargo.toml create mode 100644 cli/src/args.rs create mode 100644 cli/src/cargo.rs create mode 100644 cli/src/main.rs create mode 100644 cli/src/metadata.rs create mode 100644 cli/src/path.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f356ca5b..56b18064 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: working-directory: e2e/dependencies/consumer run: | cargo t - tsc bindings/* --noEmit --noUnusedLocals --strict + tsc bindings/*.ts --noEmit --noUnusedLocals --strict - name: dependencies e2e test with default export env working-directory: e2e/dependencies/consumer run: | @@ -143,7 +143,7 @@ jobs: - name: Test working-directory: ts-rs run: | - TS_RS_EXPORT_DIR=output cargo test --no-default-features + TS_RS_EXPORT_DIR=output cargo test --no-default-features --features ts-rs/export shopt -s globstar tsc output/**/*.ts --noEmit --noUnusedLocals --strict rm -rf output @@ -162,7 +162,7 @@ jobs: - name: Test working-directory: ts-rs run: | - TS_RS_EXPORT_DIR=$(pwd)/output cargo test --no-default-features + TS_RS_EXPORT_DIR=$(pwd)/output cargo test --no-default-features --features ts-rs/export shopt -s globstar tsc output/**/*.ts --noEmit --noUnusedLocals --strict rm -rf output @@ -181,7 +181,7 @@ jobs: - name: Test working-directory: ts-rs run: | - cargo test --no-default-features + cargo test --no-default-features --features ts-rs/export shopt -s globstar tsc bindings/**/*.ts --noEmit --noUnusedLocals rm -rf bindings diff --git a/.gitignore b/.gitignore index f5541831..b4554c33 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ target/ tsconfig.json /ts-rs/tests-out +ts_rs.meta diff --git a/Cargo.lock b/Cargo.lock index 68a217fe..7ef0ae77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,18 +14,18 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.24.2" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] [[package]] -name = "adler2" -version = "2.0.0" +name = "adler" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" @@ -70,6 +70,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys", +] + [[package]] name = "anyhow" version = "1.0.93" @@ -96,17 +146,17 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", + "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", - "windows-targets", ] [[package]] @@ -207,6 +257,14 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +[[package]] +name = "cargo-ts" +version = "0.1.0" +dependencies = [ + "clap", + "color-eyre", +] + [[package]] name = "cc" version = "1.2.2" @@ -243,6 +301,79 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "clap" +version = "4.5.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "color-eyre" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -398,6 +529,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -437,9 +578,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "hash32" @@ -664,6 +805,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "2.7.0" @@ -687,6 +834,12 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itoa" version = "1.0.14" @@ -741,11 +894,11 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ - "adler2", + "adler", ] [[package]] @@ -791,9 +944,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -813,6 +966,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1046,6 +1205,15 @@ dependencies = [ "serde", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1122,6 +1290,12 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "swc_atoms" version = "0.6.7" @@ -1341,6 +1515,16 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.36" @@ -1421,6 +1605,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", ] [[package]] @@ -1514,6 +1720,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.11.0" @@ -1524,6 +1736,12 @@ dependencies = [ "serde", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version_check" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index 3afd3eec..2269bbc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,4 @@ [workspace] -members = ["macros", "ts-rs", "example"] +members = ["macros", "ts-rs", "example", "cli"] resolver = "2" diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 00000000..a0081955 --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "cargo-ts" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "4", features = ["derive"] } +color-eyre = "0.6" diff --git a/cli/src/args.rs b/cli/src/args.rs new file mode 100644 index 00000000..86cde5f8 --- /dev/null +++ b/cli/src/args.rs @@ -0,0 +1,48 @@ +use std::path::PathBuf; + +use clap::Parser; + +use crate::metadata::FILE_NAME; + +#[derive(Parser, Debug)] +#[allow(clippy::struct_excessive_bools)] +pub struct Args { + /// Defines where your TS bindings will be saved by setting TS_RS_EXPORT_DIR + #[arg(long, short)] + pub output_directory: PathBuf, + + /// Disables warnings caused by using serde attributes that ts-rs cannot process + #[arg(long)] + pub no_warnings: bool, + + /// Adds the ".js" extension to import paths + #[arg(long)] + pub esm_imports: bool, + + /// Formats the generated TypeScript files + #[arg(long)] + pub format: bool, + + /// Generates an index.ts file in your --output-directory that re-exports all + /// types generated by ts-rs + #[arg(long = "index")] + pub generate_index_ts: bool, + + /// Generates only a single index.ts file in your --output-directory that + /// contains all exported types + #[arg(long = "merge")] + pub merge_files: bool, + + /// Do not capture `cargo test`'s output, and pass --nocapture to the test binary + #[arg(long = "nocapture")] + pub no_capture: bool, +} + +// Args is in scope for the entirety of the main function, so this will only +// be executed when the program is finished running. This helps prevent us +// from forgetting to do cleanup if some code branch early returns from main +impl Drop for Args { + fn drop(&mut self) { + _ = std::fs::remove_file(self.output_directory.join(FILE_NAME)); + } +} diff --git a/cli/src/cargo.rs b/cli/src/cargo.rs new file mode 100644 index 00000000..a9695f24 --- /dev/null +++ b/cli/src/cargo.rs @@ -0,0 +1,51 @@ +use std::process::{Command, Stdio}; + +use color_eyre::Result; + +use crate::{args::Args, path}; + +macro_rules! feature { + ($cargo_invocation: expr, $args: expr, { $($field: ident => $feature: literal),* $(,)? }) => { + $( + if $args.$field { + $cargo_invocation + .arg("--features") + .arg(format!("ts-rs/{}", $feature)); + } + )* + }; +} + +pub fn invoke(args: &Args) -> Result<()> { + let mut cargo_invocation = Command::new("cargo"); + + cargo_invocation + .arg("test") + .arg("export_bindings_") + .arg("--features") + .arg("ts-rs/export") + .arg("--features") + .arg("ts-rs/generate-metadata") + .stdout(if args.no_capture { + Stdio::inherit() + } else { + Stdio::piped() + }) + .env("TS_RS_EXPORT_DIR", path::absolute(&args.output_directory)?); + + feature!(cargo_invocation, args, { + no_warnings => "no-serde-warnings", + esm_imports => "import-esm", + format => "format", + }); + + if args.no_capture { + cargo_invocation.arg("--").arg("--nocapture"); + } else { + cargo_invocation.arg("--quiet"); + } + + cargo_invocation.spawn()?.wait()?; + + Ok(()) +} diff --git a/cli/src/main.rs b/cli/src/main.rs new file mode 100644 index 00000000..005684c5 --- /dev/null +++ b/cli/src/main.rs @@ -0,0 +1,107 @@ +#![warn(clippy::pedantic, clippy::nursery)] + +use std::{ + fs::{self, OpenOptions}, + io::{Read, Write}, +}; + +use clap::Parser; +use color_eyre::{owo_colors::OwoColorize, Result}; + +mod args; +mod cargo; +mod metadata; +mod path; + +use args::Args; +use metadata::{Metadata, FILE_NAME}; + +const BLANK_LINE: [u8; 2] = [b'\n', b'\n']; +const NOTE: &[u8; 109] = b"// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n"; + +fn main() -> Result<()> { + color_eyre::install()?; + + let args = Args::parse(); + + let metadata_path = args.output_directory.join(FILE_NAME); + if metadata_path.exists() { + fs::remove_file(&metadata_path)?; + } + + if args.merge_files && args.generate_index_ts { + eprintln!( + "{} --index is not compatible with --merge", + "Error:".red().bold() + ); + + return Ok(()); + } + + cargo::invoke(&args)?; + + let metadata_content = fs::read_to_string(&metadata_path)?; + let metadata = Metadata::try_from(&*metadata_content)?; + + let demand_unique_names = args.merge_files || args.generate_index_ts; + + if !demand_unique_names || metadata.is_empty() { + return Ok(()); + } + + if metadata.has_naming_collisions() { + metadata.report_naming_collisions(); + + eprintln!( + "{} due to the naming collisions listed above, generating an index.ts file is not possible", + "Error:".red().bold() + ); + + return Ok(()); + } + + let index_path = args.output_directory.join("index.ts"); + + if index_path.exists() { + fs::remove_file(&index_path)?; + } + + let mut index = OpenOptions::new() + .create(true) + .append(true) + .open(index_path)?; + + index.write_all(NOTE)?; + + if args.generate_index_ts { + for path in metadata.export_paths() { + index.write_fmt(format_args!("\nexport * from {path:?};"))?; + } + + return Ok(()); + } + + if args.merge_files { + for path in metadata.export_paths() { + let path = path::absolute(args.output_directory.join(path))?; + let mut file = OpenOptions::new().read(true).open(&path)?; + + let mut buf = Vec::with_capacity(file.metadata()?.len().try_into()?); + file.read_to_end(&mut buf)?; + + let Some((i, _)) = buf.windows(2).enumerate().find(|(_, w)| w == &BLANK_LINE) else { + continue; + }; + + index.write_all(&buf[i + 1..])?; + + fs::remove_file(path)?; + } + + path::remove_empty_subdirectories(&args.output_directory)?; + + return Ok(()); + } + + Ok(()) +} diff --git a/cli/src/metadata.rs b/cli/src/metadata.rs new file mode 100644 index 00000000..e04cc2bf --- /dev/null +++ b/cli/src/metadata.rs @@ -0,0 +1,104 @@ +use std::{ + collections::{HashMap, HashSet}, + path::Path, +}; + +use color_eyre::{ + eyre::{Error, OptionExt}, + owo_colors::OwoColorize, + Result, +}; + +pub const FILE_NAME: &str = "ts_rs.meta"; + +pub struct Metadata<'a> { + entries: std::collections::HashMap<&'a str, HashSet>>, +} + +impl<'a> TryFrom<&'a str> for Metadata<'a> { + type Error = Error; + + fn try_from(value: &'a str) -> Result { + Ok(Self { + entries: value.lines().try_fold( + HashMap::<&str, HashSet<_>>::default(), + |mut acc, cur| { + let (key, value) = cur.split_once(',').ok_or_eyre("Invalid metadata file")?; + let value = Entry::try_from(value)?; + + acc.entry(key).or_default().insert(value); + + Ok::<_, Error>(acc) + }, + )?, + }) + } +} + +impl<'a> Metadata<'a> { + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + pub fn has_naming_collisions(&self) -> bool { + self.entries.values().any(|x| x.len() > 1) + } + + pub fn report_naming_collisions(&self) { + self.entries + .iter() + .filter(|(_, x)| x.len() > 1) + .for_each(|(ty, entry)| name_collision_warning(ty, entry)); + } + + pub fn export_paths(&self) -> impl Iterator { + self.entries.values().flatten().map(|x| x.export_path) + } +} + +#[derive(PartialEq, Eq, Hash)] +struct Entry<'a> { + rust_name: &'a str, + export_path: &'a Path, +} + +impl<'a> TryFrom<&'a str> for Entry<'a> { + type Error = Error; + + fn try_from(value: &'a str) -> Result { + let (rust_name, export_path) = + value.split_once(',').ok_or_eyre("Invalid metadata entry")?; + + Ok(Self { + rust_name, + export_path: Path::new(export_path), + }) + } +} + +fn name_collision_warning(ts_type: &str, metadata: &HashSet) { + eprintln!( + "{} Multiple types being exported with the name \"{}\"", + "Warning:".yellow().bold(), + ts_type.green().bold() + ); + + for entry in metadata { + eprintln!( + " {} {} {}", + "-".blue().bold(), + "Type:".bold(), + entry.rust_name.cyan(), + ); + + eprintln!( + " {} {}", + "Path:".bold(), + entry.export_path.to_string_lossy() + ); + + eprintln!(); + } + + eprintln!(); +} diff --git a/cli/src/path.rs b/cli/src/path.rs new file mode 100644 index 00000000..6e4255e3 --- /dev/null +++ b/cli/src/path.rs @@ -0,0 +1,64 @@ +use std::{ + fs, + io::ErrorKind, + path::{Component as C, Path, PathBuf}, +}; + +use color_eyre::{eyre::OptionExt, Result}; + +pub fn absolute>(path: T) -> Result { + let path = path.as_ref(); + + if path.is_absolute() { + return Ok(path.to_owned()); + } + + let path = std::env::current_dir()?.join(path); + + let mut out = Vec::new(); + for comp in path.components() { + match comp { + C::CurDir => (), + C::ParentDir => { + out.pop().ok_or_eyre("Invalid path")?; + } + comp => out.push(comp), + } + } + + Ok(if out.is_empty() { + PathBuf::from(".") + } else { + out.iter().collect() + }) +} + +pub fn remove_empty_subdirectories>(path: T) -> Result<()> { + let path = path.as_ref(); + + for entry in path.read_dir()? { + let entry = entry?; + + let path = entry.path(); + + if !path.is_dir() { + continue; + } + + remove_empty_subdirectories(&path)?; + if let Err(e) = fs::remove_dir(path) { + // The other possible error kinds are either not available + // in stable rust (DirectoryNotEmpty), not possible due + // to the logic of this function (NotFound) or both (NotADirectory) + // + // The correct check would be `!matches!(e.kind(), ErrorKind::DirectoryNotEmpty)` + // as that is the only error we actually WANT to ignore... the others, + // although impossible, should be returned if they somehow happen + if matches!(e.kind(), ErrorKind::PermissionDenied) { + return Err(e.into()); + } + } + } + + Ok(()) +} diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 28510266..c1e140f0 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/Aleph-Alpha/ts-rs" [features] serde-compat = ["termcolor"] no-serde-warnings = [] +export = [] [lib] proc-macro = true diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 8f4eecc1..78b8c009 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -35,9 +35,8 @@ struct DerivedTS { impl DerivedTS { fn into_impl(mut self, rust_ty: Ident, generics: Generics) -> TokenStream { - let export = self - .export - .then(|| self.generate_export_test(&rust_ty, &generics)); + let allow_export = cfg!(feature = "export") && self.export; + let export = allow_export.then(|| self.generate_export_test(&rust_ty, &generics)); let output_path_fn = { let ts_name = &self.ts_name; diff --git a/macros/src/utils.rs b/macros/src/utils.rs index 7bd04b8c..238aed89 100644 --- a/macros/src/utils.rs +++ b/macros/src/utils.rs @@ -235,7 +235,7 @@ pub(crate) mod warning { let mut buffer = writer.buffer(); buffer.set_color(&yellow_bold)?; - write!(&mut buffer, "warning")?; + write!(&mut buffer, "Warning")?; buffer.set_color(&white_bold)?; writeln!(&mut buffer, ": {}", title)?; diff --git a/ts-rs/Cargo.toml b/ts-rs/Cargo.toml index 2c0cbe02..f64e297c 100644 --- a/ts-rs/Cargo.toml +++ b/ts-rs/Cargo.toml @@ -35,6 +35,8 @@ smol_str-impl = ["smol_str"] serde-json-impl = ["serde_json"] no-serde-warnings = ["ts-rs-macros/no-serde-warnings"] import-esm = [] +export = ["ts-rs-macros/export"] +generate-metadata = [] tokio-impl = ["tokio"] [dev-dependencies] diff --git a/ts-rs/src/export.rs b/ts-rs/src/export.rs index 420799de..4d8deb01 100644 --- a/ts-rs/src/export.rs +++ b/ts-rs/src/export.rs @@ -126,14 +126,14 @@ pub(crate) fn export_to>( std::fs::create_dir_all(parent)?; } - export_and_merge(path, type_name, buffer)?; + export_and_merge::(path, type_name, buffer)?; Ok(()) } /// Exports the type to a new file if the file hasn't yet been written to. /// Otherwise, finds its place in the already existing file and inserts it. -fn export_and_merge( +fn export_and_merge( path: PathBuf, type_name: String, generated_type: String, @@ -142,6 +142,25 @@ fn export_and_merge( let lock = &mut get_export_paths().lock().unwrap(); + if cfg!(feature = "generate-metadata") { + let relative_path = T::output_path() + .ok_or_else(std::any::type_name::) + .map_err(ExportError::CannotBeExported)? + .to_string_lossy() + .into_owned(); + + let type_ts_name = T::ident(); + let type_rs_name = std::any::type_name::().split('<').next().unwrap(); + + std::fs::OpenOptions::new() + .append(true) + .create(true) + .open(default_out_dir().join("ts_rs.meta"))? + .write_fmt(format_args!( + "{type_ts_name},{type_rs_name},./{relative_path}\n" + ))?; + } + let Some(entry) = lock.get_mut(&path) else { // The file hasn't been written to yet, so it must be // overwritten diff --git a/ts-rs/tests/integration/path_bug.rs b/ts-rs/tests/integration/path_bug.rs index 12ade1bf..4c48c6ba 100644 --- a/ts-rs/tests/integration/path_bug.rs +++ b/ts-rs/tests/integration/path_bug.rs @@ -2,7 +2,7 @@ use ts_rs::TS; #[derive(TS)] -#[ts(export, export_to = "path_bug/aaa/")] +#[ts(export_to = "path_bug/aaa/")] struct Foo { bar: Bar, } @@ -15,7 +15,7 @@ struct Bar { #[test] fn path_bug() { - export_bindings_foo(); + Foo::export_all().unwrap(); assert!(Foo::default_output_path().unwrap().is_file()); assert!(Bar::default_output_path().unwrap().is_file()); diff --git a/ts-rs/tests/integration/recursion_limit.rs b/ts-rs/tests/integration/recursion_limit.rs index 16e1a2d2..fb9738c7 100644 --- a/ts-rs/tests/integration/recursion_limit.rs +++ b/ts-rs/tests/integration/recursion_limit.rs @@ -1,3 +1,5 @@ +#![allow(clippy::upper_case_acronyms)] + use std::any::TypeId; use ts_rs::{TypeVisitor, TS}; From cfa24b229b0ee7ec5b83b856217e3039eaf69767 Mon Sep 17 00:00:00 2001 From: Gustavo Shigueo Date: Sat, 5 Apr 2025 16:19:17 -0300 Subject: [PATCH 02/15] Make `export` a default feature --- e2e/dependencies/consumer/Cargo.lock | 29 +++++++++++----------------- ts-rs/Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/e2e/dependencies/consumer/Cargo.lock b/e2e/dependencies/consumer/Cargo.lock index 50bbf2e8..4ca15b71 100644 --- a/e2e/dependencies/consumer/Cargo.lock +++ b/e2e/dependencies/consumer/Cargo.lock @@ -1,12 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 - -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +version = 4 [[package]] name = "consumer" @@ -25,9 +19,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] @@ -43,9 +37,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -63,18 +57,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.56" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", @@ -83,7 +77,7 @@ dependencies = [ [[package]] name = "ts-rs" -version = "7.1.1" +version = "10.1.0" dependencies = [ "thiserror", "ts-rs-macros", @@ -91,9 +85,8 @@ dependencies = [ [[package]] name = "ts-rs-macros" -version = "7.1.1" +version = "10.1.0" dependencies = [ - "Inflector", "proc-macro2", "quote", "syn", diff --git a/ts-rs/Cargo.toml b/ts-rs/Cargo.toml index f64e297c..a125bd17 100644 --- a/ts-rs/Cargo.toml +++ b/ts-rs/Cargo.toml @@ -26,7 +26,7 @@ bytes-impl = ["bytes"] url-impl = ["url"] serde-compat = ["ts-rs-macros/serde-compat"] format = ["dprint-plugin-typescript"] -default = ["serde-compat"] +default = ["serde-compat", "export"] indexmap-impl = ["indexmap"] ordered-float-impl = ["ordered-float"] heapless-impl = ["heapless"] From f568dea0f04f8f5d6e3db9d833b54ee7e1cf1585 Mon Sep 17 00:00:00 2001 From: Gustavo Shigueo Date: Sat, 5 Apr 2025 16:25:27 -0300 Subject: [PATCH 03/15] Fix clippy warnings --- cli/src/args.rs | 2 +- cli/src/metadata.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/args.rs b/cli/src/args.rs index 86cde5f8..cfc3629e 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -7,7 +7,7 @@ use crate::metadata::FILE_NAME; #[derive(Parser, Debug)] #[allow(clippy::struct_excessive_bools)] pub struct Args { - /// Defines where your TS bindings will be saved by setting TS_RS_EXPORT_DIR + /// Defines where your TS bindings will be saved by setting `TS_RS_EXPORT_DIR` #[arg(long, short)] pub output_directory: PathBuf, diff --git a/cli/src/metadata.rs b/cli/src/metadata.rs index e04cc2bf..ca8aef35 100644 --- a/cli/src/metadata.rs +++ b/cli/src/metadata.rs @@ -35,7 +35,7 @@ impl<'a> TryFrom<&'a str> for Metadata<'a> { } } -impl<'a> Metadata<'a> { +impl Metadata<'_> { pub fn is_empty(&self) -> bool { self.entries.is_empty() } From 3ee5fedff3b6e97ea95b839146772065aecc9a1b Mon Sep 17 00:00:00 2001 From: NyxCode Date: Sat, 5 Apr 2025 22:26:38 +0200 Subject: [PATCH 04/15] proof-of-concept of type overrides using the CLI (#334) --- Cargo.lock | 524 ++++++++++++++++++++++++++++++-------------- cli/Cargo.toml | 2 + cli/src/args.rs | 48 ---- cli/src/cargo.rs | 21 +- cli/src/config.rs | 153 +++++++++++++ cli/src/main.rs | 31 ++- cli/src/metadata.rs | 2 +- example/ts-rs.toml | 5 + ts-rs/src/lib.rs | 21 +- 9 files changed, 571 insertions(+), 236 deletions(-) delete mode 100644 cli/src/args.rs create mode 100644 cli/src/config.rs create mode 100644 example/ts-rs.toml diff --git a/Cargo.lock b/Cargo.lock index 7ef0ae77..e1d165c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,10 +34,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.2.15", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -51,9 +51,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-tzdata" @@ -122,9 +122,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "ast_node" @@ -135,7 +135,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -161,9 +161,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.1" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "better_scoped_tls" @@ -176,9 +176,9 @@ dependencies = [ [[package]] name = "bigdecimal" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f850665a0385e070b64c38d2354e6c104c8479c59868d1e48a0c13ee2c7a1c1" +checksum = "1a22f228ab7a1b23027ccc6c350b72868017af7ea8356fbdf19f8d991c690013" dependencies = [ "autocfg", "libm", @@ -190,9 +190,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bitvec" @@ -208,27 +208,29 @@ dependencies = [ [[package]] name = "borsh" -version = "1.5.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" dependencies = [ "cfg_aliases", ] [[package]] name = "bson" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068208f2b6fcfa27a7f1ee37488d2bb8ba2640f68f5475d08e1d9130696aba59" +checksum = "af8113ff51309e2779e8785a246c10fb783e8c2452f134d6257fd71cc03ccd6c" dependencies = [ "ahash", "base64", "bitvec", + "getrandom 0.2.15", + "getrandom 0.3.2", "hex", "indexmap", "js-sys", "once_cell", - "rand", + "rand 0.9.0", "serde", "serde_bytes", "serde_json", @@ -238,9 +240,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" dependencies = [ "allocator-api2", ] @@ -253,9 +255,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cargo-ts" @@ -263,13 +265,15 @@ version = "0.1.0" dependencies = [ "clap", "color-eyre", + "serde", + "toml", ] [[package]] name = "cc" -version = "1.2.2" +version = "1.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" dependencies = [ "shlex", ] @@ -288,9 +292,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", @@ -298,7 +302,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets", + "windows-link", ] [[package]] @@ -332,7 +336,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -432,9 +436,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] @@ -447,7 +451,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -509,15 +513,15 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "example" @@ -556,7 +560,7 @@ checksum = "32016f1242eb82af5474752d00fd8ebcd9004bd69b462b1c91de833972d08ed4" dependencies = [ "proc-macro2", "swc_macros_common", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -572,8 +576,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "js-sys", "libc", - "wasi", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -631,9 +651,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hstr" -version = "0.2.12" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dae404c0c5d4e95d4858876ab02eecd6a196bb8caa42050dfa809938833fc412" +checksum = "a1a26def229ea95a8709dad32868d975d0dd40235bd2ce82920e4a8fe692b5e0" dependencies = [ "hashbrown 0.14.5", "new_debug_unreachable", @@ -645,14 +665,15 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -707,9 +728,9 @@ dependencies = [ [[package]] name = "icu_locid_transform_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" @@ -731,9 +752,9 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" @@ -752,9 +773,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" @@ -781,7 +802,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -813,9 +834,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.7.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -831,7 +852,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -842,15 +863,15 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -864,9 +885,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.167" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libm" @@ -882,9 +903,9 @@ checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "log" -version = "0.4.22" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" @@ -953,15 +974,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "ordered-float" -version = "4.5.0" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c65ee1f9701bf938026630b455d5315f490640234259037edb259798b3bcf85e" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" dependencies = [ "num-traits", ] @@ -980,9 +1001,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phf" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", @@ -990,41 +1011,41 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand", + "rand 0.8.5", ] [[package]] name = "phf_macros" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] name = "phf_shared" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher", + "siphasher 1.0.1", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "powerfmt" @@ -1034,40 +1055,46 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy", + "zerocopy 0.8.24", ] [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "psm" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" +checksum = "f58e5423e24c18cc840e1c98370b3993c6649cd1678b4d24318bcf0a083cbe88" dependencies = [ "cc", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "radium" version = "0.7.0" @@ -1080,19 +1107,28 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "libc", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ "rand_chacha", - "rand_core", + "rand_core 0.9.3", + "zerocopy 0.8.24", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.3", ] [[package]] @@ -1100,8 +1136,14 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom", + "getrandom 0.3.2", ] [[package]] @@ -1145,11 +1187,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "scoped-tls" @@ -1159,44 +1207,44 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "semver" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.15" +version = "0.11.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "indexmap", "itoa", @@ -1205,6 +1253,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1226,11 +1283,17 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "smartstring" @@ -1261,9 +1324,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stacker" -version = "0.1.17" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" +checksum = "601f9201feb9b09c00266478bf459952b9ef9a6b94edb2f21eba14ab681a60a9" dependencies = [ "cc", "cfg-if", @@ -1287,7 +1350,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -1324,7 +1387,7 @@ dependencies = [ "once_cell", "rustc-hash", "serde", - "siphasher", + "siphasher 0.3.11", "swc_atoms", "swc_eq_ignore_macros", "swc_visit", @@ -1381,7 +1444,7 @@ checksum = "695a1d8b461033d32429b5befbf0ad4d7a2c4d6ba9cd5ba4e0645c615839e8e4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -1392,7 +1455,7 @@ checksum = "27e18fbfe83811ffae2bb23727e45829a0d19c6870bced7c0f545cc99ad248dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -1415,7 +1478,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -1431,9 +1494,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -1448,7 +1511,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -1486,11 +1549,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.3" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.3", + "thiserror-impl 2.0.12", ] [[package]] @@ -1501,18 +1564,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] name = "thiserror-impl" -version = "2.0.3" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -1527,9 +1590,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -1542,15 +1605,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -1568,14 +1631,48 @@ dependencies = [ [[package]] name = "tokio" -version = "1.41.1" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "pin-project-lite", ] +[[package]] +name = "toml" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.41" @@ -1595,7 +1692,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -1655,7 +1752,7 @@ dependencies = [ "serde", "serde_json", "smol_str", - "thiserror 2.0.3", + "thiserror 2.0.12", "tokio", "ts-rs-macros", "url", @@ -1668,7 +1765,7 @@ version = "10.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", "termcolor", ] @@ -1686,9 +1783,9 @@ checksum = "02aebfa694eccbbbffdd92922c7de136b9fe764396d2f10e21bce1681477cfc1" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-width" @@ -1728,12 +1825,14 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.11.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" dependencies = [ - "getrandom", + "getrandom 0.3.2", + "js-sys", "serde", + "wasm-bindgen", ] [[package]] @@ -1754,37 +1853,46 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1792,22 +1900,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "winapi-util" @@ -1820,11 +1931,61 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.52.0" +version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ - "windows-targets", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", ] [[package]] @@ -1900,6 +2061,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + [[package]] name = "write16" version = "1.0.0" @@ -1941,7 +2120,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", "synstructure", ] @@ -1951,8 +2130,16 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +dependencies = [ + "zerocopy-derive 0.8.24", ] [[package]] @@ -1963,7 +2150,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] @@ -1977,13 +2175,13 @@ dependencies = [ [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", "synstructure", ] @@ -2006,5 +2204,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index a0081955..94fb29e8 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -8,3 +8,5 @@ edition = "2021" [dependencies] clap = { version = "4", features = ["derive"] } color-eyre = "0.6" +serde = { version = "1", features = ["derive"] } +toml = "0.8" \ No newline at end of file diff --git a/cli/src/args.rs b/cli/src/args.rs deleted file mode 100644 index cfc3629e..00000000 --- a/cli/src/args.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::path::PathBuf; - -use clap::Parser; - -use crate::metadata::FILE_NAME; - -#[derive(Parser, Debug)] -#[allow(clippy::struct_excessive_bools)] -pub struct Args { - /// Defines where your TS bindings will be saved by setting `TS_RS_EXPORT_DIR` - #[arg(long, short)] - pub output_directory: PathBuf, - - /// Disables warnings caused by using serde attributes that ts-rs cannot process - #[arg(long)] - pub no_warnings: bool, - - /// Adds the ".js" extension to import paths - #[arg(long)] - pub esm_imports: bool, - - /// Formats the generated TypeScript files - #[arg(long)] - pub format: bool, - - /// Generates an index.ts file in your --output-directory that re-exports all - /// types generated by ts-rs - #[arg(long = "index")] - pub generate_index_ts: bool, - - /// Generates only a single index.ts file in your --output-directory that - /// contains all exported types - #[arg(long = "merge")] - pub merge_files: bool, - - /// Do not capture `cargo test`'s output, and pass --nocapture to the test binary - #[arg(long = "nocapture")] - pub no_capture: bool, -} - -// Args is in scope for the entirety of the main function, so this will only -// be executed when the program is finished running. This helps prevent us -// from forgetting to do cleanup if some code branch early returns from main -impl Drop for Args { - fn drop(&mut self) { - _ = std::fs::remove_file(self.output_directory.join(FILE_NAME)); - } -} diff --git a/cli/src/cargo.rs b/cli/src/cargo.rs index a9695f24..a6e5ac8f 100644 --- a/cli/src/cargo.rs +++ b/cli/src/cargo.rs @@ -2,7 +2,7 @@ use std::process::{Command, Stdio}; use color_eyre::Result; -use crate::{args::Args, path}; +use crate::{config::Args, path}; macro_rules! feature { ($cargo_invocation: expr, $args: expr, { $($field: ident => $feature: literal),* $(,)? }) => { @@ -16,7 +16,7 @@ macro_rules! feature { }; } -pub fn invoke(args: &Args) -> Result<()> { +pub fn invoke(cfg: &Args) -> Result<()> { let mut cargo_invocation = Command::new("cargo"); cargo_invocation @@ -26,20 +26,29 @@ pub fn invoke(args: &Args) -> Result<()> { .arg("ts-rs/export") .arg("--features") .arg("ts-rs/generate-metadata") - .stdout(if args.no_capture { + .stdout(if cfg.no_capture { Stdio::inherit() } else { Stdio::piped() }) - .env("TS_RS_EXPORT_DIR", path::absolute(&args.output_directory)?); + .env("TS_RS_EXPORT_DIR", path::absolute(cfg.output_directory())?); + + if !cfg.overrides.is_empty() { + cargo_invocation.env( + "TS_RS_INTERNAL_OVERRIDE", + cfg.overrides.iter().fold(String::new(), |acc, (k, v)| { + format!("{acc}{}{k}:{v}", if acc.is_empty() { "" } else { ";" }) + }), + ); + } - feature!(cargo_invocation, args, { + feature!(cargo_invocation, cfg, { no_warnings => "no-serde-warnings", esm_imports => "import-esm", format => "format", }); - if args.no_capture { + if cfg.no_capture { cargo_invocation.arg("--").arg("--nocapture"); } else { cargo_invocation.arg("--quiet"); diff --git a/cli/src/config.rs b/cli/src/config.rs new file mode 100644 index 00000000..40e32514 --- /dev/null +++ b/cli/src/config.rs @@ -0,0 +1,153 @@ +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; + +use clap::Parser; +use color_eyre::{eyre::bail, owo_colors::OwoColorize, Result}; +use serde::Deserialize; + +#[derive(Parser, Debug)] +#[allow(clippy::struct_excessive_bools)] +pub struct Args { + #[clap(skip)] + pub overrides: HashMap, + + /// Path to the `ts-rs` config file + #[arg(long)] + pub config: Option, + + /// Defines where your TS bindings will be saved by setting `TS_RS_EXPORT_DIR` + #[arg(long, short)] + pub output_directory: Option, + + /// Disables warnings caused by using serde attributes that ts-rs cannot process + #[arg(long)] + pub no_warnings: bool, + + /// Adds the ".js" extension to import paths + #[arg(long)] + pub esm_imports: bool, + + /// Formats the generated TypeScript files + #[arg(long)] + pub format: bool, + + /// Generates an index.ts file in your --output-directory that re-exports all + /// types generated by ts-rs + #[arg(long = "index")] + pub generate_index_ts: bool, + + /// Generates only a single index.ts file in your --output-directory that + /// contains all exported types + #[arg(long = "merge")] + pub merge_files: bool, + + /// Do not capture `cargo test`'s output, and pass --nocapture to the test binary + #[arg(long = "nocapture")] + pub no_capture: bool, +} + +// keeping this separate from `Args` for now :shrug: +#[derive(Default, Deserialize)] +#[serde(deny_unknown_fields, default, rename_all = "kebab-case")] +#[allow(clippy::struct_excessive_bools)] +pub struct Config { + /// Type overrides for types implemented inside ts-rs. + pub overrides: HashMap, + pub output_directory: Option, + pub no_warnings: bool, + pub esm_imports: bool, + pub format: bool, + + #[serde(rename = "index")] + pub generate_index_ts: bool, + + #[serde(rename = "merge")] + pub merge_files: bool, + + #[serde(rename = "nocapture")] + pub no_capture: bool, +} + +impl Args { + pub fn load() -> Result { + let mut args = Self::parse(); + + let cfg = Config::load_from_file(args.config.as_deref())?; + + args.merge(cfg); + args.verify()?; + + Ok(args) + } + + pub fn output_directory(&self) -> &Path { + self.output_directory + .as_deref() + .expect("Output directory must not be `None`") + } + + fn verify(&self) -> Result<()> { + if self.merge_files && self.generate_index_ts { + bail!( + "{}: --index is not compatible with --merge", + "Error".bold().red() + ); + } + + if self.output_directory.is_none() { + bail!("{}: You must provide the output diretory, either through the config file or the --output-directory flag", "Error".bold().red()) + } + + Ok(()) + } + + fn merge( + &mut self, + Config { + overrides, + output_directory, + no_warnings, + esm_imports, + format, + generate_index_ts, + merge_files, + no_capture, + }: Config, + ) { + // QUESTION: This gives the CLI flag priority over the config file's value, + // is this the correct order? + self.output_directory = output_directory.or_else(|| self.output_directory.clone()); + + self.overrides = overrides; + self.no_warnings |= no_warnings; + self.esm_imports |= esm_imports; + self.format |= format; + self.generate_index_ts |= generate_index_ts; + self.merge_files |= merge_files; + self.no_capture |= no_capture; + } +} + +impl Config { + fn load_from_file(path: Option<&Path>) -> Result { + if let Some(path) = path { + if !path.is_file() { + bail!("The provided path doesn't exist"); + } + + let content = std::fs::read_to_string(path)?; + return Ok(toml::from_str(&content)?); + } + + // TODO: from where do we actually load the config? + let path = Path::new("./ts-rs.toml"); + if !path.is_file() { + return Ok(Self::default()); + } + + let content = std::fs::read_to_string(path)?; + Ok(toml::from_str(&content)?) + } +} diff --git a/cli/src/main.rs b/cli/src/main.rs index 005684c5..254637a1 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -5,39 +5,36 @@ use std::{ io::{Read, Write}, }; -use clap::Parser; use color_eyre::{owo_colors::OwoColorize, Result}; -mod args; mod cargo; +mod config; mod metadata; mod path; -use args::Args; use metadata::{Metadata, FILE_NAME}; +use crate::config::Args; + const BLANK_LINE: [u8; 2] = [b'\n', b'\n']; const NOTE: &[u8; 109] = b"// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n"; +impl Drop for Args { + fn drop(&mut self) { + _ = fs::remove_file(self.output_directory().join(FILE_NAME)); + } +} + fn main() -> Result<()> { color_eyre::install()?; - let args = Args::parse(); + let args = Args::load()?; - let metadata_path = args.output_directory.join(FILE_NAME); + let metadata_path = args.output_directory().join(FILE_NAME); if metadata_path.exists() { fs::remove_file(&metadata_path)?; } - if args.merge_files && args.generate_index_ts { - eprintln!( - "{} --index is not compatible with --merge", - "Error:".red().bold() - ); - - return Ok(()); - } - cargo::invoke(&args)?; let metadata_content = fs::read_to_string(&metadata_path)?; @@ -60,7 +57,7 @@ fn main() -> Result<()> { return Ok(()); } - let index_path = args.output_directory.join("index.ts"); + let index_path = args.output_directory().join("index.ts"); if index_path.exists() { fs::remove_file(&index_path)?; @@ -83,7 +80,7 @@ fn main() -> Result<()> { if args.merge_files { for path in metadata.export_paths() { - let path = path::absolute(args.output_directory.join(path))?; + let path = path::absolute(args.output_directory().join(path))?; let mut file = OpenOptions::new().read(true).open(&path)?; let mut buf = Vec::with_capacity(file.metadata()?.len().try_into()?); @@ -98,7 +95,7 @@ fn main() -> Result<()> { fs::remove_file(path)?; } - path::remove_empty_subdirectories(&args.output_directory)?; + path::remove_empty_subdirectories(args.output_directory())?; return Ok(()); } diff --git a/cli/src/metadata.rs b/cli/src/metadata.rs index ca8aef35..438d43ad 100644 --- a/cli/src/metadata.rs +++ b/cli/src/metadata.rs @@ -12,7 +12,7 @@ use color_eyre::{ pub const FILE_NAME: &str = "ts_rs.meta"; pub struct Metadata<'a> { - entries: std::collections::HashMap<&'a str, HashSet>>, + entries: HashMap<&'a str, HashSet>>, } impl<'a> TryFrom<&'a str> for Metadata<'a> { diff --git a/example/ts-rs.toml b/example/ts-rs.toml new file mode 100644 index 00000000..08506ebd --- /dev/null +++ b/example/ts-rs.toml @@ -0,0 +1,5 @@ +[overrides] +u64 = "number" +i64 = "number" +u128 = "number" +i128 = "number" \ No newline at end of file diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index 9cc9303f..85dbc280 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -134,6 +134,7 @@ use std::{ }, ops::{Range, RangeInclusive}, path::{Path, PathBuf}, + sync::OnceLock, }; pub use ts_rs_macros::TS; @@ -646,13 +647,31 @@ pub trait IsOption {} impl IsOption for Option {} +static OVERRIDES: OnceLock> = OnceLock::new(); + +fn get_override(rust_type: &str) -> Option<&'static str> { + let overrides = OVERRIDES.get_or_init(|| { + std::env::var("TS_RS_INTERNAL_OVERRIDE") + .ok() + .into_iter() + .flat_map(|value| value.leak().split(';')) + .flat_map(|value| value.split_once(':')) + .collect() + }); + overrides.get(rust_type).copied() +} + // generate impls for primitive types macro_rules! impl_primitives { ($($($ty:ty),* => $l:literal),*) => { $($( impl TS for $ty { type WithoutGenerics = Self; type OptionInnerType = Self; - fn name() -> String { $l.to_owned() } + fn name() -> String { + $crate::get_override(stringify!($ty)) + .unwrap_or($l) + .to_owned() + } fn inline() -> String { ::name() } fn inline_flattened() -> String { panic!("{} cannot be flattened", ::name()) } fn decl() -> String { panic!("{} cannot be declared", ::name()) } From 292c75e5dc61d05795153d5120e5040afb13b0b2 Mon Sep 17 00:00:00 2001 From: Gustavo Shigueo Date: Sat, 5 Apr 2025 17:29:23 -0300 Subject: [PATCH 05/15] Fix line endings --- cli/src/config.rs | 306 +++++++++++++++++++++++----------------------- 1 file changed, 153 insertions(+), 153 deletions(-) diff --git a/cli/src/config.rs b/cli/src/config.rs index 40e32514..c6a49f9f 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -1,153 +1,153 @@ -use std::{ - collections::HashMap, - path::{Path, PathBuf}, -}; - -use clap::Parser; -use color_eyre::{eyre::bail, owo_colors::OwoColorize, Result}; -use serde::Deserialize; - -#[derive(Parser, Debug)] -#[allow(clippy::struct_excessive_bools)] -pub struct Args { - #[clap(skip)] - pub overrides: HashMap, - - /// Path to the `ts-rs` config file - #[arg(long)] - pub config: Option, - - /// Defines where your TS bindings will be saved by setting `TS_RS_EXPORT_DIR` - #[arg(long, short)] - pub output_directory: Option, - - /// Disables warnings caused by using serde attributes that ts-rs cannot process - #[arg(long)] - pub no_warnings: bool, - - /// Adds the ".js" extension to import paths - #[arg(long)] - pub esm_imports: bool, - - /// Formats the generated TypeScript files - #[arg(long)] - pub format: bool, - - /// Generates an index.ts file in your --output-directory that re-exports all - /// types generated by ts-rs - #[arg(long = "index")] - pub generate_index_ts: bool, - - /// Generates only a single index.ts file in your --output-directory that - /// contains all exported types - #[arg(long = "merge")] - pub merge_files: bool, - - /// Do not capture `cargo test`'s output, and pass --nocapture to the test binary - #[arg(long = "nocapture")] - pub no_capture: bool, -} - -// keeping this separate from `Args` for now :shrug: -#[derive(Default, Deserialize)] -#[serde(deny_unknown_fields, default, rename_all = "kebab-case")] -#[allow(clippy::struct_excessive_bools)] -pub struct Config { - /// Type overrides for types implemented inside ts-rs. - pub overrides: HashMap, - pub output_directory: Option, - pub no_warnings: bool, - pub esm_imports: bool, - pub format: bool, - - #[serde(rename = "index")] - pub generate_index_ts: bool, - - #[serde(rename = "merge")] - pub merge_files: bool, - - #[serde(rename = "nocapture")] - pub no_capture: bool, -} - -impl Args { - pub fn load() -> Result { - let mut args = Self::parse(); - - let cfg = Config::load_from_file(args.config.as_deref())?; - - args.merge(cfg); - args.verify()?; - - Ok(args) - } - - pub fn output_directory(&self) -> &Path { - self.output_directory - .as_deref() - .expect("Output directory must not be `None`") - } - - fn verify(&self) -> Result<()> { - if self.merge_files && self.generate_index_ts { - bail!( - "{}: --index is not compatible with --merge", - "Error".bold().red() - ); - } - - if self.output_directory.is_none() { - bail!("{}: You must provide the output diretory, either through the config file or the --output-directory flag", "Error".bold().red()) - } - - Ok(()) - } - - fn merge( - &mut self, - Config { - overrides, - output_directory, - no_warnings, - esm_imports, - format, - generate_index_ts, - merge_files, - no_capture, - }: Config, - ) { - // QUESTION: This gives the CLI flag priority over the config file's value, - // is this the correct order? - self.output_directory = output_directory.or_else(|| self.output_directory.clone()); - - self.overrides = overrides; - self.no_warnings |= no_warnings; - self.esm_imports |= esm_imports; - self.format |= format; - self.generate_index_ts |= generate_index_ts; - self.merge_files |= merge_files; - self.no_capture |= no_capture; - } -} - -impl Config { - fn load_from_file(path: Option<&Path>) -> Result { - if let Some(path) = path { - if !path.is_file() { - bail!("The provided path doesn't exist"); - } - - let content = std::fs::read_to_string(path)?; - return Ok(toml::from_str(&content)?); - } - - // TODO: from where do we actually load the config? - let path = Path::new("./ts-rs.toml"); - if !path.is_file() { - return Ok(Self::default()); - } - - let content = std::fs::read_to_string(path)?; - Ok(toml::from_str(&content)?) - } -} +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; + +use clap::Parser; +use color_eyre::{eyre::bail, owo_colors::OwoColorize, Result}; +use serde::Deserialize; + +#[derive(Parser, Debug)] +#[allow(clippy::struct_excessive_bools)] +pub struct Args { + #[clap(skip)] + pub overrides: HashMap, + + /// Path to the `ts-rs` config file + #[arg(long)] + pub config: Option, + + /// Defines where your TS bindings will be saved by setting `TS_RS_EXPORT_DIR` + #[arg(long, short)] + pub output_directory: Option, + + /// Disables warnings caused by using serde attributes that ts-rs cannot process + #[arg(long)] + pub no_warnings: bool, + + /// Adds the ".js" extension to import paths + #[arg(long)] + pub esm_imports: bool, + + /// Formats the generated TypeScript files + #[arg(long)] + pub format: bool, + + /// Generates an index.ts file in your --output-directory that re-exports all + /// types generated by ts-rs + #[arg(long = "index")] + pub generate_index_ts: bool, + + /// Generates only a single index.ts file in your --output-directory that + /// contains all exported types + #[arg(long = "merge")] + pub merge_files: bool, + + /// Do not capture `cargo test`'s output, and pass --nocapture to the test binary + #[arg(long = "nocapture")] + pub no_capture: bool, +} + +// keeping this separate from `Args` for now :shrug: +#[derive(Default, Deserialize)] +#[serde(deny_unknown_fields, default, rename_all = "kebab-case")] +#[allow(clippy::struct_excessive_bools)] +pub struct Config { + /// Type overrides for types implemented inside ts-rs. + pub overrides: HashMap, + pub output_directory: Option, + pub no_warnings: bool, + pub esm_imports: bool, + pub format: bool, + + #[serde(rename = "index")] + pub generate_index_ts: bool, + + #[serde(rename = "merge")] + pub merge_files: bool, + + #[serde(rename = "nocapture")] + pub no_capture: bool, +} + +impl Args { + pub fn load() -> Result { + let mut args = Self::parse(); + + let cfg = Config::load_from_file(args.config.as_deref())?; + + args.merge(cfg); + args.verify()?; + + Ok(args) + } + + pub fn output_directory(&self) -> &Path { + self.output_directory + .as_deref() + .expect("Output directory must not be `None`") + } + + fn verify(&self) -> Result<()> { + if self.merge_files && self.generate_index_ts { + bail!( + "{}: --index is not compatible with --merge", + "Error".bold().red() + ); + } + + if self.output_directory.is_none() { + bail!("{}: You must provide the output diretory, either through the config file or the --output-directory flag", "Error".bold().red()) + } + + Ok(()) + } + + fn merge( + &mut self, + Config { + overrides, + output_directory, + no_warnings, + esm_imports, + format, + generate_index_ts, + merge_files, + no_capture, + }: Config, + ) { + // QUESTION: This gives the CLI flag priority over the config file's value, + // is this the correct order? + self.output_directory = output_directory.or_else(|| self.output_directory.clone()); + + self.overrides = overrides; + self.no_warnings |= no_warnings; + self.esm_imports |= esm_imports; + self.format |= format; + self.generate_index_ts |= generate_index_ts; + self.merge_files |= merge_files; + self.no_capture |= no_capture; + } +} + +impl Config { + fn load_from_file(path: Option<&Path>) -> Result { + if let Some(path) = path { + if !path.is_file() { + bail!("The provided path doesn't exist"); + } + + let content = std::fs::read_to_string(path)?; + return Ok(toml::from_str(&content)?); + } + + // TODO: from where do we actually load the config? + let path = Path::new("./ts-rs.toml"); + if !path.is_file() { + return Ok(Self::default()); + } + + let content = std::fs::read_to_string(path)?; + Ok(toml::from_str(&content)?) + } +} From 3c0acc8b37b2a047244624062a2739694d9f7549 Mon Sep 17 00:00:00 2001 From: Gustavo Shigueo Date: Sat, 5 Apr 2025 18:09:18 -0300 Subject: [PATCH 06/15] Remove type duplication between Args and config --- cli/src/config.rs | 128 +++++++++++++++++++++------------------------- 1 file changed, 57 insertions(+), 71 deletions(-) diff --git a/cli/src/config.rs b/cli/src/config.rs index c6a49f9f..25adf3e7 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -7,15 +7,33 @@ use clap::Parser; use color_eyre::{eyre::bail, owo_colors::OwoColorize, Result}; use serde::Deserialize; -#[derive(Parser, Debug)] +#[derive(Debug)] #[allow(clippy::struct_excessive_bools)] -pub struct Args { +/// This type wraps `Config` and adds an implementation of +/// the `Drop` trait that deletes the metadata file when +/// the CLI finishes running +pub struct Args(Config); + +impl std::ops::Deref for Args { + type Target = Config; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Parser, Debug, Default, Deserialize)] +#[serde(deny_unknown_fields, default, rename_all = "kebab-case")] +#[allow(clippy::struct_excessive_bools)] +pub struct Config { #[clap(skip)] + /// Type overrides for types implemented inside ts-rs. pub overrides: HashMap, /// Path to the `ts-rs` config file - #[arg(long)] - pub config: Option, + #[arg(long = "config")] + #[serde(skip)] + pub config_file_path: Option, /// Defines where your TS bindings will be saved by setting `TS_RS_EXPORT_DIR` #[arg(long, short)] @@ -36,101 +54,54 @@ pub struct Args { /// Generates an index.ts file in your --output-directory that re-exports all /// types generated by ts-rs #[arg(long = "index")] + #[serde(rename = "index")] pub generate_index_ts: bool, /// Generates only a single index.ts file in your --output-directory that /// contains all exported types #[arg(long = "merge")] + #[serde(rename = "merge")] pub merge_files: bool, /// Do not capture `cargo test`'s output, and pass --nocapture to the test binary #[arg(long = "nocapture")] - pub no_capture: bool, -} - -// keeping this separate from `Args` for now :shrug: -#[derive(Default, Deserialize)] -#[serde(deny_unknown_fields, default, rename_all = "kebab-case")] -#[allow(clippy::struct_excessive_bools)] -pub struct Config { - /// Type overrides for types implemented inside ts-rs. - pub overrides: HashMap, - pub output_directory: Option, - pub no_warnings: bool, - pub esm_imports: bool, - pub format: bool, - - #[serde(rename = "index")] - pub generate_index_ts: bool, - - #[serde(rename = "merge")] - pub merge_files: bool, - #[serde(rename = "nocapture")] pub no_capture: bool, } impl Args { pub fn load() -> Result { - let mut args = Self::parse(); + let cli_args = Config::parse(); + let config_file_args = Config::load_from_file(cli_args.config_file_path.as_deref())?; - let cfg = Config::load_from_file(args.config.as_deref())?; + let cfg = cli_args.merge(config_file_args); + cfg.verify()?; - args.merge(cfg); - args.verify()?; - - Ok(args) + Ok(Self(cfg)) } +} +impl Config { pub fn output_directory(&self) -> &Path { self.output_directory .as_deref() .expect("Output directory must not be `None`") } - fn verify(&self) -> Result<()> { - if self.merge_files && self.generate_index_ts { - bail!( - "{}: --index is not compatible with --merge", - "Error".bold().red() - ); - } - - if self.output_directory.is_none() { - bail!("{}: You must provide the output diretory, either through the config file or the --output-directory flag", "Error".bold().red()) + fn merge(self, other: Config) -> Self { + Self { + output_directory: self.output_directory.or_else(|| other.output_directory), + overrides: other.overrides, + no_warnings: self.no_warnings || other.no_warnings, + esm_imports: self.esm_imports || other.esm_imports, + format: self.format || other.format, + generate_index_ts: self.generate_index_ts || other.generate_index_ts, + merge_files: self.merge_files || other.merge_files, + no_capture: self.no_capture || other.no_capture, + config_file_path: None, } - - Ok(()) - } - - fn merge( - &mut self, - Config { - overrides, - output_directory, - no_warnings, - esm_imports, - format, - generate_index_ts, - merge_files, - no_capture, - }: Config, - ) { - // QUESTION: This gives the CLI flag priority over the config file's value, - // is this the correct order? - self.output_directory = output_directory.or_else(|| self.output_directory.clone()); - - self.overrides = overrides; - self.no_warnings |= no_warnings; - self.esm_imports |= esm_imports; - self.format |= format; - self.generate_index_ts |= generate_index_ts; - self.merge_files |= merge_files; - self.no_capture |= no_capture; } -} -impl Config { fn load_from_file(path: Option<&Path>) -> Result { if let Some(path) = path { if !path.is_file() { @@ -150,4 +121,19 @@ impl Config { let content = std::fs::read_to_string(path)?; Ok(toml::from_str(&content)?) } + + fn verify(&self) -> Result<()> { + if self.merge_files && self.generate_index_ts { + bail!( + "{}: --index is not compatible with --merge", + "Error".bold().red() + ); + } + + if self.output_directory.is_none() { + bail!("{}: You must provide the output diretory, either through the config file or the --output-directory flag", "Error".bold().red()) + } + + Ok(()) + } } From 045dc888135508fbd7d1e9f2d06554221fe94f32 Mon Sep 17 00:00:00 2001 From: Gustavo Shigueo Date: Sat, 5 Apr 2025 18:13:23 -0300 Subject: [PATCH 07/15] Simplify load_from_file --- cli/src/config.rs | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/cli/src/config.rs b/cli/src/config.rs index 25adf3e7..227530fd 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -57,12 +57,6 @@ pub struct Config { #[serde(rename = "index")] pub generate_index_ts: bool, - /// Generates only a single index.ts file in your --output-directory that - /// contains all exported types - #[arg(long = "merge")] - #[serde(rename = "merge")] - pub merge_files: bool, - /// Do not capture `cargo test`'s output, and pass --nocapture to the test binary #[arg(long = "nocapture")] #[serde(rename = "nocapture")] @@ -96,24 +90,17 @@ impl Config { esm_imports: self.esm_imports || other.esm_imports, format: self.format || other.format, generate_index_ts: self.generate_index_ts || other.generate_index_ts, - merge_files: self.merge_files || other.merge_files, no_capture: self.no_capture || other.no_capture, config_file_path: None, } } fn load_from_file(path: Option<&Path>) -> Result { - if let Some(path) = path { - if !path.is_file() { - bail!("The provided path doesn't exist"); - } - - let content = std::fs::read_to_string(path)?; - return Ok(toml::from_str(&content)?); + if path.is_some_and(|x| !x.is_file()) { + bail!("The provided path doesn't exist"); } - // TODO: from where do we actually load the config? - let path = Path::new("./ts-rs.toml"); + let path = path.unwrap_or_else(|| Path::new("./ts-rs.toml")); if !path.is_file() { return Ok(Self::default()); } @@ -123,13 +110,6 @@ impl Config { } fn verify(&self) -> Result<()> { - if self.merge_files && self.generate_index_ts { - bail!( - "{}: --index is not compatible with --merge", - "Error".bold().red() - ); - } - if self.output_directory.is_none() { bail!("{}: You must provide the output diretory, either through the config file or the --output-directory flag", "Error".bold().red()) } From b83a888c05408c94494002d389af4025359eb272 Mon Sep 17 00:00:00 2001 From: Gustavo Shigueo Date: Sat, 5 Apr 2025 18:16:20 -0300 Subject: [PATCH 08/15] Remove merge flag --- cli/src/config.rs | 5 +++-- cli/src/main.rs | 29 ++--------------------------- cli/src/path.rs | 36 +----------------------------------- 3 files changed, 6 insertions(+), 64 deletions(-) diff --git a/cli/src/config.rs b/cli/src/config.rs index 227530fd..138a7358 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -33,6 +33,7 @@ pub struct Config { /// Path to the `ts-rs` config file #[arg(long = "config")] #[serde(skip)] + #[allow(clippy::struct_field_names)] pub config_file_path: Option, /// Defines where your TS bindings will be saved by setting `TS_RS_EXPORT_DIR` @@ -82,9 +83,9 @@ impl Config { .expect("Output directory must not be `None`") } - fn merge(self, other: Config) -> Self { + fn merge(self, other: Self) -> Self { Self { - output_directory: self.output_directory.or_else(|| other.output_directory), + output_directory: self.output_directory.or(other.output_directory), overrides: other.overrides, no_warnings: self.no_warnings || other.no_warnings, esm_imports: self.esm_imports || other.esm_imports, diff --git a/cli/src/main.rs b/cli/src/main.rs index 254637a1..4824b2b3 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -2,7 +2,7 @@ use std::{ fs::{self, OpenOptions}, - io::{Read, Write}, + io::Write, }; use color_eyre::{owo_colors::OwoColorize, Result}; @@ -16,7 +16,6 @@ use metadata::{Metadata, FILE_NAME}; use crate::config::Args; -const BLANK_LINE: [u8; 2] = [b'\n', b'\n']; const NOTE: &[u8; 109] = b"// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.\n"; impl Drop for Args { @@ -40,9 +39,7 @@ fn main() -> Result<()> { let metadata_content = fs::read_to_string(&metadata_path)?; let metadata = Metadata::try_from(&*metadata_content)?; - let demand_unique_names = args.merge_files || args.generate_index_ts; - - if !demand_unique_names || metadata.is_empty() { + if !args.generate_index_ts || metadata.is_empty() { return Ok(()); } @@ -78,27 +75,5 @@ fn main() -> Result<()> { return Ok(()); } - if args.merge_files { - for path in metadata.export_paths() { - let path = path::absolute(args.output_directory().join(path))?; - let mut file = OpenOptions::new().read(true).open(&path)?; - - let mut buf = Vec::with_capacity(file.metadata()?.len().try_into()?); - file.read_to_end(&mut buf)?; - - let Some((i, _)) = buf.windows(2).enumerate().find(|(_, w)| w == &BLANK_LINE) else { - continue; - }; - - index.write_all(&buf[i + 1..])?; - - fs::remove_file(path)?; - } - - path::remove_empty_subdirectories(args.output_directory())?; - - return Ok(()); - } - Ok(()) } diff --git a/cli/src/path.rs b/cli/src/path.rs index 6e4255e3..257a7145 100644 --- a/cli/src/path.rs +++ b/cli/src/path.rs @@ -1,8 +1,4 @@ -use std::{ - fs, - io::ErrorKind, - path::{Component as C, Path, PathBuf}, -}; +use std::path::{Component as C, Path, PathBuf}; use color_eyre::{eyre::OptionExt, Result}; @@ -32,33 +28,3 @@ pub fn absolute>(path: T) -> Result { out.iter().collect() }) } - -pub fn remove_empty_subdirectories>(path: T) -> Result<()> { - let path = path.as_ref(); - - for entry in path.read_dir()? { - let entry = entry?; - - let path = entry.path(); - - if !path.is_dir() { - continue; - } - - remove_empty_subdirectories(&path)?; - if let Err(e) = fs::remove_dir(path) { - // The other possible error kinds are either not available - // in stable rust (DirectoryNotEmpty), not possible due - // to the logic of this function (NotFound) or both (NotADirectory) - // - // The correct check would be `!matches!(e.kind(), ErrorKind::DirectoryNotEmpty)` - // as that is the only error we actually WANT to ignore... the others, - // although impossible, should be returned if they somehow happen - if matches!(e.kind(), ErrorKind::PermissionDenied) { - return Err(e.into()); - } - } - } - - Ok(()) -} From ca32539fe0a739df66f73f038e75d26f32d3c186 Mon Sep 17 00:00:00 2001 From: Gustavo Shigueo Date: Sat, 5 Apr 2025 18:51:54 -0300 Subject: [PATCH 09/15] Add init command to generate default config file --- cli/src/cargo.rs | 4 +- cli/src/config.rs | 51 ++++++++++++++++++++------ cli/src/main.rs | 93 +++++++++++++++++++++++++++++------------------ 3 files changed, 100 insertions(+), 48 deletions(-) diff --git a/cli/src/cargo.rs b/cli/src/cargo.rs index a6e5ac8f..baa1aa20 100644 --- a/cli/src/cargo.rs +++ b/cli/src/cargo.rs @@ -2,7 +2,7 @@ use std::process::{Command, Stdio}; use color_eyre::Result; -use crate::{config::Args, path}; +use crate::{config::ExportConfig, path}; macro_rules! feature { ($cargo_invocation: expr, $args: expr, { $($field: ident => $feature: literal),* $(,)? }) => { @@ -16,7 +16,7 @@ macro_rules! feature { }; } -pub fn invoke(cfg: &Args) -> Result<()> { +pub fn invoke(cfg: &ExportConfig) -> Result<()> { let mut cargo_invocation = Command::new("cargo"); cargo_invocation diff --git a/cli/src/config.rs b/cli/src/config.rs index 138a7358..a1b81fad 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -5,27 +5,34 @@ use std::{ use clap::Parser; use color_eyre::{eyre::bail, owo_colors::OwoColorize, Result}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; #[derive(Debug)] #[allow(clippy::struct_excessive_bools)] /// This type wraps `Config` and adds an implementation of /// the `Drop` trait that deletes the metadata file when /// the CLI finishes running -pub struct Args(Config); +pub struct Args(pub Cli); impl std::ops::Deref for Args { - type Target = Config; + type Target = Cli; fn deref(&self) -> &Self::Target { &self.0 } } -#[derive(Parser, Debug, Default, Deserialize)] +#[derive(Parser, Debug)] +#[allow(clippy::struct_excessive_bools)] +pub enum Cli { + Init, + Export(ExportConfig), +} + +#[derive(Parser, Debug, Serialize, Deserialize)] #[serde(deny_unknown_fields, default, rename_all = "kebab-case")] #[allow(clippy::struct_excessive_bools)] -pub struct Config { +pub struct ExportConfig { #[clap(skip)] /// Type overrides for types implemented inside ts-rs. pub overrides: HashMap, @@ -64,19 +71,41 @@ pub struct Config { pub no_capture: bool, } +impl Default for ExportConfig { + fn default() -> Self { + Self { + overrides: HashMap::default(), + config_file_path: None, + output_directory: Some(PathBuf::from("./bindings")), + no_warnings: false, + esm_imports: false, + format: false, + generate_index_ts: false, + no_capture: false, + } + } +} + impl Args { pub fn load() -> Result { - let cli_args = Config::parse(); - let config_file_args = Config::load_from_file(cli_args.config_file_path.as_deref())?; + let cli_args = Cli::parse(); - let cfg = cli_args.merge(config_file_args); - cfg.verify()?; + match cli_args { + Cli::Init => Ok(Self(cli_args)), + Cli::Export(export_config) => { + let config_file_args = + ExportConfig::load_from_file(export_config.config_file_path.as_deref())?; - Ok(Self(cfg)) + let cfg = export_config.merge(config_file_args); + cfg.verify()?; + + Ok(Self(Cli::Export(cfg))) + } + } } } -impl Config { +impl ExportConfig { pub fn output_directory(&self) -> &Path { self.output_directory .as_deref() diff --git a/cli/src/main.rs b/cli/src/main.rs index 4824b2b3..aa746718 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -3,6 +3,7 @@ use std::{ fs::{self, OpenOptions}, io::Write, + path::Path, }; use color_eyre::{owo_colors::OwoColorize, Result}; @@ -12,6 +13,7 @@ mod config; mod metadata; mod path; +use config::{Cli, ExportConfig}; use metadata::{Metadata, FILE_NAME}; use crate::config::Args; @@ -20,7 +22,9 @@ const NOTE: &[u8; 109] = b"// This file was generated by [ts-rs](https://github. impl Drop for Args { fn drop(&mut self) { - _ = fs::remove_file(self.output_directory().join(FILE_NAME)); + if let Self(Cli::Export(config)) = self { + _ = fs::remove_file(config.output_directory().join(FILE_NAME)); + } } } @@ -29,51 +33,70 @@ fn main() -> Result<()> { let args = Args::load()?; - let metadata_path = args.output_directory().join(FILE_NAME); - if metadata_path.exists() { - fs::remove_file(&metadata_path)?; - } + match args.0 { + Cli::Init => { + let path = Path::new("./ts-rs.toml"); + if path.exists() { + eprintln!("{} the config file already exists", "Error:".red().bold()); + return Ok(()); + } - cargo::invoke(&args)?; + let mut file = OpenOptions::new().create(true).append(true).open(path)?; - let metadata_content = fs::read_to_string(&metadata_path)?; - let metadata = Metadata::try_from(&*metadata_content)?; + let config = ExportConfig::default(); - if !args.generate_index_ts || metadata.is_empty() { - return Ok(()); - } + write!(file, "{}", toml::to_string_pretty(&config)?)?; - if metadata.has_naming_collisions() { - metadata.report_naming_collisions(); + Ok(()) + } + Cli::Export(ref args) => { + let metadata_path = args.output_directory().join(FILE_NAME); + if metadata_path.exists() { + fs::remove_file(&metadata_path)?; + } - eprintln!( - "{} due to the naming collisions listed above, generating an index.ts file is not possible", - "Error:".red().bold() - ); + cargo::invoke(args)?; - return Ok(()); - } + let metadata_content = fs::read_to_string(&metadata_path)?; + let metadata = Metadata::try_from(&*metadata_content)?; - let index_path = args.output_directory().join("index.ts"); + if !args.generate_index_ts || metadata.is_empty() { + return Ok(()); + } - if index_path.exists() { - fs::remove_file(&index_path)?; - } + if metadata.has_naming_collisions() { + metadata.report_naming_collisions(); - let mut index = OpenOptions::new() - .create(true) - .append(true) - .open(index_path)?; + eprintln!( + "{} due to the naming collisions listed above, generating an index.ts file is not possible", + "Error:".red().bold() + ); - index.write_all(NOTE)?; + return Ok(()); + } - if args.generate_index_ts { - for path in metadata.export_paths() { - index.write_fmt(format_args!("\nexport * from {path:?};"))?; - } + let index_path = args.output_directory().join("index.ts"); - return Ok(()); - } + if index_path.exists() { + fs::remove_file(&index_path)?; + } + + let mut index = OpenOptions::new() + .create(true) + .append(true) + .open(index_path)?; + + index.write_all(NOTE)?; + + if args.generate_index_ts { + for path in metadata.export_paths() { + index.write_fmt(format_args!("\nexport * from {path:?};"))?; + } - Ok(()) + return Ok(()); + } + + Ok(()) + } + } } From c22bc1a23be41489d4ca0bc23730511f81664e2f Mon Sep 17 00:00:00 2001 From: Gustavo Shigueo Date: Sat, 5 Apr 2025 18:55:32 -0300 Subject: [PATCH 10/15] Document subcommands --- cli/src/config.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/src/config.rs b/cli/src/config.rs index a1b81fad..d0925492 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -25,7 +25,10 @@ impl std::ops::Deref for Args { #[derive(Parser, Debug)] #[allow(clippy::struct_excessive_bools)] pub enum Cli { + /// Initialize the `ts-rs.toml` config file Init, + + /// Export your types into TypeScript Export(ExportConfig), } From cea32cc1969135c164b5abf83f4302d63ef0180a Mon Sep 17 00:00:00 2001 From: Gustavo Shigueo Date: Sat, 5 Apr 2025 19:07:18 -0300 Subject: [PATCH 11/15] Rename the binary to ts-rs --- Cargo.lock | 20 ++++++++++---------- cli/Cargo.toml | 8 ++++++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1d165c6..bf5bc5b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,16 +259,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" -[[package]] -name = "cargo-ts" -version = "0.1.0" -dependencies = [ - "clap", - "color-eyre", - "serde", - "toml", -] - [[package]] name = "cc" version = "1.2.18" @@ -1759,6 +1749,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "ts-rs-cli" +version = "0.1.0" +dependencies = [ + "clap", + "color-eyre", + "serde", + "toml", +] + [[package]] name = "ts-rs-macros" version = "10.1.0" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 94fb29e8..d3691d91 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,12 +1,16 @@ [package] -name = "cargo-ts" +name = "ts-rs-cli" version = "0.1.0" edition = "2021" +[[bin]] +name = "ts-rs" +path = "src/main.rs" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] clap = { version = "4", features = ["derive"] } color-eyre = "0.6" serde = { version = "1", features = ["derive"] } -toml = "0.8" \ No newline at end of file +toml = "0.8" From 658cf88c7beb22a60cf8841bcc69df8b40383f85 Mon Sep 17 00:00:00 2001 From: Gustavo Shigueo Date: Sat, 5 Apr 2025 19:44:36 -0300 Subject: [PATCH 12/15] Set workspace root as default config file path --- cli/src/cargo.rs | 24 ++++++++++++++++++++++-- cli/src/config.rs | 8 +++++++- cli/src/main.rs | 4 ++-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/cli/src/cargo.rs b/cli/src/cargo.rs index baa1aa20..90607611 100644 --- a/cli/src/cargo.rs +++ b/cli/src/cargo.rs @@ -1,6 +1,9 @@ -use std::process::{Command, Stdio}; +use std::{ + path::PathBuf, + process::{Command, Stdio}, +}; -use color_eyre::Result; +use color_eyre::{eyre::bail, Result}; use crate::{config::ExportConfig, path}; @@ -58,3 +61,20 @@ pub fn invoke(cfg: &ExportConfig) -> Result<()> { Ok(()) } + +pub fn workspace_location() -> Result { + let output = Command::new("cargo") + .arg("locate-project") + .arg("--workspace") + .arg("--message-format=plain") + .output()?; + + match output.status.code() { + Some(0) => Ok(PathBuf::from(std::str::from_utf8(&output.stdout)?) + .parent() + .map(ToOwned::to_owned) + .unwrap_or_else(|| PathBuf::from("/"))), + Some(_) => bail!("{}", std::str::from_utf8(&output.stderr)?), + None => bail!("Unable to obtain workspace path"), + } +} diff --git a/cli/src/config.rs b/cli/src/config.rs index d0925492..d4d41188 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -7,6 +7,8 @@ use clap::Parser; use color_eyre::{eyre::bail, owo_colors::OwoColorize, Result}; use serde::{Deserialize, Serialize}; +use crate::cargo::workspace_location; + #[derive(Debug)] #[allow(clippy::struct_excessive_bools)] /// This type wraps `Config` and adds an implementation of @@ -133,7 +135,11 @@ impl ExportConfig { bail!("The provided path doesn't exist"); } - let path = path.unwrap_or_else(|| Path::new("./ts-rs.toml")); + let path = match path { + Some(path) => path, + None => &workspace_location()?.join("./ts-rs.toml"), + }; + if !path.is_file() { return Ok(Self::default()); } diff --git a/cli/src/main.rs b/cli/src/main.rs index aa746718..ef0e7466 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -3,9 +3,9 @@ use std::{ fs::{self, OpenOptions}, io::Write, - path::Path, }; +use cargo::workspace_location; use color_eyre::{owo_colors::OwoColorize, Result}; mod cargo; @@ -35,7 +35,7 @@ fn main() -> Result<()> { match args.0 { Cli::Init => { - let path = Path::new("./ts-rs.toml"); + let path = workspace_location()?.join("ts-rs.toml"); if path.exists() { eprintln!("{} the config file already exists", "Error:".red().bold()); return Ok(()); From d767438d9c3190b4a55c0b8e01e2827b599b7090 Mon Sep 17 00:00:00 2001 From: Gustavo Shigueo Date: Sat, 5 Apr 2025 19:46:12 -0300 Subject: [PATCH 13/15] Add ts-rs.toml to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b4554c33..2b3da893 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ tsconfig.json /ts-rs/tests-out ts_rs.meta +ts-rs.toml From ec647446169592ca816b80ea7596426a2f7f8e76 Mon Sep 17 00:00:00 2001 From: Gustavo Shigueo Date: Sat, 5 Apr 2025 19:47:09 -0300 Subject: [PATCH 14/15] Rename metadata file to ts-rs.meta for consistency --- .gitignore | 2 +- cli/src/metadata.rs | 2 +- ts-rs/src/export.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 2b3da893..53b122db 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,5 @@ target/ tsconfig.json /ts-rs/tests-out -ts_rs.meta +ts-rs.meta ts-rs.toml diff --git a/cli/src/metadata.rs b/cli/src/metadata.rs index 438d43ad..7f5d4ec1 100644 --- a/cli/src/metadata.rs +++ b/cli/src/metadata.rs @@ -9,7 +9,7 @@ use color_eyre::{ Result, }; -pub const FILE_NAME: &str = "ts_rs.meta"; +pub const FILE_NAME: &str = "ts-rs.meta"; pub struct Metadata<'a> { entries: HashMap<&'a str, HashSet>>, diff --git a/ts-rs/src/export.rs b/ts-rs/src/export.rs index 4d8deb01..e5ed2e8c 100644 --- a/ts-rs/src/export.rs +++ b/ts-rs/src/export.rs @@ -155,7 +155,7 @@ fn export_and_merge( std::fs::OpenOptions::new() .append(true) .create(true) - .open(default_out_dir().join("ts_rs.meta"))? + .open(default_out_dir().join("ts-rs.meta"))? .write_fmt(format_args!( "{type_ts_name},{type_rs_name},./{relative_path}\n" ))?; From 39d4873437015b0978bf903172bfa3d08b7bb200 Mon Sep 17 00:00:00 2001 From: Gustavo Shigueo <58121396+gustavo-shigueo@users.noreply.github.com> Date: Thu, 29 May 2025 19:52:23 -0300 Subject: [PATCH 15/15] Use Option for proper argument merging (#402) --- cli/src/cargo.rs | 9 ++++----- cli/src/config.rs | 40 ++++++++++++++++++++-------------------- cli/src/main.rs | 4 ++-- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/cli/src/cargo.rs b/cli/src/cargo.rs index 90607611..f37f7809 100644 --- a/cli/src/cargo.rs +++ b/cli/src/cargo.rs @@ -10,7 +10,7 @@ use crate::{config::ExportConfig, path}; macro_rules! feature { ($cargo_invocation: expr, $args: expr, { $($field: ident => $feature: literal),* $(,)? }) => { $( - if $args.$field { + if $args.$field.unwrap_or(false) { $cargo_invocation .arg("--features") .arg(format!("ts-rs/{}", $feature)); @@ -29,7 +29,7 @@ pub fn invoke(cfg: &ExportConfig) -> Result<()> { .arg("ts-rs/export") .arg("--features") .arg("ts-rs/generate-metadata") - .stdout(if cfg.no_capture { + .stdout(if cfg.no_capture.unwrap_or(false) { Stdio::inherit() } else { Stdio::piped() @@ -51,7 +51,7 @@ pub fn invoke(cfg: &ExportConfig) -> Result<()> { format => "format", }); - if cfg.no_capture { + if cfg.no_capture.unwrap_or(false) { cargo_invocation.arg("--").arg("--nocapture"); } else { cargo_invocation.arg("--quiet"); @@ -72,8 +72,7 @@ pub fn workspace_location() -> Result { match output.status.code() { Some(0) => Ok(PathBuf::from(std::str::from_utf8(&output.stdout)?) .parent() - .map(ToOwned::to_owned) - .unwrap_or_else(|| PathBuf::from("/"))), + .map_or_else(|| PathBuf::from("/"), ToOwned::to_owned)), Some(_) => bail!("{}", std::str::from_utf8(&output.stderr)?), None => bail!("Unable to obtain workspace path"), } diff --git a/cli/src/config.rs b/cli/src/config.rs index d4d41188..179ebea3 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -53,27 +53,27 @@ pub struct ExportConfig { pub output_directory: Option, /// Disables warnings caused by using serde attributes that ts-rs cannot process - #[arg(long)] - pub no_warnings: bool, + #[arg(long, default_missing_value = "true", num_args = 0..=1)] + pub no_warnings: Option, /// Adds the ".js" extension to import paths - #[arg(long)] - pub esm_imports: bool, + #[arg(long, default_missing_value = "true", num_args = 0..=1)] + pub esm_imports: Option, /// Formats the generated TypeScript files - #[arg(long)] - pub format: bool, + #[arg(long, default_missing_value = "true", num_args = 0..=1)] + pub format: Option, /// Generates an index.ts file in your --output-directory that re-exports all /// types generated by ts-rs - #[arg(long = "index")] + #[arg(long = "index", default_missing_value = "true", num_args = 0..=1)] #[serde(rename = "index")] - pub generate_index_ts: bool, + pub generate_index_ts: Option, /// Do not capture `cargo test`'s output, and pass --nocapture to the test binary - #[arg(long = "nocapture")] + #[arg(long = "nocapture", default_missing_value = "true", num_args = 0..=1)] #[serde(rename = "nocapture")] - pub no_capture: bool, + pub no_capture: Option, } impl Default for ExportConfig { @@ -82,11 +82,11 @@ impl Default for ExportConfig { overrides: HashMap::default(), config_file_path: None, output_directory: Some(PathBuf::from("./bindings")), - no_warnings: false, - esm_imports: false, - format: false, - generate_index_ts: false, - no_capture: false, + no_warnings: None, + esm_imports: None, + format: None, + generate_index_ts: None, + no_capture: None, } } } @@ -121,11 +121,11 @@ impl ExportConfig { Self { output_directory: self.output_directory.or(other.output_directory), overrides: other.overrides, - no_warnings: self.no_warnings || other.no_warnings, - esm_imports: self.esm_imports || other.esm_imports, - format: self.format || other.format, - generate_index_ts: self.generate_index_ts || other.generate_index_ts, - no_capture: self.no_capture || other.no_capture, + no_warnings: self.no_warnings.or(other.no_warnings), + esm_imports: self.esm_imports.or(other.esm_imports), + format: self.format.or(other.format), + generate_index_ts: self.generate_index_ts.or(other.generate_index_ts), + no_capture: self.no_capture.or(other.no_capture), config_file_path: None, } } diff --git a/cli/src/main.rs b/cli/src/main.rs index ef0e7466..ed1768b4 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -60,7 +60,7 @@ fn main() -> Result<()> { let metadata_content = fs::read_to_string(&metadata_path)?; let metadata = Metadata::try_from(&*metadata_content)?; - if !args.generate_index_ts || metadata.is_empty() { + if !args.generate_index_ts.unwrap_or(false) || metadata.is_empty() { return Ok(()); } @@ -88,7 +88,7 @@ fn main() -> Result<()> { index.write_all(NOTE)?; - if args.generate_index_ts { + if args.generate_index_ts.unwrap_or(false) { for path in metadata.export_paths() { index.write_fmt(format_args!("\nexport * from {path:?};"))?; }