refactor: check if scope and package exist before publish (#21575)

Signed-off-by: Bartek Iwańczuk <biwanczuk@gmail.com>
Co-authored-by: Luca Casonato <lucacasonato@yahoo.com>
This commit is contained in:
Bartek Iwańczuk 2023-12-15 11:27:10 +01:00 committed by GitHub
parent a1870c70a1
commit 62e3f5060e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 191 additions and 50 deletions

View file

@ -132,31 +132,9 @@ pub fn deno_registry_url() -> &'static Url {
pub fn deno_registry_api_url() -> &'static Url {
static DENO_REGISTRY_API_URL: Lazy<Url> = Lazy::new(|| {
let env_var_name = "DENO_REGISTRY_API_URL";
if let Ok(registry_url) = std::env::var(env_var_name) {
// ensure there is a trailing slash for the directory
let registry_url = format!("{}/", registry_url.trim_end_matches('/'));
match Url::parse(&registry_url) {
Ok(url) => {
return url;
}
Err(err) => {
log::debug!(
"Invalid {} environment variable: {:#}",
env_var_name,
err,
);
}
}
}
let host = deno_graph::source::DEFAULT_DENO_REGISTRY_URL
.host_str()
.unwrap();
let mut url = deno_graph::source::DEFAULT_DENO_REGISTRY_URL.clone();
url.set_host(Some(&format!("api.{}", host))).unwrap();
url
let mut deno_registry_api_url = deno_registry_url().clone();
deno_registry_api_url.set_path("api/");
deno_registry_api_url
});
&DENO_REGISTRY_API_URL

View file

@ -3,16 +3,10 @@
static TEST_REGISTRY_URL: &str = "http://127.0.0.1:4250";
pub fn env_vars_for_registry() -> Vec<(String, String)> {
vec![
(
"DENO_REGISTRY_URL".to_string(),
TEST_REGISTRY_URL.to_string(),
),
(
"DENO_REGISTRY_API_URL".to_string(),
TEST_REGISTRY_URL.to_string(),
),
]
vec![(
"DENO_REGISTRY_URL".to_string(),
TEST_REGISTRY_URL.to_string(),
)]
}
itest!(no_token {

View file

@ -1,3 +1,3 @@
Publishing @foo/bar@1.0.0 ...
Successfully published @foo/bar@1.0.0
http://127.0.0.1:4250/@foo/bar/1.0.0_meta.json
Visit http://127.0.0.1:4250/@foo/bar@1.0.0 for details

View file

@ -1,5 +1,6 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use deno_core::error::AnyError;
use deno_core::serde_json;
use deno_runtime::deno_fetch::reqwest;
use serde::de::DeserializeOwned;
@ -108,3 +109,32 @@ pub async fn parse_response<T: DeserializeOwned>(
x_deno_ray,
})
}
pub async fn get_scope(
client: &reqwest::Client,
registry_api_url: &str,
scope: &str,
) -> Result<reqwest::Response, AnyError> {
let scope_url = format!("{}scope/{}", registry_api_url, scope);
let response = client.get(&scope_url).send().await?;
Ok(response)
}
pub fn get_package_api_url(
registry_api_url: &str,
scope: &str,
package: &str,
) -> String {
format!("{}scope/{}packages/{}", registry_api_url, scope, package)
}
pub async fn get_package(
client: &reqwest::Client,
registry_api_url: &str,
scope: &str,
package: &str,
) -> Result<reqwest::Response, AnyError> {
let package_url = get_package_api_url(registry_api_url, scope, package);
let response = client.get(&package_url).send().await?;
Ok(response)
}

View file

@ -2,6 +2,7 @@
use std::collections::HashMap;
use std::fmt::Write;
use std::io::IsTerminal;
use std::rc::Rc;
use std::sync::Arc;
@ -26,6 +27,7 @@ use serde::Serialize;
use sha2::Digest;
use crate::args::deno_registry_api_url;
use crate::args::deno_registry_url;
use crate::args::Flags;
use crate::args::PublishFlags;
use crate::factory::CliFactory;
@ -41,6 +43,11 @@ use auth::get_auth_method;
use auth::AuthMethod;
use publish_order::PublishOrderGraph;
fn ring_bell() {
// ASCII code for the bell character.
print!("\x07");
}
struct PreparedPublishPackage {
scope: String,
package: String,
@ -224,8 +231,7 @@ async fn get_auth_headers(
println!(" @{}/{}", packages[0].scope, packages[0].package);
}
// ASCII code for the bell character.
print!("\x07");
ring_bell();
println!("{}", colors::gray("Waiting..."));
let interval = std::time::Duration::from_secs(auth.poll_interval);
@ -332,6 +338,115 @@ async fn get_auth_headers(
Ok(authorizations)
}
/// Check if both `scope` and `package` already exist, if not return
/// a URL to the management panel to create them.
async fn check_if_scope_and_package_exist(
client: &reqwest::Client,
registry_api_url: &str,
registry_manage_url: &str,
scope: &str,
package: &str,
) -> Result<Option<String>, AnyError> {
let mut needs_scope = false;
let mut needs_package = false;
let response = api::get_scope(client, registry_api_url, scope).await?;
if response.status() == 404 {
needs_scope = true;
}
let response =
api::get_package(client, registry_api_url, scope, package).await?;
if response.status() == 404 {
needs_package = true;
}
if needs_scope || needs_package {
let create_url = format!(
"{}new?scope={}&package={}&from=cli",
registry_manage_url, scope, package
);
return Ok(Some(create_url));
}
Ok(None)
}
async fn ensure_scopes_and_packages_exist(
client: &reqwest::Client,
registry_api_url: String,
registry_manage_url: String,
packages: Vec<Rc<PreparedPublishPackage>>,
) -> Result<(), AnyError> {
if !std::io::stdin().is_terminal() {
let mut missing_packages_lines = vec![];
for package in packages {
let maybe_create_package_url = check_if_scope_and_package_exist(
client,
&registry_api_url,
&registry_manage_url,
&package.scope,
&package.package,
)
.await?;
if let Some(create_package_url) = maybe_create_package_url {
missing_packages_lines.push(format!(" - {}", create_package_url));
}
}
if !missing_packages_lines.is_empty() {
bail!(
"Following packages don't exist, follow the links and create them:\n{}",
missing_packages_lines.join("\n")
);
}
return Ok(());
}
for package in packages {
let maybe_create_package_url = check_if_scope_and_package_exist(
client,
&registry_api_url,
&registry_manage_url,
&package.scope,
&package.package,
)
.await?;
let Some(create_package_url) = maybe_create_package_url else {
continue;
};
ring_bell();
println!(
"'@{}/{}' doesn't exist yet. Visit {} to create the package",
&package.scope,
&package.package,
colors::cyan_with_underline(create_package_url)
);
println!("{}", colors::gray("Waiting..."));
let package_api_url = api::get_package_api_url(
&registry_api_url,
&package.scope,
&package.package,
);
loop {
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
let response = client.get(&package_api_url).send().await?;
if response.status() == 200 {
let name = format!("@{}/{}", package.scope, package.package);
println!("Package {} created", colors::green(name));
break;
}
}
}
Ok(())
}
async fn perform_publish(
http_client: &Arc<HttpClient>,
mut publish_order_graph: PublishOrderGraph,
@ -339,7 +454,8 @@ async fn perform_publish(
auth_method: AuthMethod,
) -> Result<(), AnyError> {
let client = http_client.client()?;
let registry_url = deno_registry_api_url().to_string();
let registry_api_url = deno_registry_api_url().to_string();
let registry_url = deno_registry_url().to_string();
let packages = prepared_package_by_name
.values()
@ -351,8 +467,16 @@ async fn perform_publish(
.collect::<Vec<_>>();
print_diagnostics(diagnostics);
ensure_scopes_and_packages_exist(
client,
registry_api_url.clone(),
registry_url.clone(),
packages.clone(),
)
.await?;
let mut authorizations =
get_auth_headers(client, registry_url.clone(), packages, auth_method)
get_auth_headers(client, registry_api_url.clone(), packages, auth_method)
.await?;
assert_eq!(prepared_package_by_name.len(), authorizations.len());
@ -369,14 +493,21 @@ async fn perform_publish(
package.version.clone(),
))
.unwrap();
let registry_api_url = registry_api_url.clone();
let registry_url = registry_url.clone();
let http_client = http_client.clone();
futures.spawn(async move {
let display_name =
format!("@{}/{}@{}", package.scope, package.package, package.version);
publish_package(&http_client, package, &registry_url, &authorization)
.await
.with_context(|| format!("Failed to publish {}", display_name))?;
publish_package(
&http_client,
package,
&registry_api_url,
&registry_url,
&authorization,
)
.await
.with_context(|| format!("Failed to publish {}", display_name))?;
Ok(package_name)
});
}
@ -397,6 +528,7 @@ async fn perform_publish(
async fn publish_package(
http_client: &HttpClient,
package: Rc<PreparedPublishPackage>,
registry_api_url: &str,
registry_url: &str,
authorization: &str,
) -> Result<(), AnyError> {
@ -411,7 +543,7 @@ async fn publish_package(
let url = format!(
"{}scopes/{}/packages/{}/versions/{}",
registry_url, package.scope, package.package, package.version
registry_api_url, package.scope, package.package, package.version
);
let response = client
@ -449,7 +581,7 @@ async fn publish_package(
while task.status != "success" && task.status != "failure" {
tokio::time::sleep(interval).await;
let resp = client
.get(format!("{}publish_status/{}", registry_url, task.id))
.get(format!("{}publish_status/{}", registry_api_url, task.id))
.send()
.await
.with_context(|| {
@ -486,10 +618,12 @@ async fn publish_package(
package.package,
package.version
);
// TODO(bartlomieju): return something more useful here
println!(
"{}@{}/{}/{}_meta.json",
registry_url, package.scope, package.package, package.version
"{}",
colors::gray(format!(
"Visit {}@{}/{}@{} for details",
registry_url, package.scope, package.package, package.version
))
);
Ok(())
}

View file

@ -28,7 +28,12 @@ async fn registry_server_handler(
) -> Result<Response<Body>, hyper::http::Error> {
let path = req.uri().path();
if path.starts_with("/scopes/") {
// TODO(bartlomieju): add a proper router here
if path.starts_with("/api/scope/") {
let body = serde_json::to_string_pretty(&json!({})).unwrap();
let res = Response::new(Body::from(body));
return Ok(res);
} else if path.starts_with("/api/scopes/") {
let body = serde_json::to_string_pretty(&json!({
"id": "sdfwqer-sffg-qwerasdf",
"status": "success",
@ -37,7 +42,7 @@ async fn registry_server_handler(
.unwrap();
let res = Response::new(Body::from(body));
return Ok(res);
} else if path.starts_with("/publish_status/") {
} else if path.starts_with("/api/publish_status/") {
let body = serde_json::to_string_pretty(&json!({
"id": "sdfwqer-qwer-qwerasdf",
"status": "success",