Repository: denoland/deno
Pull request: perf(publish): fetch all missing package status in parallel

<!--
Before submitting a PR, please read https://docs.deno.com/runtime/manual/references/contributing

1. Give the PR a descriptive title.

  Examples of good title:
    - fix(std/http): Fix race condition in server
    - docs(console): Update docstrings
    - feat(doc): Handle nested reexports

  Examples of bad title:
    - fix #7123
    - update docs
    - fix bugs

2. Ensure there is a related issue and it is referenced in the PR text.
3. Ensure there are tests that cover the changes.
4. Ensure `cargo test` passes.
5. Ensure `./tools/format.js` passes without changing files.
6. Ensure `./tools/lint.js` passes.
7. Open as a draft PR if your work is still in progress. The CI won't run
   all steps, but you can add '[ci]' to a commit message to force it to.
8. If you would like to run the benchmarks on the CI, add the 'ci-bench' label.
-->


Changed files:
filename: cli/registry.rs
patch: @@ -119,16 +119,6 @@ pub async fn parse_response<T: DeserializeOwned>(
   })
 }
 
-pub async fn get_scope(
-  client: &HttpClient,
-  registry_api_url: &Url,
-  scope: &str,
-) -> Result<http::Response<deno_fetch::ResBody>, AnyError> {
-  let scope_url = format!("{}scopes/{}", registry_api_url, scope);
-  let response = client.get(scope_url.parse()?)?.send().await?;
-  Ok(response)
-}
-
 pub fn get_package_api_url(
   registry_api_url: &Url,
   scope: &str,

filename: cli/tools/publish/mod.rs
patch: @@ -685,6 +685,13 @@ async fn get_auth_headers(
   Ok(authorizations)
 }
 
+#[derive(Debug)]
+struct CreatePackageInfo {
+  scope: String,
+  package: String,
+  create_url: String,
+}
+
 /// 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(
@@ -693,30 +700,22 @@ async fn check_if_scope_and_package_exist(
   registry_manage_url: &Url,
   scope: &str,
   package: &str,
-) -> Result<Option<String>, AnyError> {
-  let mut needs_scope = false;
-  let mut needs_package = false;
-
-  let response = registry::get_scope(client, registry_api_url, scope).await?;
-  if response.status() == 404 {
-    needs_scope = true;
-  }
-
+) -> Result<Option<CreatePackageInfo>, AnyError> {
   let response =
     registry::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(Some(CreatePackageInfo {
+      create_url,
+      scope: scope.to_string(),
+      package: package.to_string(),
+    }))
+  } else {
+    Ok(None)
   }
-
-  Ok(None)
 }
 
 async fn ensure_scopes_and_packages_exist(
@@ -725,23 +724,32 @@ async fn ensure_scopes_and_packages_exist(
   registry_manage_url: &Url,
   packages: &[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?;
+  let mut futures = FuturesUnordered::new();
 
-      if let Some(create_package_url) = maybe_create_package_url {
-        missing_packages_lines.push(format!(" - {}", create_package_url));
-      }
-    }
+  for package in packages {
+    let future = check_if_scope_and_package_exist(
+      client,
+      registry_api_url,
+      registry_manage_url,
+      &package.scope,
+      &package.package,
+    );
+    futures.push(future);
+  }
+
+  let mut missing_packages = vec![];
+
+  while let Some(maybe_create_package_info) = futures.next().await {
+    if let Some(create_package_info) = maybe_create_package_info? {
+      missing_packages.push(create_package_info);
+    };
+  }
 
+  if !std::io::stdin().is_terminal() {
+    let missing_packages_lines: Vec<_> = missing_packages
+      .into_iter()
+      .map(|info| format!("- {}", info.create_url))
+      .collect();
     if !missing_packages_lines.is_empty() {
       bail!(
         "Following packages don't exist, follow the links and create them:\n{}",
@@ -751,41 +759,31 @@ async fn ensure_scopes_and_packages_exist(
     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;
-    };
-
+  for create_package_info in missing_packages {
     ring_bell();
     log::warn!(
       "'@{}/{}' doesn't exist yet. Visit {} to create the package",
-      &package.scope,
-      &package.package,
-      colors::cyan_with_underline(&create_package_url)
+      &create_package_info.scope,
+      &create_package_info.package,
+      colors::cyan_with_underline(&create_package_info.create_url)
     );
     log::warn!("{}", colors::gray("Waiting..."));
-    let _ = open::that_detached(&create_package_url);
+    let _ = open::that_detached(&create_package_info.create_url);
 
     let package_api_url = registry::get_package_api_url(
       registry_api_url,
-      &package.scope,
-      &package.package,
+      &create_package_info.scope,
+      &create_package_info.package,
     );
 
     loop {
       tokio::time::sleep(std::time::Duration::from_secs(3)).await;
       let response = client.get(package_api_url.parse()?)?.send().await?;
       if response.status() == 200 {
-        let name = format!("@{}/{}", package.scope, package.package);
+        let name = format!(
+          "@{}/{}",
+          create_package_info.scope, create_package_info.package
+        );
         log::info!("Package {} created", colors::green(name));
         break;
       }