cargo_component/commands/
publish.rs1use std::{path::PathBuf, sync::Arc};
2
3use anyhow::{bail, Context, Result};
4use cargo_component_core::command::CommonOptions;
5use clap::Args;
6use wasm_pkg_client::{warg::WargRegistryConfig, Registry};
7
8use crate::{
9 config::{CargoArguments, CargoPackageSpec, Config},
10 is_wasm_target, load_metadata, publish, run_cargo_command, PackageComponentMetadata,
11 PublishOptions,
12};
13
14#[derive(Args)]
16#[clap(disable_version_flag = true)]
17pub struct PublishCommand {
18 #[clap(flatten)]
20 pub common: CommonOptions,
21
22 #[clap(long = "target", value_name = "TRIPLE")]
24 pub target: Option<String>,
25
26 #[clap(long = "frozen")]
28 pub frozen: bool,
29
30 #[clap(long = "target-dir", value_name = "DIRECTORY")]
32 pub target_dir: Option<PathBuf>,
33
34 #[clap(long = "locked")]
36 pub locked: bool,
37
38 #[clap(long = "package", short = 'p', value_name = "SPEC")]
40 pub cargo_package: Option<CargoPackageSpec>,
41
42 #[clap(long = "manifest-path", value_name = "PATH")]
44 pub manifest_path: Option<PathBuf>,
45
46 #[clap(long = "offline")]
48 pub offline: bool,
49
50 #[clap(long = "features", value_name = "FEATURES")]
52 pub features: Vec<String>,
53
54 #[clap(long = "all-features")]
56 pub all_features: bool,
57
58 #[clap(long = "no-default-features")]
60 pub no_default_features: bool,
61
62 #[clap(long = "jobs", short = 'j', value_name = "N")]
64 pub jobs: Option<i32>,
65
66 #[clap(long = "dry-run")]
68 pub dry_run: bool,
69
70 #[clap(long = "registry", value_name = "REGISTRY")]
72 pub registry: Option<Registry>,
73}
74
75impl PublishCommand {
76 pub async fn exec(self) -> Result<()> {
78 log::debug!("executing publish command");
79
80 let mut config =
81 Config::new(self.common.new_terminal(), self.common.config.clone()).await?;
82 let client = config.client(self.common.cache_dir.clone(), false).await?;
83
84 if let Some(target) = &self.target {
85 if !is_wasm_target(target) {
86 bail!("target `{}` is not a WebAssembly target", target);
87 }
88 }
89
90 let metadata = load_metadata(self.manifest_path.as_deref())?;
91 let spec = match &self.cargo_package {
92 Some(spec) => Some(spec.clone()),
93 None => CargoPackageSpec::find_current_package_spec(&metadata),
94 };
95 let packages = [PackageComponentMetadata::new(if let Some(spec) = &spec {
96 metadata
97 .packages
98 .iter()
99 .find(|p| {
100 p.name == spec.name
101 && match spec.version.as_ref() {
102 Some(v) => &p.version == v,
103 None => true,
104 }
105 })
106 .with_context(|| {
107 format!("package ID specification `{spec}` did not match any packages")
108 })?
109 } else {
110 metadata
111 .root_package()
112 .context("no root package found in manifest")?
113 })?];
114
115 let package = packages[0].package;
116 let component_metadata = &packages[0].metadata;
117
118 let name = component_metadata.section.package.as_ref().with_context(|| {
119 format!(
120 "package `{name}` is missing a `package.metadata.component.package` setting in manifest `{path}`",
121 name = package.name,
122 path = package.manifest_path
123 )
124 })?;
125
126 if let Ok(key) = std::env::var("CARGO_COMPONENT_PUBLISH_KEY") {
127 let registry = config.pkg_config.resolve_registry(name).ok_or_else(|| anyhow::anyhow!("Tried to set a signing key, but registry was not set and no default registry was found. Try setting the `--registry` option."))?.to_owned();
128 let reg_config = config
132 .pkg_config
133 .get_or_insert_registry_config_mut(®istry);
134 let mut warg_conf = WargRegistryConfig::try_from(&*reg_config).unwrap_or_default();
135 warg_conf.signing_key = Some(Arc::new(
136 key.try_into().context("Failed to parse signing key")?,
137 ));
138 reg_config.set_backend_config("warg", warg_conf)?;
139 }
140
141 let cargo_build_args = CargoArguments {
142 color: self.common.color,
143 verbose: self.common.verbose as usize,
144 help: false,
145 quiet: self.common.quiet,
146 targets: self.target.clone().into_iter().collect(),
147 manifest_path: self.manifest_path.clone(),
148 message_format: None,
149 frozen: self.frozen,
150 locked: self.locked,
151 release: true,
152 offline: self.offline,
153 workspace: false,
154 packages: self.cargo_package.clone().into_iter().collect(),
155 };
156
157 let spawn_args = self.build_args()?;
158 let outputs = run_cargo_command(
159 client.clone(),
160 &config,
161 &metadata,
162 &packages,
163 Some("build"),
164 &cargo_build_args,
165 &spawn_args,
166 )
167 .await?;
168 if outputs.len() != 1 {
169 bail!(
170 "expected one output from `cargo build`, got {len}",
171 len = outputs.len()
172 );
173 }
174
175 let options = PublishOptions {
176 package,
177 name,
178 registry: self.registry.as_ref(),
179 version: &component_metadata.version,
180 path: &outputs[0],
181 dry_run: self.dry_run,
182 };
183
184 publish(&config, client, &options).await
185 }
186
187 fn build_args(&self) -> Result<Vec<String>> {
188 let mut args = Vec::new();
189 args.push("build".to_string());
190 args.push("--release".to_string());
191
192 if self.common.quiet {
193 args.push("-q".to_string());
194 }
195
196 args.extend(
197 std::iter::repeat("-v")
198 .take(self.common.verbose as usize)
199 .map(ToString::to_string),
200 );
201
202 if let Some(color) = self.common.color {
203 args.push("--color".to_string());
204 args.push(color.to_string());
205 }
206
207 if let Some(target) = &self.target {
208 args.push("--target".to_string());
209 args.push(target.clone());
210 }
211
212 if self.frozen {
213 args.push("--frozen".to_string());
214 }
215
216 if let Some(target_dir) = &self.target_dir {
217 args.push("--target-dir".to_string());
218 args.push(
219 target_dir
220 .as_os_str()
221 .to_str()
222 .with_context(|| {
223 format!(
224 "target directory `{dir}` is not valid UTF-8",
225 dir = target_dir.display()
226 )
227 })?
228 .to_string(),
229 );
230 }
231
232 if self.locked {
233 args.push("--locked".to_string());
234 }
235
236 if let Some(spec) = &self.cargo_package {
237 args.push("--package".to_string());
238 args.push(spec.to_string());
239 }
240
241 if let Some(manifest_path) = &self.manifest_path {
242 args.push("--manifest-path".to_string());
243 args.push(
244 manifest_path
245 .as_os_str()
246 .to_str()
247 .with_context(|| {
248 format!(
249 "manifest path `{path}` is not valid UTF-8",
250 path = manifest_path.display()
251 )
252 })?
253 .to_string(),
254 );
255 }
256
257 if self.offline {
258 args.push("--offline".to_string());
259 }
260
261 if !self.features.is_empty() {
262 args.push("--features".to_string());
263 args.push(self.features.join(","));
264 }
265
266 if self.all_features {
267 args.push("--all-features".to_string());
268 }
269
270 if self.no_default_features {
271 args.push("--no-default-features".to_string());
272 }
273
274 if let Some(jobs) = self.jobs {
275 args.push("--jobs".to_string());
276 args.push(jobs.to_string());
277 }
278
279 Ok(args)
280 }
281}