// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::futures::stream::FuturesOrdered; use deno_core::futures::StreamExt; use deno_core::parking_lot::Mutex; use deno_npm::registry::NpmRegistryApi; use deno_npm::resolution::SerializedNpmResolutionSnapshot; use deno_npm::resolution::SerializedNpmResolutionSnapshotPackage; use deno_npm::resolution::ValidSerializedNpmResolutionSnapshot; use deno_npm::NpmPackageId; use deno_npm::NpmResolutionPackageSystemInfo; use deno_semver::npm::NpmPackageReq; use crate::args::ConfigFile; use crate::npm::CliNpmRegistryApi; use crate::Flags; use super::DenoSubcommand; pub use deno_lockfile::Lockfile; pub use deno_lockfile::LockfileError; pub fn discover( flags: &Flags, maybe_config_file: Option<&ConfigFile>, ) -> Result, AnyError> { if flags.no_lock || matches!( flags.subcommand, DenoSubcommand::Install(_) | DenoSubcommand::Uninstall(_) ) { return Ok(None); } let filename = match flags.lock { Some(ref lock) => PathBuf::from(lock), None => match maybe_config_file { Some(config_file) => { if config_file.specifier.scheme() == "file" { match config_file.resolve_lockfile_path()? { Some(path) => path, None => return Ok(None), } } else { return Ok(None); } } None => return Ok(None), }, }; let lockfile = Lockfile::new(filename, flags.lock_write)?; Ok(Some(lockfile)) } pub async fn snapshot_from_lockfile( lockfile: Arc>, api: &CliNpmRegistryApi, ) -> Result { let (root_packages, mut packages) = { let lockfile = lockfile.lock(); let mut root_packages = HashMap::::with_capacity( lockfile.content.npm.specifiers.len(), ); // collect the specifiers to version mappings for (key, value) in &lockfile.content.npm.specifiers { let package_req = NpmPackageReq::from_str(key) .with_context(|| format!("Unable to parse npm specifier: {key}"))?; let package_id = NpmPackageId::from_serialized(value)?; root_packages.insert(package_req, package_id.clone()); } // now fill the packages except for the dist information let mut packages = Vec::with_capacity(lockfile.content.npm.packages.len()); for (key, package) in &lockfile.content.npm.packages { let id = NpmPackageId::from_serialized(key)?; // collect the dependencies let mut dependencies = HashMap::with_capacity(package.dependencies.len()); for (name, specifier) in &package.dependencies { let dep_id = NpmPackageId::from_serialized(specifier)?; dependencies.insert(name.clone(), dep_id); } packages.push(SerializedNpmResolutionSnapshotPackage { id, dependencies, // temporarily empty system: Default::default(), dist: Default::default(), optional_dependencies: Default::default(), }); } (root_packages, packages) }; // now that the lockfile is dropped, fetch the package version information let pkg_nvs = packages.iter().map(|p| p.id.nv.clone()).collect::>(); let get_version_infos = || { FuturesOrdered::from_iter(pkg_nvs.iter().map(|nv| async move { let package_info = api.package_info(&nv.name).await?; match package_info.version_info(nv) { Ok(version_info) => Ok(version_info), Err(err) => { bail!("Could not find '{}' specified in the lockfile.", err.0); } } })) }; let mut version_infos = get_version_infos(); let mut i = 0; while let Some(result) = version_infos.next().await { match result { Ok(version_info) => { let mut package = &mut packages[i]; package.dist = version_info.dist; package.system = NpmResolutionPackageSystemInfo { cpu: version_info.cpu, os: version_info.os, }; package.optional_dependencies = version_info.optional_dependencies.into_keys().collect(); } Err(err) => { if api.mark_force_reload() { // reset and try again version_infos = get_version_infos(); i = 0; continue; } else { return Err(err); } } } i += 1; } // clear the memory cache to reduce memory usage api.clear_memory_cache(); SerializedNpmResolutionSnapshot { packages, root_packages, } .into_valid() .context("The lockfile is corrupt. You can recreate it with --lock-write") }