Skip to content

Commit

Permalink
Merge pull request #4328 from wasmerio/packge-publish-wait-flag
Browse files Browse the repository at this point in the history
  • Loading branch information
ayys authored Nov 28, 2023
2 parents 4fab39c + b644394 commit e643873
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 45 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ opener = "0.6.1"
hyper = { version = "0.14.27", features = ["server"] }
http = "0.2.9"
futures = "0.3.29"
humantime = "2.1.0"

# NOTE: Must use different features for clap because the "color" feature does not
# work on wasi due to the anstream dependency not compiling.
Expand Down
11 changes: 11 additions & 0 deletions lib/cli/src/commands/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ pub struct Publish {
/// Defaults to current working directory.
#[clap(name = "PACKAGE_PATH")]
pub package_path: Option<String>,
/// Wait for package to be available on the registry before exiting.
#[clap(long)]
pub wait: bool,
/// Timeout (in seconds) for the publish query to the registry.
///
/// Note that this is not the timeout for the entire publish process, but
/// for each individual query to the registry during the publish flow.
#[clap(long, default_value = "30s")]
pub timeout: humantime::Duration,
}

impl Publish {
Expand All @@ -46,6 +55,8 @@ impl Publish {
token,
no_validate: self.no_validate,
package_path: self.package_path.clone(),
wait: self.wait,
timeout: self.timeout.into(),
};
publish.execute().map_err(on_error)?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mutation PublishPackageMutationChunked(
$signature: InputSignature
$signedUrl: String
$private: Boolean
$wait: Boolean
) {
publishPackage(
input: {
Expand All @@ -29,6 +30,7 @@ mutation PublishPackageMutationChunked(
signature: $signature
clientMutationId: ""
private: $private
wait: $wait
}
) {
success
Expand Down
7 changes: 7 additions & 0 deletions lib/registry/src/package/builder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::time::Duration;
use std::{fs, io::IsTerminal};

use anyhow::{anyhow, bail, Context};
Expand Down Expand Up @@ -38,6 +39,10 @@ pub struct Publish {
pub no_validate: bool,
/// Directory containing the `wasmer.toml` (defaults to current root dir)
pub package_path: Option<String>,
/// Wait for package to be available on the registry before exiting
pub wait: bool,
/// Timeout (in seconds) for the publish query to the registry
pub timeout: Duration,
}

#[derive(Debug, Error)]
Expand Down Expand Up @@ -186,6 +191,8 @@ impl Publish {
&maybe_signature_data,
archived_data_size,
self.quiet,
self.wait,
self.timeout,
)
}

Expand Down
135 changes: 90 additions & 45 deletions lib/registry/src/publish.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use std::collections::BTreeMap;
use std::fmt::Write;
use std::io::BufRead;
use std::path::PathBuf;

use anyhow::Context;
use console::{style, Emoji};
use graphql_client::GraphQLQuery;
use indicatif::{ProgressBar, ProgressState, ProgressStyle};
use std::fmt::Write;
use std::io::BufRead;
use std::path::PathBuf;
use std::{collections::BTreeMap, time::Duration};

use crate::graphql::queries::get_signed_url::GetSignedUrlUrl;
use crate::graphql::{
execute_query_modifier_inner,
mutations::{publish_package_mutation_chunked, PublishPackageMutationChunked},
queries::{get_signed_url, GetSignedUrl},
};
Expand Down Expand Up @@ -39,7 +39,61 @@ pub fn try_chunked_uploading(
maybe_signature_data: &SignArchiveResult,
archived_data_size: u64,
quiet: bool,
wait: bool,
timeout: Duration,
) -> Result<(), anyhow::Error> {
let (registry, token) = initialize_registry_and_token(registry, token)?;

let maybe_signature_data = sign_package(maybe_signature_data);

// fetch this before showing the `Uploading...` message
// because there is a chance that the registry may not return a signed url.
// This usually happens if the package version already exists in the registry.
let signed_url = google_signed_url(&registry, &token, package, timeout)?;

if !quiet {
println!("{} {} Uploading...", style("[1/2]").bold().dim(), UPLOAD);
}

upload_package(&signed_url.url, archive_path, archived_data_size, timeout)?;

if !quiet {
println!("{} {}Publishing...", style("[2/2]").bold().dim(), PACKAGE);
}

let q =
PublishPackageMutationChunked::build_query(publish_package_mutation_chunked::Variables {
name: package.name.to_string(),
version: package.version.to_string(),
description: package.description.clone(),
manifest: manifest_string.to_string(),
license: package.license.clone(),
license_file: license_file.to_owned(),
readme: readme.to_owned(),
repository: package.repository.clone(),
homepage: package.homepage.clone(),
file_name: Some(archive_name.to_string()),
signature: maybe_signature_data,
signed_url: Some(signed_url.url),
private: Some(package.private),
wait: Some(wait),
});

let _response: publish_package_mutation_chunked::ResponseData =
crate::graphql::execute_query_with_timeout(&registry, &token, timeout, &q)?;

println!(
"Successfully published package `{}@{}`",
package.name, package.version
);

Ok(())
}

fn initialize_registry_and_token(
registry: Option<String>,
token: Option<String>,
) -> Result<(String, String), anyhow::Error> {
let registry = match registry.as_ref() {
Some(s) => format_graphql(s),
None => {
Expand Down Expand Up @@ -71,7 +125,13 @@ pub fn try_chunked_uploading(
}
};

let maybe_signature_data = match maybe_signature_data {
Ok((registry, token))
}

fn sign_package(
maybe_signature_data: &SignArchiveResult,
) -> Option<publish_package_mutation_chunked::InputSignature> {
match maybe_signature_data {
SignArchiveResult::Ok {
public_key_id,
signature,
Expand All @@ -90,20 +150,27 @@ pub fn try_chunked_uploading(
//warn!("Publishing package without a verifying signature. Consider registering a key pair with wasmer");
None
}
};

if !quiet {
println!("{} {} Uploading...", style("[1/2]").bold().dim(), UPLOAD);
}
}

fn google_signed_url(
registry: &str,
token: &str,
package: &wasmer_toml::Package,
timeout: Duration,
) -> Result<GetSignedUrlUrl, anyhow::Error> {
let get_google_signed_url = GetSignedUrl::build_query(get_signed_url::Variables {
name: package.name.to_string(),
version: package.version.to_string(),
expires_after_seconds: Some(60 * 30),
});

let _response: get_signed_url::ResponseData =
execute_query_modifier_inner(&registry, &token, &get_google_signed_url, None, |f| f)?;
let _response: get_signed_url::ResponseData = crate::graphql::execute_query_with_timeout(
registry,
token,
timeout,
&get_google_signed_url,
)?;

let url = _response.url.ok_or_else(|| {
anyhow::anyhow!(
Expand All @@ -112,11 +179,19 @@ pub fn try_chunked_uploading(
package.version
)
})?;
Ok(url)
}

let signed_url = url.url;
let url = url::Url::parse(&signed_url).unwrap();
fn upload_package(
signed_url: &str,
archive_path: &PathBuf,
archived_data_size: u64,
timeout: Duration,
) -> Result<(), anyhow::Error> {
let url = url::Url::parse(signed_url).context("cannot parse signed url")?;
let client = reqwest::blocking::Client::builder()
.default_headers(reqwest::header::HeaderMap::default())
.timeout(timeout)
.build()
.unwrap();

Expand Down Expand Up @@ -212,35 +287,5 @@ pub fn try_chunked_uploading(
}

pb.finish_and_clear();

if !quiet {
println!("{} {}Publishing...", style("[2/2]").bold().dim(), PACKAGE);
}

let q =
PublishPackageMutationChunked::build_query(publish_package_mutation_chunked::Variables {
name: package.name.to_string(),
version: package.version.to_string(),
description: package.description.clone(),
manifest: manifest_string.to_string(),
license: package.license.clone(),
license_file: license_file.to_owned(),
readme: readme.to_owned(),
repository: package.repository.clone(),
homepage: package.homepage.clone(),
file_name: Some(archive_name.to_string()),
signature: maybe_signature_data,
signed_url: Some(signed_url),
private: Some(package.private),
});

let _response: publish_package_mutation_chunked::ResponseData =
crate::graphql::execute_query(&registry, &token, &q)?;

println!(
"Successfully published package `{}@{}`",
package.name, package.version
);

Ok(())
}
62 changes: 62 additions & 0 deletions tests/integration/cli/tests/publish.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use assert_cmd::prelude::OutputAssertExt;
use predicates::str::contains;
use wasmer_integration_tests_cli::{fixtures, get_wasmer_path};

#[test]
Expand Down Expand Up @@ -118,3 +119,64 @@ fn wasmer_init_publish() {
"Successfully published package `{username}/randomversion@{random1}.{random2}.{random3}`\n"
));
}

#[test]
fn wasmer_publish_and_run() {
// Only run this test in the CI
if std::env::var("GITHUB_TOKEN").is_err() {
return;
}

let wapm_dev_token = std::env::var("WAPM_DEV_TOKEN").ok();
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path();
let username = "ciuser";

let random_major = format!("{}", rand::random::<u32>());
let random_minor = format!("{}", rand::random::<u32>());
let random_patch = format!("{}", rand::random::<u32>());

std::fs::copy(fixtures::qjs(), path.join("largewasmfile.wasm")).unwrap();
std::fs::write(
path.join("wasmer.toml"),
include_str!("./fixtures/init6.toml")
.replace("WAPMUSERNAME", username) // <-- TODO!
.replace("RANDOMVERSION1", &random_major)
.replace("RANDOMVERSION2", &random_minor)
.replace("RANDOMVERSION3", &random_patch),
)
.unwrap();

let package_name =
format!("{username}/largewasmfile@{random_major}.{random_minor}.{random_patch}",);

let mut cmd = std::process::Command::new(get_wasmer_path());
cmd.arg("publish")
.arg("--quiet")
.arg("--wait")
.arg("--timeout=60s")
.arg("--registry=wasmer.wtf")
.arg(path);

if let Some(token) = wapm_dev_token {
// Special case: GitHub secrets aren't visible to outside collaborators
if token.is_empty() {
return;
}
cmd.arg("--token").arg(token);
}

cmd.assert()
.success()
.stdout(format!("Successfully published package `{package_name}`\n"));

let assert = std::process::Command::new(get_wasmer_path())
.arg("run")
.arg(format!("https://wasmer.wtf/{package_name}"))
.arg("--")
.arg("--eval")
.arg("console.log('Hello, World!')")
.assert();

assert.success().stdout(contains("Hello, World!"));
}

0 comments on commit e643873

Please sign in to comment.