@@ -15,6 +15,8 @@ use analyze::get_top_level_decls;
1515use anyhow:: Context ;
1616use anyhow:: Result ;
1717
18+ use crate :: loader:: get_all_specifier_mappers;
19+ use crate :: loader:: get_types_package_for_specifier;
1820use analyze:: get_ignore_line_indexes;
1921use anyhow:: bail;
2022use deno_ast:: apply_text_changes;
@@ -107,6 +109,9 @@ pub struct TransformOutput {
107109 /// This is `None` when no config file was found, when one was explicitly
108110 /// provided, or when auto-discovery is disabled.
109111 pub discovered_config_file : Option < PathBuf > ,
112+ /// Packages that provide the type declarations of a mapped dependency
113+ /// (ex. an `@types/` package specified by an `X-TypeScript-Types` header).
114+ pub types_dependencies : Vec < Dependency > ,
110115}
111116
112117#[ cfg_attr( feature = "serialization" , derive( serde:: Serialize ) ) ]
@@ -463,8 +468,9 @@ pub async fn transform(
463468 . await ?
464469 . cloned ( ) ;
465470
471+ let file_fetcher = Rc :: new ( file_fetcher) ;
466472 let loader = Rc :: new ( DenoGraphLoader :: new (
467- Rc :: new ( file_fetcher) ,
473+ file_fetcher. clone ( ) ,
468474 resolver_factory
469475 . workspace_factory ( )
470476 . global_http_cache ( ) ?
@@ -521,6 +527,8 @@ pub async fn transform(
521527 . collect ( ) ;
522528
523529 let mut warnings = get_declaration_warnings ( & specifiers) ;
530+ let types_dependencies =
531+ get_types_dependencies ( & specifiers, & file_fetcher, & mut warnings) . await ;
524532 let mut main_env_context = EnvironmentContext {
525533 environment : TransformOutputEnvironment {
526534 entry_points : options
@@ -706,6 +714,7 @@ pub async fn transform(
706714 test : test_env_context. environment ,
707715 warnings,
708716 discovered_config_file,
717+ types_dependencies,
709718 } )
710719}
711720
@@ -991,6 +1000,95 @@ fn to_module_name(name: &str) -> String {
9911000 }
9921001}
9931002
1003+ /// Gets the packages that provide the type declarations of the mapped
1004+ /// dependencies, which are added to the dev dependencies.
1005+ ///
1006+ /// A mapped module isn't part of the module graph, so the `X-TypeScript-Types`
1007+ /// header of each is resolved here.
1008+ async fn get_types_dependencies < TSys : WorkspaceFactorySys > (
1009+ specifiers : & Specifiers ,
1010+ file_fetcher : & PermissionedFileFetcher <
1011+ NullBlobStore ,
1012+ TSys ,
1013+ impl deno_cache_dir:: file_fetcher:: HttpClient ,
1014+ > ,
1015+ warnings : & mut Vec < String > ,
1016+ ) -> Vec < Dependency > {
1017+ let mappers = get_all_specifier_mappers ( ) ;
1018+ let mut types_packages = specifiers. types_packages . clone ( ) ;
1019+ let header_packages = futures:: future:: join_all (
1020+ specifiers
1021+ . main
1022+ . mapped
1023+ . keys ( )
1024+ . chain ( specifiers. test . mapped . keys ( ) )
1025+ // only a module mapped by one of the cdn mappers may have a types
1026+ // header, so don't fetch the modules the user mapped themselves
1027+ . filter ( |s| mappers. iter ( ) . any ( |m| m. map ( s) . is_some ( ) ) )
1028+ . map ( |specifier| {
1029+ let mappers = & mappers;
1030+ async move {
1031+ match get_types_header_specifier ( file_fetcher, specifier) . await {
1032+ Ok ( types_specifier) => Ok (
1033+ types_specifier
1034+ . and_then ( |s| get_types_package_for_specifier ( mappers, & s) ) ,
1035+ ) ,
1036+ Err ( err) => Err ( format ! (
1037+ "Failed getting the type declarations of {}. {:#}" ,
1038+ specifier, err
1039+ ) ) ,
1040+ }
1041+ }
1042+ } ) ,
1043+ )
1044+ . await ;
1045+ for result in header_packages {
1046+ match result {
1047+ Ok ( Some ( package) ) => {
1048+ // a declaration file specified in the code takes precedence
1049+ types_packages
1050+ . entry ( package. name . clone ( ) )
1051+ . or_insert ( package) ;
1052+ }
1053+ Ok ( None ) => { }
1054+ Err ( warning) => warnings. push ( warning) ,
1055+ }
1056+ }
1057+
1058+ types_packages
1059+ . into_values ( )
1060+ . filter_map ( |p| {
1061+ Some ( Dependency {
1062+ name : p. name ,
1063+ version : p. version ?,
1064+ peer_dependency : false ,
1065+ } )
1066+ } )
1067+ . collect ( )
1068+ }
1069+
1070+ /// Gets the specifier of the declaration file in a module's
1071+ /// `X-TypeScript-Types` header.
1072+ async fn get_types_header_specifier < TSys : WorkspaceFactorySys > (
1073+ file_fetcher : & PermissionedFileFetcher <
1074+ NullBlobStore ,
1075+ TSys ,
1076+ impl deno_cache_dir:: file_fetcher:: HttpClient ,
1077+ > ,
1078+ specifier : & ModuleSpecifier ,
1079+ ) -> Result < Option < ModuleSpecifier > > {
1080+ let mut file = file_fetcher. fetch_bypass_permissions ( specifier) . await ?;
1081+ let Some ( types_header) = file
1082+ . maybe_headers
1083+ . as_mut ( )
1084+ . and_then ( |h| h. remove ( "x-typescript-types" ) )
1085+ else {
1086+ return Ok ( None ) ;
1087+ } ;
1088+ // resolve relative to the final url so that redirects are handled
1089+ Ok ( file. url . join ( & types_header) . ok ( ) )
1090+ }
1091+
9941092fn get_dependencies (
9951093 mappings : BTreeMap < ModuleSpecifier , PackageMappedSpecifier > ,
9961094) -> Vec < Dependency > {
0 commit comments