char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
61e3521
main
1use std:: collections::{ BTreeMap , BTreeSet , HashMap }; 2use std:: path::{ Path , PathBuf }; 3use std:: sync::{ Arc , Mutex }; 4 5use super :: package::{ 6NpmImportsTarget , NpmManifest , NpmModuleKind , NpmPackageScope , NpmPackageType , 7 classify_npm_module, npm_conditions, parse_npm_manifest, parse_npm_scope, platform_satisfied, 8 resolve_exports_target, resolve_imports_target, split_bare, 9}; 10use super ::{ 11MAX_NPM_CONCURRENT_DOWNLOADS , NpmArtifact , NpmIdentity , NpmRequest , npm_virtual_module, 12 parse_npm_request, storage, validate_npm_name, 13}; 14use crate :: registry::{ Access , NpmInstancePin , PackageStore }; 15 16/// Failures preparing and resolving npm package graphs. 17# [ derive ( Clone , Debug , thiserror :: Error )] 18pub enum ResolutionError { 19# [ error ( "Frozen lockfile is missing {name}" )] 20FrozenLockfileMissing { name : String }, 21# [ error ( "Invalid npm export subpath: {subpath}" )] 22InvalidNpmExportSubpath { subpath : String }, 23# [ error ( "Invalid npm lock identity" )] 24InvalidNpmLockIdentity , 25# [ error ( "Invalid npm relative import: {specifier:?}" )] 26InvalidNpmRelativeImport { specifier : String }, 27# [ error ( "Invalid npm version range {range:?}: {detail}" )] 28InvalidNpmVersionRange { range : String , detail : String }, 29# [ error ( "Invalid TypeScript version {version:?}: {detail}" )] 30InvalidTypeScriptVersion { version : String , detail : String }, 31# [ error ( "Package {name} uses typesVersions; call set_typescript_version first" )] 32TypeScriptVersionRequired { name : String }, 33# [ error ( "JSON modules are not supported: {rel}" )] 34JsonModuleUnsupported { rel : String }, 35# [ error ( "Locked npm instance changed for {id}" )] 36LockedNpmInstanceChanged { id : String }, 37# [ error ( "Lockfile changed concurrently for {key}" )] 38LockfileChanged { key : String }, 39# [ error ( "Native addons are not supported: {rel}/index.node" )] 40NativeAddonUnsupported { rel : String }, 41# [ error ( "node: builtin modules are not supported: {specifier}" )] 42NodeBuiltinUnsupported { specifier : String }, 43# [ error ( "npm browser remap of {specifier:?} is cyclic or too deep" )] 44NpmBrowserRemapCyclic { specifier : String }, 45# [ error ( "npm browser remap target is not in the package manifest: {target:?}" )] 46NpmBrowserRemapTargetNotInManifest { target : String }, 47# [ error ( "npm cache path is not owned by a prepared instance: {}" , path . display ())] 48NpmCachePathUnowned { path : std:: path:: PathBuf }, 49# [ error ( "npm declaration has no executable code: {rel}" )] 50NpmDeclarationNoCode { rel : String }, 51# [ error ( "npm dependency graph is too deep" )] 52NpmDependencyGraphTooDeep , 53# [ error ( "npm dependency graph is too large" )] 54NpmDependencyGraphTooLarge , 55# [ error ( "npm dependency {alias:?} could not be resolved" )] 56NpmDependencyUnresolvable { alias : String }, 57# [ error ( "npm dependency {alias:?} uses unsupported protocol {protocol:?}" )] 58NpmDependencyUnsupportedProtocol { alias : String , protocol : String }, 59# [ error ( "npm export target escapes its package: {target:?}" )] 60NpmExportTargetEscapes { target : String }, 61# [ error ( "npm export target is not in the package manifest: {target:?}" )] 62NpmExportTargetNotInManifest { target : String }, 63# [ error ( "npm exports mixes subpath and condition keys" )] 64NpmExportsMixesKeys , 65# [ error ( "npm file integrity mismatch: {rel}" )] 66NpmFileIntegrityMismatch { rel : String }, 67# [ error ( "npm file is not in the package manifest: {path}" )] 68NpmFileNotInManifest { path : String }, 69# [ error ( "Frozen lockfile is missing npm instance {id}" )] 70NpmFrozenLockfileMissingInstance { id : String }, 71# [ error ( "npm import target is not in the package manifest: {specifier:?}" )] 72NpmImportTargetNotInManifest { specifier : String }, 73# [ error ( "npm imports target {target:?} is not a declared dependency" )] 74NpmImportsTargetUndeclared { target : String }, 75# [ error ( "npm instance is not prepared: {id}" )] 76NpmInstanceNotPrepared { id : String }, 77# [ error ( "npm module is not UTF-8: {}" , path . display ())] 78NpmModuleNotUtf8 { path : std:: path:: PathBuf }, 79# [ error ( "npm package {name} has no export {export:?}" )] 80NpmPackageMissingExport { name : String , export : String }, 81# [ error ( "npm package {name} has no file {export:?}" )] 82NpmPackageMissingFile { name : String , export : String }, 83# [ error ( "npm package {name} has no import {specifier:?}" )] 84NpmPackageMissingImport { name : String , specifier : String }, 85# [ error ( "npm package {name} has no usable entry point" )] 86NpmPackageNoEntryPoint { name : String }, 87# [ error ( "npm package {name:?} is not a declared dependency of {owner}" )] 88NpmPackageNotDeclared { name : String , owner : String }, 89# [ error ( "npm package is not prepared: {request_key}" )] 90NpmPackageNotPrepared { request_key : String }, 91# [ error ( "npm package is not prepared: {request}" )] 92NpmPackageNotPreparedRequest { request : String }, 93# [ error ( "npm package is not prepared: {target}" )] 94NpmPackageNotPreparedTarget { target : String }, 95# [ error ( "npm package is not supported here: {request_key}" )] 96NpmPackageNotSupportedHere { request_key : String }, 97# [ error ( "npm package {name}@{version} is not supported on this platform" )] 98NpmPackagePlatformUnsupported { name : String , version : String }, 99# [ error ( "npm path escapes its instance: {}" , path . display ())] 100NpmPathEscapesInstance { path : std:: path:: PathBuf }, 101# [ error ( "npm path escapes its package: {rel:?}" )] 102NpmPathEscapesPackage { rel : String }, 103# [ error ( "npm path must be UTF-8" )] 104NpmPathMustBeUtf8 , 105# [ error ( "npm path must be UTF-8: {}" , path . display ())] 106NpmPathNotUtf8 { path : std:: path:: PathBuf }, 107# [ error ( "npm peer dependency {peer:?} could not be resolved" )] 108NpmPeerUnresolvable { peer : String }, 109# [ error ( "npm peer dependency {peer:?} wants {wanted} but the context provides {provided}" )] 110NpmPeerWantsMismatch { 111peer : String , 112wanted : String , 113provided : String , 114}, 115# [ error ( "npm referrer must be an absolute path" )] 116NpmReferrerNotAbsolute , 117# [ error ( "npm relative import escapes its package" )] 118NpmRelativeImportEscapes , 119# [ error ( "npm relative imports must name a file" )] 120NpmRelativeImportMustNameFile , 121# [ error ( "npm relative import is not in the package manifest: {specifier:?}" )] 122NpmRelativeImportNotInManifest { specifier : String }, 123# [ error ( "npm relative import must be UTF-8" )] 124NpmRelativeImportNotUtf8 , 125# [ error ( 126"npm request {request_key} is prepared in several peer contexts; resolve it from an owning file instead" 127)] 128NpmRequestAmbiguousContexts { request_key : String }, 129# [ error ( "npm request {request_key} is not prepared in this context" )] 130NpmRequestNotPrepared { request_key : String }, 131# [ error ( 132"npm request {request_key} is not prepared in this context (sync require cannot acquire new packages)" 133)] 134NpmRequestNotPreparedSync { request_key : String }, 135# [ error ( "npm require lookup is too deep" )] 136NpmRequireLookupTooDeep , 137# [ error ( "npm require lookup is too deep: {specifier:?}" )] 138NpmRequireLookupTooDeepSpecifier { specifier : String }, 139# [ error ( "npm resolver does not own {}" , path . display ())] 140NpmResolverNotOwner { path : std:: path:: PathBuf }, 141# [ error ( transparent )] 142NpmUnsupportedModule ( # [ from ] super :: package:: NpmUnsupported ), 143# [ error ( transparent )] 144Npm ( # [ from ] super :: NpmError ), 145# [ error ( transparent )] 146Storage ( # [ from ] super :: storage:: StorageError ), 147# [ error ( transparent )] 148Package ( # [ from ] super :: package:: PackageError ), 149} 150use futures:: stream::{ self , StreamExt }; 151 152/// One root the caller wants prepared, with the peer providers its consumer 153/// already selected: every root in a `prepare` call shares this context, so a 154/// plugin and its host bind the same provider instances. 155# [ derive ( Clone , Debug )] 156pub struct NpmRootRequest { 157pub request : String , 158pub context : BTreeMap < String , String >, 159} 160 161/// The roots one `prepare` call established: request lock keys to instance 162/// ids. Later integration layers keep one handle per owning local config 163/// instead of re-deriving contexts from paths. 164# [ derive ( Clone , Debug , Default )] 165pub struct PreparedNpmRoots { 166pub roots : BTreeMap < String , String >, 167} 168 169impl PreparedNpmRoots { 170/// Exact lock keys in this handle for one package request. 171pub fn keys_for_request ( & self , request : & str ) ->Vec < String > { 172let mut keys: Vec < String > =self 173. roots 174. keys () 175. filter ( |key| key. as_str () == request || key. starts_with ( & format! ( "{request}\0" ))) 176. cloned () 177. collect (); 178 keys. sort (); 179 keys 180} 181} 182 183/// A file resolved inside an npm instance, with a canonical absolute path. 184# [ derive ( Clone , Debug )] 185pub struct ResolvedNpmModule { 186pub path : PathBuf , 187pub format : NpmModuleKind , 188pub instance : String , 189} 190 191/// How a specifier is loaded: `import` or `require` conditions, and 192/// whether the target must be executable code or may be a declaration. 193# [ derive ( Clone , Copy , PartialEq , Eq , PartialOrd , Ord , Hash , Debug )] 194pub enum ResolutionMode { 195Import , 196/// CommonJS `require` conditions, with Node-style extension and 197/// directory inference for package-relative paths. 198Require , 199TypeImport , 200TypeRequire , 201} 202 203impl ResolutionMode { 204pub fn is_require ( self ) ->bool { 205matches! ( self , Self :: Require |Self :: TypeRequire ) 206} 207 208pub fn is_type ( self ) ->bool { 209matches! ( self , Self :: TypeImport |Self :: TypeRequire ) 210} 211} 212 213enum RequireLookup { 214Missing , 215Err ( ResolutionError ), 216} 217 218/// Stands in for files and bare specifiers a package's `browser` field maps 219/// to `false`: an empty CommonJS module with an identity per excluded key. 220pub const BROWSER_EXCLUDED : & str ="\0browser-false.cjs" ; 221 222# [ derive ( Debug )] 223struct ResolvedNode { 224instance : String , 225identity : NpmIdentity , 226dir : PathBuf , 227files : BTreeMap < String , String >, 228manifest : NpmManifest , 229scopes : Mutex < BTreeMap < String , Arc < NpmPackageScope >>>, 230deps : BTreeMap < String , String >, 231peers : BTreeMap < String , String >, 232} 233 234/// Upper bound on resolution graph size; malicious dependency graphs fail 235/// with an error instead of expanding forever. 236const MAX_NPM_GRAPH_NODES : usize =4096 ; 237 238/// Upper bound on traversal depth. The node cap alone does not bound async 239/// recursion depth, so deep chains fail instead of overflowing the stack. 240const MAX_NPM_GRAPH_DEPTH : usize =256 ; 241 242# [ derive ( Debug )] 243struct PreparedInstance { 244pin : NpmInstancePin , 245dir : PathBuf , 246loaded : Mutex < Option < Arc < ResolvedNode >>>, 247} 248 249# [ derive ( Clone , Debug , Default )] 250struct ResolverState { 251roots : BTreeMap < String , String >, 252nodes : BTreeMap < String , Arc < PreparedInstance >>, 253instances_by_dir : HashMap < PathBuf , String >, 254} 255 256/// Local per-prepare occurrence graph. Memoized by (identity, context) with 257/// an in-progress stack for cycles; refinement later merges behaviorally 258/// identical occurrences into shared instances. 259# [ derive ( Default )] 260struct OccGraph { 261occs : Vec < Occurrence >, 262index : BTreeMap <( String , String ), usize >, 263roots : BTreeMap < String , usize >, 264} 265 266/// Mutable accumulators carried through one occurrence-graph walk. 267struct Walk < ' a > { 268build : &' a mut OccGraph , 269acq : &' a mut BTreeMap < String , NpmArtifact >, 270stack : &' a mut Vec < usize >, 271} 272 273struct Occurrence { 274identity : NpmIdentity , 275artifact : NpmArtifact , 276manifest : NpmManifest , 277dep_edges : BTreeMap < String , usize >, 278peer_edges : BTreeMap < String , usize >, 279} 280 281struct FinalizedGraph { 282roots : BTreeMap < String , String >, 283nodes : BTreeMap < String , Arc < PreparedInstance >>, 284} 285 286/// Standalone npm package resolver. It prepares version graphs into 287/// peer-scoped instances and resolves specifiers to verified files without 288/// consulting project configs, import maps, or the local filesystem beyond 289/// the reserved instance tree. 290pub struct NpmResolver { 291store : Arc < PackageStore >, 292state : Mutex < ResolverState >, 293/// Serializes graph builds. Preparation stages bindings locally and 294/// never holds the state lock across an await. 295prepare_gate : futures:: lock:: Mutex <()>, 296/// Browser bundles select `browser` conditions and honor the `browser` 297/// field instead of the runtime's `avril` branches; fixed at construction. 298browser : bool , 299typescript_version : Mutex < Option < deno_semver:: Version >>, 300} 301 302impl NpmResolver { 303pub fn new ( store : Arc < PackageStore >) ->Self { 304Self { 305 store, 306state : Mutex :: new ( ResolverState :: default ()), 307prepare_gate : futures:: lock:: Mutex :: new (()), 308browser : false , 309typescript_version : Mutex :: new ( None ), 310} 311} 312 313pub fn for_browser ( store : Arc < PackageStore >) ->Self { 314Self { 315browser : true , 316 ..Self :: new ( store) 317} 318} 319 320fn conditions ( & self , mode : ResolutionMode ) ->&' static [ &' static str ] { 321match ( self . browser , mode) { 322( false , ResolutionMode :: Import ) =>& [ "avril" , "import" , "module" , "default" ], 323( false , ResolutionMode :: Require ) =>& [ "avril" , "require" , "default" ], 324( true , ResolutionMode :: Import ) =>& [ "browser" , "import" , "module" , "default" ], 325( true , ResolutionMode :: Require ) =>& [ "browser" , "require" , "default" ], 326( false , ResolutionMode :: TypeImport ) =>{ 327& [ "types" , "avril" , "import" , "module" , "default" ] 328} 329( true , ResolutionMode :: TypeImport ) =>{ 330& [ "types" , "browser" , "import" , "module" , "default" ] 331} 332( false , ResolutionMode :: TypeRequire ) =>& [ "types" , "avril" , "require" , "default" ], 333( true , ResolutionMode :: TypeRequire ) =>& [ "types" , "browser" , "require" , "default" ], 334} 335} 336 337/// Declared `sideEffects` for one resolved file, if the package says anything usable. 338pub fn side_effects ( & self , path : & Path ) ->Option < bool > { 339let ( node, rel) =self . identify_locked ( path). ok () ?; 340super :: package:: declared_side_effects ( node. manifest . side_effects . as_ref () ?, & rel) 341} 342 343pub fn store ( & self ) ->& Arc < PackageStore > { 344& self . store 345} 346 347pub fn set_typescript_version ( & self , version : & str ) ->Result <(), ResolutionError > { 348let parsed = deno_semver:: Version :: parse_from_npm ( version). map_err ( |detail|{ 349ResolutionError :: InvalidTypeScriptVersion { 350version : version. to_owned (), 351detail : detail. to_string (), 352} 353}) ?; 354* self . typescript_version . lock (). unwrap () =Some ( parsed); 355Ok (()) 356} 357 358pub fn audit ( & self ) ->Result <(), ResolutionError > { 359let state =self . state . lock (). unwrap (); 360let nodes: Vec < _ > = state 361. nodes 362. keys () 363. map ( |id|self . node_locked ( & state, id)) 364. collect ::< Result < _ , _ >>() ?; 365drop ( state); 366let options =self . store . options (); 367let mut audited =BTreeSet :: new (); 368for nodein nodes{ 369super :: check_npm_exec ( 370self . store . authority . as_ref (), 371& node. identity . registry , 372& node. identity . name , 373& node. identity . version , 374& node. files , 375) ?; 376let backing = storage:: npm_package_dir ( 377 options, 378super :: NPM_DEFAULT_INSTANCE , 379& node. identity . name , 380& node. identity . version , 381); 382for dirin [ & node. dir , & backing] { 383if audited. insert ( dir. clone ()) { 384 storage:: verify_and_load_npm_dir ( & options. project_root , dir, & node. files ) ?; 385} 386} 387} 388Ok (()) 389} 390 391/// Prepare several roots together. Root identities are selected first 392/// so every root traverses against the same provider context; late roots 393/// reuse established instances instead of rebinding them. Returns a 394/// handle callers keep per owning config for context-pinned resolution. 395pub async fn prepare ( 396& self , 397roots : & [ NpmRootRequest ], 398) ->Result < PreparedNpmRoots , ResolutionError > { 399let mut normalized: Vec <( String , BTreeMap < String , String >)> =Vec :: new (); 400for rootin roots{ 401let ( request_key, _) =split_npm_request ( & root. request ) ?; 402for ( peer, provider) in & root. context { 403validate_npm_name ( peer) ?; 404super :: parse_npm_key ( provider) ?; 405} 406 normalized. push (( request_key, root. context . clone ())); 407} 408// Exact tuples only: same request under different caller contexts 409// resolves independently and must not be merged away. 410 normalized. sort (); 411 normalized. dedup (); 412if normalized. is_empty () { 413return Ok ( PreparedNpmRoots :: default ()); 414} 415let _gate =self . prepare_gate . lock (). await ; 416let mut handle =PreparedNpmRoots :: default (); 417let finalized =if let Some ( graph) =self . restore_locked ( & normalized). await ?{ 418 graph 419} else { 420if let Some ( sink) =& self . store . progress { 421sink ( & super :: super :: ProgressEvent :: NpmResolving ); 422} 423// All root identities are selected before any traversal so roots 424// share providers deterministically regardless of root order. 425// Selection goes through acquisition (one code path, cached per 426// prepare), never a parallel probe that could diverge from it. 427let mut preselected: BTreeMap < String , String > =BTreeMap :: new (); 428let mut build =OccGraph :: default (); 429let mut acq: BTreeMap < String , NpmArtifact > =BTreeMap :: new (); 430let preselect_keys: Vec < String > = 431 normalized. iter (). map ( |( key, _) | key. clone ()). collect (); 432self . acquire_many ( & mut acq, & preselect_keys). await ?; 433for request_keyin & preselect_keys{ 434let request =super :: parse_npm_request ( request_key) ?; 435let identity = acq 436. get ( request_key) 437. ok_or_else ( ||ResolutionError :: NpmPackageNotPrepared { 438request_key : request_key. to_owned (), 439}) ? 440. identity 441. key (); 442 preselected. insert ( request. name . clone (), identity); 443} 444// Roots and instances are recorded only by record_graph_locked on 445// success below. Artifact pins that acquisition caches along the way 446// are verified truth reusable like the JSR cache, so failures leave 447// no graph behind to poison a retry; deleting shared usage here 448// would race concurrent resolvers and workers instead. 449{ 450let mut stack =Vec :: new (); 451let mut walk =Walk { 452build : & mut build, 453acq : & mut acq, 454stack : & mut stack, 455}; 456self . build_roots ( & mut walk, & mut handle, & normalized, & preselected) 457. await ?; 458} 459if build. roots . is_empty () { 460return Ok ( handle); 461} 462self . finalize_build ( build). await ? 463}; 464self . record_graph_locked ( & finalized) ?; 465{ 466let mut state =self . state . lock (). unwrap (); 467for ( key, instance) in & finalized. roots { 468 state 469. roots 470. entry ( key. clone ()) 471. or_insert_with ( || instance. clone ()); 472 handle. roots . insert ( key. clone (), instance. clone ()); 473} 474for ( id, node) in & finalized. nodes { 475let directory = state 476. nodes 477. entry ( id. clone ()) 478. or_insert_with ( || node. clone ()) 479. dir 480. clone (); 481 state 482. instances_by_dir 483. entry ( directory) 484. or_insert_with ( || id. clone ()); 485} 486} 487Ok ( handle) 488} 489 490/// Restore bindings, not resolution inputs. The lock owns dependency and 491/// peer choices; package documents are authenticated only when consumed. 492async fn restore_locked ( 493& self , 494normalized : & [( String , BTreeMap < String , String >)], 495) ->Result < Option < FinalizedGraph >, ResolutionError > { 496if self . store . options (). update { 497return Ok ( None ); 498} 499let lock =self . store . lock . lock (). unwrap (). npm . clone (); 500let options =self . store . options (); 501let mut selected =BTreeMap :: new (); 502for ( key, _) in normalized{ 503let Some ( version) = lock. specifiers . get ( key) else { 504if options. frozen { 505return Err ( ResolutionError :: FrozenLockfileMissing { name : key. clone () }); 506} 507return Ok ( None ); 508}; 509let request =parse_npm_request ( key) ?; 510 selected. insert ( 511 request. name . clone (), 512format! ( "{}|{}|{version}" , options. npm_registry , request. name ), 513); 514} 515let mut roots =BTreeMap :: new (); 516for ( request, context) in normalized{ 517let key =root_lock_key ( request, & effective_context ( & selected, context). merged ()); 518let Some ( id) = lock. roots . get ( & key) else { 519if options. frozen { 520return Err ( ResolutionError :: FrozenLockfileMissing { name : key}); 521} 522return Ok ( None ); 523}; 524let pin = lock. instances . get ( id). ok_or_else ( ||{ 525super :: NpmError :: LockfileMissingNpmInstance { instance : id. clone () } 526}) ?; 527let name =parse_npm_request ( request) ?. name ; 528let expected =format! ( 529"{}|{name}|{}" , 530 options. npm_registry , lock. specifiers [ request] 531); 532if pin. package != expected{ 533return Err ( ResolutionError :: InvalidNpmLockIdentity ); 534} 535 roots. insert ( key, id. clone ()); 536} 537let mut nodes =BTreeMap :: new (); 538let mut pending: Vec < _ > = roots. values (). cloned (). collect (); 539let mut acquired =BTreeMap ::< String , NpmArtifact >:: new (); 540while let Some ( id) = pending. pop () { 541if nodes. contains_key ( & id) { 542continue ; 543} 544if nodes. len () >=MAX_NPM_GRAPH_NODES { 545return Err ( ResolutionError :: NpmDependencyGraphTooLarge ); 546} 547let pin = lock. instances . get ( & id). ok_or_else ( ||{ 548super :: NpmError :: LockfileMissingNpmInstance { instance : id. clone () } 549}) ?; 550let ( registry, name, version) =super :: parse_npm_key ( & pin. package ) ?; 551if registry != options. npm_registry { 552return Err ( super :: NpmError :: NpmOriginOutsideRegistry { 553url : registry. to_string (), 554} 555. into ()); 556} 557let base =super :: npm_version_base ( & registry, & name, & version) ?; 558Access :: Package ( base) 559. check ( self . store . authority . as_ref ()) 560. map_err ( super :: NpmError :: from) ?; 561let package_pin = lock. packages . get ( & pin. package ). ok_or_else ( ||{ 562super :: NpmError :: LockfileMissingNpmPackage { package : pin. package . clone () } 563}) ?; 564let disk = storage:: npm_disk_instance ( & id, & pin. package ); 565let dir = storage:: npm_package_dir ( options, disk, & name, & version); 566let backing = 567 storage:: npm_package_dir ( options, super :: NPM_DEFAULT_INSTANCE , & name, & version); 568if !storage:: npm_instance_ready ( options, & dir, & backing, package_pin) ?{ 569if !acquired. contains_key ( & pin. package ) { 570let identity =NpmIdentity { registry, name, version}; 571let artifact =self 572. store 573. npm_acquire_locked ( & identity, package_pin) 574. await ?; 575 acquired. insert ( pin. package . clone (), artifact); 576} 577 storage:: ensure_npm_instance ( options, & acquired[ & pin. package ], disk) ?; 578} 579 pending. extend ( pin. deps . values (). chain ( pin. peers . values ()). cloned ()); 580 nodes. insert ( 581 id, 582Arc :: new ( PreparedInstance { 583pin : pin. clone (), 584 dir, 585loaded : Mutex :: new ( None ), 586}), 587); 588} 589Ok ( Some ( FinalizedGraph { roots, nodes})) 590} 591 592/// Resolve an explicit `npm:` request, preparing its package on demand. 593/// A context-free request matches the single prepared root for its 594/// package request; several contexts for one request are ambiguous. 595pub async fn resolve_request ( 596& self , 597specifier : & str , 598mode : ResolutionMode , 599) ->Result < ResolvedNpmModule , ResolutionError > { 600let request =super :: parse_npm_request ( specifier) ?; 601self . resolve_request_parsed ( & request, mode). await 602} 603 604pub async fn resolve_request_parsed ( 605& self , 606request : & super :: NpmRequest , 607mode : ResolutionMode , 608) ->Result < ResolvedNpmModule , ResolutionError > { 609let request_key = request. request_key (); 610let subpath = request. subpath . clone (); 611let instance ={ 612let state =self . state . lock (). unwrap (); 613self . lookup_root_locked ( & state, & request_key) ? 614}; 615let instance =match instance{ 616Some ( instance) => instance, 617None =>{ 618let handle =self 619. prepare ( & [ NpmRootRequest { 620request : request_key. clone (), 621context : BTreeMap :: new (), 622}]) 623. await ?; 624 handle. roots . values (). next (). cloned (). ok_or_else ( ||{ 625ResolutionError :: NpmPackageNotPrepared { 626request_key : request_key. to_owned (), 627} 628}) ? 629} 630}; 631let export =match subpath{ 632Some ( path) =>subpath_to_export ( & path) ?, 633None =>"." . into (), 634}; 635self . resolve_in_instance ( & instance, & export, mode) 636} 637 638/// ESM-warmable requests for a package or the export prefix its 639/// subpath names, in the specifier's own range form so frozen runs pin 640/// the same roots. Exact export keys resolve directly; wildcard 641/// patterns invert over verified files to their public subpaths, never 642/// the private file names. Conditions select executable branches only: 643/// misses, blocks, and non-executable kinds are skipped, never 644/// compiled. Any other resolution or security error fails the whole 645/// enumeration instead of warming a partial prefix. 646pub async fn export_requests ( & self , specifier : & str ) ->Result < Vec < String >, ResolutionError > { 647let ( request_key, subpath) =split_npm_request ( specifier) ?; 648let scope = subpath 649. map ( |path| path. trim_end_matches ( '/' ). to_owned ()) 650. unwrap_or_default (); 651if self 652. lookup_root_locked ( & self . state . lock (). unwrap (), & request_key) ? 653. is_none () 654{ 655self . prepare ( & [ NpmRootRequest { 656request : request_key. clone (), 657context : BTreeMap :: new (), 658}]) 659. await ?; 660} 661let state =self . state . lock (). unwrap (); 662let instance =self 663. lookup_root_locked ( & state, & request_key) ? 664. ok_or_else ( ||ResolutionError :: NpmPackageNotPrepared { 665request_key : request_key. to_owned (), 666}) ?; 667let node =self . node_locked ( & state, & instance) ?; 668self . export_requests_in ( & node, & request_key, & scope) 669} 670 671fn export_requests_in ( 672& self , 673node : & ResolvedNode , 674base : & str , 675scope : & str , 676) ->Result < Vec < String >, ResolutionError > { 677match & node. manifest . exports_raw { 678// Without an exports map only the entry file is public; legacy 679// subpaths warm per file beneath a prefix scope instead. 680None if scope. is_empty () =>Ok ( vec! [ base. to_owned ()]), 681None =>{ 682let mut requests =Vec :: new (); 683for relin node. files . keys () { 684if rel != scope && !rel. starts_with ( & format! ( "{scope}/" )) { 685continue ; 686} 687if !self . export_file_executable ( node, rel) ?{ 688continue ; 689} 690 requests. push ( format! ( "{base}/{rel}" )); 691} 692 requests. sort (); 693 requests. dedup (); 694Ok ( requests) 695} 696Some ( exports) =>self . export_requests_mapped ( node, base, scope, exports), 697} 698} 699 700fn export_requests_mapped ( 701& self , 702node : & ResolvedNode , 703base : & str , 704scope : & str , 705exports : & serde_json:: Value , 706) ->Result < Vec < String >, ResolutionError > { 707// A bare target, a fallback list, or a condition object only 708// ever selects the root. 709let map =match exports{ 710 serde_json:: Value :: Object ( map) => map, 711 _ =>{ 712if scope. is_empty () &&self . export_executable_in ( node, exports, "." ) ?. is_some () { 713return Ok ( vec! [ base. to_owned ()]); 714} 715return Ok ( Vec :: new ()); 716} 717}; 718let subpaths = map. keys (). filter ( |key| key. starts_with ( '.' )). count (); 719if subpaths >0 && subpaths != map. len () { 720return Err ( ResolutionError :: NpmExportsMixesKeys ); 721} 722if subpaths ==0 { 723if scope. is_empty () &&self . export_executable_in ( node, exports, "." ) ?. is_some () { 724return Ok ( vec! [ base. to_owned ()]); 725} 726return Ok ( Vec :: new ()); 727} 728let mut subs: BTreeSet < String > =BTreeSet :: new (); 729if scope. is_empty () && map. contains_key ( "." ) { 730 subs. insert ( "." . to_owned ()); 731} 732for keyin map. keys () { 733if !key. starts_with ( "./" ) || key. contains ( '*' ) { 734continue ; 735} 736if map. get ( key). is_some_and ( |value| value. is_null ()) { 737continue ; 738} 739if under_export_scope ( & key[ "./" . len () ..], scope) { 740 subs. insert ( key. clone ()); 741} 742} 743for keyin map. keys () { 744let Some (( key_prefix, _)) = key. split_once ( '*' ) else { 745continue ; 746}; 747if !key. starts_with ( "./" ) || key[ key_prefix. len () +1 ..]. contains ( '*' ) { 748continue ; 749} 750let ( _, key_suffix) = key. split_once ( '*' ). unwrap (); 751let mut targets =Vec :: new (); 752pattern_targets ( & map[ key], & mut targets); 753for targetin targets{ 754// File keys omit the trailer's leading `./`. 755let target = target. strip_prefix ( "./" ). unwrap_or ( & target); 756let Some (( target_prefix, target_suffix)) = target. split_once ( '*' ) else { 757continue ; 758}; 759for filein node. files . keys () { 760if file. len () < target_prefix. len () + target_suffix. len () 761 || !file. starts_with ( target_prefix) 762 || !file. ends_with ( & target_suffix) 763{ 764continue ; 765} 766let captured =& file[ target_prefix. len () ..file. len () - target_suffix. len ()]; 767let public =format! ( "{key_prefix}{captured}{key_suffix}" ); 768if !public. starts_with ( "./" ) { 769continue ; 770} 771if !under_export_scope ( & public[ "./" . len () ..], scope) { 772continue ; 773} 774match resolve_exports_target ( exports, & public, & npm_conditions ()) { 775Ok ( Some ( _)) =>{ 776 subs. insert ( public); 777} 778Ok ( None ) =>{} 779Err ( error) =>return Err ( error. into ()), 780} 781} 782} 783} 784let mut requests =Vec :: new (); 785for subin subs{ 786if self . export_executable_in ( node, exports, & sub) ?. is_none () { 787continue ; 788} 789if sub =="." { 790 requests. push ( base. to_owned ()); 791} else { 792 requests. push ( format! ( "{base}/{}" , sub. strip_prefix ( "./" ). unwrap_or ( & sub))); 793} 794} 795 requests. sort (); 796 requests. dedup (); 797Ok ( requests) 798} 799 800/// Resolve one public export to its verified file when the active 801/// conditions select executable code. Misses and non-executable kinds 802/// (including explicitly blocked entries) yield None without failing; 803/// malformed, missing, escaping, or unauthorized targets fail. 804fn export_executable_in ( 805& self , 806node : & ResolvedNode , 807exports : & serde_json:: Value , 808export : & str , 809) ->Result < Option < String >, ResolutionError > { 810if exports 811. as_object () 812. and_then ( |map| map. get ( export)) 813. is_some_and ( |value| value. is_null ()) 814{ 815return Ok ( None ); 816} 817let target =match resolve_exports_target ( exports, export, & npm_conditions ()) { 818Ok ( Some ( target)) => target, 819Ok ( None ) =>return Ok ( None ), 820Err ( error) =>return Err ( error. into ()), 821}; 822let rel = target 823. strip_prefix ( "./" ) 824. ok_or_else ( ||ResolutionError :: NpmExportTargetEscapes { target : target. to_owned () }) ?; 825super :: validate_npm_relpath ( rel) ?; 826if rel. split ( '/' ). any ( |part| part =="node_modules" ) { 827return Err ( ResolutionError :: NpmExportTargetEscapes { target : target. to_owned () }); 828} 829if !node. files . contains_key ( rel) { 830return Err ( ResolutionError :: NpmExportTargetNotInManifest { 831target : target. to_owned (), 832}); 833} 834let url =super :: npm_virtual_module ( 835& node. identity . registry , 836& node. identity . name , 837& node. identity . version , 838 rel, 839) ?; 840super :: check_npm_url ( self . store . authority . as_ref (), & url) ?; 841match node. classify ( & self . store . options (). project_root , rel) { 842Ok ( NpmModuleKind :: Esm ) 843 |Ok ( NpmModuleKind :: TypeScript ) 844 |Ok ( NpmModuleKind :: CommonJs ) =>Ok ( Some ( rel. to_owned ())), 845Ok ( _) |Err ( ResolutionError :: NpmUnsupportedModule ( _)) =>Ok ( None ), 846Err ( error) =>Err ( error), 847} 848} 849 850/// Whether one legacy file warms as executable code: same 851/// classification execution applies, without an exports map. 852fn export_file_executable ( 853& self , 854node : & ResolvedNode , 855rel : & str , 856) ->Result < bool , ResolutionError > { 857let url =super :: npm_virtual_module ( 858& node. identity . registry , 859& node. identity . name , 860& node. identity . version , 861 rel, 862) ?; 863super :: check_npm_url ( self . store . authority . as_ref (), & url) ?; 864match node. classify ( & self . store . options (). project_root , rel) { 865Ok ( NpmModuleKind :: Esm ) 866 |Ok ( NpmModuleKind :: TypeScript ) 867 |Ok ( NpmModuleKind :: CommonJs ) =>Ok ( true ), 868Ok ( _) |Err ( ResolutionError :: NpmUnsupportedModule ( _)) =>Ok ( false ), 869Err ( error) =>Err ( error), 870} 871} 872 873/// The exact recorded root key behind one resolution, for lock 874/// tracking that keeps successful contexts only. 875pub fn tracking_key ( & self , request_key : & str , instance : & str ) ->Option < String > { 876let state =self . state . lock (). unwrap (); 877let mut keys: Vec < String > = state 878. roots 879. iter () 880. filter ( |( key, existing) |{ 881 existing. as_str () == instance &&root_request_part ( key) == request_key 882}) 883. map ( |( key, _) | key. clone ()) 884. collect (); 885 keys. sort (); 886 keys. into_iter (). next () 887} 888 889pub ( crate ) fn resolve_require_path ( 890& self , 891path : & Path , 892) ->Result < Option < ResolvedNpmModule >, ResolutionError > { 893let state =self . state . lock (). unwrap (); 894let instance =self . owning_instance_locked ( & state, path) ?; 895let node =self . node_locked ( & state, & instance) ?; 896let rel = path 897. strip_prefix ( & node. dir ) 898. map_err ( |_|ResolutionError :: NpmPathEscapesInstance { path : path. to_owned () }) ? 899. to_str () 900. ok_or_else ( ||ResolutionError :: NpmPathNotUtf8 { path : path. to_owned () }) ? 901. replace ( '\\' , "/" ); 902match self . resolve_relative_require ( & node, & rel, & path. display (). to_string (), 0 ) { 903Ok ( module) =>Ok ( Some ( module)), 904Err ( ResolutionError :: NpmRelativeImportNotInManifest { ..}) =>Ok ( None ), 905Err ( error) =>Err ( error), 906} 907} 908 909/// Resolve `specifier` against the roots of one prepare handle. 910pub fn resolve_in ( 911& self , 912prepared : & PreparedNpmRoots , 913specifier : & str , 914mode : ResolutionMode , 915) ->Result < ResolvedNpmModule , ResolutionError > { 916let ( request_key, subpath) =split_npm_request ( specifier) ?; 917let mut candidates = prepared 918. roots 919. iter () 920. filter ( |( key, _) |root_request_part ( key) == request_key) 921. map ( |( _, instance) | instance. clone ()) 922. collect ::< Vec < _ >>(); 923 candidates. sort (); 924 candidates. dedup (); 925if candidates. len () !=1 { 926return Err ( if mode ==ResolutionMode :: Require { 927ResolutionError :: NpmRequestNotPreparedSync { 928request_key : request_key. to_owned (), 929} 930} else { 931ResolutionError :: NpmRequestNotPrepared { 932request_key : request_key. to_owned (), 933} 934}); 935} 936let export =match subpath{ 937Some ( path) =>subpath_to_export ( & path) ?, 938None =>"." . into (), 939}; 940self . resolve_in_instance ( & candidates. into_iter (). next (). unwrap (), & export, mode) 941} 942 943fn npm_absolute_path ( specifier : & str ) ->Option < PathBuf > { 944let path =Path :: new ( specifier); 945if path. is_absolute () { 946return Some ( path. to_owned ()); 947} 948if specifier. starts_with ( "file:" ) 949 &&let Ok ( url) = url:: Url :: parse ( specifier) 950 && url. scheme () =="file" 951 && url. query (). is_none () 952 && url. fragment (). is_none () 953{ 954return url. to_file_path (). ok (); 955} 956None 957} 958 959/// Resolve `specifier` against the instance owning `referrer`. The 960/// referrer must be a canonical absolute path inside a prepared instance; 961/// anything else (including unowned reserved cache paths) is an error. 962/// `require` modes add Node-style extension/directory inference, still 963/// only over verified manifests and never the filesystem directly. 964pub fn resolve_from_file ( 965& self , 966specifier : & str , 967referrer : & Path , 968mode : ResolutionMode , 969) ->Result < ResolvedNpmModule , ResolutionError > { 970let state =self . state . lock (). unwrap (); 971let instance =self . owning_instance_locked ( & state, referrer) ?; 972let node =self . node_locked ( & state, & instance) ?; 973if specifier. starts_with ( '.' ) || specifier. starts_with ( "file:" ) { 974return self . resolve_relative_locked ( & node, referrer, specifier, mode); 975} 976if let Some ( absolute) =Self :: npm_absolute_path ( specifier) { 977if let Ok ( rel) = absolute. strip_prefix ( & node. dir ) { 978let rel = rel 979. to_str () 980. ok_or ( ResolutionError :: NpmPathMustBeUtf8 ) ? 981. replace ( '\\' , "/" ); 982let trimmed = rel. trim_end_matches ( '/' ); 983if mode. is_type () { 984return self . resolve_types_target_locked ( 985& node, 986& format! ( "./{trimmed}" ), 987 trimmed, 988 mode, 989); 990} 991if mode. is_require () { 992return self . resolve_relative_require ( & node, trimmed, specifier, 0 ); 993} 994super :: validate_npm_relpath ( trimmed) ?; 995if !node. files . contains_key ( trimmed) { 996return Err ( ResolutionError :: NpmFileNotInManifest { 997path : specifier. to_owned (), 998}); 999} 1000return self . finish_file_locked ( & node, trimmed, mode); 1001} 1002return Err ( ResolutionError :: NpmPathEscapesPackage { rel : specifier. to_owned () }); 1003} 1004if specifier. starts_with ( '#' ) { 1005return self . resolve_hash_locked ( & state, & node, specifier, mode); 1006} 1007if specifier. starts_with ( "npm:" ) { 1008let ( request_key, sub) =split_npm_request ( specifier) ?; 1009let instance = state. roots . get ( & request_key). cloned (). ok_or_else ( ||{ 1010ResolutionError :: NpmPackageNotPrepared { 1011request_key : request_key. to_owned (), 1012} 1013}) ?; 1014let node =self . node_locked ( & state, & instance) ?; 1015let export =match sub{ 1016Some ( path) =>subpath_to_export ( & path) ?, 1017None =>"." . into (), 1018}; 1019return self . resolve_in_node_locked ( & node, & export, mode); 1020} 1021self . resolve_bare_locked ( & state, & node, specifier, mode, 0 ) 1022} 1023 1024fn resolve_bare_locked ( 1025& self , 1026state : & ResolverState , 1027node : & ResolvedNode , 1028specifier : & str , 1029mode : ResolutionMode , 1030depth : usize , 1031) ->Result < ResolvedNpmModule , ResolutionError > { 1032let ( name, sub) =split_bare ( specifier); 1033if name =="node" || name. starts_with ( "node:" ) { 1034return Err ( ResolutionError :: NodeBuiltinUnsupported { 1035specifier : specifier. to_owned (), 1036}); 1037} 1038// An exact `dep/sub` key replaces the whole specifier; a `dep` key keeps `/sub`. 1039let map =self . browser_map ( node); 1040let remap =match map. and_then ( |map| map. get ( specifier)) { 1041Some ( target) =>Some (( target, None )), 1042None => sub. and_then ( |sub|Some (( map?. get ( name) ?, Some ( sub)))), 1043}; 1044match remap{ 1045Some (( serde_json:: Value :: Bool ( false ), _)) =>return Ok ( excluded ( node, specifier)), 1046Some (( serde_json:: Value :: String ( _), _)) if depth >=8 =>{ 1047return Err ( ResolutionError :: NpmBrowserRemapCyclic { 1048specifier : specifier. to_owned (), 1049}); 1050} 1051Some (( serde_json:: Value :: String ( target), None )) if target. starts_with ( '.' ) =>{ 1052if mode. is_type () { 1053return self . resolve_types_target_locked ( 1054 node, 1055& format! ( "./{}" , target. trim_start_matches ( "./" )), 1056 specifier, 1057 mode, 1058); 1059} 1060return self . finish_file_locked ( node, target. trim_start_matches ( "./" ), mode); 1061} 1062Some (( serde_json:: Value :: String ( target), rest)) if !target. starts_with ( '.' ) =>{ 1063let target =match rest{ 1064Some ( rest) =>format! ( "{target}/{rest}" ), 1065None => target. clone (), 1066}; 1067return self . resolve_bare_locked ( state, node, & target, mode, depth +1 ); 1068} 1069 _ =>{} 1070} 1071if name == node. manifest . name && node. manifest . exports_raw . is_some () { 1072let export = sub 1073. map ( |path|format! ( "./{path}" )) 1074. unwrap_or_else ( ||"." . into ()); 1075return self . resolve_in_node_locked ( node, & export, mode); 1076} 1077let export = sub 1078. map ( |path|format! ( "./{path}" )) 1079. unwrap_or_else ( ||"." . into ()); 1080let resolved =if let Some ( provider) = node. deps . get ( name). or_else ( || node. peers . get ( name)) 1081{ 1082let provider =self . node_locked ( state, provider) ?; 1083Some ( self . resolve_in_node_locked ( & provider, & export, mode) ?) 1084} else { 1085None 1086}; 1087if mode. is_type () 1088 && !name. starts_with ( "@types/" ) 1089 && resolved. as_ref (). is_none_or ( |module|{ 1090 !matches! ( 1091 module 1092. path 1093. extension () 1094. and_then ( |extension| extension. to_str ()), 1095Some ( "ts" |"tsx" |"mts" |"cts" ) 1096) 1097}) 1098{ 1099let types =super :: package:: types_package_name ( name); 1100if let Some ( provider) = node. deps . get ( & types). or_else ( || node. peers . get ( & types)) { 1101let provider =self . node_locked ( state, provider) ?; 1102return self . resolve_in_node_locked ( & provider, & export, mode); 1103} 1104} 1105if let Some ( resolved) = resolved{ 1106return Ok ( resolved); 1107} 1108Err ( ResolutionError :: NpmPackageNotDeclared { 1109name : name. to_owned (), 1110owner : node. manifest . name . clone (), 1111}) 1112} 1113 1114/// Synchronous `import.meta`-style resolution from prepared metadata. 1115pub fn resolve_import_meta ( & self , specifier : & str ) ->Result < String , ResolutionError > { 1116self . resolve_import_meta_parsed ( & parse_npm_request ( specifier) ?) 1117} 1118 1119pub fn resolve_import_meta_parsed ( 1120& self , 1121request : & NpmRequest , 1122) ->Result < String , ResolutionError > { 1123let request_key = request. request_key (); 1124let state =self . state . lock (). unwrap (); 1125let instance =self 1126. lookup_root_locked ( & state, & request_key) ? 1127. ok_or_else ( ||ResolutionError :: NpmPackageNotPrepared { 1128request_key : request_key. to_owned (), 1129}) ?; 1130let node =self . node_locked ( & state, & instance) ?; 1131let export =match & request. subpath { 1132Some ( path) =>subpath_to_export ( path) ?, 1133None =>"." . into (), 1134}; 1135Ok ( self 1136. resolve_in_node_locked ( & node, & export, ResolutionMode :: Import ) ? 1137. path 1138. to_string_lossy () 1139. into_owned ()) 1140} 1141 1142/// Read one resolved file as text, rechecking execution authority and 1143/// integrity at use time. 1144pub fn npm_source ( & self , path : & Path ) ->Result < String , ResolutionError > { 1145let ( node, rel) =self . identify_locked ( path) ?; 1146let url =npm_virtual_module ( 1147& node. identity . registry , 1148& node. identity . name , 1149& node. identity . version , 1150& rel, 1151) ?; 1152super :: check_npm_url ( self . store . authority . as_ref (), & url) ?; 1153let bytes = node. read_verified ( & self . store . options (). project_root , & rel) ?; 1154String :: from_utf8 ( bytes) 1155. map_err ( |_|ResolutionError :: NpmModuleNotUtf8 { path : path. to_owned () }) 1156} 1157 1158pub fn owning_instance ( & self , path : & Path ) ->Option < String > { 1159let state =self . state . lock (). unwrap (); 1160self . owning_instance_locked ( & state, path). ok () 1161} 1162 1163pub fn describe_instance ( & self , id : & str ) ->Result < NpmIdentity , ResolutionError > { 1164let state =self . state . lock (). unwrap (); 1165let instance = state 1166. nodes 1167. get ( id) 1168. ok_or_else ( ||ResolutionError :: NpmInstanceNotPrepared { id : id. to_owned () }) ?; 1169let ( registry, name, version) =super :: parse_npm_key ( & instance. pin . package ) ?; 1170Ok ( NpmIdentity { registry, name, version}) 1171} 1172 1173/// Authorize one reserved-tree file: ownership, kind, and virtual-URL 1174/// execution authority. Local project grants never satisfy this; only 1175/// registry `registry/name/version[/file]` grants do. 1176pub fn check_access ( 1177& self , 1178path : & Path , 1179) ->Result < super :: package:: NpmModuleKind , ResolutionError > { 1180let ( node, rel) =self . identify_locked ( path) ?; 1181let url =super :: npm_virtual_module ( 1182& node. identity . registry , 1183& node. identity . name , 1184& node. identity . version , 1185& rel, 1186) ?; 1187super :: check_npm_url ( self . store . authority . as_ref (), & url) ?; 1188 node. classify ( & self . store . options (). project_root , & rel) 1189} 1190 1191/// Reserved tree membership, independent of prepared state: callers must 1192/// never treat reserved-but-unowned paths as local files. 1193pub fn is_reserved_npm_path ( & self , path : & Path ) ->bool { 1194Self :: reserved_npm_path ( & self . store . options (). project_root , path) 1195} 1196 1197fn owning_instance_locked ( 1198& self , 1199state : & ResolverState , 1200path : & Path , 1201) ->Result < String , ResolutionError > { 1202for directoryin path. ancestors () { 1203if let Some ( id) = state. instances_by_dir . get ( directory) { 1204return Ok ( id. clone ()); 1205} 1206} 1207if Self :: reserved_npm_path ( & self . store . options (). project_root , path) { 1208return Err ( ResolutionError :: NpmCachePathUnowned { path : path. to_owned () }); 1209} 1210Err ( ResolutionError :: NpmResolverNotOwner { path : path. to_owned () }) 1211} 1212 1213fn lookup_root_locked ( 1214& self , 1215state : & ResolverState , 1216request_key : & str , 1217) ->Result < Option < String >, ResolutionError > { 1218if let Some ( instance) = state. roots . get ( request_key) { 1219return Ok ( Some ( instance. clone ())); 1220} 1221// Distinct contexts that agree on one instance are not ambiguous. 1222let mut instances = state 1223. roots 1224. keys () 1225. filter ( |key|{ 1226 key. split_once ( '\0' ) 1227. is_some_and ( |( base, _) | base == request_key) 1228}) 1229. filter_map ( |key| state. roots . get ( key)) 1230. collect ::< Vec < _ >>(); 1231 instances. sort (); 1232 instances. dedup (); 1233match instances. len () { 12340 =>Ok ( None ), 12351 =>Ok ( Some ( instances. into_iter (). next (). unwrap (). clone ())), 1236 _ =>Err ( ResolutionError :: NpmRequestAmbiguousContexts { 1237request_key : request_key. to_owned (), 1238}), 1239} 1240} 1241 1242fn node_locked ( 1243& self , 1244state : & ResolverState , 1245id : & str , 1246) ->Result < Arc < ResolvedNode >, ResolutionError > { 1247let instance = state 1248. nodes 1249. get ( id) 1250. ok_or_else ( ||ResolutionError :: NpmInstanceNotPrepared { id : id. to_owned () }) ?; 1251let mut loaded = instance. loaded . lock (). unwrap (); 1252if let Some ( node) =&* loaded{ 1253return Ok ( node. clone ()); 1254} 1255let ( registry, name, version) =super :: parse_npm_key ( & instance. pin . package ) ?; 1256let base =super :: npm_version_base ( & registry, & name, & version) ?; 1257Access :: Package ( base) 1258. check ( self . store . authority . as_ref ()) 1259. map_err ( super :: NpmError :: from) ?; 1260let pin =self 1261. store 1262. lock 1263. lock () 1264. unwrap () 1265. npm 1266. packages 1267. get ( & instance. pin . package ) 1268. cloned () 1269. ok_or_else ( ||super :: NpmError :: LockfileMissingNpmPackage { 1270package : instance. pin . package . clone (), 1271}) ?; 1272let files =match pin. files { 1273Some ( files) => files, 1274None => storage:: load_manifest_map ( 1275self . store . options (), 1276 pin. manifest 1277. as_deref () 1278. ok_or ( storage:: StorageError :: NpmPinMissingManifest ) ?, 1279) ? 1280. ok_or ( storage:: StorageError :: NpmManifestMissing ) ?, 1281}; 1282super :: check_npm_exec ( 1283self . store . authority . as_ref (), 1284& registry, 1285& name, 1286& version, 1287& files, 1288) ?; 1289let package = storage:: verify_npm_metadata ( 1290& self . store . options (). project_root , 1291& instance. dir , 1292& files, 1293) ? 1294. ok_or ( storage:: StorageError :: NpmManifestMissing ) ?; 1295let manifest =parse_npm_manifest ( & name, & version, & package) ?; 1296if !platform_satisfied ( manifest. os . as_ref (), super :: package:: npm_host_os ()) 1297 || !platform_satisfied ( manifest. cpu . as_ref (), super :: package:: npm_host_arch ()) 1298{ 1299return Err ( ResolutionError :: NpmPackagePlatformUnsupported { name, version}); 1300} 1301let node =Arc :: new ( ResolvedNode { 1302instance : id. to_owned (), 1303identity : NpmIdentity { registry, name, version}, 1304dir : instance. dir . clone (), 1305 files, 1306 manifest, 1307scopes : Mutex :: new ( BTreeMap :: new ()), 1308deps : instance. pin . deps . clone (), 1309peers : instance. pin . peers . clone (), 1310}); 1311* loaded =Some ( node. clone ()); 1312Ok ( node) 1313} 1314 1315fn identify_locked ( & self , path : & Path ) ->Result <( Arc < ResolvedNode >, String ), ResolutionError > { 1316let state =self . state . lock (). unwrap (); 1317let id =self . owning_instance_locked ( & state, path) ?; 1318let node =self . node_locked ( & state, & id) ?; 1319let rel = path 1320. strip_prefix ( & node. dir ) 1321. map_err ( |_|ResolutionError :: NpmPathEscapesInstance { path : path. to_owned () }) ?; 1322let rel = rel 1323. to_str () 1324. ok_or_else ( ||ResolutionError :: NpmPathNotUtf8 { path : path. to_owned () }) ? 1325. replace ( '\\' , "/" ); 1326super :: validate_npm_relpath ( & rel) ?; 1327if !node. files . contains_key ( & rel) { 1328return Err ( ResolutionError :: NpmFileNotInManifest { path : path. display (). to_string () }); 1329} 1330Ok (( node, rel)) 1331} 1332 1333fn resolve_in_instance ( 1334& self , 1335instance : & str , 1336export : & str , 1337mode : ResolutionMode , 1338) ->Result < ResolvedNpmModule , ResolutionError > { 1339let state =self . state . lock (). unwrap (); 1340let node =self . node_locked ( & state, instance) ?; 1341self . resolve_in_node_locked ( & node, export, mode) 1342} 1343 1344fn resolve_in_node_locked ( 1345& self , 1346node : & ResolvedNode , 1347export : & str , 1348mode : ResolutionMode , 1349) ->Result < ResolvedNpmModule , ResolutionError > { 1350let target =if let Some ( exports) =& node. manifest . exports_raw { 1351resolve_exports_target ( exports, export, self . conditions ( mode)) ?. ok_or_else ( ||{ 1352ResolutionError :: NpmPackageMissingExport { 1353name : node. manifest . name . clone (), 1354export : export. to_owned (), 1355} 1356}) ? 1357} else { 1358if mode. is_type () 1359 &&let Some ( mapped) =self . resolve_types_versions_locked ( node, export, mode) ? 1360{ 1361return Ok ( mapped); 1362} 1363match self . legacy_entry ( node, export, mode) { 1364Err ( ResolutionError :: NpmPackageNoEntryPoint { ..}) 1365if mode. is_type () && node. files . contains_key ( "index.d.ts" ) => 1366{ 1367"./index.d.ts" . into () 1368} 1369 result => result?, 1370} 1371}; 1372if mode. is_type () { 1373self . resolve_types_target_locked ( node, & target, export, mode) 1374} else { 1375self . resolve_target_locked ( node, & target, mode) 1376} 1377} 1378 1379fn resolve_types_versions_locked ( 1380& self , 1381node : & ResolvedNode , 1382export : & str , 1383mode : ResolutionMode , 1384) ->Result < Option < ResolvedNpmModule >, ResolutionError > { 1385let Some ( raw) = node. manifest . types_versions . as_ref () else { 1386return Ok ( None ); 1387}; 1388let Some ( ranges) = raw. as_object () else { 1389return Ok ( None ); 1390}; 1391let mut valid_ranges =Vec :: new (); 1392for ( range, mappings) in ranges{ 1393let Ok ( requirement) = deno_semver:: VersionReq :: parse_from_npm ( range) else { 1394continue ; 1395}; 1396if requirement. range (). is_none () { 1397continue ; 1398} 1399let Some ( mappings) = mappings. as_object () else { 1400continue ; 1401}; 1402if !mappings. iter (). all ( |( pattern, targets) |{ 1403valid_types_versions_pattern ( pattern) 1404 && targets. as_array (). is_some_and ( |targets|{ 1405 targets 1406. iter () 1407. all ( |target| target. as_str (). is_some_and ( valid_types_versions_target)) 1408}) 1409}) { 1410continue ; 1411} 1412 valid_ranges. push (( range. as_str (), mappings)); 1413} 1414if valid_ranges. is_empty () { 1415return Ok ( None ); 1416} 1417let version =self 1418. typescript_version 1419. lock () 1420. unwrap () 1421. clone () 1422. ok_or_else ( ||ResolutionError :: TypeScriptVersionRequired { 1423name : node. manifest . name . clone (), 1424}) ?; 1425let Some (( _, mappings)) = valid_ranges 1426. into_iter () 1427. find ( |( range, _) |typescript_range_matches ( range, & version)) 1428else { 1429return Ok ( None ); 1430}; 1431let module_name =if export =="." { 1432 node. manifest 1433. types 1434. as_ref () 1435. and_then ( serde_json:: Value :: as_str) 1436. or_else ( ||{ 1437 node. manifest 1438. typings 1439. as_ref () 1440. and_then ( serde_json:: Value :: as_str) 1441}) 1442. or ( node. manifest . module . as_deref ()) 1443. or ( node. manifest . main . as_deref ()) 1444. map ( |path| path. trim_start_matches ( "./" )) 1445. unwrap_or ( "index" ) 1446} else { 1447 export. strip_prefix ( "./" ). unwrap_or ( export) 1448}; 1449let Some (( pattern, matched)) =match_types_versions_pattern ( mappings, module_name) else { 1450return Ok ( None ); 1451}; 1452let Some ( targets) = mappings. get ( pattern). and_then ( serde_json:: Value :: as_array) else { 1453return Ok ( None ); 1454}; 1455for targetin targets. iter (). filter_map ( serde_json:: Value :: as_str) { 1456let target = target. replacen ( '*' , matched, 1 ); 1457let target = target. strip_prefix ( "./" ). unwrap_or ( & target); 1458let target =format! ( "./{target}" ); 1459match self . resolve_types_target_locked ( node, & target, export, mode) { 1460Ok ( module) =>return Ok ( Some ( module)), 1461Err ( ResolutionError :: NpmExportTargetNotInManifest { ..}) =>{} 1462Err ( error) =>return Err ( error), 1463} 1464} 1465Ok ( None ) 1466} 1467 1468fn resolve_types_target_locked ( 1469& self , 1470node : & ResolvedNode , 1471target : & str , 1472export : & str , 1473mode : ResolutionMode , 1474) ->Result < ResolvedNpmModule , ResolutionError > { 1475let rel = target 1476. strip_prefix ( "./" ) 1477. ok_or_else ( ||ResolutionError :: NpmExportTargetEscapes { target : target. into () }) ?; 1478super :: validate_npm_relpath ( rel) ?; 1479if rel. split ( '/' ). any ( |part| part =="node_modules" ) { 1480return Err ( ResolutionError :: NpmExportTargetEscapes { target : target. into () }); 1481} 1482if let Some ( found) =declaration_candidates ( rel) 1483. iter () 1484. find ( |candidate| node. files . contains_key ( * candidate)) 1485{ 1486return self . finish_file_locked ( node, found, mode); 1487} 1488if export =="." 1489 && node. manifest . exports_raw . is_none () 1490 && node. manifest . types . is_none () 1491 && node. manifest . typings . is_none () 1492 && node. files . contains_key ( "index.d.ts" ) 1493{ 1494return self . finish_file_locked ( node, "index.d.ts" , mode); 1495} 1496if node. files . contains_key ( rel) { 1497return self . finish_file_locked ( node, rel, mode); 1498} 1499Err ( ResolutionError :: NpmExportTargetNotInManifest { target : target. into () }) 1500} 1501 1502fn legacy_entry ( 1503& self , 1504node : & ResolvedNode , 1505export : & str , 1506mode : ResolutionMode , 1507) ->Result < String , ResolutionError > { 1508if mode. is_type () { 1509if export !="." { 1510return Ok ( format! ( "./{}" , export. strip_prefix ( "./" ). unwrap_or ( export))); 1511} 1512if let Some (( key, entry)) = node 1513. manifest 1514. types 1515. as_ref () 1516. map ( |entry|( "types" , entry)) 1517. or_else ( ||{ 1518 node. manifest 1519. typings 1520. as_ref () 1521. map ( |entry|( "typings" , entry)) 1522}) 1523{ 1524let entry = entry. as_str (). ok_or_else ( ||{ 1525super :: package:: PackageError :: NpmMustBePathString { key : key. into () } 1526}) ?; 1527if entry. starts_with ( '/' ) || entry. contains ( '\\' ) { 1528return Err ( ResolutionError :: NpmExportTargetEscapes { target : entry. into () }); 1529} 1530return Ok ( format! ( "./{}" , entry. strip_prefix ( "./" ). unwrap_or ( entry))); 1531} 1532} 1533if let Some ( entry) = node 1534. manifest 1535. browser 1536. as_ref () 1537. and_then ( |entry| entry. as_str ()) 1538 &&self . browser 1539 && export =="." 1540{ 1541match self . require_lookup_rel ( node, entry. trim_start_matches ( "./" ), 0 ) { 1542Ok ( found) =>return Ok ( format! ( "./{found}" )), 1543Err ( RequireLookup :: Err ( error)) =>return Err ( error), 1544Err ( RequireLookup :: Missing ) =>{} 1545} 1546} 1547if mode. is_require () { 1548return self . legacy_require_entry ( node, export); 1549} 1550if export =="." { 1551let entries =[ & node. manifest . module , & node. manifest . main ]; 1552for entryin entries. into_iter (). flatten () { 1553let rel = entry. strip_prefix ( "./" ). unwrap_or ( entry); 1554if node. files . contains_key ( rel) { 1555return Ok ( format! ( "./{rel}" )); 1556} 1557} 1558if node. files . contains_key ( "index.js" ) { 1559return Ok ( "./index.js" . into ()); 1560} 1561return Err ( ResolutionError :: NpmPackageNoEntryPoint { 1562name : node. manifest . name . clone (), 1563}); 1564} 1565let rel = export. strip_prefix ( "./" ). unwrap_or ( export); 1566if node. files . contains_key ( rel) { 1567return Ok ( export. to_owned ()); 1568} 1569Err ( ResolutionError :: NpmPackageMissingFile { 1570name : node. manifest . name . clone (), 1571export : export. to_owned (), 1572}) 1573} 1574 1575fn legacy_require_entry ( 1576& self , 1577node : & ResolvedNode , 1578export : & str , 1579) ->Result < String , ResolutionError > { 1580if export =="." { 1581match self . require_lookup_rel ( node, "" , 0 ) { 1582Ok ( found) =>return Ok ( format! ( "./{found}" )), 1583Err ( RequireLookup :: Missing ) =>{ 1584return Err ( ResolutionError :: NpmPackageNoEntryPoint { 1585name : node. manifest . name . clone (), 1586}); 1587} 1588Err ( RequireLookup :: Err ( error)) =>return Err ( error), 1589} 1590} 1591let rel = export. strip_prefix ( "./" ). unwrap_or ( export); 1592match self . require_lookup_rel ( node, rel, 0 ) { 1593Ok ( found) =>Ok ( format! ( "./{found}" )), 1594Err ( RequireLookup :: Missing ) =>Err ( ResolutionError :: NpmPackageMissingFile { 1595name : node. manifest . name . clone (), 1596export : export. to_owned (), 1597}), 1598Err ( RequireLookup :: Err ( error)) =>Err ( error), 1599} 1600} 1601 1602fn require_lookup_rel ( 1603& self , 1604node : & ResolvedNode , 1605rel : & str , 1606depth : usize , 1607) ->Result < String , RequireLookup > { 1608if depth >5 { 1609return Err ( RequireLookup :: Err ( ResolutionError :: NpmRequireLookupTooDeep )); 1610} 1611let rel = rel. trim_end_matches ( '/' ); 1612if rel. is_empty () { 1613if let Some ( main) =& node. manifest . main { 1614let main = main. strip_prefix ( "./" ). unwrap_or ( main); 1615if !matches! ( main, "" |"." |"/" ) { 1616match self . require_lookup_rel ( node, main, depth +1 ) { 1617Ok ( found) =>return Ok ( found), 1618Err ( RequireLookup :: Missing ) =>{} 1619Err ( other) =>return Err ( other), 1620} 1621} 1622} 1623for candidatein [ "index.js" , "index.json" , "index.cjs" ] { 1624if node. files . contains_key ( candidate) { 1625return Ok ( candidate. to_owned ()); 1626} 1627} 1628if node. files . contains_key ( "index.node" ) { 1629return Err ( RequireLookup :: Err ( 1630ResolutionError :: NativeAddonUnsupported { rel : "index.node" . to_owned () }, 1631)); 1632} 1633return Err ( RequireLookup :: Missing ); 1634} 1635super :: validate_npm_relpath ( rel). map_err ( |error|RequireLookup :: Err ( error. into ())) ?; 1636if rel. split ( '/' ). any ( |part| part =="node_modules" ) { 1637return Err ( RequireLookup :: Err ( ResolutionError :: NpmPathEscapesPackage { 1638rel : rel. to_owned (), 1639})); 1640} 1641if node. files . contains_key ( rel) { 1642return Ok ( rel. to_owned ()); 1643} 1644for candidatein [ 1645format! ( "{rel}.js" ), 1646format! ( "{rel}.json" ), 1647format! ( "{rel}.node" ), 1648] { 1649if node. files . contains_key ( & candidate) { 1650return Ok ( candidate); 1651} 1652} 1653let prefix =format! ( "{rel}/" ); 1654if !node. files . keys (). any ( |file| file. starts_with ( & prefix)) { 1655return Err ( RequireLookup :: Missing ); 1656} 1657let package_rel =format! ( "{rel}/package.json" ); 1658if node. files . contains_key ( & package_rel) { 1659let project_root =self . store . options (). project_root . clone (); 1660let url =super :: npm_virtual_module ( 1661& node. identity . registry , 1662& node. identity . name , 1663& node. identity . version , 1664& package_rel, 1665) 1666. map_err ( |error|RequireLookup :: Err ( error. into ())) ?; 1667super :: check_npm_url ( self . store . authority . as_ref (), & url) 1668. map_err ( |error|RequireLookup :: Err ( error. into ())) ?; 1669let scope = node 1670. scope ( & project_root, & package_rel) 1671. map_err ( RequireLookup :: Err ) ?; 1672if let Some ( main) =& scope. main { 1673let main = main. strip_prefix ( "./" ). unwrap_or ( main); 1674if !matches! ( main, "" |"." |"/" ) { 1675let candidate =format! ( "{rel}/{main}" ); 1676match self . require_lookup_rel ( node, & candidate, depth +1 ) { 1677Ok ( found) =>return Ok ( found), 1678Err ( RequireLookup :: Missing ) =>{} 1679Err ( other) =>return Err ( other), 1680} 1681} 1682} 1683} 1684for candidatein [ 1685format! ( "{rel}/index.js" ), 1686format! ( "{rel}/index.json" ), 1687format! ( "{rel}/index.cjs" ), 1688] { 1689if node. files . contains_key ( & candidate) { 1690return Ok ( candidate); 1691} 1692} 1693if node. files . contains_key ( & format! ( "{rel}/index.node" )) { 1694return Err ( RequireLookup :: Err ( 1695ResolutionError :: NativeAddonUnsupported { rel : rel. to_owned () }, 1696)); 1697} 1698Err ( RequireLookup :: Missing ) 1699} 1700 1701fn resolve_relative_locked ( 1702& self , 1703node : & ResolvedNode , 1704referrer : & Path , 1705specifier : & str , 1706mode : ResolutionMode , 1707) ->Result < ResolvedNpmModule , ResolutionError > { 1708if specifier. contains ( '\\' ) 1709 || specifier. contains ( '\0' ) 1710 || specifier. bytes (). any ( |byte| byte. is_ascii_control ()) 1711{ 1712return Err ( ResolutionError :: InvalidNpmRelativeImport { 1713specifier : specifier. to_owned (), 1714}); 1715} 1716let lower = specifier. to_ascii_lowercase (); 1717if lower. contains ( "%2f" ) || lower. contains ( "%5c" ) { 1718return Err ( ResolutionError :: InvalidNpmRelativeImport { 1719specifier : specifier. to_owned (), 1720}); 1721} 1722let base = url:: Url :: from_file_path ( referrer) 1723. map_err ( |_|ResolutionError :: NpmReferrerNotAbsolute ) ?; 1724let joined = 1725 base. join ( specifier) 1726. map_err ( |_|ResolutionError :: InvalidNpmRelativeImport { 1727specifier : specifier. to_owned (), 1728}) ?; 1729if joined. scheme () !="file" || !joined. host_str (). is_none_or ( |host| host. is_empty ()) { 1730return Err ( ResolutionError :: InvalidNpmRelativeImport { 1731specifier : specifier. to_owned (), 1732}); 1733} 1734let current = 1735 joined 1736. to_file_path () 1737. map_err ( |_|ResolutionError :: InvalidNpmRelativeImport { 1738specifier : specifier. to_owned (), 1739}) ?; 1740if mode. is_require () && current == node. dir { 1741if mode. is_type () { 1742return self . resolve_in_node_locked ( node, "." , mode); 1743} 1744return self . resolve_relative_require ( node, "" , specifier, 0 ); 1745} 1746if current == node. dir { 1747return Err ( ResolutionError :: NpmRelativeImportMustNameFile ); 1748} 1749let rel = current 1750. strip_prefix ( & node. dir ) 1751. map_err ( |_|ResolutionError :: NpmRelativeImportEscapes ) ? 1752. to_str () 1753. ok_or ( ResolutionError :: NpmRelativeImportNotUtf8 ) ? 1754. replace ( '\\' , "/" ); 1755match mode{ 1756ResolutionMode :: Import |ResolutionMode :: TypeImport |ResolutionMode :: TypeRequire =>{ 1757super :: validate_npm_relpath ( & rel) ?; 1758if mode. is_type () 1759 &&let Some ( found) =declaration_candidates ( & rel) 1760. iter () 1761. find ( |candidate| node. files . contains_key ( * candidate)) 1762{ 1763return self . finish_file_locked ( node, found, mode); 1764} 1765if !node. files . contains_key ( & rel) { 1766return Err ( ResolutionError :: NpmRelativeImportNotInManifest { 1767specifier : specifier. to_owned (), 1768}); 1769} 1770self . finish_file_locked ( node, & rel, mode) 1771} 1772ResolutionMode :: Require =>{ 1773let trimmed = rel. trim_end_matches ( '/' ); 1774self . resolve_relative_require ( node, trimmed, specifier, 0 ) 1775} 1776} 1777} 1778 1779/// Executable `require` of a package-relative path with Node-style 1780/// extension and directory inference. 1781fn resolve_relative_require ( 1782& self , 1783node : & ResolvedNode , 1784rel : & str , 1785specifier : & str , 1786depth : usize , 1787) ->Result < ResolvedNpmModule , ResolutionError > { 1788if depth >5 { 1789return Err ( ResolutionError :: NpmRequireLookupTooDeepSpecifier { 1790specifier : specifier. to_owned (), 1791}); 1792} 1793// Empty rel is the package root directory (`require('./')`). 1794let trimmed = rel. trim_end_matches ( '/' ); 1795match self . require_lookup_rel ( node, trimmed, depth) { 1796Ok ( found) =>self . finish_file_locked ( node, & found, ResolutionMode :: Require ), 1797Err ( RequireLookup :: Missing ) =>Err ( ResolutionError :: NpmRelativeImportNotInManifest { 1798specifier : specifier. to_owned (), 1799}), 1800Err ( RequireLookup :: Err ( error)) =>Err ( error), 1801} 1802} 1803 1804fn resolve_hash_locked ( 1805& self , 1806state : & ResolverState , 1807node : & ResolvedNode , 1808specifier : & str , 1809mode : ResolutionMode , 1810) ->Result < ResolvedNpmModule , ResolutionError > { 1811let imports = node. manifest . imports_raw . clone (). ok_or_else ( ||{ 1812ResolutionError :: NpmPackageMissingImport { 1813name : node. manifest . name . clone (), 1814specifier : specifier. to_owned (), 1815} 1816}) ?; 1817let declared: BTreeSet < String > = 1818 node. deps . keys (). chain ( node. peers . keys ()). cloned (). collect (); 1819let conditions =self . conditions ( mode); 1820match resolve_imports_target ( & imports, specifier, conditions, & declared) ?{ 1821None =>Err ( ResolutionError :: NpmPackageMissingImport { 1822name : node. manifest . name . clone (), 1823specifier : specifier. to_owned (), 1824}), 1825Some ( NpmImportsTarget :: File { path}) =>{ 1826super :: validate_npm_relpath ( & path) ?; 1827if mode. is_type () { 1828self . resolve_types_target_locked ( node, & format! ( "./{path}" ), specifier, mode) 1829} else if !node. files . contains_key ( & path) { 1830Err ( ResolutionError :: NpmImportTargetNotInManifest { 1831specifier : specifier. to_owned (), 1832}) 1833} else { 1834self . finish_file_locked ( node, & path, mode) 1835} 1836} 1837Some ( NpmImportsTarget :: External { request}) =>{ 1838let ( name, sub) =split_bare ( request. trim_start_matches ( "npm:" )); 1839let provider = node 1840. deps 1841. get ( name) 1842. or_else ( || node. peers . get ( name)) 1843. cloned () 1844. ok_or_else ( ||ResolutionError :: NpmImportsTargetUndeclared { 1845target : request. to_owned (), 1846}) ?; 1847let provider =self . node_locked ( state, & provider) ?; 1848let export =match sub{ 1849Some ( path) =>subpath_to_export ( path) ?, 1850None =>"." . into (), 1851}; 1852self . resolve_in_node_locked ( & provider, & export, mode) 1853} 1854} 1855} 1856 1857fn resolve_target_locked ( 1858& self , 1859node : & ResolvedNode , 1860target : & str , 1861mode : ResolutionMode , 1862) ->Result < ResolvedNpmModule , ResolutionError > { 1863let rel = target 1864. strip_prefix ( "./" ) 1865. ok_or_else ( ||ResolutionError :: NpmExportTargetEscapes { target : target. to_owned () }) ?; 1866super :: validate_npm_relpath ( rel) ?; 1867if rel. split ( '/' ). any ( |part| part =="node_modules" ) { 1868return Err ( ResolutionError :: NpmExportTargetEscapes { target : target. to_owned () }); 1869} 1870if !node. files . contains_key ( rel) { 1871return Err ( ResolutionError :: NpmExportTargetNotInManifest { 1872target : target. to_owned (), 1873}); 1874} 1875// Export targets stay exact in both modes: no extension inference. 1876self . finish_file_locked ( node, rel, mode) 1877} 1878 1879fn browser_map < ' a >( 1880& self , 1881node : &' a ResolvedNode , 1882) ->Option < &' a serde_json:: Map < String , serde_json:: Value >> { 1883 node. manifest 1884. browser 1885. as_ref () ? 1886. as_object () 1887. filter ( |_|self . browser ) 1888} 1889 1890fn finish_file_locked ( 1891& self , 1892node : & ResolvedNode , 1893rel : & str , 1894mode : ResolutionMode , 1895) ->Result < ResolvedNpmModule , ResolutionError > { 1896// Exact `browser` file keys win over extensionless ones. 1897let base =[ ".js" , ".json" , ".mjs" , ".cjs" ] 1898. iter () 1899. find_map ( |extension| rel. strip_suffix ( extension)); 1900let remap =self . browser_map ( node). and_then ( |map|{ 1901[ Some ( rel), base] 1902. into_iter () 1903. flatten () 1904. find_map ( |key| map. get ( & format! ( "./{key}" )). or_else ( || map. get ( key))) 1905}); 1906let rel =match remap{ 1907Some ( serde_json:: Value :: Bool ( false )) =>return Ok ( excluded ( node, rel)), 1908Some ( serde_json:: Value :: String ( target)) =>{ 1909let target = target. trim_start_matches ( "./" ); 1910super :: validate_npm_relpath ( target) ?; 1911if !node. files . contains_key ( target) { 1912return Err ( ResolutionError :: NpmBrowserRemapTargetNotInManifest { 1913target : target. to_owned (), 1914}); 1915} 1916 target 1917} 1918 _ => rel, 1919}; 1920let url =npm_virtual_module ( 1921& node. identity . registry , 1922& node. identity . name , 1923& node. identity . version , 1924 rel, 1925) ?; 1926super :: check_npm_url ( self . store . authority . as_ref (), & url) ?; 1927let kind = node. classify ( & self . store . options (). project_root , rel) ?; 1928if !mode. is_type () && kind ==NpmModuleKind :: Declaration { 1929return Err ( ResolutionError :: NpmDeclarationNoCode { rel : rel. to_owned () }); 1930} 1931// ESM `import` of JSON stays unsupported; `require` allows JSON and 1932// CJS, while ESM `import` of CJS resolves for synthetic wrappers. 1933if !mode. is_require () && kind ==NpmModuleKind :: Json && !self . browser { 1934return Err ( ResolutionError :: JsonModuleUnsupported { rel : rel. to_owned () }); 1935} 1936Ok ( ResolvedNpmModule { 1937path : node. dir . join ( rel), 1938format : kind, 1939instance : node. instance . clone (), 1940}) 1941} 1942 1943async fn build_roots ( 1944& self , 1945walk : & mut Walk < ' _ >, 1946handle : & mut PreparedNpmRoots , 1947normalized : & [( String , BTreeMap < String , String >)], 1948preselected : & BTreeMap < String , String >, 1949) ->Result <(), ResolutionError > { 1950for ( request_key, context) in normalized{ 1951let effective =effective_context ( preselected, context); 1952let key =root_lock_key ( request_key, & effective. merged ()); 1953if let Some ( instance) =self . state . lock (). unwrap (). roots . get ( & key) { 1954 handle. roots . insert ( key, instance. clone ()); 1955continue ; 1956} 1957let idx =self 1958. build_occurrence ( walk, request_key, & effective, false ) 1959. await ? 1960. ok_or_else ( ||ResolutionError :: NpmPackageNotSupportedHere { 1961request_key : request_key. to_owned (), 1962}) ?; 1963 walk. build . roots . insert ( key, idx); 1964} 1965Ok (()) 1966} 1967 1968/// Build one occurrence: memoize, acquire (cached per prepare), 1969/// then descend. Ancestor occurrences with a satisfying version are 1970/// reused so shadowing binds the exact provider instance instead of 1971/// re-instantiating it. Returns `None` for skippable (optional + 1972/// platform-mismatched) packages only. 1973async fn build_occurrence ( 1974& self , 1975walk : & mut Walk < ' _ >, 1976request_key : & str , 1977ctx : & ScopeCtx , 1978skippable : bool , 1979) ->Result < Option < usize >, ResolutionError > { 1980let key =( request_key. to_owned (), scopeprint ( ctx)); 1981if let Some ( idx) = walk. build . index . get ( & key) { 1982return Ok ( Some ( * idx)); 1983} 1984if walk. stack . len () >=MAX_NPM_GRAPH_DEPTH { 1985return Err ( ResolutionError :: NpmDependencyGraphTooDeep ); 1986} 1987if walk. build . occs . len () >=MAX_NPM_GRAPH_NODES { 1988return Err ( ResolutionError :: NpmDependencyGraphTooLarge ); 1989} 1990let artifact =self . acquire_cached ( walk. acq , request_key). await ?; 1991let manifest =parse_npm_manifest ( 1992& artifact. identity . name , 1993& artifact. identity . version , 1994& artifact. package , 1995) ?; 1996if !platform_satisfied ( manifest. os . as_ref (), super :: package:: npm_host_os ()) 1997 || !platform_satisfied ( manifest. cpu . as_ref (), super :: package:: npm_host_arch ()) 1998{ 1999if skippable{ 2000return Ok ( None ); 2001} 2002return Err ( ResolutionError :: NpmPackagePlatformUnsupported { 2003name : manifest. name . clone (), 2004version : manifest. version . clone (), 2005}); 2006} 2007let idx = walk. build . occs . len (); 2008 walk. build . occs . push ( Occurrence { 2009identity : artifact. identity . clone (), 2010 artifact, 2011manifest : manifest. clone (), 2012dep_edges : BTreeMap :: new (), 2013peer_edges : BTreeMap :: new (), 2014}); 2015 walk. build . index . insert ( key, idx); 2016 walk. stack . push ( idx); 2017let mut bindings: BTreeMap < String , ( String , String )> =BTreeMap :: new (); 2018let mut missing_peers: Vec <( String , String )> =Vec :: new (); 2019for ( peer, spec) in manifest. peers . clone () { 2020if let Some ( provider) = ctx. strict . get ( & peer) { 2021if !peer_version_satisfies ( & spec. range , provider) ?{ 2022if spec. optional { 2023continue ; 2024} 2025return Err ( ResolutionError :: NpmPeerWantsMismatch { 2026peer : peer. to_owned (), 2027wanted : spec. range . clone (), 2028provided : peer_identity_version ( provider) ?, 2029}); 2030} 2031 bindings. insert ( 2032 peer. clone (), 2033( provider. clone (), peer_exact_request ( provider) ?), 2034); 2035continue ; 2036} 2037if let Some ( provider) = ctx. flexible . get ( & peer) 2038 &&peer_version_satisfies ( & spec. range , provider) ? 2039{ 2040 bindings. insert ( 2041 peer. clone (), 2042( provider. clone (), peer_exact_request ( provider) ?), 2043); 2044continue ; 2045} 2046if spec. optional { 2047continue ; 2048} 2049let request =format! ( "npm:{}@{}" , peer, spec. range ); 2050super :: parse_npm_request ( & request) ?; 2051 missing_peers. push (( peer. clone (), request)); 2052} 2053let missing: Vec < String > = missing_peers 2054. iter () 2055. map ( |( _, request) | request. clone ()) 2056. collect (); 2057self . acquire_many ( walk. acq , & missing). await ?; 2058for ( peer, request) in & missing_peers{ 2059let provider = walk 2060. acq 2061. get ( request) 2062. ok_or_else ( ||ResolutionError :: NpmPackageNotPreparedRequest { 2063request : request. to_owned (), 2064}) ? 2065. identity 2066. key (); 2067 bindings. insert ( peer. clone (), ( provider, request. clone ())); 2068} 2069let mut dep_requests: BTreeMap < String , ( String , String )> =BTreeMap :: new (); 2070let mut missing_deps: Vec <( String , String )> =Vec :: new (); 2071for ( alias, dep) in manifest. dependencies . clone () { 2072let target =match & dep. kind { 2073super :: package:: NpmDepKind :: Registry { request} => request. clone (), 2074super :: package:: NpmDepKind :: Alias { target} => target. clone (), 2075super :: package:: NpmDepKind :: Unsupported { protocol} =>{ 2076return Err ( ResolutionError :: NpmDependencyUnsupportedProtocol { 2077alias : alias. to_owned (), 2078protocol : protocol. to_owned (), 2079}); 2080} 2081}; 2082super :: parse_npm_request ( & target) ?; 2083 missing_deps. push (( alias, target)); 2084} 2085let missing: Vec < String > = missing_deps 2086. iter () 2087. map ( |( _, target) | target. clone ()) 2088. collect (); 2089self . acquire_many ( walk. acq , & missing). await ?; 2090for ( alias, target) in & missing_deps{ 2091let provider = walk 2092. acq 2093. get ( target) 2094. ok_or_else ( ||ResolutionError :: NpmPackageNotPreparedTarget { 2095target : target. to_owned (), 2096}) ? 2097. identity 2098. key (); 2099 dep_requests. insert ( alias. clone (), ( provider, target. clone ())); 2100} 2101let mut child =ScopeCtx { 2102strict : ctx. strict . clone (), 2103flexible : ctx. flexible . clone (), 2104}; 2105for ( provider, _) in dep_requests. values () { 2106let ( _, name, _) =super :: parse_npm_key ( provider) ?; 2107 child. flexible . insert ( name, provider. clone ()); 2108} 2109for ( peer, ( provider, _)) in & bindings{ 2110 child. flexible . insert ( peer. clone (), provider. clone ()); 2111} 2112// Peers resolve from the importer's context, not its private 2113// dependencies: otherwise a sibling dep (x2) would rebind an 2114// already-selected provider's peer (x1) into a fresh instance 2115// instead of sharing the established one. 2116let mut peer_child =ScopeCtx { 2117strict : ctx. strict . clone (), 2118flexible : ctx. flexible . clone (), 2119}; 2120for ( peer, ( provider, _)) in & bindings{ 2121 peer_child. flexible . insert ( peer. clone (), provider. clone ()); 2122} 2123let mut dep_edges: BTreeMap < String , usize > =BTreeMap :: new (); 2124for ( alias, ( provider, request)) in & dep_requests{ 2125let optional = manifest 2126. dependencies 2127. get ( alias) 2128. is_some_and ( |dep| dep. optional ); 2129let ( _, name, _) =super :: parse_npm_key ( provider) ?; 2130match Box :: pin ( self . child_occurrence ( walk, & name, request, & child, optional)). await ?{ 2131Some ( target) =>{ 2132 dep_edges. insert ( alias. clone (), target); 2133} 2134None if optional =>{} 2135None =>{ 2136return Err ( ResolutionError :: NpmDependencyUnresolvable { 2137alias : alias. to_owned (), 2138}); 2139} 2140} 2141} 2142let mut peer_edges: BTreeMap < String , usize > =BTreeMap :: new (); 2143for ( peer, ( _, request)) in & bindings{ 2144let optional = manifest. peers . get ( peer). is_some_and ( |entry| entry. optional ); 2145match Box :: pin ( self . child_occurrence ( walk, peer, request, & peer_child, optional)) 2146. await ? 2147{ 2148Some ( target) =>{ 2149 peer_edges. insert ( peer. clone (), target); 2150} 2151None if optional =>{} 2152None =>{ 2153return Err ( ResolutionError :: NpmPeerUnresolvable { peer : peer. to_owned () }); 2154} 2155} 2156} 2157 walk. build . occs [ idx]. dep_edges = dep_edges; 2158 walk. build . occs [ idx]. peer_edges = peer_edges; 2159 walk. stack . pop (); 2160Ok ( Some ( idx)) 2161} 2162 2163/// Concurrent downloads across package names; same-name requests stay 2164/// sequential so later ranges reuse earlier pins, and the first input 2165/// failure wins once started groups finish. 2166async fn acquire_many ( 2167& self , 2168acq : & mut BTreeMap < String , NpmArtifact >, 2169keys : & [ String ], 2170) ->Result <(), ResolutionError > { 2171let mut ordered: Vec < String > =Vec :: new (); 2172let mut seen: BTreeSet < String > =BTreeSet :: new (); 2173for keyin keys{ 2174if acq. contains_key ( key) || !seen. insert ( key. clone ()) { 2175continue ; 2176} 2177 ordered. push ( key. clone ()); 2178} 2179let mut names: Vec < String > =Vec :: new (); 2180let mut groups: BTreeMap < String , Vec <( usize , String )>> =BTreeMap :: new (); 2181for ( index, key) in ordered. iter (). enumerate () { 2182let name =super :: parse_npm_request ( key) ?. name ; 2183if !groups. contains_key ( & name) { 2184 names. push ( name. clone ()); 2185} 2186 groups. entry ( name). or_default (). push (( index, key. clone ())); 2187} 2188let outcomes = stream:: iter ( names. into_iter (). map ( |name|{ 2189let entries = groups. remove ( & name). expect ( "grouped package name present" ); 2190let store =self . store . clone (); 2191async move { 2192let mut done: Vec <( String , NpmArtifact )> =Vec :: new (); 2193for ( index, key) in entries{ 2194match store. npm_acquire_for_resolution ( & key). await { 2195Ok ( artifact) => done. push (( key, artifact)), 2196Err ( error) =>return ( done, Some (( index, error))), 2197} 2198} 2199( done, None ) 2200} 2201})) 2202. buffer_unordered ( MAX_NPM_CONCURRENT_DOWNLOADS ) 2203. collect ::< Vec < _ >>() 2204. await ; 2205let mut failure: Option <( usize , ResolutionError )> =None ; 2206for ( done, failed) in outcomes{ 2207for ( key, artifact) in done{ 2208 acq. insert ( key, artifact); 2209} 2210if let Some (( index, error)) = failed 2211 && failure. as_ref (). is_none_or ( |current| index < current. 0 ) 2212{ 2213 failure =Some (( index, error. into ())); 2214} 2215} 2216if let Some (( _, error)) = failure{ 2217return Err ( error); 2218} 2219Ok (()) 2220} 2221 2222/// One acquisition per request per prepare; hits skip re-verification. 2223/// Artifacts stay local to the build: nothing here bypasses the 2224/// store's verification on first fetch. 2225async fn acquire_cached ( 2226& self , 2227acq : & mut BTreeMap < String , NpmArtifact >, 2228request_key : & str , 2229) ->Result < NpmArtifact , ResolutionError > { 2230if let Some ( artifact) = acq. get ( request_key) { 2231return Ok ( artifact. clone ()); 2232} 2233let artifact =self . store . npm_acquire_for_resolution ( request_key). await ?; 2234 acq. insert ( request_key. to_owned (), artifact. clone ()); 2235Ok ( artifact) 2236} 2237 2238/// Reuse matching ancestor occurrences to close package cycles. 2239async fn child_occurrence ( 2240& self , 2241walk : & mut Walk < ' _ >, 2242name : & str , 2243request : & str , 2244ctx : & ScopeCtx , 2245skippable : bool , 2246) ->Result < Option < usize >, ResolutionError > { 2247let parsed =super :: parse_npm_request ( request) ?; 2248for ancestorin walk. stack . iter (). rev () { 2249let occ =& walk. build . occs [ * ancestor]; 2250if occ. identity . name != name{ 2251continue ; 2252} 2253if request_matches_version ( & parsed, & occ. identity . version ) ?{ 2254return Ok ( Some ( * ancestor)); 2255} 2256} 2257self . build_occurrence ( walk, request, ctx, skippable). await 2258} 2259 2260fn canonicalize ( build : & OccGraph ) ->BTreeMap < usize , String > { 2261let identities: BTreeSet < _ > = build. occs . iter (). map ( |occ| occ. identity . key ()). collect (); 2262let initial: BTreeMap < _ , _ > = identities 2263. into_iter () 2264. enumerate () 2265. map ( |( i, key) |( key, i)) 2266. collect (); 2267let mut labels: Vec < usize > = build 2268. occs 2269. iter () 2270. map ( |occ| initial[ & occ. identity . key ()]) 2271. collect (); 2272loop { 2273let signatures: Vec < _ > = build 2274. occs 2275. iter () 2276. enumerate () 2277. map ( |( i, occ) |{ 2278let edges = |edges : & BTreeMap < String , usize > |{ 2279 edges 2280. iter () 2281. map ( |( name, target) |( name. clone (), labels[ * target])) 2282. collect ::< Vec < _ >>() 2283}; 2284( labels[ i], edges ( & occ. dep_edges ), edges ( & occ. peer_edges )) 2285}) 2286. collect (); 2287let classes: BTreeMap < _ , _ > = signatures 2288. iter () 2289. cloned () 2290. collect ::< BTreeSet < _ >>() 2291. into_iter () 2292. enumerate () 2293. map ( |( i, signature) |( signature, i)) 2294. collect (); 2295let next: Vec < _ > = signatures 2296. iter () 2297. map ( |signature| classes[ signature]) 2298. collect (); 2299let stable = classes. len () == labels. iter (). copied (). collect ::< BTreeSet < _ >>(). len (); 2300 labels = next; 2301if stable{ 2302break ; 2303} 2304} 2305let representatives: BTreeMap < _ , _ > = labels 2306. iter () 2307. copied () 2308. enumerate () 2309. map ( |( i, label) |( label, i)) 2310. collect (); 2311let ids: BTreeMap < _ , _ > = representatives 2312. iter () 2313. map ( |( label, representative) |{ 2314let root =& build. occs [ * representative]; 2315if root. dep_edges . is_empty () && root. peer_edges . is_empty () { 2316return ( * label, root. identity . key ()); 2317} 2318// Number only the reachable quotient graph, so unrelated roots and 2319// traversal order cannot change an instance's identity, even in cycles. 2320let mut numbers =BTreeMap :: from ([( * label, 0usize )]); 2321let mut pending =vec! [ * label]; 2322let mut graph =Vec :: new (); 2323let mut cursor =0 ; 2324while cursor < pending. len () { 2325let occ =& build. occs [ representatives[ & pending[ cursor]]]; 2326let mut edges = |edges : & BTreeMap < String , usize > |{ 2327 edges 2328. iter () 2329. map ( |( name, target) |{ 2330let target = labels[ * target]; 2331let next = numbers. len (); 2332let number =* numbers. entry ( target). or_insert_with ( ||{ 2333 pending. push ( target); 2334 next 2335}); 2336( name. clone (), number) 2337}) 2338. collect ::< BTreeMap < _ , _ >>() 2339}; 2340let deps =edges ( & occ. dep_edges ); 2341let peers =edges ( & occ. peer_edges ); 2342 graph. push (( occ. identity . key (), deps, peers)); 2343 cursor +=1 ; 2344} 2345let encoded = serde_json:: to_string ( & graph). unwrap (); 2346( 2347* label, 2348format! ( "p-{}" , & super :: artifact_key ( & encoded)[ ..62 ]), 2349) 2350}) 2351. collect (); 2352 labels 2353. into_iter () 2354. enumerate () 2355. map ( |( i, label) |( i, ids[ & label]. clone ())) 2356. collect () 2357} 2358 2359async fn finalize_build ( & self , build : OccGraph ) ->Result < FinalizedGraph , ResolutionError > { 2360let ids =Self :: canonicalize ( & build); 2361let mut order: BTreeMap < String , usize > =BTreeMap :: new (); 2362for ( idx, _) in build. occs . iter (). enumerate () { 2363 order. entry ( ids[ & idx]. clone ()). or_insert ( idx); 2364} 2365let mut nodes =BTreeMap :: new (); 2366for ( id, rep) in & order{ 2367let occ =& build. occs [ * rep]; 2368let deps = occ 2369. dep_edges 2370. iter () 2371. map ( |( alias, target) |( alias. clone (), ids[ target]. clone ())) 2372. collect (); 2373let peers = occ 2374. peer_edges 2375. iter () 2376. map ( |( peer, target) |( peer. clone (), ids[ target]. clone ())) 2377. collect (); 2378let mut artifact = occ. artifact . clone (); 2379let package_key = artifact. identity . key (); 2380let disk_instance = storage:: npm_disk_instance ( id, & package_key); 2381 artifact. dir = 2382 storage:: ensure_npm_instance ( self . store . options (), & artifact, disk_instance) ?; 2383let node =Arc :: new ( ResolvedNode { 2384instance : id. clone (), 2385identity : artifact. identity , 2386dir : artifact. dir , 2387files : artifact. files , 2388manifest : occ. manifest . clone (), 2389scopes : Mutex :: new ( BTreeMap :: new ()), 2390 deps, 2391 peers, 2392}); 2393 nodes. insert ( 2394 id. clone (), 2395Arc :: new ( PreparedInstance { 2396pin : NpmInstancePin { 2397package : package_key, 2398deps : node. deps . clone (), 2399peers : node. peers . clone (), 2400}, 2401dir : node. dir . clone (), 2402loaded : Mutex :: new ( Some ( node)), 2403}), 2404); 2405} 2406let roots = build 2407. roots 2408. iter () 2409. map ( |( key, idx) |( key. clone (), ids[ idx]. clone ())) 2410. collect (); 2411Ok ( FinalizedGraph { roots, nodes}) 2412} 2413 2414pub fn reserved_npm_path ( project_root : & Path , path : & Path ) ->bool { 2415 path. strip_prefix ( project_root. join ( ".avril" ). join ( "npm" )) 2416. is_ok () 2417} 2418 2419/// Staged bindings must still match the lock at publication. New resolutions 2420/// cannot overwrite established instances; frozen never fills missing entries. 2421fn record_graph_locked ( & self , finalized : & FinalizedGraph ) ->Result <(), ResolutionError > { 2422let frozen =self . store . options (). frozen ; 2423let pins: BTreeMap < & String , NpmInstancePin > = finalized 2424. nodes 2425. iter () 2426. map ( |( id, node) |( id, node. pin . clone ())) 2427. collect (); 2428let mut lock =self . store . lock . lock (). unwrap (); 2429for ( request, instance) in & finalized. roots { 2430match lock. npm . roots . get ( request) { 2431Some ( existing) if existing == instance =>{} 2432Some ( _) =>{ 2433return Err ( ResolutionError :: LockfileChanged { key : request. to_owned () }); 2434} 2435None if frozen =>{ 2436return Err ( ResolutionError :: FrozenLockfileMissing { 2437name : request. to_owned (), 2438}); 2439} 2440None =>{} 2441} 2442} 2443for ( id, pin) in & pins{ 2444match lock. npm . instances . get ( * id) { 2445Some ( existing) if existing == pin =>{} 2446Some ( _) =>{ 2447return Err ( ResolutionError :: LockedNpmInstanceChanged { id : id. to_string () }); 2448} 2449None if frozen =>{ 2450return Err ( ResolutionError :: NpmFrozenLockfileMissingInstance { 2451id : id. to_string (), 2452}); 2453} 2454None =>{} 2455} 2456} 2457let mut used =self . store . used . lock (). unwrap (); 2458let packages: BTreeSet < _ > = pins. values (). map ( |pin| pin. package . clone ()). collect (); 2459for ( request, version) in & lock. npm . specifiers { 2460let name =parse_npm_request ( request) ?. name ; 2461let package =format! ( "{}|{name}|{version}" , self . store . options (). npm_registry ); 2462if packages. contains ( & package) 2463 || finalized 2464. roots 2465. keys () 2466. any ( |key|root_request_part ( key) == request) 2467{ 2468 used. npm_specifiers . insert ( request. clone ()); 2469} 2470} 2471 used. npm_packages . extend ( packages); 2472for ( request, instance) in & finalized. roots { 2473 used. npm_roots . insert ( request. clone ()); 2474if lock. npm . roots . get ( request) !=Some ( instance) { 2475 used. pending_npm_roots . insert ( request. clone ()); 2476} 2477 lock. npm . roots . insert ( request. clone (), instance. clone ()); 2478} 2479for ( id, pin) in & pins{ 2480 used. npm_instances . insert (( * id). clone ()); 2481if lock. npm . instances . get ( * id) !=Some ( pin) { 2482 used. pending_npm_instances . insert (( * id). clone ()); 2483} 2484 lock. npm . instances . insert (( * id). clone (), pin. clone ()); 2485} 2486Ok (()) 2487} 2488} 2489 2490fn excluded ( node : & ResolvedNode , key : & str ) ->ResolvedNpmModule { 2491ResolvedNpmModule { 2492path : node. dir . join ( format! ( "{key}{BROWSER_EXCLUDED}" )), 2493format : NpmModuleKind :: CommonJs , 2494instance : node. instance . clone (), 2495} 2496} 2497 2498fn peer_identity_version ( identity : & str ) ->Result < String , ResolutionError > { 2499let parts: Vec < & str > = identity. split ( '|' ). collect (); 2500if parts. len () !=3 { 2501return Err ( ResolutionError :: InvalidNpmLockIdentity ); 2502} 2503Ok ( parts[ 2 ]. to_owned ()) 2504} 2505 2506fn peer_version_satisfies ( range : & str , identity : & str ) ->Result < bool , ResolutionError > { 2507request_matches_text ( range, & peer_identity_version ( identity) ?) 2508} 2509 2510fn request_matches_version ( 2511request : & super :: NpmRequest , 2512version : & str , 2513) ->Result < bool , ResolutionError > { 2514if request. version_req . tag (). is_some () || request. version_req . version_text () =="*" { 2515return Ok ( true ); 2516} 2517let parsed =super :: validate_npm_version ( version) ?; 2518Ok ( request. version_req . matches ( & parsed)) 2519} 2520 2521fn request_matches_text ( range : & str , version : & str ) ->Result < bool , ResolutionError > { 2522if range. is_empty () || range =="*" { 2523return Ok ( true ); 2524} 2525let requirement = deno_semver:: VersionReq :: parse_from_npm ( range). map_err ( |detail|{ 2526ResolutionError :: InvalidNpmVersionRange { 2527range : range. to_owned (), 2528detail : detail. to_string (), 2529} 2530}) ?; 2531if requirement. tag (). is_some () { 2532return Ok ( true ); 2533} 2534let parsed =super :: validate_npm_version ( version) ?; 2535Ok ( requirement. matches ( & parsed)) 2536} 2537 2538fn peer_exact_request ( identity : & str ) ->Result < String , ResolutionError > { 2539let parts: Vec < & str > = identity. split ( '|' ). collect (); 2540if parts. len () !=3 { 2541return Err ( ResolutionError :: InvalidNpmLockIdentity ); 2542} 2543Ok ( format! ( "npm:{}@{}" , parts[ 1 ], parts[ 2 ])) 2544} 2545 2546fn ctxprint ( ctx : & BTreeMap < String , String >) ->String { 2547 ctx. iter () 2548. map ( |( name, identity) |format! ( "{name}={identity}" )) 2549. collect ::< Vec < _ >>() 2550. join ( "," ) 2551} 2552 2553/// Traversal environments. `strict` holds caller-preselected choices whose 2554/// violation is an explicit error; `flexible` holds inherited selections 2555/// that a contradictory local range silently shadows with a nested copy. 2556# [ derive ( Clone , Debug , Default )] 2557struct ScopeCtx { 2558strict : BTreeMap < String , String >, 2559flexible : BTreeMap < String , String >, 2560} 2561 2562fn scopeprint ( ctx : & ScopeCtx ) ->String { 2563format! ( "{}|{}" , ctxprint ( & ctx. strict ), ctxprint ( & ctx. flexible )) 2564} 2565 2566fn root_lock_key ( request_key : & str , context : & BTreeMap < String , String >) ->String { 2567if context. is_empty () { 2568return request_key. to_owned (); 2569} 2570format! ( "{request_key}\0ctx:{}" , ctxprint ( context)) 2571} 2572 2573fn root_request_part ( key : & str ) ->& str { 2574 key. split ( '\0' ). next (). unwrap_or ( key) 2575} 2576 2577fn effective_context ( 2578preselected : & BTreeMap < String , String >, 2579caller : & BTreeMap < String , String >, 2580) ->ScopeCtx { 2581ScopeCtx { 2582strict : caller. clone (), 2583flexible : preselected. clone (), 2584} 2585} 2586 2587impl ScopeCtx { 2588fn merged ( & self ) ->BTreeMap < String , String > { 2589let mut merged =self . flexible . clone (); 2590for ( peer, provider) in & self . strict { 2591 merged. insert ( peer. clone (), provider. clone ()); 2592} 2593 merged 2594} 2595} 2596/// Whether an export subpath falls beneath a warm scope: the scope 2597/// itself, anything beneath it, or everything for a whole-package scope. 2598fn under_export_scope ( sub : & str , scope : & str ) ->bool { 2599 scope. is_empty () || sub == scope || sub. starts_with ( & format! ( "{scope}/" )) 2600} 2601 2602/// Every wildcard target pattern a pattern value can select, recursing 2603/// through fallback arrays and condition objects. Literal trailers cannot 2604/// capture and contribute nothing to inversion. 2605fn pattern_targets ( value : & serde_json:: Value , out : & mut Vec < String >) { 2606match value{ 2607 serde_json:: Value :: String ( target) =>{ 2608if target. contains ( '*' ) { 2609 out. push ( target. clone ()); 2610} 2611} 2612 serde_json:: Value :: Array ( targets) =>{ 2613for targetin targets{ 2614pattern_targets ( target, out); 2615} 2616} 2617 serde_json:: Value :: Object ( map) =>{ 2618for targetin map. values () { 2619pattern_targets ( target, out); 2620} 2621} 2622 _ =>{} 2623} 2624} 2625 2626fn split_npm_request ( specifier : & str ) ->Result <( String , Option < String >), ResolutionError > { 2627let request =super :: parse_npm_request ( specifier) ?; 2628Ok (( request. request_key (), request. subpath )) 2629} 2630 2631fn typescript_range_matches ( text : & str , version : & deno_semver:: Version ) ->bool { 2632use deno_semver::{ RangeBound , Version , VersionBoundKind , VersionReq }; 2633 2634let matches = |term : & str |{ 2635let Ok ( requirement) =VersionReq :: parse_from_npm ( term) else { 2636return false ; 2637}; 2638let shorthand = term. starts_with ([ '~' , '^' ]) || term. contains ( " - " ); 2639let operand = term. trim_start_matches ([ '>' , '<' , '=' ]); 2640let partial = !shorthand &&Version :: parse_from_npm ( operand). is_err (); 2641if partial && operand. starts_with ([ 'v' , 'V' ]) { 2642return false ; 2643} 2644 requirement. range (). is_some_and ( |ranges|{ 2645 ranges. 0 . iter (). any ( |range|{ 2646let mut range = range. clone (); 2647for ( bound, lower) in [( & mut range. start , true ), ( & mut range. end , false )] { 2648if let RangeBound :: Version ( bound) = bound{ 2649if partial{ 2650 bound. version . pre =[ "0" . into ()]. into (); 2651} else if shorthand && !lower && bound. kind ==VersionBoundKind :: Exclusive { 2652// TS shorthand upper bounds include the next release's prereleases. 2653 bound. version . pre =Default :: default (); 2654} 2655} 2656} 2657// Compiler ranges include prereleases; npm package selection deliberately does not. 2658 range. intersects_version ( version) 2659}) 2660}) 2661}; 2662 text. split ( "||" ). any ( |alternative|{ 2663if alternative. contains ( " - " ) { 2664return matches ( alternative. trim ()); 2665} 2666let mut terms = alternative. split_whitespace (); 2667while let Some ( term) = terms. next () { 2668let matched =if matches! ( term, ">" |">=" |"<" |"<=" |"=" |"~" |"^" ) { 2669 terms 2670. next () 2671. is_some_and ( |operand|matches ( & format! ( "{term}{operand}" ))) 2672} else { 2673matches ( term) 2674}; 2675if !matched{ 2676return false ; 2677} 2678} 2679true 2680}) 2681} 2682 2683fn valid_types_versions_pattern ( pattern : & str ) ->bool { 2684 pattern. matches ( '*' ). count () <=1 &&valid_types_versions_target ( & pattern. replace ( '*' , "x" )) 2685} 2686 2687fn valid_types_versions_target ( target : & str ) ->bool { 2688let path = target. strip_prefix ( "./" ). unwrap_or ( target); 2689super :: validate_npm_relpath ( path). is_ok () 2690 && !path 2691. split ( '/' ) 2692. any ( |part| part. eq_ignore_ascii_case ( "node_modules" )) 2693} 2694 2695fn match_types_versions_pattern < ' m , ' c >( 2696mappings : &' m serde_json:: Map < String , serde_json:: Value >, 2697candidate : &' c str , 2698) ->Option <( &' m str , &' c str )> { 2699if let Some (( pattern, _)) = mappings. get_key_value ( candidate) { 2700return Some (( pattern, "" )); 2701} 2702// Equal-prefix patterns retain manifest order, unlike export patterns. 2703 mappings 2704. keys () 2705. enumerate () 2706. filter_map ( |( order, pattern) |{ 2707let ( prefix, suffix) = pattern. split_once ( '*' ) ?; 2708let matched = candidate. strip_prefix ( prefix) ?. strip_suffix ( suffix) ?; 2709Some (( pattern. as_str (), matched, prefix. len (), order)) 2710}) 2711. max_by_key ( |( _, _, prefix, order) |( * prefix, std:: cmp:: Reverse ( * order))) 2712. map ( |( pattern, matched, _, _) |( pattern, matched)) 2713} 2714 2715fn declaration_candidates ( rel : & str ) ->Vec < String > { 2716if rel. ends_with ( ".d.ts" ) || rel. ends_with ( ".d.mts" ) || rel. ends_with ( ".d.cts" ) { 2717return vec! [ rel. to_owned ()]; 2718} 2719let substitutions: & [( & str , & [ & str ])] =& [ 2720( ".js" , & [ ".ts" , ".tsx" , ".d.ts" ]), 2721( ".mjs" , & [ ".mts" , ".d.mts" ]), 2722( ".cjs" , & [ ".cts" , ".d.cts" ]), 2723( ".tsx" , & [ ".tsx" , ".ts" , ".d.ts" ]), 2724( ".ts" , & [ ".ts" , ".tsx" , ".d.ts" ]), 2725( ".mts" , & [ ".mts" , ".d.mts" ]), 2726( ".cts" , & [ ".cts" , ".d.cts" ]), 2727]; 2728let ( stem, suffixes) = substitutions 2729. iter () 2730. find_map ( |( extension, suffixes) | rel. strip_suffix ( extension). map ( |stem|( stem, * suffixes))) 2731. unwrap_or (( rel, & [ ".ts" , ".tsx" , ".d.ts" , "/index.ts" , "/index.d.ts" ])); 2732 suffixes 2733. iter () 2734. map ( |suffix|format! ( "{stem}{suffix}" )) 2735. collect () 2736} 2737 2738fn subpath_to_export ( subpath : & str ) ->Result < String , ResolutionError > { 2739if let Some ( path) = subpath. strip_prefix ( "./" ) { 2740if path. is_empty () { 2741return Err ( ResolutionError :: InvalidNpmExportSubpath { subpath : subpath. to_owned () }); 2742} 2743return Ok ( subpath. to_owned ()); 2744} 2745if subpath =="." || subpath. is_empty () { 2746return Ok ( "." . into ()); 2747} 2748Ok ( format! ( "./{subpath}" )) 2749} 2750 2751impl ResolvedNode { 2752fn classify ( & self , project_root : & Path , rel : & str ) ->Result < NpmModuleKind , ResolutionError > { 2753classify_npm_module ( rel, ||{ 2754// The module hint belongs to this file, regardless of how it was reached. 2755if let Some ( entry) =& self . manifest . module 2756 && entry. strip_prefix ( "./" ). unwrap_or ( entry) == rel 2757{ 2758return Ok ( NpmPackageType :: Module ); 2759} 2760let mut directory = rel. rsplit_once ( '/' ). map_or ( "" , |( parent, _) | parent); 2761while !directory. is_empty () { 2762let metadata =format! ( "{directory}/package.json" ); 2763if self . files . contains_key ( & metadata) 2764 &&let Some ( package_type) =self . scope ( project_root, & metadata) ?. package_type 2765{ 2766return Ok ( package_type); 2767} 2768 directory = directory. rsplit_once ( '/' ). map_or ( "" , |( parent, _) | parent); 2769} 2770Ok ( self . manifest . package_type ) 2771}) 2772} 2773 2774fn scope ( 2775& self , 2776project_root : & Path , 2777rel : & str , 2778) ->Result < Arc < NpmPackageScope >, ResolutionError > { 2779let mut scopes =self . scopes . lock (). unwrap (); 2780if let Some ( scope) = scopes. get ( rel) { 2781return Ok ( scope. clone ()); 2782} 2783let bytes =self . read_verified ( project_root, rel) ?; 2784let scope =Arc :: new ( parse_npm_scope ( & bytes, rel) ?); 2785 scopes. insert ( rel. to_owned (), scope. clone ()); 2786Ok ( scope) 2787} 2788 2789fn read_verified ( & self , project_root : & Path , rel : & str ) ->Result < Vec < u8 >, ResolutionError > { 2790let expected =self 2791. files 2792. get ( rel) 2793. ok_or_else ( ||ResolutionError :: NpmFileNotInManifest { path : rel. to_owned () }) ?; 2794let bytes = 2795 storage:: read_project_file ( project_root, & self . dir , rel, super :: MAX_NPM_FILE_BYTES ) ?; 2796if crate :: registry:: checksum ( & bytes) !=* expected{ 2797return Err ( ResolutionError :: NpmFileIntegrityMismatch { rel : rel. to_owned () }); 2798} 2799Ok ( bytes) 2800} 2801}