mirror of
https://github.com/denoland/deno.git
synced 2024-12-18 21:35:31 -05:00
6f506208f6
Currently deno eagerly caches all npm packages in the workspace's npm resolution. So, for instance, running a file `foo.ts` that imports `npm:chalk` will also install all dependencies listed in `package.json` and all `npm` dependencies listed in the lockfile. This PR refactors things to give more control over when and what npm packages are automatically cached while building the module graph. After this PR, by default the current behavior is unchanged _except_ for `deno install --entrypoint`, which will only cache npm packages used by the given entrypoint. For the other subcommands, this behavior can be enabled with `--unstable-npm-lazy-caching` Fixes #25782. --------- Signed-off-by: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Co-authored-by: Luca Casonato <hello@lcas.dev>
807 lines
25 KiB
Rust
807 lines
25 KiB
Rust
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
|
|
use std::collections::HashMap;
|
|
use std::collections::HashSet;
|
|
use std::fmt;
|
|
use std::fmt::Write;
|
|
use std::sync::Arc;
|
|
|
|
use deno_ast::ModuleSpecifier;
|
|
use deno_core::anyhow::bail;
|
|
use deno_core::error::AnyError;
|
|
use deno_core::resolve_url_or_path;
|
|
use deno_core::serde_json;
|
|
use deno_core::url;
|
|
use deno_graph::Dependency;
|
|
use deno_graph::GraphKind;
|
|
use deno_graph::Module;
|
|
use deno_graph::ModuleError;
|
|
use deno_graph::ModuleGraph;
|
|
use deno_graph::Resolution;
|
|
use deno_npm::npm_rc::ResolvedNpmRc;
|
|
use deno_npm::resolution::NpmResolutionSnapshot;
|
|
use deno_npm::NpmPackageId;
|
|
use deno_npm::NpmResolutionPackage;
|
|
use deno_semver::npm::NpmPackageNvReference;
|
|
use deno_semver::npm::NpmPackageReqReference;
|
|
use deno_semver::package::PackageNv;
|
|
use deno_terminal::colors;
|
|
|
|
use crate::args::Flags;
|
|
use crate::args::InfoFlags;
|
|
use crate::display;
|
|
use crate::factory::CliFactory;
|
|
use crate::graph_util::graph_exit_integrity_errors;
|
|
use crate::npm::CliNpmResolver;
|
|
use crate::npm::ManagedCliNpmResolver;
|
|
use crate::util::checksum;
|
|
|
|
const JSON_SCHEMA_VERSION: u8 = 1;
|
|
|
|
pub async fn info(
|
|
flags: Arc<Flags>,
|
|
info_flags: InfoFlags,
|
|
) -> Result<(), AnyError> {
|
|
let factory = CliFactory::from_flags(flags);
|
|
let cli_options = factory.cli_options()?;
|
|
if let Some(specifier) = info_flags.file {
|
|
let module_graph_builder = factory.module_graph_builder().await?;
|
|
let module_graph_creator = factory.module_graph_creator().await?;
|
|
let npm_resolver = factory.npm_resolver().await?;
|
|
let maybe_lockfile = cli_options.maybe_lockfile();
|
|
let resolver = factory.workspace_resolver().await?.clone();
|
|
let npmrc = cli_options.npmrc();
|
|
let node_resolver = factory.node_resolver().await?;
|
|
|
|
let cwd_url =
|
|
url::Url::from_directory_path(cli_options.initial_cwd()).unwrap();
|
|
|
|
let maybe_import_specifier = if let Ok(resolved) =
|
|
resolver.resolve(&specifier, &cwd_url)
|
|
{
|
|
match resolved {
|
|
deno_config::workspace::MappedResolution::Normal {
|
|
specifier, ..
|
|
}
|
|
| deno_config::workspace::MappedResolution::ImportMap {
|
|
specifier,
|
|
..
|
|
}
|
|
| deno_config::workspace::MappedResolution::WorkspaceJsrPackage {
|
|
specifier,
|
|
..
|
|
} => Some(specifier),
|
|
deno_config::workspace::MappedResolution::WorkspaceNpmPackage {
|
|
target_pkg_json,
|
|
sub_path,
|
|
..
|
|
} => Some(node_resolver.resolve_package_subpath_from_deno_module(
|
|
target_pkg_json.clone().dir_path(),
|
|
sub_path.as_deref(),
|
|
Some(&cwd_url),
|
|
node_resolver::ResolutionMode::Import,
|
|
node_resolver::NodeResolutionKind::Execution,
|
|
)?),
|
|
deno_config::workspace::MappedResolution::PackageJson {
|
|
alias,
|
|
sub_path,
|
|
dep_result,
|
|
..
|
|
} => match dep_result.as_ref().map_err(|e| e.clone())? {
|
|
deno_package_json::PackageJsonDepValue::Workspace(version_req) => {
|
|
let pkg_folder = resolver
|
|
.resolve_workspace_pkg_json_folder_for_pkg_json_dep(
|
|
alias,
|
|
version_req,
|
|
)?;
|
|
Some(node_resolver.resolve_package_subpath_from_deno_module(
|
|
pkg_folder,
|
|
sub_path.as_deref(),
|
|
Some(&cwd_url),
|
|
node_resolver::ResolutionMode::Import,
|
|
node_resolver::NodeResolutionKind::Execution,
|
|
)?)
|
|
}
|
|
deno_package_json::PackageJsonDepValue::Req(req) => {
|
|
Some(ModuleSpecifier::parse(&format!(
|
|
"npm:{}{}",
|
|
req,
|
|
sub_path.map(|s| format!("/{}", s)).unwrap_or_default()
|
|
))?)
|
|
}
|
|
},
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let specifier = match maybe_import_specifier {
|
|
Some(specifier) => specifier,
|
|
None => resolve_url_or_path(&specifier, cli_options.initial_cwd())?,
|
|
};
|
|
|
|
let mut loader = module_graph_builder.create_graph_loader();
|
|
loader.enable_loading_cache_info(); // for displaying the cache information
|
|
let graph = module_graph_creator
|
|
.create_graph_with_loader(
|
|
GraphKind::All,
|
|
vec![specifier],
|
|
&mut loader,
|
|
crate::graph_util::NpmCachingStrategy::Eager,
|
|
)
|
|
.await?;
|
|
|
|
// write out the lockfile if there is one
|
|
if let Some(lockfile) = &maybe_lockfile {
|
|
graph_exit_integrity_errors(&graph);
|
|
lockfile.write_if_changed()?;
|
|
}
|
|
|
|
if info_flags.json {
|
|
let mut json_graph = serde_json::json!(graph);
|
|
if let Some(output) = json_graph.as_object_mut() {
|
|
output.shift_insert(
|
|
0,
|
|
"version".to_string(),
|
|
JSON_SCHEMA_VERSION.into(),
|
|
);
|
|
}
|
|
|
|
add_npm_packages_to_json(&mut json_graph, npm_resolver.as_ref(), npmrc);
|
|
display::write_json_to_stdout(&json_graph)?;
|
|
} else {
|
|
let mut output = String::new();
|
|
GraphDisplayContext::write(&graph, npm_resolver.as_ref(), &mut output)?;
|
|
display::write_to_stdout_ignore_sigpipe(output.as_bytes())?;
|
|
}
|
|
} else {
|
|
// If it was just "deno info" print location of caches and exit
|
|
print_cache_info(
|
|
&factory,
|
|
info_flags.json,
|
|
cli_options.location_flag().as_ref(),
|
|
)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[allow(clippy::print_stdout)]
|
|
fn print_cache_info(
|
|
factory: &CliFactory,
|
|
json: bool,
|
|
location: Option<&deno_core::url::Url>,
|
|
) -> Result<(), AnyError> {
|
|
let dir = factory.deno_dir()?;
|
|
#[allow(deprecated)]
|
|
let modules_cache = factory.global_http_cache()?.get_global_cache_location();
|
|
let npm_cache = factory.deno_dir()?.npm_folder_path();
|
|
let typescript_cache = &dir.gen_cache.location;
|
|
let registry_cache = dir.registries_folder_path();
|
|
let mut origin_dir = dir.origin_data_folder_path();
|
|
let deno_dir = dir.root_path_for_display().to_string();
|
|
let web_cache_dir = crate::worker::get_cache_storage_dir();
|
|
|
|
if let Some(location) = &location {
|
|
origin_dir =
|
|
origin_dir.join(checksum::gen(&[location.to_string().as_bytes()]));
|
|
}
|
|
|
|
let local_storage_dir = origin_dir.join("local_storage");
|
|
|
|
if json {
|
|
let mut json_output = serde_json::json!({
|
|
"version": JSON_SCHEMA_VERSION,
|
|
"denoDir": deno_dir,
|
|
"modulesCache": modules_cache,
|
|
"npmCache": npm_cache,
|
|
"typescriptCache": typescript_cache,
|
|
"registryCache": registry_cache,
|
|
"originStorage": origin_dir,
|
|
"webCacheStorage": web_cache_dir,
|
|
});
|
|
|
|
if location.is_some() {
|
|
json_output["localStorage"] = serde_json::to_value(local_storage_dir)?;
|
|
}
|
|
|
|
display::write_json_to_stdout(&json_output)
|
|
} else {
|
|
println!("{} {}", colors::bold("DENO_DIR location:"), deno_dir);
|
|
println!(
|
|
"{} {}",
|
|
colors::bold("Remote modules cache:"),
|
|
modules_cache.display()
|
|
);
|
|
println!(
|
|
"{} {}",
|
|
colors::bold("npm modules cache:"),
|
|
npm_cache.display()
|
|
);
|
|
println!(
|
|
"{} {}",
|
|
colors::bold("Emitted modules cache:"),
|
|
typescript_cache.display()
|
|
);
|
|
println!(
|
|
"{} {}",
|
|
colors::bold("Language server registries cache:"),
|
|
registry_cache.display(),
|
|
);
|
|
println!(
|
|
"{} {}",
|
|
colors::bold("Origin storage:"),
|
|
origin_dir.display()
|
|
);
|
|
println!(
|
|
"{} {}",
|
|
colors::bold("Web cache storage:"),
|
|
web_cache_dir.display()
|
|
);
|
|
if location.is_some() {
|
|
println!(
|
|
"{} {}",
|
|
colors::bold("Local Storage:"),
|
|
local_storage_dir.display(),
|
|
);
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn add_npm_packages_to_json(
|
|
json: &mut serde_json::Value,
|
|
npm_resolver: &dyn CliNpmResolver,
|
|
npmrc: &ResolvedNpmRc,
|
|
) {
|
|
let Some(npm_resolver) = npm_resolver.as_managed() else {
|
|
return; // does not include byonm to deno info's output
|
|
};
|
|
|
|
// ideally deno_graph could handle this, but for now we just modify the json here
|
|
let snapshot = npm_resolver.snapshot();
|
|
let json = json.as_object_mut().unwrap();
|
|
let modules = json.get_mut("modules").and_then(|m| m.as_array_mut());
|
|
if let Some(modules) = modules {
|
|
for module in modules.iter_mut() {
|
|
if matches!(module.get("kind").and_then(|k| k.as_str()), Some("npm")) {
|
|
// If there is only one module and it's "external", then that means
|
|
// someone provided an npm specifier as a cli argument. In this case,
|
|
// we want to show which npm package the cli argument resolved to.
|
|
let maybe_package = module
|
|
.get("specifier")
|
|
.and_then(|k| k.as_str())
|
|
.and_then(|specifier| NpmPackageNvReference::from_str(specifier).ok())
|
|
.and_then(|package_ref| {
|
|
snapshot
|
|
.resolve_package_from_deno_module(package_ref.nv())
|
|
.ok()
|
|
});
|
|
if let Some(pkg) = maybe_package {
|
|
if let Some(module) = module.as_object_mut() {
|
|
module
|
|
.insert("npmPackage".to_string(), pkg.id.as_serialized().into());
|
|
}
|
|
}
|
|
}
|
|
|
|
let dependencies = module
|
|
.get_mut("dependencies")
|
|
.and_then(|d| d.as_array_mut());
|
|
if let Some(dependencies) = dependencies {
|
|
for dep in dependencies.iter_mut().flat_map(|d| d.as_object_mut()) {
|
|
if let Some(specifier) = dep.get("specifier").and_then(|s| s.as_str())
|
|
{
|
|
if let Ok(npm_ref) = NpmPackageReqReference::from_str(specifier) {
|
|
if let Ok(pkg) = snapshot.resolve_pkg_from_pkg_req(npm_ref.req())
|
|
{
|
|
dep.insert(
|
|
"npmPackage".to_string(),
|
|
pkg.id.as_serialized().into(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// don't show this in the output unless someone needs it
|
|
if let Some(code) =
|
|
dep.get_mut("code").and_then(|c| c.as_object_mut())
|
|
{
|
|
code.remove("resolutionMode");
|
|
}
|
|
if let Some(types) =
|
|
dep.get_mut("types").and_then(|c| c.as_object_mut())
|
|
{
|
|
types.remove("resolutionMode");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut sorted_packages =
|
|
snapshot.all_packages_for_every_system().collect::<Vec<_>>();
|
|
sorted_packages.sort_by(|a, b| a.id.cmp(&b.id));
|
|
let mut json_packages = serde_json::Map::with_capacity(sorted_packages.len());
|
|
for pkg in sorted_packages {
|
|
let mut kv = serde_json::Map::new();
|
|
kv.insert("name".to_string(), pkg.id.nv.name.clone().into());
|
|
kv.insert("version".to_string(), pkg.id.nv.version.to_string().into());
|
|
let mut deps = pkg.dependencies.values().collect::<Vec<_>>();
|
|
deps.sort();
|
|
let deps = deps
|
|
.into_iter()
|
|
.map(|id| serde_json::Value::String(id.as_serialized()))
|
|
.collect::<Vec<_>>();
|
|
kv.insert("dependencies".to_string(), deps.into());
|
|
let registry_url = npmrc.get_registry_url(&pkg.id.nv.name);
|
|
kv.insert("registryUrl".to_string(), registry_url.to_string().into());
|
|
|
|
json_packages.insert(pkg.id.as_serialized(), kv.into());
|
|
}
|
|
|
|
json.insert("npmPackages".to_string(), json_packages.into());
|
|
}
|
|
|
|
struct TreeNode {
|
|
text: String,
|
|
children: Vec<TreeNode>,
|
|
}
|
|
|
|
impl TreeNode {
|
|
pub fn from_text(text: String) -> Self {
|
|
Self {
|
|
text,
|
|
children: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn print_tree_node<TWrite: Write>(
|
|
tree_node: &TreeNode,
|
|
writer: &mut TWrite,
|
|
) -> fmt::Result {
|
|
fn print_children<TWrite: Write>(
|
|
writer: &mut TWrite,
|
|
prefix: &str,
|
|
children: &[TreeNode],
|
|
) -> fmt::Result {
|
|
const SIBLING_CONNECTOR: char = '├';
|
|
const LAST_SIBLING_CONNECTOR: char = '└';
|
|
const CHILD_DEPS_CONNECTOR: char = '┬';
|
|
const CHILD_NO_DEPS_CONNECTOR: char = '─';
|
|
const VERTICAL_CONNECTOR: char = '│';
|
|
const EMPTY_CONNECTOR: char = ' ';
|
|
|
|
let child_len = children.len();
|
|
for (index, child) in children.iter().enumerate() {
|
|
let is_last = index + 1 == child_len;
|
|
let sibling_connector = if is_last {
|
|
LAST_SIBLING_CONNECTOR
|
|
} else {
|
|
SIBLING_CONNECTOR
|
|
};
|
|
let child_connector = if child.children.is_empty() {
|
|
CHILD_NO_DEPS_CONNECTOR
|
|
} else {
|
|
CHILD_DEPS_CONNECTOR
|
|
};
|
|
writeln!(
|
|
writer,
|
|
"{} {}",
|
|
colors::gray(format!("{prefix}{sibling_connector}─{child_connector}")),
|
|
child.text
|
|
)?;
|
|
let child_prefix = format!(
|
|
"{}{}{}",
|
|
prefix,
|
|
if is_last {
|
|
EMPTY_CONNECTOR
|
|
} else {
|
|
VERTICAL_CONNECTOR
|
|
},
|
|
EMPTY_CONNECTOR
|
|
);
|
|
print_children(writer, &child_prefix, &child.children)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
writeln!(writer, "{}", tree_node.text)?;
|
|
print_children(writer, "", &tree_node.children)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Precached information about npm packages that are used in deno info.
|
|
#[derive(Default)]
|
|
struct NpmInfo {
|
|
package_sizes: HashMap<NpmPackageId, u64>,
|
|
resolved_ids: HashMap<PackageNv, NpmPackageId>,
|
|
packages: HashMap<NpmPackageId, NpmResolutionPackage>,
|
|
}
|
|
|
|
impl NpmInfo {
|
|
pub fn build<'a>(
|
|
graph: &'a ModuleGraph,
|
|
npm_resolver: &'a ManagedCliNpmResolver,
|
|
npm_snapshot: &'a NpmResolutionSnapshot,
|
|
) -> Self {
|
|
let mut info = NpmInfo::default();
|
|
if graph.npm_packages.is_empty() {
|
|
return info; // skip going over the modules if there's no npm packages
|
|
}
|
|
|
|
for module in graph.modules() {
|
|
if let Module::Npm(module) = module {
|
|
let nv = module.nv_reference.nv();
|
|
if let Ok(package) = npm_snapshot.resolve_package_from_deno_module(nv) {
|
|
info.resolved_ids.insert(nv.clone(), package.id.clone());
|
|
if !info.packages.contains_key(&package.id) {
|
|
info.fill_package_info(package, npm_resolver, npm_snapshot);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
info
|
|
}
|
|
|
|
fn fill_package_info<'a>(
|
|
&mut self,
|
|
package: &NpmResolutionPackage,
|
|
npm_resolver: &'a ManagedCliNpmResolver,
|
|
npm_snapshot: &'a NpmResolutionSnapshot,
|
|
) {
|
|
self.packages.insert(package.id.clone(), package.clone());
|
|
if let Ok(size) = npm_resolver.package_size(&package.id) {
|
|
self.package_sizes.insert(package.id.clone(), size);
|
|
}
|
|
for id in package.dependencies.values() {
|
|
if !self.packages.contains_key(id) {
|
|
if let Some(package) = npm_snapshot.package_from_id(id) {
|
|
self.fill_package_info(package, npm_resolver, npm_snapshot);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn resolve_package(
|
|
&self,
|
|
nv: &PackageNv,
|
|
) -> Option<&NpmResolutionPackage> {
|
|
let id = self.resolved_ids.get(nv)?;
|
|
self.packages.get(id)
|
|
}
|
|
}
|
|
|
|
struct GraphDisplayContext<'a> {
|
|
graph: &'a ModuleGraph,
|
|
npm_info: NpmInfo,
|
|
seen: HashSet<String>,
|
|
}
|
|
|
|
impl<'a> GraphDisplayContext<'a> {
|
|
pub fn write<TWrite: Write>(
|
|
graph: &'a ModuleGraph,
|
|
npm_resolver: &'a dyn CliNpmResolver,
|
|
writer: &mut TWrite,
|
|
) -> Result<(), AnyError> {
|
|
let npm_info = match npm_resolver.as_managed() {
|
|
Some(npm_resolver) => {
|
|
let npm_snapshot = npm_resolver.snapshot();
|
|
NpmInfo::build(graph, npm_resolver, &npm_snapshot)
|
|
}
|
|
None => NpmInfo::default(),
|
|
};
|
|
Self {
|
|
graph,
|
|
npm_info,
|
|
seen: Default::default(),
|
|
}
|
|
.into_writer(writer)
|
|
}
|
|
|
|
fn into_writer<TWrite: Write>(
|
|
mut self,
|
|
writer: &mut TWrite,
|
|
) -> Result<(), AnyError> {
|
|
if self.graph.roots.is_empty() || self.graph.roots.len() > 1 {
|
|
bail!("displaying graphs that have multiple roots is not supported.");
|
|
}
|
|
|
|
let root_specifier = self.graph.resolve(&self.graph.roots[0]);
|
|
match self.graph.try_get(root_specifier) {
|
|
Ok(Some(root)) => {
|
|
let maybe_cache_info = match root {
|
|
Module::Js(module) => module.maybe_cache_info.as_ref(),
|
|
Module::Json(module) => module.maybe_cache_info.as_ref(),
|
|
Module::Wasm(module) => module.maybe_cache_info.as_ref(),
|
|
Module::Node(_) | Module::Npm(_) | Module::External(_) => None,
|
|
};
|
|
if let Some(cache_info) = maybe_cache_info {
|
|
if let Some(local) = &cache_info.local {
|
|
writeln!(
|
|
writer,
|
|
"{} {}",
|
|
colors::bold("local:"),
|
|
local.to_string_lossy()
|
|
)?;
|
|
}
|
|
}
|
|
if let Some(module) = root.js() {
|
|
writeln!(writer, "{} {}", colors::bold("type:"), module.media_type)?;
|
|
}
|
|
let total_modules_size = self
|
|
.graph
|
|
.modules()
|
|
.map(|m| {
|
|
let size = match m {
|
|
Module::Js(module) => module.size(),
|
|
Module::Json(module) => module.size(),
|
|
Module::Wasm(module) => module.size(),
|
|
Module::Node(_) | Module::Npm(_) | Module::External(_) => 0,
|
|
};
|
|
size as f64
|
|
})
|
|
.sum::<f64>();
|
|
let total_npm_package_size = self
|
|
.npm_info
|
|
.package_sizes
|
|
.values()
|
|
.map(|s| *s as f64)
|
|
.sum::<f64>();
|
|
let total_size = total_modules_size + total_npm_package_size;
|
|
let dep_count = self.graph.modules().count() - 1 // -1 for the root module
|
|
+ self.npm_info.packages.len()
|
|
- self.npm_info.resolved_ids.len();
|
|
writeln!(
|
|
writer,
|
|
"{} {} unique",
|
|
colors::bold("dependencies:"),
|
|
dep_count,
|
|
)?;
|
|
writeln!(
|
|
writer,
|
|
"{} {}",
|
|
colors::bold("size:"),
|
|
display::human_size(total_size),
|
|
)?;
|
|
writeln!(writer)?;
|
|
let root_node = self.build_module_info(root, false);
|
|
print_tree_node(&root_node, writer)?;
|
|
Ok(())
|
|
}
|
|
Err(err) => {
|
|
if let ModuleError::Missing(_, _) = *err {
|
|
bail!("module could not be found");
|
|
} else {
|
|
bail!("{:#}", err);
|
|
}
|
|
}
|
|
Ok(None) => {
|
|
bail!("an internal error occurred");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn build_dep_info(&mut self, dep: &Dependency) -> Vec<TreeNode> {
|
|
let mut children = Vec::with_capacity(2);
|
|
if !dep.maybe_code.is_none() {
|
|
if let Some(child) = self.build_resolved_info(&dep.maybe_code, false) {
|
|
children.push(child);
|
|
}
|
|
}
|
|
if !dep.maybe_type.is_none() {
|
|
if let Some(child) = self.build_resolved_info(&dep.maybe_type, true) {
|
|
children.push(child);
|
|
}
|
|
}
|
|
children
|
|
}
|
|
|
|
fn build_module_info(&mut self, module: &Module, type_dep: bool) -> TreeNode {
|
|
enum PackageOrSpecifier {
|
|
Package(Box<NpmResolutionPackage>),
|
|
Specifier(ModuleSpecifier),
|
|
}
|
|
|
|
use PackageOrSpecifier::*;
|
|
|
|
let package_or_specifier = match module.npm() {
|
|
Some(npm) => match self.npm_info.resolve_package(npm.nv_reference.nv()) {
|
|
Some(package) => Package(Box::new(package.clone())),
|
|
None => Specifier(module.specifier().clone()), // should never happen
|
|
},
|
|
None => Specifier(module.specifier().clone()),
|
|
};
|
|
let was_seen = !self.seen.insert(match &package_or_specifier {
|
|
Package(package) => package.id.as_serialized(),
|
|
Specifier(specifier) => specifier.to_string(),
|
|
});
|
|
let header_text = if was_seen {
|
|
let specifier_str = if type_dep {
|
|
colors::italic_gray(module.specifier()).to_string()
|
|
} else {
|
|
colors::gray(module.specifier()).to_string()
|
|
};
|
|
format!("{} {}", specifier_str, colors::gray("*"))
|
|
} else {
|
|
let header_text = if type_dep {
|
|
colors::italic(module.specifier()).to_string()
|
|
} else {
|
|
module.specifier().to_string()
|
|
};
|
|
let maybe_size = match &package_or_specifier {
|
|
Package(package) => {
|
|
self.npm_info.package_sizes.get(&package.id).copied()
|
|
}
|
|
Specifier(_) => match module {
|
|
Module::Js(module) => Some(module.size() as u64),
|
|
Module::Json(module) => Some(module.size() as u64),
|
|
Module::Wasm(module) => Some(module.size() as u64),
|
|
Module::Node(_) | Module::Npm(_) | Module::External(_) => None,
|
|
},
|
|
};
|
|
format!("{} {}", header_text, maybe_size_to_text(maybe_size))
|
|
};
|
|
|
|
let mut tree_node = TreeNode::from_text(header_text);
|
|
|
|
if !was_seen {
|
|
match &package_or_specifier {
|
|
Package(package) => {
|
|
tree_node.children.extend(self.build_npm_deps(package));
|
|
}
|
|
Specifier(_) => match module {
|
|
Module::Js(module) => {
|
|
if let Some(types_dep) = &module.maybe_types_dependency {
|
|
if let Some(child) =
|
|
self.build_resolved_info(&types_dep.dependency, true)
|
|
{
|
|
tree_node.children.push(child);
|
|
}
|
|
}
|
|
for dep in module.dependencies.values() {
|
|
tree_node.children.extend(self.build_dep_info(dep));
|
|
}
|
|
}
|
|
Module::Wasm(module) => {
|
|
for dep in module.dependencies.values() {
|
|
tree_node.children.extend(self.build_dep_info(dep));
|
|
}
|
|
}
|
|
Module::Json(_)
|
|
| Module::Npm(_)
|
|
| Module::Node(_)
|
|
| Module::External(_) => {}
|
|
},
|
|
}
|
|
}
|
|
tree_node
|
|
}
|
|
|
|
fn build_npm_deps(
|
|
&mut self,
|
|
package: &NpmResolutionPackage,
|
|
) -> Vec<TreeNode> {
|
|
let mut deps = package.dependencies.values().collect::<Vec<_>>();
|
|
deps.sort();
|
|
let mut children = Vec::with_capacity(deps.len());
|
|
for dep_id in deps.into_iter() {
|
|
let maybe_size = self.npm_info.package_sizes.get(dep_id).cloned();
|
|
let size_str = maybe_size_to_text(maybe_size);
|
|
let mut child = TreeNode::from_text(format!(
|
|
"npm:/{} {}",
|
|
dep_id.as_serialized(),
|
|
size_str
|
|
));
|
|
if let Some(package) = self.npm_info.packages.get(dep_id) {
|
|
if !package.dependencies.is_empty() {
|
|
let was_seen = !self.seen.insert(package.id.as_serialized());
|
|
if was_seen {
|
|
child.text = format!("{} {}", child.text, colors::gray("*"));
|
|
} else {
|
|
let package = package.clone();
|
|
child.children.extend(self.build_npm_deps(&package));
|
|
}
|
|
}
|
|
}
|
|
children.push(child);
|
|
}
|
|
children
|
|
}
|
|
|
|
fn build_error_info(
|
|
&mut self,
|
|
err: &ModuleError,
|
|
specifier: &ModuleSpecifier,
|
|
) -> TreeNode {
|
|
self.seen.insert(specifier.to_string());
|
|
match err {
|
|
ModuleError::InvalidTypeAssertion { .. } => {
|
|
self.build_error_msg(specifier, "(invalid import attribute)")
|
|
}
|
|
ModuleError::LoadingErr(_, _, err) => {
|
|
use deno_graph::ModuleLoadError::*;
|
|
let message = match err {
|
|
HttpsChecksumIntegrity(_) => "(checksum integrity error)",
|
|
Decode(_) => "(loading decode error)",
|
|
Loader(err) => {
|
|
match deno_runtime::errors::get_error_class_name(err) {
|
|
Some("NotCapable") => "(not capable, requires --allow-import)",
|
|
_ => "(loading error)",
|
|
}
|
|
}
|
|
Jsr(_) => "(loading error)",
|
|
NodeUnknownBuiltinModule(_) => "(unknown node built-in error)",
|
|
Npm(_) => "(npm loading error)",
|
|
TooManyRedirects => "(too many redirects error)",
|
|
};
|
|
self.build_error_msg(specifier, message.as_ref())
|
|
}
|
|
ModuleError::ParseErr(_, _) | ModuleError::WasmParseErr(_, _) => {
|
|
self.build_error_msg(specifier, "(parsing error)")
|
|
}
|
|
ModuleError::UnsupportedImportAttributeType { .. } => {
|
|
self.build_error_msg(specifier, "(unsupported import attribute)")
|
|
}
|
|
ModuleError::UnsupportedMediaType { .. } => {
|
|
self.build_error_msg(specifier, "(unsupported)")
|
|
}
|
|
ModuleError::Missing(_, _) | ModuleError::MissingDynamic(_, _) => {
|
|
self.build_error_msg(specifier, "(missing)")
|
|
}
|
|
}
|
|
}
|
|
|
|
fn build_error_msg(
|
|
&self,
|
|
specifier: &ModuleSpecifier,
|
|
error_msg: &str,
|
|
) -> TreeNode {
|
|
TreeNode::from_text(format!(
|
|
"{} {}",
|
|
colors::red(specifier),
|
|
colors::red_bold(error_msg)
|
|
))
|
|
}
|
|
|
|
fn build_resolved_info(
|
|
&mut self,
|
|
resolution: &Resolution,
|
|
type_dep: bool,
|
|
) -> Option<TreeNode> {
|
|
match resolution {
|
|
Resolution::Ok(resolved) => {
|
|
let specifier = &resolved.specifier;
|
|
let resolved_specifier = self.graph.resolve(specifier);
|
|
Some(match self.graph.try_get(resolved_specifier) {
|
|
Ok(Some(module)) => self.build_module_info(module, type_dep),
|
|
Err(err) => self.build_error_info(err, resolved_specifier),
|
|
Ok(None) => TreeNode::from_text(format!(
|
|
"{} {}",
|
|
colors::red(specifier),
|
|
colors::red_bold("(missing)")
|
|
)),
|
|
})
|
|
}
|
|
Resolution::Err(err) => Some(TreeNode::from_text(format!(
|
|
"{} {}",
|
|
colors::italic(err.to_string()),
|
|
colors::red_bold("(resolve error)")
|
|
))),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn maybe_size_to_text(maybe_size: Option<u64>) -> String {
|
|
colors::gray(format!(
|
|
"({})",
|
|
match maybe_size {
|
|
Some(size) => display::human_size(size as f64),
|
|
None => "unknown".to_string(),
|
|
}
|
|
))
|
|
.to_string()
|
|
}
|