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