@@ -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 ) ) ]
@@ -462,8 +467,9 @@ pub async fn transform(
462467 . await ?
463468 . cloned ( ) ;
464469
470+ let file_fetcher = Rc :: new ( file_fetcher) ;
465471 let loader = Rc :: new ( DenoGraphLoader :: new (
466- Rc :: new ( file_fetcher) ,
472+ file_fetcher. clone ( ) ,
467473 resolver_factory
468474 . workspace_factory ( )
469475 . global_http_cache ( ) ?
@@ -520,6 +526,8 @@ pub async fn transform(
520526 . collect ( ) ;
521527
522528 let mut warnings = get_declaration_warnings ( & specifiers) ;
529+ let types_dependencies =
530+ get_types_dependencies ( & specifiers, & file_fetcher, & mut warnings) . await ;
523531 let mut main_env_context = EnvironmentContext {
524532 environment : TransformOutputEnvironment {
525533 entry_points : options
@@ -705,6 +713,7 @@ pub async fn transform(
705713 test : test_env_context. environment ,
706714 warnings,
707715 discovered_config_file,
716+ types_dependencies,
708717 } )
709718}
710719
@@ -974,6 +983,95 @@ fn check_add_shim_file_to_environment(
974983 }
975984}
976985
986+ /// Gets the packages that provide the type declarations of the mapped
987+ /// dependencies, which are added to the dev dependencies.
988+ ///
989+ /// A mapped module isn't part of the module graph, so the `X-TypeScript-Types`
990+ /// header of each is resolved here.
991+ async fn get_types_dependencies < TSys : WorkspaceFactorySys > (
992+ specifiers : & Specifiers ,
993+ file_fetcher : & PermissionedFileFetcher <
994+ NullBlobStore ,
995+ TSys ,
996+ impl deno_cache_dir:: file_fetcher:: HttpClient ,
997+ > ,
998+ warnings : & mut Vec < String > ,
999+ ) -> Vec < Dependency > {
1000+ let mappers = get_all_specifier_mappers ( ) ;
1001+ let mut types_packages = specifiers. types_packages . clone ( ) ;
1002+ let header_packages = futures:: future:: join_all (
1003+ specifiers
1004+ . main
1005+ . mapped
1006+ . keys ( )
1007+ . chain ( specifiers. test . mapped . keys ( ) )
1008+ // only a module mapped by one of the cdn mappers may have a types
1009+ // header, so don't fetch the modules the user mapped themselves
1010+ . filter ( |s| mappers. iter ( ) . any ( |m| m. map ( s) . is_some ( ) ) )
1011+ . map ( |specifier| {
1012+ let mappers = & mappers;
1013+ async move {
1014+ match get_types_header_specifier ( file_fetcher, specifier) . await {
1015+ Ok ( types_specifier) => Ok (
1016+ types_specifier
1017+ . and_then ( |s| get_types_package_for_specifier ( mappers, & s) ) ,
1018+ ) ,
1019+ Err ( err) => Err ( format ! (
1020+ "Failed getting the type declarations of {}. {:#}" ,
1021+ specifier, err
1022+ ) ) ,
1023+ }
1024+ }
1025+ } ) ,
1026+ )
1027+ . await ;
1028+ for result in header_packages {
1029+ match result {
1030+ Ok ( Some ( package) ) => {
1031+ // a declaration file specified in the code takes precedence
1032+ types_packages
1033+ . entry ( package. name . clone ( ) )
1034+ . or_insert ( package) ;
1035+ }
1036+ Ok ( None ) => { }
1037+ Err ( warning) => warnings. push ( warning) ,
1038+ }
1039+ }
1040+
1041+ types_packages
1042+ . into_values ( )
1043+ . filter_map ( |p| {
1044+ Some ( Dependency {
1045+ name : p. name ,
1046+ version : p. version ?,
1047+ peer_dependency : false ,
1048+ } )
1049+ } )
1050+ . collect ( )
1051+ }
1052+
1053+ /// Gets the specifier of the declaration file in a module's
1054+ /// `X-TypeScript-Types` header.
1055+ async fn get_types_header_specifier < TSys : WorkspaceFactorySys > (
1056+ file_fetcher : & PermissionedFileFetcher <
1057+ NullBlobStore ,
1058+ TSys ,
1059+ impl deno_cache_dir:: file_fetcher:: HttpClient ,
1060+ > ,
1061+ specifier : & ModuleSpecifier ,
1062+ ) -> Result < Option < ModuleSpecifier > > {
1063+ let mut file = file_fetcher. fetch_bypass_permissions ( specifier) . await ?;
1064+ let Some ( types_header) = file
1065+ . maybe_headers
1066+ . as_mut ( )
1067+ . and_then ( |h| h. remove ( "x-typescript-types" ) )
1068+ else {
1069+ return Ok ( None ) ;
1070+ } ;
1071+ // resolve relative to the final url so that redirects are handled
1072+ Ok ( file. url . join ( & types_header) . ok ( ) )
1073+ }
1074+
9771075fn get_dependencies (
9781076 mappings : BTreeMap < ModuleSpecifier , PackageMappedSpecifier > ,
9791077) -> Vec < Dependency > {
0 commit comments