From 3dfe561ab86e9ea35018d98c46fb3a6ad8d46573 Mon Sep 17 00:00:00 2001 From: yihau Date: Thu, 18 Jun 2026 19:07:48 +0800 Subject: [PATCH 1/5] add 'cargo trustpub list' --- crates/crates-io/lib.rs | 29 +++++++++++ src/bin/cargo/commands/mod.rs | 3 ++ src/bin/cargo/commands/trustpub.rs | 39 +++++++++++++++ src/cargo/ops/mod.rs | 3 ++ src/cargo/ops/registry/mod.rs | 4 ++ src/cargo/ops/registry/trustpub.rs | 77 ++++++++++++++++++++++++++++++ 6 files changed, 155 insertions(+) create mode 100644 src/bin/cargo/commands/trustpub.rs create mode 100644 src/cargo/ops/registry/trustpub.rs diff --git a/crates/crates-io/lib.rs b/crates/crates-io/lib.rs index a2541ce5d75..cd351cba8d2 100644 --- a/crates/crates-io/lib.rs +++ b/crates/crates-io/lib.rs @@ -145,6 +145,23 @@ struct Crates { meta: TotalCrates, } +#[derive(Deserialize)] +pub struct GitHubConfig { + pub id: u32, + #[serde(rename = "crate")] + pub krate: String, + pub repository_owner: String, + pub repository_owner_id: Option, + pub repository_name: String, + pub workflow_filename: String, + pub environment: Option, + pub created_at: Option, +} +#[derive(Deserialize)] +struct GitHubConfigs { + github_configs: Vec, +} + /// Error returned when interacting with a registry. #[derive(Debug, thiserror::Error)] #[non_exhaustive] @@ -274,6 +291,18 @@ impl Registry { Ok(serde_json::from_str::(&body)?.users) } + pub fn list_github_trustpub_configs( + &mut self, + krate: &str, + ) -> RegistryResult, T::Error> { + let krate = percent_encode(krate.as_bytes(), NON_ALPHANUMERIC); + let body = self.get(&format!( + "/trusted_publishing/github_configs?crate={}", + krate + ))?; + Ok(serde_json::from_str::(&body)?.github_configs) + } + pub fn publish( &mut self, krate: &NewCrate, diff --git a/src/bin/cargo/commands/mod.rs b/src/bin/cargo/commands/mod.rs index b507226f3a9..b59cf653883 100644 --- a/src/bin/cargo/commands/mod.rs +++ b/src/bin/cargo/commands/mod.rs @@ -35,6 +35,7 @@ pub fn builtin() -> Vec { search::cli(), test::cli(), tree::cli(), + trustpub::cli(), uninstall::cli(), update::cli(), vendor::cli(), @@ -81,6 +82,7 @@ pub fn builtin_exec(cmd: &str) -> Option { "search" => search::exec, "test" => test::exec, "tree" => tree::exec, + "trustpub" => trustpub::exec, "uninstall" => uninstall::exec, "update" => update::exec, "vendor" => vendor::exec, @@ -125,6 +127,7 @@ pub mod rustdoc; pub mod search; pub mod test; pub mod tree; +pub mod trustpub; pub mod uninstall; pub mod update; pub mod vendor; diff --git a/src/bin/cargo/commands/trustpub.rs b/src/bin/cargo/commands/trustpub.rs new file mode 100644 index 00000000000..c504cd083aa --- /dev/null +++ b/src/bin/cargo/commands/trustpub.rs @@ -0,0 +1,39 @@ +use crate::command_prelude::*; + +use cargo::ops::{self, TrustpubCommand, TrustpubOptions}; +use cargo_credential::Secret; + +pub fn cli() -> Command { + subcommand("trustpub") + .about("Manage Trusted Publishing configuration for a crate on the registry") + .subcommand_required(true) + .arg_required_else_help(true) + .arg( + opt("crate", "Crate to operate on") + .value_name("CRATE") + .global(true), + ) + .arg( + opt("token", "API token to use when authenticating") + .value_name("TOKEN") + .global(true), + ) + .arg_silent_suggestion() + .subcommand(subcommand("list").about("List the Trusted Publishing configs for a crate")) +} + +pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { + let command = match args.subcommand() { + Some(("list", _)) => TrustpubCommand::List, + Some((cmd, _)) => unreachable!("unexpected command {}", cmd), + None => unreachable!("unexpected command"), + }; + + let opts = TrustpubOptions { + krate: args.get_one::("crate").cloned(), + token: args.get_one::("token").cloned().map(Secret::from), + command, + }; + ops::trusted_publish(gctx, &opts)?; + Ok(()) +} diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs index 04de1164244..4f5014d617d 100644 --- a/src/cargo/ops/mod.rs +++ b/src/cargo/ops/mod.rs @@ -39,6 +39,8 @@ pub use self::fix::{ pub use self::lockfile::{load_pkg_lockfile, resolve_to_string, write_pkg_lockfile}; pub use self::registry::OwnersOptions; pub use self::registry::PublishOpts; +pub use self::registry::TrustpubCommand; +pub use self::registry::TrustpubOptions; pub use self::registry::RegistryCredentialConfig; pub use self::registry::RegistryOrIndex; pub use self::registry::info; @@ -47,6 +49,7 @@ pub use self::registry::publish; pub use self::registry::registry_login; pub use self::registry::registry_logout; pub use self::registry::search; +pub use self::registry::trusted_publish; pub use self::registry::yank; pub use self::resolve::{ WorkspaceResolve, add_overrides, get_resolved_packages, resolve_with_previous, resolve_ws, diff --git a/src/cargo/ops/registry/mod.rs b/src/cargo/ops/registry/mod.rs index cc63efb2df7..850d770c567 100644 --- a/src/cargo/ops/registry/mod.rs +++ b/src/cargo/ops/registry/mod.rs @@ -8,6 +8,7 @@ mod logout; mod owner; mod publish; mod search; +mod trustpub; mod yank; use std::collections::HashSet; @@ -35,6 +36,9 @@ pub use self::owner::modify_owners; pub use self::publish::PublishOpts; pub use self::publish::publish; pub use self::search::search; +pub use self::trustpub::TrustpubCommand; +pub use self::trustpub::TrustpubOptions; +pub use self::trustpub::trusted_publish; pub use self::yank::yank; pub(crate) use self::publish::prepare_transmit; diff --git a/src/cargo/ops/registry/trustpub.rs b/src/cargo/ops/registry/trustpub.rs new file mode 100644 index 00000000000..45478042805 --- /dev/null +++ b/src/cargo/ops/registry/trustpub.rs @@ -0,0 +1,77 @@ +use anyhow::Context as _; +use cargo_credential::Operation; +use cargo_credential::Secret; + +use crate::CargoResult; +use crate::GlobalContext; +use crate::core::Workspace; +use crate::drop_print; +use crate::drop_println; +use crate::util::important_paths::find_root_manifest_for_wd; + +pub enum TrustpubCommand { + List, +} + +pub struct TrustpubOptions { + pub krate: Option, + pub token: Option>, + pub command: TrustpubCommand, +} + +pub fn trusted_publish(gctx: &GlobalContext, opts: &TrustpubOptions) -> CargoResult<()> { + let name = match opts.krate { + Some(ref name) => name.clone(), + None => { + let manifest_path = find_root_manifest_for_wd(gctx.cwd())?; + let ws = Workspace::new(&manifest_path, gctx)?; + ws.current()?.package_id().name().to_string() + } + }; + + let operation = Operation::Owners { name: &name }; + let source_ids = super::get_source_id(gctx, None)?; + let (mut registry, _) = super::registry( + gctx, + &source_ids, + opts.token.as_ref().map(Secret::as_deref), + None, + true, + Some(operation), + )?; + + match opts.command { + TrustpubCommand::List => { + let configs = registry.list_github_trustpub_configs(&name).with_context(|| { + format!( + "failed to list trusted publishing configs for crate `{}` on registry at {}", + name, + registry.host() + ) + })?; + if configs.is_empty() { + drop_println!( + gctx, + "no trusted publishing configs found for crate `{}`", + name + ); + } + for config in configs.iter() { + drop_print!( + gctx, + "{}: github {}/{} workflow={}", + config.id, + config.repository_owner, + config.repository_name, + config.workflow_filename, + ); + match config.environment.as_ref() { + Some(env) => drop_println!(gctx, " environment={}", env), + None => drop_println!(gctx), + } + } + } + } + + Ok(()) +} From 797c851a4ec62735d6077839d125c3e5914e617e Mon Sep 17 00:00:00 2001 From: yihau Date: Thu, 18 Jun 2026 19:13:21 +0800 Subject: [PATCH 2/5] add 'cargo trustpub add' --- crates/crates-io/lib.rs | 43 ++++++++++++++++++++++++++++ src/bin/cargo/commands/trustpub.rs | 29 +++++++++++++++++++ src/cargo/ops/registry/trustpub.rs | 46 +++++++++++++++++++++++++++++- 3 files changed, 117 insertions(+), 1 deletion(-) diff --git a/crates/crates-io/lib.rs b/crates/crates-io/lib.rs index cd351cba8d2..24dee6bcf7f 100644 --- a/crates/crates-io/lib.rs +++ b/crates/crates-io/lib.rs @@ -161,6 +161,24 @@ pub struct GitHubConfig { struct GitHubConfigs { github_configs: Vec, } +#[derive(Deserialize)] +struct GitHubConfigResponse { + github_config: GitHubConfig, +} +#[derive(Serialize)] +struct NewGitHubConfig<'a> { + #[serde(rename = "crate")] + krate: &'a str, + repository_owner: &'a str, + repository_name: &'a str, + workflow_filename: &'a str, + #[serde(skip_serializing_if = "Option::is_none")] + environment: Option<&'a str>, +} +#[derive(Serialize)] +struct NewGitHubConfigReq<'a> { + github_config: NewGitHubConfig<'a>, +} /// Error returned when interacting with a registry. #[derive(Debug, thiserror::Error)] @@ -303,6 +321,27 @@ impl Registry { Ok(serde_json::from_str::(&body)?.github_configs) } + pub fn add_github_trustpub_config( + &mut self, + krate: &str, + repository_owner: &str, + repository_name: &str, + workflow_filename: &str, + environment: Option<&str>, + ) -> RegistryResult { + let body = serde_json::to_string(&NewGitHubConfigReq { + github_config: NewGitHubConfig { + krate, + repository_owner, + repository_name, + workflow_filename, + environment, + }, + })?; + let body = self.post("/trusted_publishing/github_configs", Some(body.as_bytes()))?; + Ok(serde_json::from_str::(&body)?.github_config) + } + pub fn publish( &mut self, krate: &NewCrate, @@ -419,6 +458,10 @@ impl Registry { self.req(Method::PUT, path, b, Auth::Authorized) } + fn post(&mut self, path: &str, b: Option<&[u8]>) -> RegistryResult { + self.req(Method::POST, path, b, Auth::Authorized) + } + fn get(&mut self, path: &str) -> RegistryResult { self.req(Method::GET, path, None, Auth::Authorized) } diff --git a/src/bin/cargo/commands/trustpub.rs b/src/bin/cargo/commands/trustpub.rs index c504cd083aa..cb860ba2d68 100644 --- a/src/bin/cargo/commands/trustpub.rs +++ b/src/bin/cargo/commands/trustpub.rs @@ -20,11 +20,40 @@ pub fn cli() -> Command { ) .arg_silent_suggestion() .subcommand(subcommand("list").about("List the Trusted Publishing configs for a crate")) + .subcommand( + subcommand("add") + .about("Add a GitHub Actions Trusted Publishing config to a crate") + .arg( + opt("owner", "GitHub repository owner (user or organization)") + .value_name("OWNER") + .required(true), + ) + .arg( + opt("repo", "GitHub repository name") + .value_name("REPO") + .required(true), + ) + .arg( + opt("pipeline", "GitHub Actions workflow filename (e.g. `ci.yml`)") + .value_name("PIPELINE") + .required(true), + ) + .arg( + opt("env", "GitHub Actions environment the workflow must run in") + .value_name("ENV"), + ), + ) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { let command = match args.subcommand() { Some(("list", _)) => TrustpubCommand::List, + Some(("add", sub)) => TrustpubCommand::Add { + repository_owner: sub.get_one::("owner").cloned().unwrap(), + repository_name: sub.get_one::("repo").cloned().unwrap(), + workflow_filename: sub.get_one::("pipeline").cloned().unwrap(), + environment: sub.get_one::("env").cloned(), + }, Some((cmd, _)) => unreachable!("unexpected command {}", cmd), None => unreachable!("unexpected command"), }; diff --git a/src/cargo/ops/registry/trustpub.rs b/src/cargo/ops/registry/trustpub.rs index 45478042805..c23241ab24e 100644 --- a/src/cargo/ops/registry/trustpub.rs +++ b/src/cargo/ops/registry/trustpub.rs @@ -11,6 +11,12 @@ use crate::util::important_paths::find_root_manifest_for_wd; pub enum TrustpubCommand { List, + Add { + repository_owner: String, + repository_name: String, + workflow_filename: String, + environment: Option, + }, } pub struct TrustpubOptions { @@ -40,7 +46,7 @@ pub fn trusted_publish(gctx: &GlobalContext, opts: &TrustpubOptions) -> CargoRes Some(operation), )?; - match opts.command { + match &opts.command { TrustpubCommand::List => { let configs = registry.list_github_trustpub_configs(&name).with_context(|| { format!( @@ -71,6 +77,44 @@ pub fn trusted_publish(gctx: &GlobalContext, opts: &TrustpubOptions) -> CargoRes } } } + TrustpubCommand::Add { + repository_owner, + repository_name, + workflow_filename, + environment, + } => { + let config = registry + .add_github_trustpub_config( + &name, + repository_owner, + repository_name, + workflow_filename, + environment.as_deref(), + ) + .with_context(|| { + format!( + "failed to add trusted publishing config to crate `{}` on registry at {}", + name, + registry.host() + ) + })?; + let environment = match config.environment.as_ref() { + Some(env) => format!(" environment={}", env), + None => String::new(), + }; + gctx.shell().status( + "Added", + format!( + "trusted publishing config {} ({}/{} workflow={}{}) for crate `{}`", + config.id, + config.repository_owner, + config.repository_name, + config.workflow_filename, + environment, + name, + ), + )?; + } } Ok(()) From 3ddb0ccfd6755d5a38ed75e23d752b68154f67ba Mon Sep 17 00:00:00 2001 From: yihau Date: Thu, 18 Jun 2026 19:16:03 +0800 Subject: [PATCH 3/5] add 'cargo trustpub remove' --- crates/crates-io/lib.rs | 8 ++++++++ src/bin/cargo/commands/trustpub.rs | 13 +++++++++++++ src/cargo/ops/registry/trustpub.rs | 19 +++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/crates/crates-io/lib.rs b/crates/crates-io/lib.rs index 24dee6bcf7f..ebb9f2a173c 100644 --- a/crates/crates-io/lib.rs +++ b/crates/crates-io/lib.rs @@ -342,6 +342,14 @@ impl Registry { Ok(serde_json::from_str::(&body)?.github_config) } + pub fn remove_github_trustpub_config(&mut self, id: u32) -> RegistryResult<(), T::Error> { + self.delete( + &format!("/trusted_publishing/github_configs/{}", id), + None, + )?; + Ok(()) + } + pub fn publish( &mut self, krate: &NewCrate, diff --git a/src/bin/cargo/commands/trustpub.rs b/src/bin/cargo/commands/trustpub.rs index cb860ba2d68..fe8a3bbf983 100644 --- a/src/bin/cargo/commands/trustpub.rs +++ b/src/bin/cargo/commands/trustpub.rs @@ -43,6 +43,16 @@ pub fn cli() -> Command { .value_name("ENV"), ), ) + .subcommand( + subcommand("remove") + .about("Remove a Trusted Publishing config from a crate") + .arg( + opt("id", "Id of the config to remove (see `cargo trustpub list`)") + .value_name("ID") + .value_parser(value_parser!(u32)) + .required(true), + ), + ) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { @@ -54,6 +64,9 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { workflow_filename: sub.get_one::("pipeline").cloned().unwrap(), environment: sub.get_one::("env").cloned(), }, + Some(("remove", sub)) => TrustpubCommand::Remove { + id: *sub.get_one::("id").unwrap(), + }, Some((cmd, _)) => unreachable!("unexpected command {}", cmd), None => unreachable!("unexpected command"), }; diff --git a/src/cargo/ops/registry/trustpub.rs b/src/cargo/ops/registry/trustpub.rs index c23241ab24e..85656fd12de 100644 --- a/src/cargo/ops/registry/trustpub.rs +++ b/src/cargo/ops/registry/trustpub.rs @@ -17,6 +17,9 @@ pub enum TrustpubCommand { workflow_filename: String, environment: Option, }, + Remove { + id: u32, + }, } pub struct TrustpubOptions { @@ -115,6 +118,22 @@ pub fn trusted_publish(gctx: &GlobalContext, opts: &TrustpubOptions) -> CargoRes ), )?; } + TrustpubCommand::Remove { id } => { + registry + .remove_github_trustpub_config(*id) + .with_context(|| { + format!( + "failed to remove trusted publishing config {} from crate `{}` on registry at {}", + id, + name, + registry.host() + ) + })?; + gctx.shell().status( + "Removed", + format!("trusted publishing config {} for crate `{}`", id, name), + )?; + } } Ok(()) From bf64d7c4179d1fb8f605443c973b7e378fe3c3ed Mon Sep 17 00:00:00 2001 From: yihau Date: Thu, 18 Jun 2026 19:22:18 +0800 Subject: [PATCH 4/5] add 'cargo trustpub set' --- crates/crates-io/lib.rs | 25 +++++++++++++++++++++++++ src/bin/cargo/commands/trustpub.rs | 16 ++++++++++++++++ src/cargo/ops/registry/trustpub.rs | 18 ++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/crates/crates-io/lib.rs b/crates/crates-io/lib.rs index ebb9f2a173c..a853e10929e 100644 --- a/crates/crates-io/lib.rs +++ b/crates/crates-io/lib.rs @@ -179,6 +179,15 @@ struct NewGitHubConfig<'a> { struct NewGitHubConfigReq<'a> { github_config: NewGitHubConfig<'a>, } +#[derive(Serialize)] +struct CrateUpdate { + trustpub_only: bool, +} +#[derive(Serialize)] +struct CrateUpdateReq { + #[serde(rename = "crate")] + krate: CrateUpdate, +} /// Error returned when interacting with a registry. #[derive(Debug, thiserror::Error)] @@ -350,6 +359,18 @@ impl Registry { Ok(()) } + pub fn set_trustpub_only( + &mut self, + krate: &str, + trustpub_only: bool, + ) -> RegistryResult<(), T::Error> { + let body = serde_json::to_string(&CrateUpdateReq { + krate: CrateUpdate { trustpub_only }, + })?; + self.patch(&format!("/crates/{}", krate), Some(body.as_bytes()))?; + Ok(()) + } + pub fn publish( &mut self, krate: &NewCrate, @@ -470,6 +491,10 @@ impl Registry { self.req(Method::POST, path, b, Auth::Authorized) } + fn patch(&mut self, path: &str, b: Option<&[u8]>) -> RegistryResult { + self.req(Method::PATCH, path, b, Auth::Authorized) + } + fn get(&mut self, path: &str) -> RegistryResult { self.req(Method::GET, path, None, Auth::Authorized) } diff --git a/src/bin/cargo/commands/trustpub.rs b/src/bin/cargo/commands/trustpub.rs index fe8a3bbf983..239639203bb 100644 --- a/src/bin/cargo/commands/trustpub.rs +++ b/src/bin/cargo/commands/trustpub.rs @@ -53,6 +53,19 @@ pub fn cli() -> Command { .required(true), ), ) + .subcommand( + subcommand("set") + .about("Control whether new versions must be published via Trusted Publishing") + .arg( + opt( + "trustpub-only", + "Require Trusted Publishing for new versions of the crate", + ) + .value_name("BOOL") + .value_parser(value_parser!(bool)) + .required(true), + ), + ) } pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { @@ -67,6 +80,9 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult { Some(("remove", sub)) => TrustpubCommand::Remove { id: *sub.get_one::("id").unwrap(), }, + Some(("set", sub)) => TrustpubCommand::Set { + trustpub_only: *sub.get_one::("trustpub-only").unwrap(), + }, Some((cmd, _)) => unreachable!("unexpected command {}", cmd), None => unreachable!("unexpected command"), }; diff --git a/src/cargo/ops/registry/trustpub.rs b/src/cargo/ops/registry/trustpub.rs index 85656fd12de..4be81e67c44 100644 --- a/src/cargo/ops/registry/trustpub.rs +++ b/src/cargo/ops/registry/trustpub.rs @@ -20,6 +20,9 @@ pub enum TrustpubCommand { Remove { id: u32, }, + Set { + trustpub_only: bool, + }, } pub struct TrustpubOptions { @@ -134,6 +137,21 @@ pub fn trusted_publish(gctx: &GlobalContext, opts: &TrustpubOptions) -> CargoRes format!("trusted publishing config {} for crate `{}`", id, name), )?; } + TrustpubCommand::Set { trustpub_only } => { + registry + .set_trustpub_only(&name, *trustpub_only) + .with_context(|| { + format!( + "failed to update `trustpub_only` for crate `{}` on registry at {}", + name, + registry.host() + ) + })?; + gctx.shell().status( + "Updated", + format!("`trustpub_only` for crate `{}` to {}", name, trustpub_only), + )?; + } } Ok(()) From 1d5cab2150830fe7f86f001c6a6479692c54d127 Mon Sep 17 00:00:00 2001 From: yihau Date: Thu, 18 Jun 2026 19:50:39 +0800 Subject: [PATCH 5/5] fix fmt --- crates/crates-io/lib.rs | 5 +---- src/bin/cargo/commands/trustpub.rs | 20 +++++++++++++------- src/cargo/ops/mod.rs | 4 ++-- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/crates/crates-io/lib.rs b/crates/crates-io/lib.rs index a853e10929e..3aea1ff6ea8 100644 --- a/crates/crates-io/lib.rs +++ b/crates/crates-io/lib.rs @@ -352,10 +352,7 @@ impl Registry { } pub fn remove_github_trustpub_config(&mut self, id: u32) -> RegistryResult<(), T::Error> { - self.delete( - &format!("/trusted_publishing/github_configs/{}", id), - None, - )?; + self.delete(&format!("/trusted_publishing/github_configs/{}", id), None)?; Ok(()) } diff --git a/src/bin/cargo/commands/trustpub.rs b/src/bin/cargo/commands/trustpub.rs index 239639203bb..bd65bb27a93 100644 --- a/src/bin/cargo/commands/trustpub.rs +++ b/src/bin/cargo/commands/trustpub.rs @@ -34,9 +34,12 @@ pub fn cli() -> Command { .required(true), ) .arg( - opt("pipeline", "GitHub Actions workflow filename (e.g. `ci.yml`)") - .value_name("PIPELINE") - .required(true), + opt( + "pipeline", + "GitHub Actions workflow filename (e.g. `ci.yml`)", + ) + .value_name("PIPELINE") + .required(true), ) .arg( opt("env", "GitHub Actions environment the workflow must run in") @@ -47,10 +50,13 @@ pub fn cli() -> Command { subcommand("remove") .about("Remove a Trusted Publishing config from a crate") .arg( - opt("id", "Id of the config to remove (see `cargo trustpub list`)") - .value_name("ID") - .value_parser(value_parser!(u32)) - .required(true), + opt( + "id", + "Id of the config to remove (see `cargo trustpub list`)", + ) + .value_name("ID") + .value_parser(value_parser!(u32)) + .required(true), ), ) .subcommand( diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs index 4f5014d617d..ba8567e66e3 100644 --- a/src/cargo/ops/mod.rs +++ b/src/cargo/ops/mod.rs @@ -39,10 +39,10 @@ pub use self::fix::{ pub use self::lockfile::{load_pkg_lockfile, resolve_to_string, write_pkg_lockfile}; pub use self::registry::OwnersOptions; pub use self::registry::PublishOpts; -pub use self::registry::TrustpubCommand; -pub use self::registry::TrustpubOptions; pub use self::registry::RegistryCredentialConfig; pub use self::registry::RegistryOrIndex; +pub use self::registry::TrustpubCommand; +pub use self::registry::TrustpubOptions; pub use self::registry::info; pub use self::registry::modify_owners; pub use self::registry::publish;