char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
05d50bd
main
1use std:: collections:: BTreeMap ; 2use std:: path::{ Path , PathBuf }; 3use std:: sync:: Arc ; 4 5use deno_semver::{ Version , jsr:: JsrPackageReqReference }; 6use serde:: Deserialize ; 7use url:: Url ; 8 9use super :: http:: http_url; 10use super ::{ Access , MAX_ARTIFACT_SIZE , PackageStore , checksum, valid_checksum, verify}; 11 12/// Failures resolving and fetching JSR packages. 13# [ derive ( Clone , Debug , thiserror :: Error )] 14pub enum JsrError { 15# [ error ( "Not a registry module: {name}" )] 16NotRegistryModule { name : String }, 17# [ error ( "Invalid registry module URL: {name}" )] 18InvalidRegistryModuleUrl { name : String }, 19# [ error ( "Unsupported remote module: {name}" )] 20UnsupportedRemoteModule { name : String }, 21# [ error ( "Invalid registry module version: {name}" )] 22InvalidRegistryModuleVersion { name : String }, 23# [ error ( "Expected a registry module file: {name}" )] 24ExpectedRegistryModuleFile { name : String }, 25# [ error ( "Conflicting version selections for {key}" )] 26ConflictingVersionSelections { key : String }, 27# [ error ( "Package is not prepared for synchronous resolution: {key}" )] 28PackageNotPreparedSync { key : String }, 29# [ error ( "Package metadata is not prepared for synchronous resolution: {key}" )] 30PackageMetadataNotPreparedSync { key : String }, 31# [ error ( "{detail}" )] 32JsrRequest { detail : String }, 33# [ error ( "JSR version tags are not supported; use a semver range" )] 34JsrVersionTagsUnsupported , 35# [ error ( "Package {key} has no export {export}" )] 36PackageMissingExport { key : String , export : String }, 37# [ error ( "Export {export} is not in the package manifest" )] 38ExportNotInManifest { export : String }, 39# [ error ( "Invalid locked package version" )] 40InvalidLockedPackageVersion , 41# [ error ( "Locked version {version} does not satisfy {key}" )] 42LockedVersionUnsatisfied { version : String , key : String }, 43# [ error ( "Frozen lockfile is missing {name}" )] 44FrozenLockfileMissing { name : String }, 45# [ error ( "No non-yanked version satisfies {key}" )] 46NoMatchingVersion { key : String }, 47# [ error ( "Invalid package index {url}: {source}" )] 48InvalidPackageIndex { 49url : String , 50source : Arc < serde_json:: Error >, 51}, 52 53# [ error ( "Invalid version metadata {url}: {source}" )] 54InvalidVersionMetadata { 55url : String , 56source : Arc < serde_json:: Error >, 57}, 58# [ error ( "Invalid file manifest in {url}" )] 59InvalidFileManifest { url : String }, 60# [ error ( "Module is not in the package manifest: {name}" )] 61ModuleNotInManifest { name : String }, 62# [ error ( "Manifest size mismatch: {name}" )] 63ManifestSizeMismatch { name : String }, 64# [ error ( "JSR packages must be scoped: {name}" )] 65JsrPackageUnscoped { name : String }, 66# [ error ( "Invalid JSR package name: {name}" )] 67InvalidJsrPackageName { name : String }, 68# [ error ( "Package export must be a relative file path" )] 69PackageExportNotRelative , 70# [ error ( "Registry module queries and fragments are not supported" )] 71RegistryModuleQueryFragment , 72# [ error ( "Registry module URL is not canonical" )] 73RegistryModuleNotCanonical , 74# [ error ( "Module escapes its registry package" )] 75ModuleEscapesRegistryPackage , 76# [ error ( "Invalid registry module path" )] 77InvalidRegistryModulePath , 78# [ error ( "Module path must be UTF-8" )] 79ModulePathNotUtf8 , 80# [ error ( "Vendor artifact is outside the registry" )] 81VendorArtifactOutsideRegistry , 82# [ error ( "Vendor path must be UTF-8" )] 83VendorPathNotUtf8 , 84# [ error ( "Invalid vendor artifact path" )] 85InvalidVendorArtifactPath , 86# [ error ( "Module is not UTF-8: {name}" )] 87ModuleNotUtf8 { name : String }, 88# [ error ( transparent )] 89Registry ( Box < super :: RegistryError >), 90# [ error ( transparent )] 91Http ( # [ from ] super :: http:: HttpError ), 92# [ error ( transparent )] 93Caps ( # [ from ] crate :: caps:: CapabilitiesError ), 94# [ error ( transparent )] 95Url ( # [ from ] url:: ParseError ), 96} 97 98/// A parsed `jsr:` request. Unlike `JsrPackageReqReference`, the specifier 99/// string is retained verbatim: the canonical key drops a leading `jsr:/` 100/// slash that import maps and metadata must reproduce byte-for-byte. 101# [ derive ( Clone , Debug , PartialEq , Eq )] 102pub struct JsrRequest { 103pub name : String , 104pub range : Option < String >, 105pub version_req : deno_semver:: VersionReq , 106pub subpath : Option < String >, 107pub specifier : String , 108} 109 110impl JsrRequest { 111/// The canonical lock and specifier key: `jsr:{name}` or `jsr:{name}@{range}`. 112pub fn request_key ( & self ) ->String { 113match & self . range { 114None =>format! ( "jsr:{}" , self . name ), 115Some ( range) =>format! ( "jsr:{}@{range}" , self . name ), 116} 117} 118 119/// The normalized export name (`./sub` or `.`), mirroring deno's resolution. 120pub fn export_name ( & self ) ->String { 121 deno_semver:: jsr:: normalized_export_name ( self . subpath . as_deref ()). into_owned () 122} 123} 124 125/// Parses a `jsr:` specifier once; validation beyond the grammar 126/// (whole-package shape, version tags) happens at each call site. 127pub fn parse_jsr_request ( specifier : & str ) ->Result < JsrRequest , JsrError > { 128let reference =JsrPackageReqReference :: from_str ( specifier) 129. map_err ( |detail|JsrError :: JsrRequest { detail : detail. to_string () }) ?; 130let req = reference. req (); 131let range = 132( req. version_req . version_text () !="*" ). then ( || req. version_req . version_text (). to_owned ()); 133Ok ( JsrRequest { 134name : req. name . to_string (), 135 range, 136version_req : req. version_req . clone (), 137subpath : reference. sub_path (). map ( str:: to_owned), 138specifier : specifier. to_owned (), 139}) 140} 141 142impl From < super :: RegistryError > for JsrError { 143fn from ( error : super :: RegistryError ) ->Self { 144Self :: Registry ( Box :: new ( error)) 145} 146} 147 148# [ derive ( Deserialize )] 149pub ( super ) struct PackageIndex { 150versions : BTreeMap < String , IndexVersion >, 151} 152 153# [ derive ( Deserialize )] 154struct IndexVersion { 155# [ serde ( default )] 156yanked : bool , 157} 158 159# [ derive ( Clone , Deserialize )] 160pub ( super ) struct ManifestEntry { 161size : usize , 162pub ( super ) checksum : String , 163} 164 165# [ derive ( Deserialize )] 166pub ( super ) struct VersionMetadata { 167pub ( super ) manifest : BTreeMap < String , ManifestEntry >, 168exports : BTreeMap < String , String >, 169# [ serde ( default , rename = "moduleGraph2" )] 170graph : serde_json:: Value , 171} 172 173impl PackageStore { 174pub fn module_base ( & self , name : & str ) ->Result < Url , JsrError > { 175let url = 176Url :: parse ( name). map_err ( |_|JsrError :: NotRegistryModule { name : name. to_owned () }) ?; 177if url. query (). is_some () 178 || url. fragment (). is_some () 179 || url. as_str () != name 180 ||crate :: caps:: canonical_remote_url ( & url) ? != url 181{ 182return Err ( JsrError :: InvalidRegistryModuleUrl { name : name. to_owned () }); 183} 184let relative = name 185. strip_prefix ( self . options . jsr_registry . as_str ()) 186. ok_or_else ( ||JsrError :: UnsupportedRemoteModule { name : name. to_owned () }) ?; 187let mut parts = relative. splitn ( 4 , '/' ); 188let scope = parts. next (). unwrap_or_default (); 189let package = parts. next (). unwrap_or_default (); 190let version = parts. next (). unwrap_or_default (); 191let path = parts. next (). unwrap_or_default (); 192validate_name ( & format! ( "{scope}/{package}" )) ?; 193Version :: parse_standard ( version) 194. map_err ( |_|JsrError :: InvalidRegistryModuleVersion { name : name. to_owned () }) ?; 195if path. is_empty () { 196return Err ( JsrError :: ExpectedRegistryModuleFile { name : name. to_owned () }); 197} 198self . options 199. jsr_registry 200. join ( & format! ( "{scope}/{package}/{version}/" )) 201. map_err ( JsrError :: from) 202} 203 204pub async fn resolve ( & self , specifier : & str ) ->Result < String , JsrError > { 205let request =parse_jsr_request ( specifier) ?; 206self . resolve_request ( & request). await 207} 208 209pub async fn resolve_request ( & self , request : & JsrRequest ) ->Result < String , JsrError > { 210self . check_package ( request) ?; 211let key = request. request_key (); 212let version =self . select ( request). await ?; 213let base =self 214. options 215. jsr_registry 216. join ( & format! ( "{}/{version}/" , request. name )) ?; 217let metadata =self . version_metadata ( & base). await ?; 218let name =self . export_target ( request, & base, & metadata) ?; 219let mut lock =self . lock . lock (). unwrap (); 220if let Some ( existing) = lock. specifiers . get ( & key) 221 && existing !=& version 222{ 223return Err ( JsrError :: ConflictingVersionSelections { key : key. to_owned () }); 224} 225let mut used =self . used . lock (). unwrap (); 226 used. specifiers . insert ( key. clone ()); 227if lock. specifiers . get ( & key) !=Some ( & version) ||self . options . update { 228 used. pending_specifiers . insert ( key. clone ()); 229} 230 lock. specifiers . insert ( key, version); 231Ok ( name. to_string ()) 232} 233 234fn check_package ( & self , request : & JsrRequest ) ->Result <(), JsrError > { 235validate_name ( & request. name ) ?; 236let package =self 237. options 238. jsr_registry 239. join ( & format! ( "{}/" , request. name )) ?; 240Access :: Package ( package). check ( self . authority . as_ref ()) ?; 241if request. version_req . tag (). is_some () { 242return Err ( JsrError :: JsrVersionTagsUnsupported ); 243} 244Ok (()) 245} 246 247pub fn resolve_import_meta ( & self , specifier : & str ) ->Result < String , JsrError > { 248self . resolve_import_meta_request ( & parse_jsr_request ( specifier) ?) 249} 250 251pub fn resolve_import_meta_request ( & self , request : & JsrRequest ) ->Result < String , JsrError > { 252self . check_package ( request) ?; 253let key = request. request_key (); 254let version =self 255. locked_version ( request) ? 256. ok_or_else ( ||JsrError :: PackageNotPreparedSync { key : key. to_owned () }) ?; 257let base =self 258. options 259. jsr_registry 260. join ( & format! ( "{}/{version}/" , request. name )) ?; 261let metadata_url =format! ( "{}_meta.json" , base. as_str (). trim_end_matches ( '/' )); 262let metadata =self 263. metadata 264. lock () 265. unwrap () 266. get ( metadata_url. as_str ()) 267. cloned () 268. ok_or_else ( ||JsrError :: PackageMetadataNotPreparedSync { key : key. to_owned () }) ?; 269self . export_target ( request, & base, & metadata) 270. map ( String :: from) 271} 272 273fn export_target ( 274& self , 275request : & JsrRequest , 276base : & Url , 277metadata : & VersionMetadata , 278) ->Result < Url , JsrError > { 279let key = request. request_key (); 280let export = request. export_name (); 281let target = metadata. exports . get ( export. as_str ()). ok_or_else ( ||{ 282JsrError :: PackageMissingExport { 283key : key. to_owned (), 284export : format! ( "{export:?}" ), 285} 286}) ?; 287let name =exported_file ( base, target) ?; 288if !metadata. manifest . contains_key ( & manifest_path ( base, & name) ?) { 289return Err ( JsrError :: ExportNotInManifest { export : format! ( "{export:?}" ) }); 290} 291Access :: Module ( name. clone ()). check ( self . authority . as_ref ()) ?; 292Ok ( name) 293} 294 295fn locked_version ( & self , request : & JsrRequest ) ->Result < Option < String >, JsrError > { 296let key = request. request_key (); 297let selected =self . lock . lock (). unwrap (). specifiers . get ( & key). cloned (); 298if let Some ( version) =& selected{ 299let parsed =Version :: parse_standard ( version) 300. map_err ( |_|JsrError :: InvalidLockedPackageVersion ) ?; 301if !request. version_req . matches ( & parsed) { 302return Err ( JsrError :: LockedVersionUnsatisfied { 303version : version. to_owned (), 304key : key. to_owned (), 305}); 306} 307} 308Ok ( selected) 309} 310 311/// The newest non-yanked version of a package, excluding prereleases unless requested. 312pub async fn latest ( & self , name : & str , prerelease : bool ) ->Result < Option < String >, JsrError > { 313validate_name ( name) ?; 314Ok ( self 315. index ( name) 316. await ? 317. versions 318. iter () 319. filter ( |( _, data) | !data. yanked ) 320. filter_map ( |( version, _) |Version :: parse_standard ( version). ok ()) 321. filter ( |version| prerelease || version. pre . is_empty ()) 322. max () 323. map ( |version| version. to_string ())) 324} 325 326/// The export keys of the version a `jsr:` specifier selects. 327pub async fn exports ( & self , specifier : & str ) ->Result < Vec < String >, JsrError > { 328let request =parse_jsr_request ( specifier) ?; 329validate_name ( & request. name ) ?; 330if request. version_req . tag (). is_some () { 331return Err ( JsrError :: JsrVersionTagsUnsupported ); 332} 333let version =self . select ( & request). await ?; 334let base =self 335. options 336. jsr_registry 337. join ( & format! ( "{}/{version}/" , request. name )) ?; 338Ok ( self 339. version_metadata ( & base) 340. await ? 341. exports 342. keys () 343. cloned () 344. collect ()) 345} 346 347async fn select ( & self , request : & JsrRequest ) ->Result < String , JsrError > { 348let key = request. request_key (); 349Ok ( match self . locked_version ( request) ?{ 350Some ( version) => version, 351None =>{ 352if self . options . frozen { 353return Err ( JsrError :: FrozenLockfileMissing { name : key. clone () }); 354} 355let index =self . index ( request. name . as_str ()). await ?; 356 index 357. versions 358. iter () 359. filter ( |( _, data) | !data. yanked ) 360. filter_map ( |( version, _) |Version :: parse_standard ( version). ok ()) 361. filter ( |version| request. version_req . matches ( version)) 362. max () 363. ok_or_else ( ||JsrError :: NoMatchingVersion { key : key. clone () }) ? 364. to_string () 365} 366}) 367} 368 369async fn index ( & self , name : & str ) ->Result < Arc < PackageIndex >, JsrError > { 370let url =self 371. options 372. jsr_registry 373. join ( & format! ( "{name}/meta.json" )) ?; 374let cached =self . indexes . lock (). unwrap (). get ( url. as_str ()). cloned (); 375Ok ( match cached{ 376Some ( index) => index, 377None =>{ 378let package =self . options . jsr_registry . join ( & format! ( "{name}/" )) ?; 379let bytes =self 380. bytes ( 381 url. clone (), 382None , 383 !self . options . offline , 384Access :: Package ( package), 385) 386. await ?; 387let index: PackageIndex = serde_json:: from_slice ( & bytes). map_err ( |source|{ 388JsrError :: InvalidPackageIndex { 389url : url. to_string (), 390source : std:: sync:: Arc :: new ( source), 391} 392}) ?; 393let index =Arc :: new ( index); 394self . indexes 395. lock () 396. unwrap () 397. insert ( url. to_string (), index. clone ()); 398 index 399} 400}) 401} 402 403pub ( super ) async fn version_metadata ( 404& self , 405base : & Url , 406) ->Result < Arc < VersionMetadata >, JsrError > { 407Access :: Package ( base. clone ()). check ( self . authority . as_ref ()) ?; 408let metadata_url =Url :: parse ( & format! ( 409"{}_meta.json" , 410 base. as_str (). trim_end_matches ( '/' ) 411)) ?; 412let cached =self 413. metadata 414. lock () 415. unwrap () 416. get ( metadata_url. as_str ()) 417. cloned (); 418if let Some ( metadata) = cached{ 419let mut used =self . used . lock (). unwrap (); 420if used. packages . insert ( metadata_url. to_string ()) { 421 used. pending_packages . insert ( metadata_url. to_string ()); 422} 423return Ok ( metadata); 424} 425let expected =self 426. lock 427. lock () 428. unwrap () 429. packages 430. get ( metadata_url. as_str ()) 431. cloned (); 432if expected. is_none () &&self . options . frozen { 433return Err ( JsrError :: FrozenLockfileMissing { name : metadata_url. to_string () }); 434} 435let bytes =self 436. bytes ( 437 metadata_url. clone (), 438 expected. clone (), 439 expected. is_none () && !self . options . offline , 440Access :: Package ( base. clone ()), 441) 442. await ?; 443let metadata: VersionMetadata = 444 serde_json:: from_slice ( & bytes). map_err ( |source|JsrError :: InvalidVersionMetadata { 445url : metadata_url. to_string (), 446source : std:: sync:: Arc :: new ( source), 447}) ?; 448for entryin metadata. manifest . values () { 449if entry. size >MAX_ARTIFACT_SIZE || !valid_checksum ( & entry. checksum ) { 450return Err ( JsrError :: InvalidFileManifest { url : metadata_url. to_string () }); 451} 452} 453let checksum =checksum ( & bytes); 454let mut lock =self . lock . lock (). unwrap (); 455if let Some ( existing) = lock. packages . get ( metadata_url. as_str ()) { 456verify ( & bytes, existing) ?; 457} 458let mut used =self . used . lock (). unwrap (); 459 used. packages . insert ( metadata_url. to_string ()); 460if lock. packages . get ( metadata_url. as_str ()) !=Some ( & checksum) { 461 used. pending_packages . insert ( metadata_url. to_string ()); 462} 463 lock. packages . insert ( metadata_url. to_string (), checksum); 464let metadata =Arc :: new ( metadata); 465self . metadata 466. lock () 467. unwrap () 468. insert ( metadata_url. to_string (), metadata. clone ()); 469Ok ( metadata) 470} 471 472pub fn resolve_relative ( & self , specifier : & str , referrer : & str ) ->Result < String , JsrError > { 473let base =self . module_base ( referrer) ?; 474let url =Url :: parse ( referrer) ?. join ( specifier) ?; 475manifest_path ( & base, & url) ?; 476Ok ( url. to_string ()) 477} 478 479pub async fn source ( & self , name : & str ) ->Result < String , JsrError > { 480let base =self . module_base ( name) ?; 481let url =Url :: parse ( name) ?; 482Access :: Module ( url. clone ()). check ( self . authority . as_ref ()) ?; 483let metadata =self . version_metadata ( & base). await ?; 484let path =manifest_path ( & base, & url) ?; 485let entry = metadata 486. manifest 487. get ( & path) 488. ok_or_else ( ||JsrError :: ModuleNotInManifest { name : name. to_owned () }) ?; 489let bytes =self 490. bytes ( 491 url. clone (), 492Some ( entry. checksum . clone ()), 493false , 494Access :: Module ( url), 495) 496. await ?; 497if bytes. len () != entry. size { 498return Err ( JsrError :: ManifestSizeMismatch { name : name. to_owned () }); 499} 500let source =String :: from_utf8 ( bytes) 501. map_err ( |_|JsrError :: ModuleNotUtf8 { name : name. to_owned () }) ?; 502self . used . lock (). unwrap (). sources . insert ( name. to_owned ()); 503Ok ( source) 504} 505 506pub async fn hints ( & self , name : & str ) ->Result < Vec < String >, JsrError > { 507if !name. starts_with ( self . options . jsr_registry . as_str ()) { 508http_url ( name) ?; 509return Ok ( vec! []); 510} 511let base =self . module_base ( name) ?; 512let metadata =self . version_metadata ( & base). await ?; 513let url =Url :: parse ( name) ?; 514let path =manifest_path ( & base, & url) ?; 515let Some ( dependencies) = metadata 516. graph 517. get ( & path) 518. and_then ( |info| info. get ( "dependencies" )) 519. and_then ( |value| value. as_array ()) 520else { 521return Ok ( vec! []); 522}; 523Ok ( dependencies 524. iter () 525. filter ( |dep|{ 526 dep. get ( "type" ). and_then ( |value| value. as_str ()) ==Some ( "static" ) 527 && dep. get ( "kind" ). and_then ( |value| value. as_str ()) ==Some ( "import" ) 528}) 529. filter_map ( |dep| dep. get ( "specifier" ). and_then ( |value| value. as_str ())) 530. filter ( |specifier| specifier. starts_with ( "./" ) || specifier. starts_with ( "../" )) 531. filter_map ( |specifier| url. join ( specifier). ok ()) 532. filter ( |url|{ 533manifest_path ( & base, url). is_ok_and ( |path| metadata. manifest . contains_key ( & path)) 534}) 535. filter ( |url|{ 536matches! ( 537Path :: new ( url. path ()) 538. extension () 539. and_then ( |ext| ext. to_str ()), 540Some ( "js" |"mjs" |"ts" |"mts" ) 541) && !url. path (). ends_with ( ".d.ts" ) 542 && !url. path (). ends_with ( ".d.mts" ) 543}) 544. take ( 32 ) 545. map ( String :: from) 546. collect ()) 547} 548} 549 550pub ( crate ) fn validate_name ( name : & str ) ->Result <(), JsrError > { 551let Some (( scope, package)) = name. strip_prefix ( '@' ). and_then ( |name| name. split_once ( '/' )) 552else { 553return Err ( JsrError :: JsrPackageUnscoped { name : name. to_owned () }); 554}; 555if [ scope, package]. iter (). any ( |part|{ 556 part. is_empty () 557 || !part 558. chars () 559. all ( |ch| ch. is_ascii_lowercase () || ch. is_ascii_digit () || ch =='-' || ch =='_' ) 560}) { 561return Err ( JsrError :: InvalidJsrPackageName { name : name. to_owned () }); 562} 563Ok (()) 564} 565 566fn exported_file ( base : & Url , target : & str ) ->Result < Url , JsrError > { 567let target = target 568. strip_prefix ( "./" ) 569. or_else ( || target. strip_prefix ( '/' )) 570. ok_or ( JsrError :: PackageExportNotRelative ) ?; 571let url = base. join ( target) ?; 572manifest_path ( base, & url) ?; 573Ok ( url) 574} 575 576pub ( super ) fn manifest_path ( base : & Url , url : & Url ) ->Result < String , JsrError > { 577if url. query (). is_some () || url. fragment (). is_some () { 578return Err ( JsrError :: RegistryModuleQueryFragment ); 579} 580if crate :: caps:: canonical_remote_url ( url) ? !=* url{ 581return Err ( JsrError :: RegistryModuleNotCanonical ); 582} 583let suffix = url 584. as_str () 585. strip_prefix ( base. as_str ()) 586. ok_or ( JsrError :: ModuleEscapesRegistryPackage ) ?; 587let lower = suffix. to_ascii_lowercase (); 588if lower. contains ( "%2f" ) || lower. contains ( "%5c" ) || suffix. is_empty () { 589return Err ( JsrError :: InvalidRegistryModulePath ); 590} 591let decoded = percent_encoding:: percent_decode_str ( suffix) 592. decode_utf8 () 593. map_err ( |_|JsrError :: ModulePathNotUtf8 ) ?; 594Ok ( format! ( "/{decoded}" )) 595} 596 597pub ( super ) fn vendor_path ( registry : & Url , url : & Url ) ->Result < PathBuf , JsrError > { 598let suffix = url 599. as_str () 600. strip_prefix ( registry. as_str ()) 601. ok_or ( JsrError :: VendorArtifactOutsideRegistry ) ?; 602let decoded = percent_encoding:: percent_decode_str ( suffix) 603. decode_utf8 () 604. map_err ( |_|JsrError :: VendorPathNotUtf8 ) ?; 605let path =PathBuf :: from ( decoded. as_ref ()); 606if path 607. components () 608. any ( |component| !matches! ( component, std:: path:: Component :: Normal ( _))) 609{ 610return Err ( JsrError :: InvalidVendorArtifactPath ); 611} 612Ok ( path) 613}