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