wasmer_cli/commands/package/
push.rs1use super::common::{macros::*, *};
2use crate::{
3 commands::{AsyncCliCommand, PackageBuild},
4 config::WasmerEnv,
5};
6use anyhow::Context;
7use bytes::Bytes;
8use colored::Colorize;
9use sha2::Digest;
10use std::io::IsTerminal as _;
11use std::path::{Path, PathBuf};
12use wasmer_backend_api::WasmerClient;
13use wasmer_config::package::{Manifest, PackageHash};
14
15#[derive(Debug, clap::Parser)]
20pub struct PackagePush {
21 #[clap(flatten)]
22 pub env: WasmerEnv,
23
24 #[clap(long, name = "dry-run")]
26 pub dry_run: bool,
27
28 #[clap(long)]
30 pub quiet: bool,
31
32 #[clap(long = "namespace")]
34 pub package_namespace: Option<String>,
35
36 #[clap(long = "name")]
39 pub package_name: Option<String>,
40
41 #[clap(long, default_value = "5m")]
46 pub timeout: humantime::Duration,
47
48 #[clap(long, default_value_t = !std::io::stdin().is_terminal())]
50 pub non_interactive: bool,
51
52 #[clap(name = "path", default_value = ".")]
59 pub package_path: PathBuf,
60}
61
62impl PackagePush {
63 async fn get_namespace(
64 &self,
65 client: &WasmerClient,
66 manifest: &Manifest,
67 ) -> anyhow::Result<String> {
68 if let Some(owner) = &self.package_namespace {
69 return Ok(owner.clone());
70 }
71
72 if let Some(pkg) = &manifest.package
73 && let Some(ns) = &pkg.name
74 && let Some(first) = ns.split('/').next()
75 {
76 return Ok(first.to_string());
77 }
78
79 if self.non_interactive {
80 anyhow::bail!("No package namespace specified: use --namespace XXX");
82 }
83
84 let user = wasmer_backend_api::query::current_user_with_namespaces(client, None).await?;
85 let owner = crate::utils::prompts::prompt_for_namespace(
86 "Choose a namespace to push the package to",
87 None,
88 Some(&user),
89 )?;
90
91 Ok(owner.clone())
92 }
93
94 async fn get_name(&self, manifest: &Manifest) -> anyhow::Result<Option<String>> {
95 if let Some(name) = &self.package_name {
96 return Ok(Some(name.clone()));
97 }
98
99 if let Some(pkg) = &manifest.package
100 && let Some(ns) = &pkg.name
101 && let Some(name) = ns.split('/').nth(1)
102 {
103 return Ok(Some(name.to_string()));
104 }
105
106 Ok(None)
107 }
108
109 fn get_privacy(&self, manifest: &Manifest) -> bool {
110 match &manifest.package {
111 Some(pkg) => pkg.private,
112 None => true,
113 }
114 }
115
116 async fn should_push(&self, client: &WasmerClient, hash: &PackageHash) -> anyhow::Result<bool> {
117 let res = wasmer_backend_api::query::get_package_release(client, &hash.to_string()).await;
118 tracing::info!("{:?}", res);
119 res.map(|p| p.is_none())
120 }
121
122 async fn do_push(
123 &self,
124 client: &WasmerClient,
125 namespace: &str,
126 name: Option<String>,
127 package_bytes: Bytes,
128 package_hash: &PackageHash,
129 private: bool,
130 ) -> anyhow::Result<()> {
131 let pb = make_spinner!(self.quiet, "Uploading the package..");
132
133 let signed_url = upload(
134 client,
135 package_hash,
136 self.timeout,
137 package_bytes,
138 pb.clone(),
139 self.env.proxy()?,
140 )
141 .await?;
142 spinner_ok!(pb, "Package correctly uploaded");
143
144 let pb = make_spinner!(self.quiet, "Waiting for package to become available...");
145 match wasmer_backend_api::query::push_package_release(
146 client,
147 name.as_deref(),
148 namespace,
149 &signed_url,
150 Some(private),
151 )
152 .await?
153 {
154 Some(r) => {
155 if r.success {
156 r.package_webc.unwrap().id
157 } else {
158 anyhow::bail!(
159 "An unidentified error occurred while publishing the package. (response had success: false)"
160 )
161 }
162 }
163 None => anyhow::bail!("An unidentified error occurred while publishing the package."), };
165
166 let msg = format!("Succesfully pushed release to namespace {namespace} on the registry");
167 spinner_ok!(pb, msg);
168
169 Ok(())
170 }
171
172 pub async fn push(
173 &self,
174 client: &WasmerClient,
175 manifest: &Manifest,
176 manifest_path: &Path,
177 ) -> anyhow::Result<(String, PackageHash)> {
178 let is_webc = manifest_path.is_file()
180 && manifest_path.extension().and_then(|s| s.to_str()) == Some("webc");
181
182 let (hash, package_bytes) = if is_webc {
183 tracing::info!("Loading pre-built package from webc");
184 let pb = make_spinner!(self.quiet, "Loading the package...");
185
186 let package_data = std::fs::read(manifest_path).with_context(|| {
188 format!("Failed to read webc file '{}'", manifest_path.display())
189 })?;
190
191 let hash_bytes: [u8; 32] = sha2::Sha256::digest(&package_data).into();
193 let hash = PackageHash::from_sha256_bytes(hash_bytes);
194
195 let package_bytes = bytes::Bytes::from(package_data);
198 wasmer_package::utils::from_bytes(package_bytes.clone()).with_context(|| {
199 format!("Failed to parse webc file '{}'", manifest_path.display())
200 })?;
201
202 spinner_ok!(pb, "Correctly loaded pre-built package");
203
204 (hash, package_bytes)
205 } else {
206 tracing::info!("Building package");
207 let pb = make_spinner!(self.quiet, "Creating the package locally...");
208 let (package, hash) = PackageBuild::check(manifest_path.to_path_buf())
209 .execute()
210 .context("While trying to build the package locally")?;
211
212 spinner_ok!(pb, "Correctly built package locally");
213
214 let package_bytes = package.serialize()?;
216
217 (hash, package_bytes)
218 };
219
220 tracing::info!("Package has hash: {hash}");
221
222 let namespace = self.get_namespace(client, manifest).await?;
223 let name = self.get_name(manifest).await?;
224
225 let private = self.get_privacy(manifest);
226 tracing::info!(
227 "If published, package privacy is {private}, namespace is {namespace} and name is {name:?}"
228 );
229
230 let pb = make_spinner!(
231 self.quiet,
232 "Checking if package is already in the registry.."
233 );
234 if self.should_push(client, &hash).await.map_err(on_error)? {
235 if !self.dry_run {
236 tracing::info!("Package should be published");
237 pb.finish_and_clear();
238 self.do_push(client, &namespace, name, package_bytes, &hash, private)
241 .await
242 .map_err(on_error)?;
243 } else {
244 tracing::info!("Package should be published, but dry-run is set");
245 spinner_ok!(pb, "Skipping push as dry-run is set");
246 }
247 } else {
248 tracing::info!("Package should not be published");
249 spinner_ok!(pb, "Package was already in the registry, no push needed");
250 }
251
252 tracing::info!("Proceeding to invalidate query cache..");
253
254 if let Err(e) = invalidate_graphql_query_cache(&self.env.cache_dir) {
256 tracing::warn!(
257 error = &*e,
258 "Unable to invalidate the cache used for package version queries",
259 );
260 }
261
262 Ok((namespace, hash))
263 }
264}
265
266#[async_trait::async_trait]
267impl AsyncCliCommand for PackagePush {
268 type Output = ();
269
270 async fn run_async(self) -> Result<Self::Output, anyhow::Error> {
271 tracing::info!("Checking if user is logged in");
272 let client = login_user(&self.env, !self.non_interactive, "push a package").await?;
273
274 tracing::info!("Loading manifest");
275 let (manifest_path, manifest) = get_manifest(&self.package_path)?;
276 tracing::info!("Got manifest at path {}", manifest_path.display());
277
278 let (_, hash) = self.push(&client, &manifest, &manifest_path).await?;
279
280 let bin_name = bin_name!();
281 if let Some(package) = &manifest.package {
282 if package.name.is_some() {
283 let mut manifest_path_dir = manifest_path.clone();
284 manifest_path_dir.pop();
285
286 eprintln!(
287 "{} You can now tag your package with `{}`",
288 "ð–¥”".yellow().bold(),
289 format!(
290 "{bin_name} package tag {}{}",
291 hash,
292 if manifest_path_dir.canonicalize()? == std::env::current_dir()? {
293 String::new()
294 } else {
295 format!(" {}", manifest_path_dir.display())
296 }
297 )
298 .bold()
299 )
300 } else {
301 eprintln!(
302 "{} Successfully pushed package ({hash})",
303 "✔".green().bold()
304 );
305 }
306 } else {
307 eprintln!(
308 "{} Successfully pushed package ({hash})",
309 "✔".green().bold()
310 );
311 }
312
313 Ok(())
314 }
315}