fix(lockfile): re-fetch packuments if version not found, properly pass patch packages (#28964)

Fixes two issues:
- If a cached packument was out of date and missing a version from the
lockfile, we would fail. Instead we should try again with a forced
re-fetch
- We weren't threading through the workspace patch packages correctly
This commit is contained in:
Nathan Whitaker 2025-04-18 16:08:15 -07:00 committed by GitHub
parent 6ce1e9b7f7
commit dbb5373eab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 311 additions and 135 deletions

View file

@ -293,7 +293,7 @@ impl CliLockfile {
pub async fn read_from_path( pub async fn read_from_path(
sys: &CliSys, sys: &CliSys,
opts: CliLockfileReadFromPathOptions, opts: CliLockfileReadFromPathOptions,
api: &(dyn NpmPackageInfoProvider + Send + Sync), api: &(dyn deno_lockfile::NpmPackageInfoProvider + Send + Sync),
) -> Result<CliLockfile, AnyError> { ) -> Result<CliLockfile, AnyError> {
let lockfile = match std::fs::read_to_string(&opts.file_path) { let lockfile = match std::fs::read_to_string(&opts.file_path) {
Ok(text) => { Ok(text) => {

View file

@ -404,10 +404,7 @@ impl CliFactory {
let workspace_directory = workspace_factory.workspace_directory()?; let workspace_directory = workspace_factory.workspace_directory()?;
let maybe_external_import_map = let maybe_external_import_map =
self.workspace_external_import_map_loader()?.get_or_load()?; self.workspace_external_import_map_loader()?.get_or_load()?;
let provider = self.npm_registry_info_provider()?.clone(); let adapter = self.lockfile_npm_package_info_provider()?;
let adapter = crate::npm::NpmPackageInfoApiAdapter(Arc::new(
provider.as_npm_registry_api(),
));
let maybe_lock_file = CliLockfile::discover( let maybe_lock_file = CliLockfile::discover(
&self.sys(), &self.sys(),
@ -660,6 +657,7 @@ impl CliFactory {
.map(|p| p.to_path_buf()), .map(|p| p.to_path_buf()),
cli_options.lifecycle_scripts_config(), cli_options.lifecycle_scripts_config(),
cli_options.npm_system_info(), cli_options.npm_system_info(),
self.workspace_npm_patch_packages()?.clone(),
))) )))
}) })
.await .await
@ -680,6 +678,15 @@ impl CliFactory {
}) })
} }
pub fn lockfile_npm_package_info_provider(
&self,
) -> Result<crate::npm::NpmPackageInfoApiAdapter, AnyError> {
Ok(crate::npm::NpmPackageInfoApiAdapter::new(
Arc::new(self.npm_registry_info_provider()?.as_npm_registry_api()),
self.workspace_npm_patch_packages()?.clone(),
))
}
pub async fn npm_resolution_initializer( pub async fn npm_resolution_initializer(
&self, &self,
) -> Result<&Arc<NpmResolutionInitializer>, AnyError> { ) -> Result<&Arc<NpmResolutionInitializer>, AnyError> {

View file

@ -42,7 +42,6 @@ use deno_lib::args::has_flag_env_var;
use deno_lib::util::hash::FastInsecureHasher; use deno_lib::util::hash::FastInsecureHasher;
use deno_lint::linter::LintConfig as DenoLintConfig; use deno_lint::linter::LintConfig as DenoLintConfig;
use deno_npm::npm_rc::ResolvedNpmRc; use deno_npm::npm_rc::ResolvedNpmRc;
use deno_npm::registry::NpmRegistryApi;
use deno_package_json::PackageJsonCache; use deno_package_json::PackageJsonCache;
use deno_path_util::url_to_file_path; use deno_path_util::url_to_file_path;
use deno_resolver::npmrc::discover_npmrc_from_workspace; use deno_resolver::npmrc::discover_npmrc_from_workspace;
@ -1270,7 +1269,9 @@ impl ConfigData {
deno_json_cache: &(dyn DenoJsonCache + Sync), deno_json_cache: &(dyn DenoJsonCache + Sync),
pkg_json_cache: &(dyn PackageJsonCache + Sync), pkg_json_cache: &(dyn PackageJsonCache + Sync),
workspace_cache: &(dyn WorkspaceCache + Sync), workspace_cache: &(dyn WorkspaceCache + Sync),
npm_package_info_provider: &Arc<dyn NpmRegistryApi + Send + Sync>, lockfile_package_info_provider: &Arc<
dyn deno_lockfile::NpmPackageInfoProvider + Send + Sync,
>,
) -> Self { ) -> Self {
let scope = scope.clone(); let scope = scope.clone();
let discover_result = match scope.to_file_path() { let discover_result = match scope.to_file_path() {
@ -1309,7 +1310,7 @@ impl ConfigData {
scope, scope,
settings, settings,
Some(file_fetcher), Some(file_fetcher),
npm_package_info_provider, lockfile_package_info_provider,
) )
.await .await
} }
@ -1325,7 +1326,7 @@ impl ConfigData {
scope.clone(), scope.clone(),
settings, settings,
Some(file_fetcher), Some(file_fetcher),
npm_package_info_provider, lockfile_package_info_provider,
) )
.await; .await;
// check if any of these need to be added to the workspace // check if any of these need to be added to the workspace
@ -1368,7 +1369,9 @@ impl ConfigData {
scope: Arc<ModuleSpecifier>, scope: Arc<ModuleSpecifier>,
settings: &Settings, settings: &Settings,
file_fetcher: Option<&Arc<CliFileFetcher>>, file_fetcher: Option<&Arc<CliFileFetcher>>,
npm_package_info_provider: &Arc<dyn NpmRegistryApi + Send + Sync>, lockfile_package_info_provider: &Arc<
dyn deno_lockfile::NpmPackageInfoProvider + Send + Sync,
>,
) -> Self { ) -> Self {
let (settings, workspace_folder) = settings.get_for_specifier(&scope); let (settings, workspace_folder) = settings.get_for_specifier(&scope);
let mut watched_files = HashMap::with_capacity(10); let mut watched_files = HashMap::with_capacity(10);
@ -1513,10 +1516,12 @@ impl ConfigData {
let vendor_dir = member_dir.workspace.vendor_dir_path().cloned(); let vendor_dir = member_dir.workspace.vendor_dir_path().cloned();
// todo(dsherret): add caching so we don't load this so many times // todo(dsherret): add caching so we don't load this so many times
let lockfile = let lockfile = resolve_lockfile_from_workspace(
resolve_lockfile_from_workspace(&member_dir, npm_package_info_provider) &member_dir,
.await lockfile_package_info_provider,
.map(Arc::new); )
.await
.map(Arc::new);
if let Some(lockfile) = &lockfile { if let Some(lockfile) = &lockfile {
if let Ok(specifier) = ModuleSpecifier::from_file_path(&lockfile.filename) if let Ok(specifier) = ModuleSpecifier::from_file_path(&lockfile.filename)
{ {
@ -1928,7 +1933,9 @@ impl ConfigTree {
workspace_files: &IndexSet<PathBuf>, workspace_files: &IndexSet<PathBuf>,
file_fetcher: &Arc<CliFileFetcher>, file_fetcher: &Arc<CliFileFetcher>,
deno_dir: &DenoDir, deno_dir: &DenoDir,
npm_package_info_provider: &Arc<dyn NpmRegistryApi + Send + Sync>, lockfile_package_info_provider: &Arc<
dyn deno_lockfile::NpmPackageInfoProvider + Send + Sync,
>,
) { ) {
lsp_log!("Refreshing configuration tree..."); lsp_log!("Refreshing configuration tree...");
// since we're resolving a workspace multiple times in different // since we're resolving a workspace multiple times in different
@ -1961,7 +1968,7 @@ impl ConfigTree {
&deno_json_cache, &deno_json_cache,
&pkg_json_cache, &pkg_json_cache,
&workspace_cache, &workspace_cache,
npm_package_info_provider, lockfile_package_info_provider,
) )
.await, .await,
), ),
@ -1995,7 +2002,7 @@ impl ConfigTree {
&deno_json_cache, &deno_json_cache,
&pkg_json_cache, &pkg_json_cache,
&workspace_cache, &workspace_cache,
npm_package_info_provider, lockfile_package_info_provider,
) )
.await, .await,
); );
@ -2012,7 +2019,7 @@ impl ConfigTree {
&deno_json_cache, &deno_json_cache,
&pkg_json_cache, &pkg_json_cache,
&workspace_cache, &workspace_cache,
npm_package_info_provider, lockfile_package_info_provider,
) )
.await; .await;
scopes.insert(member_scope.clone(), Arc::new(member_data)); scopes.insert(member_scope.clone(), Arc::new(member_data));
@ -2030,7 +2037,9 @@ impl ConfigTree {
pub async fn inject_config_file( pub async fn inject_config_file(
&mut self, &mut self,
config_file: ConfigFile, config_file: ConfigFile,
npm_package_info_provider: &Arc<dyn NpmRegistryApi + Send + Sync>, lockfile_package_info_provider: &Arc<
dyn deno_lockfile::NpmPackageInfoProvider + Send + Sync,
>,
) { ) {
use sys_traits::FsCreateDirAll; use sys_traits::FsCreateDirAll;
use sys_traits::FsWrite; use sys_traits::FsWrite;
@ -2061,7 +2070,7 @@ impl ConfigTree {
scope.clone(), scope.clone(),
&Default::default(), &Default::default(),
None, None,
npm_package_info_provider, lockfile_package_info_provider,
) )
.await, .await,
); );
@ -2072,7 +2081,9 @@ impl ConfigTree {
async fn resolve_lockfile_from_workspace( async fn resolve_lockfile_from_workspace(
workspace: &WorkspaceDirectory, workspace: &WorkspaceDirectory,
npm_package_info_provider: &Arc<dyn NpmRegistryApi + Send + Sync>, lockfile_package_info_provider: &Arc<
dyn deno_lockfile::NpmPackageInfoProvider + Send + Sync,
>,
) -> Option<CliLockfile> { ) -> Option<CliLockfile> {
let lockfile_path = match workspace.workspace.resolve_lockfile_path() { let lockfile_path = match workspace.workspace.resolve_lockfile_path() {
Ok(Some(value)) => value, Ok(Some(value)) => value,
@ -2087,8 +2098,12 @@ async fn resolve_lockfile_from_workspace(
.root_deno_json() .root_deno_json()
.and_then(|c| c.to_lock_config().ok().flatten().map(|c| c.frozen())) .and_then(|c| c.to_lock_config().ok().flatten().map(|c| c.frozen()))
.unwrap_or(false); .unwrap_or(false);
resolve_lockfile_from_path(lockfile_path, frozen, npm_package_info_provider) resolve_lockfile_from_path(
.await lockfile_path,
frozen,
lockfile_package_info_provider,
)
.await
} }
fn resolve_node_modules_dir( fn resolve_node_modules_dir(
@ -2124,7 +2139,9 @@ fn resolve_node_modules_dir(
async fn resolve_lockfile_from_path( async fn resolve_lockfile_from_path(
lockfile_path: PathBuf, lockfile_path: PathBuf,
frozen: bool, frozen: bool,
npm_package_info_provider: &Arc<dyn NpmRegistryApi + Send + Sync>, lockfile_package_info_provider: &Arc<
dyn deno_lockfile::NpmPackageInfoProvider + Send + Sync,
>,
) -> Option<CliLockfile> { ) -> Option<CliLockfile> {
match CliLockfile::read_from_path( match CliLockfile::read_from_path(
&CliSys::default(), &CliSys::default(),
@ -2133,7 +2150,7 @@ async fn resolve_lockfile_from_path(
frozen, frozen,
skip_write: false, skip_write: false,
}, },
&crate::npm::NpmPackageInfoApiAdapter(npm_package_info_provider.clone()), &**lockfile_package_info_provider,
) )
.await .await
{ {
@ -2198,8 +2215,6 @@ mod tests {
use deno_core::resolve_url; use deno_core::resolve_url;
use deno_core::serde_json; use deno_core::serde_json;
use deno_core::serde_json::json; use deno_core::serde_json::json;
use deno_npm::registry::NpmPackageInfo;
use deno_npm::registry::NpmRegistryPackageInfoLoadError;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use super::*; use super::*;
@ -2465,16 +2480,20 @@ mod tests {
struct DefaultRegistry; struct DefaultRegistry;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl deno_npm::registry::NpmRegistryApi for DefaultRegistry { impl deno_lockfile::NpmPackageInfoProvider for DefaultRegistry {
async fn package_info( async fn get_npm_package_info(
&self, &self,
_name: &str, values: &[deno_semver::package::PackageNv],
) -> Result<Arc<NpmPackageInfo>, NpmRegistryPackageInfoLoadError> { ) -> Result<
Ok(Arc::new(NpmPackageInfo::default())) Vec<deno_lockfile::Lockfile5NpmInfo>,
Box<dyn std::error::Error + Send + Sync>,
> {
Ok(values.iter().map(|_| Default::default()).collect())
} }
} }
fn default_registry() -> Arc<dyn NpmRegistryApi + Send + Sync> { fn default_registry(
) -> Arc<dyn deno_lockfile::NpmPackageInfoProvider + Send + Sync> {
Arc::new(DefaultRegistry) Arc::new(DefaultRegistry)
} }

View file

@ -1967,9 +1967,7 @@ mod tests {
use deno_config::deno_json::ConfigFile; use deno_config::deno_json::ConfigFile;
use deno_core::resolve_url; use deno_core::resolve_url;
use deno_npm::registry::NpmPackageInfo; use deno_semver::package::PackageNv;
use deno_npm::registry::NpmRegistryApi;
use deno_npm::registry::NpmRegistryPackageInfoLoadError;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use test_util::TempDir; use test_util::TempDir;
@ -2011,16 +2009,20 @@ mod tests {
struct DefaultRegistry; struct DefaultRegistry;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl NpmRegistryApi for DefaultRegistry { impl deno_lockfile::NpmPackageInfoProvider for DefaultRegistry {
async fn package_info( async fn get_npm_package_info(
&self, &self,
_name: &str, values: &[PackageNv],
) -> Result<Arc<NpmPackageInfo>, NpmRegistryPackageInfoLoadError> { ) -> Result<
Ok(Arc::new(NpmPackageInfo::default())) Vec<deno_lockfile::Lockfile5NpmInfo>,
Box<dyn std::error::Error + Send + Sync>,
> {
Ok(values.iter().map(|_| Default::default()).collect())
} }
} }
fn default_registry() -> Arc<dyn NpmRegistryApi + Send + Sync> { fn default_registry(
) -> Arc<dyn deno_lockfile::NpmPackageInfoProvider + Send + Sync> {
Arc::new(DefaultRegistry) Arc::new(DefaultRegistry)
} }

View file

@ -2054,9 +2054,6 @@ mod tests {
use deno_config::deno_json::ConfigFile; use deno_config::deno_json::ConfigFile;
use deno_core::serde_json; use deno_core::serde_json;
use deno_core::serde_json::json; use deno_core::serde_json::json;
use deno_npm::registry::NpmPackageInfo;
use deno_npm::registry::NpmRegistryApi;
use deno_npm::registry::NpmRegistryPackageInfoLoadError;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use test_util::TempDir; use test_util::TempDir;
@ -2066,16 +2063,20 @@ mod tests {
struct DefaultRegistry; struct DefaultRegistry;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl deno_npm::registry::NpmRegistryApi for DefaultRegistry { impl deno_lockfile::NpmPackageInfoProvider for DefaultRegistry {
async fn package_info( async fn get_npm_package_info(
&self, &self,
_name: &str, values: &[deno_semver::package::PackageNv],
) -> Result<Arc<NpmPackageInfo>, NpmRegistryPackageInfoLoadError> { ) -> Result<
Ok(Arc::new(NpmPackageInfo::default())) Vec<deno_lockfile::Lockfile5NpmInfo>,
Box<dyn std::error::Error + Send + Sync>,
> {
Ok(values.iter().map(|_| Default::default()).collect())
} }
} }
fn default_registry() -> Arc<dyn NpmRegistryApi + Send + Sync> { fn default_registry(
) -> Arc<dyn deno_lockfile::NpmPackageInfoProvider + Send + Sync> {
Arc::new(DefaultRegistry) Arc::new(DefaultRegistry)
} }

View file

@ -29,7 +29,6 @@ use deno_graph::Resolution;
use deno_lib::args::get_root_cert_store; use deno_lib::args::get_root_cert_store;
use deno_lib::args::CaData; use deno_lib::args::CaData;
use deno_lib::version::DENO_VERSION_INFO; use deno_lib::version::DENO_VERSION_INFO;
use deno_npm::registry::NpmRegistryApi;
use deno_path_util::url_to_file_path; use deno_path_util::url_to_file_path;
use deno_runtime::deno_tls::rustls::RootCertStore; use deno_runtime::deno_tls::rustls::RootCertStore;
use deno_runtime::deno_tls::RootCertStoreProvider; use deno_runtime::deno_tls::RootCertStoreProvider;
@ -260,7 +259,8 @@ pub struct Inner {
/// Set to `self.config.settings.enable_settings_hash()` after /// Set to `self.config.settings.enable_settings_hash()` after
/// refreshing `self.workspace_files`. /// refreshing `self.workspace_files`.
workspace_files_hash: u64, workspace_files_hash: u64,
registry_provider: Arc<dyn NpmRegistryApi + Send + Sync>, registry_provider:
Arc<dyn deno_lockfile::NpmPackageInfoProvider + Send + Sync>,
_tracing: Option<super::trace::TracingGuard>, _tracing: Option<super::trace::TracingGuard>,
} }
@ -299,7 +299,9 @@ impl std::fmt::Debug for Inner {
impl LanguageServer { impl LanguageServer {
pub fn new( pub fn new(
client: Client, client: Client,
registry_provider: Arc<dyn NpmRegistryApi + Send + Sync>, registry_provider: Arc<
dyn deno_lockfile::NpmPackageInfoProvider + Send + Sync,
>,
) -> Self { ) -> Self {
let performance = Arc::new(Performance::default()); let performance = Arc::new(Performance::default());
Self { Self {
@ -533,7 +535,9 @@ impl Inner {
fn new( fn new(
client: Client, client: Client,
performance: Arc<Performance>, performance: Arc<Performance>,
registry_provider: Arc<dyn NpmRegistryApi + Send + Sync>, registry_provider: Arc<
dyn deno_lockfile::NpmPackageInfoProvider + Send + Sync,
>,
) -> Self { ) -> Self {
let cache = LspCache::default(); let cache = LspCache::default();
let http_client_provider = Arc::new(HttpClientProvider::new(None, None)); let http_client_provider = Arc::new(HttpClientProvider::new(None, None));

View file

@ -3,7 +3,6 @@
use std::sync::Arc; use std::sync::Arc;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_npm::registry::NpmRegistryApi;
pub use repl::ReplCompletionItem; pub use repl::ReplCompletionItem;
pub use repl::ReplLanguageServer; pub use repl::ReplLanguageServer;
use tower_lsp::LspService; use tower_lsp::LspService;
@ -42,7 +41,9 @@ mod tsc;
mod urls; mod urls;
pub async fn start( pub async fn start(
registry_provider: Arc<dyn NpmRegistryApi + Send + Sync>, registry_provider: Arc<
dyn deno_lockfile::NpmPackageInfoProvider + Send + Sync,
>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let stdin = tokio::io::stdin(); let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout(); let stdout = tokio::io::stdout();

View file

@ -9,7 +9,6 @@ use deno_ast::SourceTextInfo;
use deno_core::anyhow::anyhow; use deno_core::anyhow::anyhow;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::serde_json; use deno_core::serde_json;
use deno_npm::registry::NpmRegistryApi;
use lsp_types::Uri; use lsp_types::Uri;
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
use tower_lsp::lsp_types::ClientCapabilities; use tower_lsp::lsp_types::ClientCapabilities;
@ -63,7 +62,9 @@ pub struct ReplLanguageServer {
impl ReplLanguageServer { impl ReplLanguageServer {
pub async fn new_initialized( pub async fn new_initialized(
registry_provider: Arc<dyn NpmRegistryApi + Send + Sync>, registry_provider: Arc<
dyn deno_lockfile::NpmPackageInfoProvider + Send + Sync,
>,
) -> Result<ReplLanguageServer, AnyError> { ) -> Result<ReplLanguageServer, AnyError> {
// downgrade info and warn lsp logging to debug // downgrade info and warn lsp logging to debug
super::logging::set_lsp_log_level(log::Level::Debug); super::logging::set_lsp_log_level(log::Level::Debug);

View file

@ -811,7 +811,7 @@ impl<'a> ResolverFactory<'a> {
registry_info_provider.clone(), registry_info_provider.clone(),
self.services.npm_resolution.clone(), self.services.npm_resolution.clone(),
maybe_lockfile.clone(), maybe_lockfile.clone(),
patch_packages, patch_packages.clone(),
)); ));
let npm_installer = Arc::new(NpmInstaller::new( let npm_installer = Arc::new(NpmInstaller::new(
npm_cache.clone(), npm_cache.clone(),
@ -827,6 +827,7 @@ impl<'a> ResolverFactory<'a> {
maybe_node_modules_path.clone(), maybe_node_modules_path.clone(),
LifecycleScriptsConfig::default(), LifecycleScriptsConfig::default(),
NpmSystemInfo::default(), NpmSystemInfo::default(),
patch_packages,
)); ));
self.set_npm_installer(npm_installer); self.set_npm_installer(npm_installer);
if let Err(err) = npm_resolution_initializer.ensure_initialized().await { if let Err(err) = npm_resolution_initializer.ensure_initialized().await {

View file

@ -5751,9 +5751,6 @@ impl TscRequest {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use deno_npm::registry::NpmPackageInfo;
use deno_npm::registry::NpmRegistryApi;
use deno_npm::registry::NpmRegistryPackageInfoLoadError;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use test_util::TempDir; use test_util::TempDir;
@ -5770,16 +5767,20 @@ mod tests {
struct DefaultRegistry; struct DefaultRegistry;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl deno_npm::registry::NpmRegistryApi for DefaultRegistry { impl deno_lockfile::NpmPackageInfoProvider for DefaultRegistry {
async fn package_info( async fn get_npm_package_info(
&self, &self,
_name: &str, values: &[deno_semver::package::PackageNv],
) -> Result<Arc<NpmPackageInfo>, NpmRegistryPackageInfoLoadError> { ) -> Result<
Ok(Arc::new(NpmPackageInfo::default())) Vec<deno_lockfile::Lockfile5NpmInfo>,
Box<dyn std::error::Error + Send + Sync>,
> {
Ok(values.iter().map(|_| Default::default()).collect())
} }
} }
fn default_registry() -> Arc<dyn NpmRegistryApi + Send + Sync> { fn default_registry(
) -> Arc<dyn deno_lockfile::NpmPackageInfoProvider + Send + Sync> {
Arc::new(DefaultRegistry) Arc::new(DefaultRegistry)
} }

View file

@ -193,7 +193,7 @@ async fn run_subcommand(flags: Arc<Flags>) -> Result<i32, AnyError> {
", colors::cyan("deno lsp")); ", colors::cyan("deno lsp"));
} }
let factory = CliFactory::from_flags(flags.clone()); let factory = CliFactory::from_flags(flags.clone());
lsp::start(Arc::new(factory.npm_registry_info_provider()?.as_npm_registry_api())).await lsp::start(Arc::new(factory.lockfile_npm_package_info_provider()?)).await
}), }),
DenoSubcommand::Lint(lint_flags) => spawn_subcommand(async { DenoSubcommand::Lint(lint_flags) => spawn_subcommand(async {
if lint_flags.rules { if lint_flags.rules {

View file

@ -1,6 +1,5 @@
// Copyright 2018-2025 the Deno authors. MIT license. // Copyright 2018-2025 the Deno authors. MIT license.
use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
@ -14,6 +13,7 @@ use deno_semver::package::PackageNv;
use super::PackageCaching; use super::PackageCaching;
use crate::npm::CliNpmCache; use crate::npm::CliNpmCache;
use crate::npm::WorkspaceNpmPatchPackages;
pub mod bin_entries; pub mod bin_entries;
pub mod lifecycle_scripts; pub mod lifecycle_scripts;
@ -56,6 +56,7 @@ pub struct ExtraInfoProvider {
npm_cache: Arc<CliNpmCache>, npm_cache: Arc<CliNpmCache>,
npm_registry_info_provider: Arc<dyn NpmRegistryApi + Send + Sync>, npm_registry_info_provider: Arc<dyn NpmRegistryApi + Send + Sync>,
cache: RwLock<rustc_hash::FxHashMap<PackageNv, NpmPackageExtraInfo>>, cache: RwLock<rustc_hash::FxHashMap<PackageNv, NpmPackageExtraInfo>>,
workspace_patch_packages: Arc<WorkspaceNpmPatchPackages>,
} }
impl std::fmt::Debug for ExtraInfoProvider { impl std::fmt::Debug for ExtraInfoProvider {
@ -71,11 +72,13 @@ impl ExtraInfoProvider {
pub fn new( pub fn new(
npm_cache: Arc<CliNpmCache>, npm_cache: Arc<CliNpmCache>,
npm_registry_info_provider: Arc<dyn NpmRegistryApi + Send + Sync>, npm_registry_info_provider: Arc<dyn NpmRegistryApi + Send + Sync>,
workspace_patch_packages: Arc<WorkspaceNpmPatchPackages>,
) -> Self { ) -> Self {
Self { Self {
npm_cache, npm_cache,
npm_registry_info_provider, npm_registry_info_provider,
cache: RwLock::new(rustc_hash::FxHashMap::default()), cache: RwLock::new(rustc_hash::FxHashMap::default()),
workspace_patch_packages,
} }
} }
} }
@ -107,9 +110,8 @@ impl ExtraInfoProvider {
.package_info(&package_nv.name) .package_info(&package_nv.name)
.await .await
.map_err(JsErrorBox::from_err)?; .map_err(JsErrorBox::from_err)?;
let patched_packages = HashMap::new();
let version_info = package_info let version_info = package_info
.version_info(package_nv, &patched_packages) .version_info(package_nv, &self.workspace_patch_packages.0)
.map_err(JsErrorBox::from_err)?; .map_err(JsErrorBox::from_err)?;
Ok(NpmPackageExtraInfo { Ok(NpmPackageExtraInfo {
deprecated: version_info.deprecated.clone(), deprecated: version_info.deprecated.clone(),

View file

@ -49,6 +49,7 @@ use crate::colors;
use crate::npm::installer::common::NpmPackageExtraInfoProvider; use crate::npm::installer::common::NpmPackageExtraInfoProvider;
use crate::npm::CliNpmCache; use crate::npm::CliNpmCache;
use crate::npm::CliNpmTarballCache; use crate::npm::CliNpmTarballCache;
use crate::npm::WorkspaceNpmPatchPackages;
use crate::sys::CliSys; use crate::sys::CliSys;
use crate::util::fs::clone_dir_recursive; use crate::util::fs::clone_dir_recursive;
use crate::util::fs::symlink_dir; use crate::util::fs::symlink_dir;
@ -70,6 +71,7 @@ pub struct LocalNpmPackageInstaller {
system_info: NpmSystemInfo, system_info: NpmSystemInfo,
npm_registry_info_provider: npm_registry_info_provider:
Arc<dyn deno_npm::registry::NpmRegistryApi + Send + Sync>, Arc<dyn deno_npm::registry::NpmRegistryApi + Send + Sync>,
workspace_patch_packages: Arc<WorkspaceNpmPatchPackages>,
} }
impl std::fmt::Debug for LocalNpmPackageInstaller { impl std::fmt::Debug for LocalNpmPackageInstaller {
@ -103,6 +105,7 @@ impl LocalNpmPackageInstaller {
npm_registry_info_provider: Arc< npm_registry_info_provider: Arc<
dyn deno_npm::registry::NpmRegistryApi + Send + Sync, dyn deno_npm::registry::NpmRegistryApi + Send + Sync,
>, >,
workspace_patch_packages: Arc<WorkspaceNpmPatchPackages>,
) -> Self { ) -> Self {
Self { Self {
cache, cache,
@ -115,6 +118,7 @@ impl LocalNpmPackageInstaller {
root_node_modules_path: node_modules_folder, root_node_modules_path: node_modules_folder,
system_info, system_info,
npm_registry_info_provider, npm_registry_info_provider,
workspace_patch_packages,
} }
} }
} }
@ -140,6 +144,7 @@ impl NpmPackageFsInstaller for LocalNpmPackageInstaller {
&self.sys, &self.sys,
&self.system_info, &self.system_info,
&self.lifecycle_scripts, &self.lifecycle_scripts,
&self.workspace_patch_packages,
) )
.await .await
.map_err(JsErrorBox::from_err) .map_err(JsErrorBox::from_err)
@ -246,6 +251,7 @@ async fn sync_resolution_with_fs(
sys: &CliSys, sys: &CliSys,
system_info: &NpmSystemInfo, system_info: &NpmSystemInfo,
lifecycle_scripts_config: &LifecycleScriptsConfig, lifecycle_scripts_config: &LifecycleScriptsConfig,
workspace_patch_packages: &Arc<WorkspaceNpmPatchPackages>,
) -> Result<(), SyncResolutionWithFsError> { ) -> Result<(), SyncResolutionWithFsError> {
if snapshot.is_empty() && npm_install_deps_provider.local_pkgs().is_empty() { if snapshot.is_empty() && npm_install_deps_provider.local_pkgs().is_empty() {
return Ok(()); // don't create the directory return Ok(()); // don't create the directory
@ -318,6 +324,7 @@ async fn sync_resolution_with_fs(
let extra_info_provider = Arc::new(super::common::ExtraInfoProvider::new( let extra_info_provider = Arc::new(super::common::ExtraInfoProvider::new(
cache.clone(), cache.clone(),
npm_registry_info_provider.clone(), npm_registry_info_provider.clone(),
workspace_patch_packages.clone(),
)); ));
for package in &package_partitions.packages { for package in &package_partitions.packages {
if let Some(current_pkg) = if let Some(current_pkg) =

View file

@ -21,6 +21,7 @@ use self::local::LocalNpmPackageInstaller;
pub use self::resolution::AddPkgReqsResult; pub use self::resolution::AddPkgReqsResult;
pub use self::resolution::NpmResolutionInstaller; pub use self::resolution::NpmResolutionInstaller;
use super::NpmResolutionInitializer; use super::NpmResolutionInitializer;
use super::WorkspaceNpmPatchPackages;
use crate::args::CliLockfile; use crate::args::CliLockfile;
use crate::args::LifecycleScriptsConfig; use crate::args::LifecycleScriptsConfig;
use crate::args::NpmInstallDepsProvider; use crate::args::NpmInstallDepsProvider;
@ -71,6 +72,7 @@ impl NpmInstaller {
maybe_node_modules_path: Option<PathBuf>, maybe_node_modules_path: Option<PathBuf>,
lifecycle_scripts: LifecycleScriptsConfig, lifecycle_scripts: LifecycleScriptsConfig,
system_info: NpmSystemInfo, system_info: NpmSystemInfo,
workspace_patch_packages: Arc<WorkspaceNpmPatchPackages>,
) -> Self { ) -> Self {
let fs_installer: Arc<dyn NpmPackageFsInstaller> = let fs_installer: Arc<dyn NpmPackageFsInstaller> =
match maybe_node_modules_path { match maybe_node_modules_path {
@ -85,6 +87,7 @@ impl NpmInstaller {
lifecycle_scripts, lifecycle_scripts,
system_info, system_info,
npm_registry_info_provider, npm_registry_info_provider,
workspace_patch_packages,
)), )),
None => Arc::new(GlobalNpmPackageInstaller::new( None => Arc::new(GlobalNpmPackageInstaller::new(
npm_cache, npm_cache,

View file

@ -8,7 +8,6 @@ use std::sync::Arc;
use dashmap::DashMap; use dashmap::DashMap;
use deno_config::workspace::Workspace; use deno_config::workspace::Workspace;
use deno_core::anyhow::anyhow;
use deno_core::futures::stream::FuturesOrdered; use deno_core::futures::stream::FuturesOrdered;
use deno_core::futures::TryStreamExt; use deno_core::futures::TryStreamExt;
use deno_core::serde_json; use deno_core::serde_json;
@ -56,7 +55,85 @@ pub type CliNpmResolverCreateOptions =
pub type CliByonmNpmResolverCreateOptions = pub type CliByonmNpmResolverCreateOptions =
ByonmNpmResolverCreateOptions<CliSys>; ByonmNpmResolverCreateOptions<CliSys>;
pub struct NpmPackageInfoApiAdapter(pub Arc<dyn NpmRegistryApi + Send + Sync>); pub struct NpmPackageInfoApiAdapter {
api: Arc<dyn NpmRegistryApi + Send + Sync>,
workspace_patch_packages: Arc<WorkspaceNpmPatchPackages>,
}
impl NpmPackageInfoApiAdapter {
pub fn new(
api: Arc<dyn NpmRegistryApi + Send + Sync>,
workspace_patch_packages: Arc<WorkspaceNpmPatchPackages>,
) -> Self {
Self {
api,
workspace_patch_packages,
}
}
}
async fn get_infos(
info_provider: &(dyn NpmRegistryApi + Send + Sync),
workspace_patch_packages: &WorkspaceNpmPatchPackages,
values: &[PackageNv],
) -> Result<
Vec<deno_lockfile::Lockfile5NpmInfo>,
Box<dyn std::error::Error + Send + Sync>,
> {
let futs = values
.iter()
.map(|v| async move {
let info = info_provider.package_info(v.name.as_str()).await?;
let version_info = info.version_info(v, &workspace_patch_packages.0)?;
Ok::<_, Box<dyn std::error::Error + Send + Sync>>(
deno_lockfile::Lockfile5NpmInfo {
tarball_url: version_info.dist.as_ref().and_then(|d| {
if d.tarball
== DefaultTarballUrl.default_tarball_url(&NpmPackageId {
nv: v.clone(),
// TODO(nathanwhit): this function takes an `NpmPackageId`
// but it should take a `PackageNv`. For now just
// use default here.
peer_dependencies: Default::default(),
})
{
None
} else {
Some(d.tarball.clone())
}
}),
optional_dependencies: version_info
.optional_dependencies
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect::<std::collections::BTreeMap<_, _>>(),
cpu: version_info.cpu.iter().map(|s| s.to_string()).collect(),
os: version_info.os.iter().map(|s| s.to_string()).collect(),
deprecated: version_info.deprecated.is_some(),
has_bin: version_info.bin.is_some(),
has_scripts: version_info.scripts.contains_key("preinstall")
|| version_info.scripts.contains_key("install")
|| version_info.scripts.contains_key("postinstall"),
optional_peers: version_info
.peer_dependencies_meta
.iter()
.filter_map(|(k, v)| {
if v.optional {
version_info
.peer_dependencies
.get(k)
.map(|v| (k.to_string(), v.to_string()))
} else {
None
}
})
.collect::<std::collections::BTreeMap<_, _>>(),
},
)
})
.collect::<FuturesOrdered<_>>();
let package_infos = futs.try_collect::<Vec<_>>().await?;
Ok(package_infos)
}
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl deno_lockfile::NpmPackageInfoProvider for NpmPackageInfoApiAdapter { impl deno_lockfile::NpmPackageInfoProvider for NpmPackageInfoApiAdapter {
@ -67,62 +144,19 @@ impl deno_lockfile::NpmPackageInfoProvider for NpmPackageInfoApiAdapter {
Vec<deno_lockfile::Lockfile5NpmInfo>, Vec<deno_lockfile::Lockfile5NpmInfo>,
Box<dyn std::error::Error + Send + Sync>, Box<dyn std::error::Error + Send + Sync>,
> { > {
let futs = values let package_infos =
.iter() get_infos(&*self.api, &self.workspace_patch_packages, values).await;
.map(|v| async move {
let info = self.0.package_info(v.name.as_str()).await?; match package_infos {
let version_info = info.versions.get(&v.version).ok_or_else(|| { Ok(package_infos) => Ok(package_infos),
anyhow!("Version {} not found for package {}", v.version, v.name) Err(err) => {
})?; if self.api.mark_force_reload() {
Ok::<_, Box<dyn std::error::Error + Send + Sync>>( get_infos(&*self.api, &self.workspace_patch_packages, values).await
deno_lockfile::Lockfile5NpmInfo { } else {
tarball_url: version_info.dist.as_ref().and_then(|d| { Err(err)
if d.tarball }
== DefaultTarballUrl.default_tarball_url(&NpmPackageId { }
nv: v.clone(), }
// TODO(nathanwhit): this function takes an `NpmPackageId`
// but it should take a `PackageNv`. For now just
// use default here.
peer_dependencies: Default::default(),
})
{
None
} else {
Some(d.tarball.clone())
}
}),
optional_dependencies: version_info
.optional_dependencies
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect::<std::collections::BTreeMap<_, _>>(),
cpu: version_info.cpu.iter().map(|s| s.to_string()).collect(),
os: version_info.os.iter().map(|s| s.to_string()).collect(),
deprecated: version_info.deprecated.is_some(),
has_bin: version_info.bin.is_some(),
has_scripts: version_info.scripts.contains_key("preinstall")
|| version_info.scripts.contains_key("install")
|| version_info.scripts.contains_key("postinstall"),
optional_peers: version_info
.peer_dependencies_meta
.iter()
.filter_map(|(k, v)| {
if v.optional {
version_info
.peer_dependencies
.get(k)
.map(|v| (k.to_string(), v.to_string()))
} else {
None
}
})
.collect::<std::collections::BTreeMap<_, _>>(),
},
)
})
.collect::<FuturesOrdered<_>>();
let package_infos = futs.try_collect::<Vec<_>>().await?;
Ok(package_infos)
} }
} }

View file

@ -62,7 +62,7 @@ pub async fn kernel(
let factory = CliFactory::from_flags(flags); let factory = CliFactory::from_flags(flags);
let registry_provider = let registry_provider =
Arc::new(factory.npm_registry_info_provider()?.as_npm_registry_api()); Arc::new(factory.lockfile_npm_package_info_provider()?);
let cli_options = factory.cli_options()?; let cli_options = factory.cli_options()?;
let main_module = let main_module =
resolve_url_or_path("./$deno$jupyter.mts", cli_options.initial_cwd()) resolve_url_or_path("./$deno$jupyter.mts", cli_options.initial_cwd())

View file

@ -200,7 +200,7 @@ pub async fn run(
worker, worker,
main_module.clone(), main_module.clone(),
test_event_receiver, test_event_receiver,
Arc::new(factory.npm_registry_info_provider()?.as_npm_registry_api()), Arc::new(factory.lockfile_npm_package_info_provider()?),
) )
.await?; .await?;
let rustyline_channel = rustyline_channel(); let rustyline_channel = rustyline_channel();

View file

@ -33,7 +33,6 @@ use deno_graph::Position;
use deno_graph::PositionRange; use deno_graph::PositionRange;
use deno_graph::SpecifierWithRange; use deno_graph::SpecifierWithRange;
use deno_lib::util::result::any_and_jserrorbox_downcast_ref; use deno_lib::util::result::any_and_jserrorbox_downcast_ref;
use deno_npm::registry::NpmRegistryApi;
use deno_runtime::worker::MainWorker; use deno_runtime::worker::MainWorker;
use deno_semver::npm::NpmPackageReqReference; use deno_semver::npm::NpmPackageReqReference;
use node_resolver::NodeResolutionKind; use node_resolver::NodeResolutionKind;
@ -210,7 +209,9 @@ impl ReplSession {
mut worker: MainWorker, mut worker: MainWorker,
main_module: ModuleSpecifier, main_module: ModuleSpecifier,
test_event_receiver: TestEventReceiver, test_event_receiver: TestEventReceiver,
registry_provider: Arc<dyn NpmRegistryApi + Send + Sync>, registry_provider: Arc<
dyn deno_lockfile::NpmPackageInfoProvider + Send + Sync,
>,
) -> Result<Self, AnyError> { ) -> Result<Self, AnyError> {
let language_server = let language_server =
ReplLanguageServer::new_initialized(registry_provider).await?; ReplLanguageServer::new_initialized(registry_provider).await?;

View file

@ -0,0 +1,37 @@
{
"tempDir": true,
"envs": {
"DENO_DIR": "$PWD/deno_dir"
},
"steps": [
{
"args": "install",
"output": "[WILDCARD]"
},
{
"args": "run -A --no-lock ./rm-version.ts $PWD/deno_dir/npm/localhost_4260/@denotest/add/registry.json 1.0.0",
"output": "[WILDCARD]"
},
{
"args": "run -A --no-lock ./mv.ts ./deno.lock.old ./deno.lock",
"output": ""
},
// trigger a lockfile update
{
"args": "install npm:chalk",
"output": "[WILDCARD]"
},
{
"args": "remove npm:chalk",
"output": "[WILDCARD]"
},
{
"args": [
"eval",
"--no-lock",
"console.log(Deno.readTextFileSync('./deno.lock').trim())"
],
"output": "deno.lock.out"
}
]
}

View file

@ -0,0 +1,5 @@
{
"imports": {
"@denotest/add": "npm:@denotest/add@1.0.0"
}
}

View file

@ -0,0 +1,16 @@
{
"version": "4",
"specifiers": {
"npm:@denotest/add@1.0.0": "1.0.0"
},
"npm": {
"@denotest/add@1.0.0": {
"integrity": "3b2e675c1ad7fba2a45bc251992e01aff08a3c974ac09079b11e6a5b95d4bfcb"
}
},
"workspace": {
"dependencies": [
"npm:@denotest/add@1.0.0"
]
}
}

View file

@ -0,0 +1,17 @@
{
"version": "5",
"specifiers": {
"npm:@denotest/add@1.0.0": "1.0.0"
},
"npm": {
"@denotest/add@1.0.0": {
"integrity": "sha512-P7KqAn8qFLI/13TfERSPS0NYt7CmA0diT6bhscyT6FzZjFaAVCja3K/dk96eZDsCyI1gtwtHg5Ahh8XDIvAgrw==",
"tarball": "http://localhost:4260/@denotest/add/1.0.0.tgz"
}
},
"workspace": {
"dependencies": [
"npm:@denotest/add@1.0.0"
]
}
}

View file

@ -0,0 +1,3 @@
const [from, to] = Deno.args;
Deno.renameSync(from.trim(), to.trim());

View file

@ -0,0 +1,14 @@
const registryPath = Deno.args[0].trim();
const version = Deno.args[1].trim();
const registryJson = JSON.parse(Deno.readTextFileSync(registryPath));
delete registryJson.versions[version];
if (registryJson["dist-tags"]["latest"] === version) {
const latestVersion = Object.keys(registryJson.versions).sort()[0];
registryJson["dist-tags"]["latest"] = latestVersion;
}
const registryJsonString = JSON.stringify(registryJson, null, 2);
Deno.writeTextFileSync(registryPath, registryJsonString);
console.log(registryJsonString);