mirror of
https://github.com/denoland/deno.git
synced 2024-11-04 08:54:20 -05:00
3479bc7661
This PR fixes peer dependency resolution to only resolve peers based on the current graph traversal path. Previously, it would resolve a peers by looking at a graph node's ancestors, which is not correct because graph nodes are shared by different resolutions. It also stores more information about peer dependency resolution in the lockfile.
687 lines
20 KiB
Rust
687 lines
20 KiB
Rust
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
|
|
use std::collections::HashMap;
|
|
use std::collections::HashSet;
|
|
use std::fmt;
|
|
use std::fmt::Write;
|
|
|
|
use deno_ast::ModuleSpecifier;
|
|
use deno_core::error::AnyError;
|
|
use deno_core::resolve_url_or_path;
|
|
use deno_core::serde_json;
|
|
use deno_core::serde_json::json;
|
|
use deno_graph::npm::NpmPackageReq;
|
|
use deno_graph::npm::NpmPackageReqReference;
|
|
use deno_graph::Dependency;
|
|
use deno_graph::Module;
|
|
use deno_graph::ModuleGraph;
|
|
use deno_graph::ModuleGraphError;
|
|
use deno_graph::Resolution;
|
|
use deno_runtime::colors;
|
|
|
|
use crate::args::Flags;
|
|
use crate::args::InfoFlags;
|
|
use crate::display;
|
|
use crate::npm::NpmPackageId;
|
|
use crate::npm::NpmPackageResolver;
|
|
use crate::npm::NpmResolutionPackage;
|
|
use crate::npm::NpmResolutionSnapshot;
|
|
use crate::proc_state::ProcState;
|
|
use crate::util::checksum;
|
|
|
|
pub async fn info(flags: Flags, info_flags: InfoFlags) -> Result<(), AnyError> {
|
|
let ps = ProcState::build(flags).await?;
|
|
if let Some(specifier) = info_flags.file {
|
|
let specifier = resolve_url_or_path(&specifier)?;
|
|
let mut loader = ps.create_graph_loader();
|
|
loader.enable_loading_cache_info(); // for displaying the cache information
|
|
let graph = ps
|
|
.create_graph_with_loader(vec![specifier], &mut loader)
|
|
.await?;
|
|
|
|
if info_flags.json {
|
|
let mut json_graph = json!(graph);
|
|
add_npm_packages_to_json(&mut json_graph, &ps.npm_resolver);
|
|
display::write_json_to_stdout(&json_graph)?;
|
|
} else {
|
|
let mut output = String::new();
|
|
GraphDisplayContext::write(&graph, &ps.npm_resolver, &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(
|
|
&ps,
|
|
info_flags.json,
|
|
ps.options.location_flag().as_ref(),
|
|
)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn print_cache_info(
|
|
state: &ProcState,
|
|
json: bool,
|
|
location: Option<&deno_core::url::Url>,
|
|
) -> Result<(), AnyError> {
|
|
let deno_dir = &state.dir.root_path_for_display();
|
|
let modules_cache = &state.file_fetcher.get_http_cache_location();
|
|
let npm_cache = &state.npm_cache.as_readonly().get_cache_location();
|
|
let typescript_cache = &state.dir.gen_cache.location;
|
|
let registry_cache = &state.dir.registries_folder_path();
|
|
let mut origin_dir = state.dir.origin_data_folder_path();
|
|
|
|
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 output = json!({
|
|
"denoDir": deno_dir.to_string(),
|
|
"modulesCache": modules_cache,
|
|
"npmCache": npm_cache,
|
|
"typescriptCache": typescript_cache,
|
|
"registryCache": registry_cache,
|
|
"originStorage": origin_dir,
|
|
});
|
|
|
|
if location.is_some() {
|
|
output["localStorage"] = serde_json::to_value(local_storage_dir)?;
|
|
}
|
|
|
|
display::write_json_to_stdout(&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()
|
|
);
|
|
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: &NpmPackageResolver,
|
|
) {
|
|
// 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 {
|
|
if modules.len() == 1
|
|
&& modules[0].get("kind").and_then(|k| k.as_str()) == Some("external")
|
|
{
|
|
// 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 module = &mut modules[0];
|
|
let maybe_package = module
|
|
.get("specifier")
|
|
.and_then(|k| k.as_str())
|
|
.and_then(|specifier| NpmPackageReqReference::from_str(specifier).ok())
|
|
.and_then(|package_ref| {
|
|
snapshot
|
|
.resolve_package_from_deno_module(&package_ref.req)
|
|
.ok()
|
|
});
|
|
if let Some(pkg) = maybe_package {
|
|
if let Some(module) = module.as_object_mut() {
|
|
module.insert(
|
|
"npmPackage".to_string(),
|
|
pkg.pkg_id.as_serialized().into(),
|
|
);
|
|
// change the "kind" to be "npm"
|
|
module.insert("kind".to_string(), "npm".into());
|
|
}
|
|
}
|
|
} else {
|
|
// Filter out npm package references from the modules and instead
|
|
// have them only listed as dependencies. This is done because various
|
|
// npm specifiers modules in the graph are really just unresolved
|
|
// references. So there could be listed multiple npm specifiers
|
|
// that would resolve to a single npm package.
|
|
for i in (0..modules.len()).rev() {
|
|
if modules[i].get("kind").and_then(|k| k.as_str()) == Some("external") {
|
|
modules.remove(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
for module in modules.iter_mut() {
|
|
let dependencies = module
|
|
.get_mut("dependencies")
|
|
.and_then(|d| d.as_array_mut());
|
|
if let Some(dependencies) = dependencies {
|
|
for dep in dependencies.iter_mut() {
|
|
if let serde_json::Value::Object(dep) = dep {
|
|
let specifier = dep.get("specifier").and_then(|s| s.as_str());
|
|
if let Some(specifier) = specifier {
|
|
if let Ok(npm_ref) = NpmPackageReqReference::from_str(specifier) {
|
|
if let Ok(pkg) =
|
|
snapshot.resolve_package_from_deno_module(&npm_ref.req)
|
|
{
|
|
dep.insert(
|
|
"npmPackage".to_string(),
|
|
pkg.pkg_id.as_serialized().into(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut sorted_packages = snapshot.all_packages();
|
|
sorted_packages.sort_by(|a, b| a.pkg_id.cmp(&b.pkg_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.pkg_id.nv.name.to_string().into());
|
|
kv.insert(
|
|
"version".to_string(),
|
|
pkg.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());
|
|
|
|
json_packages.insert(pkg.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: &Vec<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_reqs: HashMap<NpmPackageReq, NpmPackageId>,
|
|
packages: HashMap<NpmPackageId, NpmResolutionPackage>,
|
|
specifiers: HashMap<ModuleSpecifier, NpmPackageReq>,
|
|
}
|
|
|
|
impl NpmInfo {
|
|
pub fn build<'a>(
|
|
graph: &'a ModuleGraph,
|
|
npm_resolver: &'a NpmPackageResolver,
|
|
npm_snapshot: &'a NpmResolutionSnapshot,
|
|
) -> Self {
|
|
let mut info = NpmInfo::default();
|
|
if !npm_resolver.has_packages() {
|
|
return info; // skip going over the specifiers if there's no npm packages
|
|
}
|
|
|
|
for (specifier, _) in graph.specifiers() {
|
|
if let Ok(reference) = NpmPackageReqReference::from_specifier(specifier) {
|
|
info
|
|
.specifiers
|
|
.insert(specifier.clone(), reference.req.clone());
|
|
if let Ok(package) =
|
|
npm_snapshot.resolve_package_from_deno_module(&reference.req)
|
|
{
|
|
info
|
|
.resolved_reqs
|
|
.insert(reference.req, package.pkg_id.clone());
|
|
if !info.packages.contains_key(&package.pkg_id) {
|
|
info.fill_package_info(package, npm_resolver, npm_snapshot);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
info
|
|
}
|
|
|
|
fn fill_package_info<'a>(
|
|
&mut self,
|
|
package: &NpmResolutionPackage,
|
|
npm_resolver: &'a NpmPackageResolver,
|
|
npm_snapshot: &'a NpmResolutionSnapshot,
|
|
) {
|
|
self
|
|
.packages
|
|
.insert(package.pkg_id.clone(), package.clone());
|
|
if let Ok(size) = npm_resolver.package_size(&package.pkg_id) {
|
|
self.package_sizes.insert(package.pkg_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 package_from_specifier(
|
|
&self,
|
|
specifier: &ModuleSpecifier,
|
|
) -> Option<&NpmResolutionPackage> {
|
|
self
|
|
.specifiers
|
|
.get(specifier)
|
|
.and_then(|package_req| self.resolved_reqs.get(package_req))
|
|
.and_then(|id| 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 NpmPackageResolver,
|
|
writer: &mut TWrite,
|
|
) -> fmt::Result {
|
|
let npm_snapshot = npm_resolver.snapshot();
|
|
let npm_info = NpmInfo::build(graph, npm_resolver, &npm_snapshot);
|
|
Self {
|
|
graph,
|
|
npm_info,
|
|
seen: Default::default(),
|
|
}
|
|
.into_writer(writer)
|
|
}
|
|
|
|
fn into_writer<TWrite: Write>(mut self, writer: &mut TWrite) -> fmt::Result {
|
|
if self.graph.roots.is_empty() || self.graph.roots.len() > 1 {
|
|
return writeln!(
|
|
writer,
|
|
"{} displaying graphs that have multiple roots is not supported.",
|
|
colors::red("error:")
|
|
);
|
|
}
|
|
|
|
let root_specifier = self.graph.resolve(&self.graph.roots[0]);
|
|
match self.graph.try_get(&root_specifier) {
|
|
Ok(Some(root)) => {
|
|
if let Some(cache_info) = root.maybe_cache_info.as_ref() {
|
|
if let Some(local) = &cache_info.local {
|
|
writeln!(
|
|
writer,
|
|
"{} {}",
|
|
colors::bold("local:"),
|
|
local.to_string_lossy()
|
|
)?;
|
|
}
|
|
if let Some(emit) = &cache_info.emit {
|
|
writeln!(
|
|
writer,
|
|
"{} {}",
|
|
colors::bold("emit:"),
|
|
emit.to_string_lossy()
|
|
)?;
|
|
}
|
|
if let Some(map) = &cache_info.map {
|
|
writeln!(
|
|
writer,
|
|
"{} {}",
|
|
colors::bold("map:"),
|
|
map.to_string_lossy()
|
|
)?;
|
|
}
|
|
}
|
|
writeln!(writer, "{} {}", colors::bold("type:"), root.media_type)?;
|
|
let total_modules_size =
|
|
self.graph.modules().map(|m| m.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
|
|
+ self.npm_info.packages.len()
|
|
- self.npm_info.resolved_reqs.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 ModuleGraphError::Missing(_, _) = *err {
|
|
writeln!(
|
|
writer,
|
|
"{} module could not be found",
|
|
colors::red("error:")
|
|
)
|
|
} else {
|
|
writeln!(writer, "{} {}", colors::red("error:"), err)
|
|
}
|
|
}
|
|
Ok(None) => {
|
|
writeln!(
|
|
writer,
|
|
"{} an internal error occurred",
|
|
colors::red("error:")
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
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(NpmResolutionPackage),
|
|
Specifier(ModuleSpecifier),
|
|
}
|
|
|
|
use PackageOrSpecifier::*;
|
|
|
|
let package_or_specifier =
|
|
match self.npm_info.package_from_specifier(&module.specifier) {
|
|
Some(package) => Package(package.clone()),
|
|
None => Specifier(module.specifier.clone()),
|
|
};
|
|
let was_seen = !self.seen.insert(match &package_or_specifier {
|
|
Package(package) => package.pkg_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 specifier_str = if type_dep {
|
|
colors::italic(&module.specifier).to_string()
|
|
} else {
|
|
module.specifier.to_string()
|
|
};
|
|
let header_text = match &package_or_specifier {
|
|
Package(package) => {
|
|
format!("{} - {}", specifier_str, package.pkg_id.nv.version)
|
|
}
|
|
Specifier(_) => specifier_str,
|
|
};
|
|
let maybe_size = match &package_or_specifier {
|
|
Package(package) => {
|
|
self.npm_info.package_sizes.get(&package.pkg_id).copied()
|
|
}
|
|
Specifier(_) => module
|
|
.maybe_source
|
|
.as_ref()
|
|
.map(|s| s.as_bytes().len() as u64),
|
|
};
|
|
format!("{} {}", header_text, maybe_size_to_text(maybe_size))
|
|
};
|
|
|
|
let mut tree_node = TreeNode::from_text(header_text);
|
|
|
|
if !was_seen {
|
|
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);
|
|
}
|
|
}
|
|
match &package_or_specifier {
|
|
Package(package) => {
|
|
tree_node.children.extend(self.build_npm_deps(package));
|
|
}
|
|
Specifier(_) => {
|
|
for dep in module.dependencies.values() {
|
|
tree_node.children.extend(self.build_dep_info(dep));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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.pkg_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: &ModuleGraphError,
|
|
specifier: &ModuleSpecifier,
|
|
) -> TreeNode {
|
|
self.seen.insert(specifier.to_string());
|
|
match err {
|
|
ModuleGraphError::InvalidTypeAssertion { .. } => {
|
|
self.build_error_msg(specifier, "(invalid import assertion)")
|
|
}
|
|
ModuleGraphError::LoadingErr(_, _, _) => {
|
|
self.build_error_msg(specifier, "(loading error)")
|
|
}
|
|
ModuleGraphError::ParseErr(_, _) => {
|
|
self.build_error_msg(specifier, "(parsing error)")
|
|
}
|
|
ModuleGraphError::ResolutionError(_) => {
|
|
self.build_error_msg(specifier, "(resolution error)")
|
|
}
|
|
ModuleGraphError::UnsupportedImportAssertionType { .. } => {
|
|
self.build_error_msg(specifier, "(unsupported import assertion)")
|
|
}
|
|
ModuleGraphError::UnsupportedMediaType { .. } => {
|
|
self.build_error_msg(specifier, "(unsupported)")
|
|
}
|
|
ModuleGraphError::Missing(_, _)
|
|
| ModuleGraphError::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()
|
|
}
|