/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
cli/registry.rs
293 строки
8 KB
Nathan Whitaker
fix(cli): escape control characters in external metadata (#36198)
06 авг 2026, 00:25
Не верифицирован
06 авг 2026, 00:25
fdda8b7
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. use deno_core::anyhow::bail; use deno_core::error::AnyError; use deno_core::serde_json; use deno_core::url::Url; use deno_runtime::deno_fetch; use serde::de::DeserializeOwned; use crate::http_util; use crate::http_util::HttpClient; use crate::util::console::escape_terminal_control_chars; #[derive(serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct CreateAuthorizationResponse { pub verification_url: String, pub code: String, pub exchange_token: String, pub poll_interval: u64, } #[derive(serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct ExchangeAuthorizationResponse { pub token: String, pub user: User, } #[derive(serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct User { pub name: String, } #[derive(serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct OidcTokenResponse { pub value: String, } #[derive(serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct PublishingTaskError { #[allow(dead_code, reason = "currently unused")] pub code: String, pub message: String, } #[derive(serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct PublishingTask { pub id: String, pub status: String, pub error: Option<PublishingTaskError>, } #[derive(serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct ApiError { pub code: String, pub message: String, #[serde(flatten)] pub data: serde_json::Value, #[serde(skip)] pub x_deno_ray: Option<String>, } impl std::fmt::Display for ApiError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{} ({})", escape_terminal_control_chars(&self.message), escape_terminal_control_chars(&self.code) )?; if let Some(x_deno_ray) = &self.x_deno_ray { write!( f, "[x-deno-ray: {}]", escape_terminal_control_chars(x_deno_ray) )?; } Ok(()) } } impl std::fmt::Debug for ApiError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self, f) } } impl std::error::Error for ApiError {} pub async fn parse_response<T: DeserializeOwned>( response: http::Response<deno_fetch::ResBody>, ) -> Result<T, ApiError> { let status = response.status(); let x_deno_ray = response .headers() .get("x-deno-ray") .and_then(|value| value.to_str().ok()) .map(|s| s.to_string()); let text = http_util::body_to_string(response).await.unwrap(); if !status.is_success() { match serde_json::from_str::<ApiError>(&text) { Ok(mut err) => { err.x_deno_ray = x_deno_ray; return Err(err); } Err(_) => { let err = ApiError { code: "unknown".to_string(), message: format!("{}: {}", status, text), x_deno_ray, data: serde_json::json!({}), }; return Err(err); } } } serde_json::from_str(&text).map_err(|err| ApiError { code: "unknown".to_string(), message: format!("Failed to parse response: {}, response: '{}'", err, text), x_deno_ray, data: serde_json::json!({}), }) } pub fn get_package_api_url( registry_api_url: &Url, scope: &str, package: &str, ) -> String { format!("{}scopes/{}/packages/{}", registry_api_url, scope, package) } pub fn get_package_version_api_url( registry_api_url: &Url, scope: &str, package: &str, version: &str, params: Option<&str>, ) -> String { if let Some(params) = params { format!( "{}scopes/{}/packages/{}/versions/{}?{}", registry_api_url, scope, package, version, params ) } else { format!( "{}scopes/{}/packages/{}/versions/{}", registry_api_url, scope, package, version ) } } pub async fn get_package( client: &HttpClient, registry_api_url: &Url, scope: &str, package: &str, ) -> Result<http::Response<deno_fetch::ResBody>, AnyError> { let package_url = get_package_api_url(registry_api_url, scope, package); let response = client.get(package_url.parse()?)?.send().await?; Ok(response) } /// Splits a fully qualified JSR package name (e.g. `@scope/package`) into its /// `(scope, package)` parts. pub fn parse_package_name(name: &str) -> Result<(&str, &str), AnyError> { let Some((scope, package)) = name .strip_prefix('@') .and_then(|no_at| no_at.split_once('/')) else { bail!("Invalid package name, use '@<scope_name>/<package_name>' format"); }; Ok((scope, package)) } /// Returns `true` if the given package version is already published to the /// registry. /// /// Only a `200 OK` response is treated as "already published". A `404` (and any /// other non-success status) is treated as "not published" so that this /// up-front optimization never blocks a legitimate publish because of a /// transient registry error. pub async fn check_version_exists( client: &HttpClient, registry_api_url: &Url, scope: &str, package: &str, version: &str, ) -> Result<bool, AnyError> { let url = get_package_version_api_url( registry_api_url, scope, package, version, None, ); let response = client.get(url.parse()?)?.send().await?; Ok(response.status() == 200) } pub fn get_jsr_alternative(imported: &Url) -> Option<String> { if matches!(imported.host_str(), Some("esm.sh")) { let mut segments = imported.path_segments()?; match segments.next()? { "gh" => None, module => Some(format!("\"npm:{module}\"")), } } else if imported.as_str().starts_with("https://deno.land/") { let mut segments = imported.path_segments()?; let maybe_std = segments.next()?; if maybe_std != "std" && !maybe_std.starts_with("std@") { return None; } let module = segments.next()?; let export = segments .next() .filter(|s| *s != "mod.ts") .map(|s| s.strip_suffix(".ts").unwrap_or(s).replace("_", "-")); Some(format!( "\"jsr:@std/{}@1{}\"", module, export.map(|s| format!("/{}", s)).unwrap_or_default() )) } else { None } } #[cfg(test)] mod test { use super::*; #[test] fn test_jsr_alternative() { #[track_caller] fn run_test(imported: &str, output: Option<&str>) { let imported = Url::parse(imported).unwrap(); let output = output.map(|s| s.to_string()); assert_eq!(get_jsr_alternative(&imported), output); } run_test("https://esm.sh/ts-morph", Some("\"npm:ts-morph\"")); run_test( "https://deno.land/std/path/mod.ts", Some("\"jsr:@std/path@1\""), ); run_test( "https://deno.land/std/path/join.ts", Some("\"jsr:@std/path@1/join\""), ); run_test( "https://deno.land/std@0.229.0/path/join.ts", Some("\"jsr:@std/path@1/join\""), ); run_test( "https://deno.land/std@0.229.0/path/something_underscore.ts", Some("\"jsr:@std/path@1/something-underscore\""), ); } #[test] fn test_parse_package_name() { assert_eq!(parse_package_name("@deno/doc").unwrap(), ("deno", "doc")); assert!(parse_package_name("deno/doc").is_err()); assert!(parse_package_name("@deno").is_err()); } #[test] fn api_error_display_escapes_terminal_controls() { let err = ApiError { code: "bad\u{202e}code".to_string(), message: "failed\x1b[2J\nagain".to_string(), data: serde_json::json!({}), x_deno_ray: Some("ray\u{009b}31m".to_string()), }; let display = err.to_string(); assert_eq!( display, r"failed\u{1b}[2J\nagain (bad\u{202e}code)[x-deno-ray: ray\u{9b}31m]" ); assert_eq!(format!("{err:?}"), display); assert_eq!(err.code, "bad\u{202e}code"); } }