diff options
-rw-r--r-- | cli/display.rs | 28 | ||||
-rw-r--r-- | cli/main.rs | 125 | ||||
-rw-r--r-- | cli/tools/doc.rs | 4 | ||||
-rw-r--r-- | cli/tools/info.rs | 684 | ||||
-rw-r--r-- | cli/tools/mod.rs | 1 |
5 files changed, 718 insertions, 124 deletions
diff --git a/cli/display.rs b/cli/display.rs index bcd23f47c..f13965e28 100644 --- a/cli/display.rs +++ b/cli/display.rs @@ -1,5 +1,9 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +use deno_core::error::AnyError; +use deno_core::serde_json; +use std::io::Write; + /// A function that converts a float to a string the represents a human /// readable version of that number. pub fn human_size(size: f64) -> String { @@ -38,6 +42,30 @@ pub fn human_elapsed(elapsed: u128) -> String { format!("{}m{}s", minutes, seconds_remainder) } +pub fn write_to_stdout_ignore_sigpipe( + bytes: &[u8], +) -> Result<(), std::io::Error> { + use std::io::ErrorKind; + + match std::io::stdout().write_all(bytes) { + Ok(()) => Ok(()), + Err(e) => match e.kind() { + ErrorKind::BrokenPipe => Ok(()), + _ => Err(e), + }, + } +} + +pub fn write_json_to_stdout<T>(value: &T) -> Result<(), AnyError> +where + T: ?Sized + serde::ser::Serialize, +{ + let mut writer = std::io::BufWriter::new(std::io::stdout()); + serde_json::to_writer_pretty(&mut writer, value)?; + writeln!(&mut writer)?; + Ok(()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/cli/main.rs b/cli/main.rs index e9fbeba5b..af0b532c0 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -84,8 +84,6 @@ use deno_core::futures::future::FutureExt; use deno_core::futures::Future; use deno_core::parking_lot::RwLock; use deno_core::resolve_url_or_path; -use deno_core::serde_json; -use deno_core::serde_json::json; use deno_core::v8_set_flags; use deno_core::ModuleSpecifier; use deno_runtime::colors; @@ -97,114 +95,12 @@ use log::info; use npm::NpmPackageReference; use std::env; use std::io::Read; -use std::io::Write; use std::iter::once; use std::path::PathBuf; use std::pin::Pin; use std::sync::Arc; use worker::create_main_worker; -pub fn write_to_stdout_ignore_sigpipe( - bytes: &[u8], -) -> Result<(), std::io::Error> { - use std::io::ErrorKind; - - match std::io::stdout().write_all(bytes) { - Ok(()) => Ok(()), - Err(e) => match e.kind() { - ErrorKind::BrokenPipe => Ok(()), - _ => Err(e), - }, - } -} - -pub fn write_json_to_stdout<T>(value: &T) -> Result<(), AnyError> -where - T: ?Sized + serde::ser::Serialize, -{ - let mut writer = std::io::BufWriter::new(std::io::stdout()); - serde_json::to_writer_pretty(&mut writer, value)?; - writeln!(&mut writer)?; - Ok(()) -} - -fn print_cache_info( - state: &ProcState, - json: bool, - location: Option<&deno_core::url::Url>, -) -> Result<(), AnyError> { - let deno_dir = &state.dir.root; - 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.root.join(lsp::language_server::REGISTRIES_PATH); - let mut origin_dir = state.dir.root.join("location_data"); - - 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, - "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)?; - } - - write_json_to_stdout(&output) - } else { - println!( - "{} {}", - colors::bold("DENO_DIR location:"), - deno_dir.display() - ); - 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(()) - } -} - pub fn get_types(unstable: bool) -> String { let mut types = vec![ tsc::DENO_NS_LIB, @@ -314,22 +210,7 @@ async fn info_command( flags: Flags, info_flags: InfoFlags, ) -> Result<i32, AnyError> { - let ps = ProcState::build(flags).await?; - if let Some(specifier) = info_flags.file { - let specifier = resolve_url_or_path(&specifier)?; - let graph = ps - .create_graph(vec![(specifier, deno_graph::ModuleKind::Esm)]) - .await?; - - if info_flags.json { - write_json_to_stdout(&json!(graph))?; - } else { - write_to_stdout_ignore_sigpipe(graph.to_string().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())?; - } + tools::info::info(flags, info_flags).await?; Ok(0) } @@ -926,13 +807,13 @@ async fn completions_command( _flags: Flags, completions_flags: CompletionsFlags, ) -> Result<i32, AnyError> { - write_to_stdout_ignore_sigpipe(&completions_flags.buf)?; + display::write_to_stdout_ignore_sigpipe(&completions_flags.buf)?; Ok(0) } async fn types_command(flags: Flags) -> Result<i32, AnyError> { let types = get_types(flags.unstable); - write_to_stdout_ignore_sigpipe(types.as_bytes())?; + display::write_to_stdout_ignore_sigpipe(types.as_bytes())?; Ok(0) } diff --git a/cli/tools/doc.rs b/cli/tools/doc.rs index b02a188b0..e301c0000 100644 --- a/cli/tools/doc.rs +++ b/cli/tools/doc.rs @@ -3,11 +3,11 @@ use crate::args::DocFlags; use crate::args::Flags; use crate::colors; +use crate::display::write_json_to_stdout; +use crate::display::write_to_stdout_ignore_sigpipe; use crate::file_fetcher::File; use crate::get_types; use crate::proc_state::ProcState; -use crate::write_json_to_stdout; -use crate::write_to_stdout_ignore_sigpipe; use deno_ast::MediaType; use deno_core::anyhow::bail; use deno_core::error::AnyError; diff --git a/cli/tools/info.rs b/cli/tools/info.rs new file mode 100644 index 000000000..b5cae32ee --- /dev/null +++ b/cli/tools/info.rs @@ -0,0 +1,684 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +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::Dependency; +use deno_graph::Module; +use deno_graph::ModuleGraph; +use deno_graph::ModuleGraphError; +use deno_graph::ModuleKind; +use deno_graph::Resolved; +use deno_runtime::colors; + +use crate::args::Flags; +use crate::args::InfoFlags; +use crate::checksum; +use crate::display; +use crate::lsp; +use crate::proc_state::ProcState; + +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 graph = ps.create_graph(vec![(specifier, ModuleKind::Esm)]).await?; + + if info_flags.json { + display::write_json_to_stdout(&json!(graph))?; + } else { + let mut output = String::new(); + fmt_module_graph(&graph, &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())?; + } + Ok(()) +} + +fn print_cache_info( + state: &ProcState, + json: bool, + location: Option<&deno_core::url::Url>, +) -> Result<(), AnyError> { + let deno_dir = &state.dir.root; + 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.root.join(lsp::language_server::REGISTRIES_PATH); + let mut origin_dir = state.dir.root.join("location_data"); + + 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, + "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.display() + ); + 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(()) + } +} + +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 = ' '; + +fn fmt_module_graph(graph: &ModuleGraph, f: &mut impl Write) -> fmt::Result { + if graph.roots.is_empty() || graph.roots.len() > 1 { + return writeln!( + f, + "{} displaying graphs that have multiple roots is not supported.", + colors::red("error:") + ); + } + let root_specifier = graph.resolve(&graph.roots[0].0); + match 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!( + f, + "{} {}", + colors::bold("local:"), + local.to_string_lossy() + )?; + } + if let Some(emit) = &cache_info.emit { + writeln!(f, "{} {}", colors::bold("emit:"), emit.to_string_lossy())?; + } + if let Some(map) = &cache_info.map { + writeln!(f, "{} {}", colors::bold("map:"), map.to_string_lossy())?; + } + } + writeln!(f, "{} {}", colors::bold("type:"), root.media_type)?; + let modules = graph.modules(); + let total_size: f64 = modules.iter().map(|m| m.size() as f64).sum(); + let dep_count = modules.len() - 1; + writeln!( + f, + "{} {} unique {}", + colors::bold("dependencies:"), + dep_count, + colors::gray(format!("(total {})", display::human_size(total_size))) + )?; + writeln!( + f, + "\n{} {}", + root_specifier, + colors::gray(format!("({})", display::human_size(root.size() as f64))) + )?; + let mut seen = HashSet::new(); + let dep_len = root.dependencies.len(); + for (idx, (_, dep)) in root.dependencies.iter().enumerate() { + fmt_dep_info( + dep, + f, + "", + idx == dep_len - 1 && root.maybe_types_dependency.is_none(), + graph, + &mut seen, + )?; + } + Ok(()) + } + Err(ModuleGraphError::Missing(_)) => { + writeln!(f, "{} module could not be found", colors::red("error:")) + } + Err(err) => { + writeln!(f, "{} {}", colors::red("error:"), err) + } + Ok(None) => { + writeln!(f, "{} an internal error occurred", colors::red("error:")) + } + } +} + +fn fmt_dep_info<S: AsRef<str> + fmt::Display + Clone>( + dep: &Dependency, + f: &mut impl Write, + prefix: S, + last: bool, + graph: &ModuleGraph, + seen: &mut HashSet<ModuleSpecifier>, +) -> fmt::Result { + if !dep.maybe_code.is_none() { + fmt_resolved_info( + &dep.maybe_code, + f, + prefix.clone(), + dep.maybe_type.is_none() && last, + graph, + false, + seen, + )?; + } + if !dep.maybe_type.is_none() { + fmt_resolved_info(&dep.maybe_type, f, prefix, last, graph, true, seen)?; + } + Ok(()) +} + +fn fmt_module_info<S: AsRef<str> + fmt::Display + Clone>( + module: &Module, + f: &mut impl Write, + prefix: S, + last: bool, + graph: &ModuleGraph, + type_dep: bool, + seen: &mut HashSet<ModuleSpecifier>, +) -> fmt::Result { + let was_seen = seen.contains(&module.specifier); + let children = !((module.dependencies.is_empty() + && module.maybe_types_dependency.is_none()) + || was_seen); + let (specifier_str, size_str) = if was_seen { + let specifier_str = if type_dep { + colors::italic_gray(&module.specifier).to_string() + } else { + colors::gray(&module.specifier).to_string() + }; + (specifier_str, colors::gray(" *").to_string()) + } else { + let specifier_str = if type_dep { + colors::italic(&module.specifier).to_string() + } else { + module.specifier.to_string() + }; + let size_str = + colors::gray(format!(" ({})", display::human_size(module.size() as f64))) + .to_string(); + (specifier_str, size_str) + }; + + seen.insert(module.specifier.clone()); + + fmt_info_msg( + f, + prefix.clone(), + last, + children, + format!("{}{}", specifier_str, size_str), + )?; + + if !was_seen { + let mut prefix = prefix.to_string(); + if last { + prefix.push(EMPTY_CONNECTOR); + } else { + prefix.push(VERTICAL_CONNECTOR); + } + prefix.push(EMPTY_CONNECTOR); + let dep_len = module.dependencies.len(); + if let Some((_, type_dep)) = &module.maybe_types_dependency { + fmt_resolved_info(type_dep, f, &prefix, dep_len == 0, graph, true, seen)?; + } + for (idx, (_, dep)) in module.dependencies.iter().enumerate() { + fmt_dep_info( + dep, + f, + &prefix, + idx == dep_len - 1 && module.maybe_types_dependency.is_none(), + graph, + seen, + )?; + } + } + Ok(()) +} + +fn fmt_error_info<S: AsRef<str> + fmt::Display + Clone>( + err: &ModuleGraphError, + f: &mut impl Write, + prefix: S, + last: bool, + specifier: &ModuleSpecifier, + seen: &mut HashSet<ModuleSpecifier>, +) -> fmt::Result { + seen.insert(specifier.clone()); + match err { + ModuleGraphError::InvalidSource(_, _) => { + fmt_error_msg(f, prefix, last, specifier, "(invalid source)") + } + ModuleGraphError::InvalidTypeAssertion { .. } => { + fmt_error_msg(f, prefix, last, specifier, "(invalid import assertion)") + } + ModuleGraphError::LoadingErr(_, _) => { + fmt_error_msg(f, prefix, last, specifier, "(loading error)") + } + ModuleGraphError::ParseErr(_, _) => { + fmt_error_msg(f, prefix, last, specifier, "(parsing error)") + } + ModuleGraphError::ResolutionError(_) => { + fmt_error_msg(f, prefix, last, specifier, "(resolution error)") + } + ModuleGraphError::UnsupportedImportAssertionType(_, _) => fmt_error_msg( + f, + prefix, + last, + specifier, + "(unsupported import assertion)", + ), + ModuleGraphError::UnsupportedMediaType(_, _) => { + fmt_error_msg(f, prefix, last, specifier, "(unsupported)") + } + ModuleGraphError::Missing(_) => { + fmt_error_msg(f, prefix, last, specifier, "(missing)") + } + } +} + +fn fmt_info_msg<S, M>( + f: &mut impl Write, + prefix: S, + last: bool, + children: bool, + msg: M, +) -> fmt::Result +where + S: AsRef<str> + fmt::Display + Clone, + M: AsRef<str> + fmt::Display, +{ + let sibling_connector = if last { + LAST_SIBLING_CONNECTOR + } else { + SIBLING_CONNECTOR + }; + let child_connector = if children { + CHILD_DEPS_CONNECTOR + } else { + CHILD_NO_DEPS_CONNECTOR + }; + writeln!( + f, + "{} {}", + colors::gray(format!( + "{}{}─{}", + prefix, sibling_connector, child_connector + )), + msg + ) +} + +fn fmt_error_msg<S, M>( + f: &mut impl Write, + prefix: S, + last: bool, + specifier: &ModuleSpecifier, + error_msg: M, +) -> fmt::Result +where + S: AsRef<str> + fmt::Display + Clone, + M: AsRef<str> + fmt::Display, +{ + fmt_info_msg( + f, + prefix, + last, + false, + format!("{} {}", colors::red(specifier), colors::red_bold(error_msg)), + ) +} + +fn fmt_resolved_info<S: AsRef<str> + fmt::Display + Clone>( + resolved: &Resolved, + f: &mut impl Write, + prefix: S, + last: bool, + graph: &ModuleGraph, + type_dep: bool, + seen: &mut HashSet<ModuleSpecifier>, +) -> fmt::Result { + match resolved { + Resolved::Ok { specifier, .. } => { + let resolved_specifier = graph.resolve(specifier); + match graph.try_get(&resolved_specifier) { + Ok(Some(module)) => { + fmt_module_info(module, f, prefix, last, graph, type_dep, seen) + } + Err(err) => { + fmt_error_info(&err, f, prefix, last, &resolved_specifier, seen) + } + Ok(None) => fmt_info_msg( + f, + prefix, + last, + false, + format!( + "{} {}", + colors::red(specifier), + colors::red_bold("(missing)") + ), + ), + } + } + Resolved::Err(err) => fmt_info_msg( + f, + prefix, + last, + false, + format!( + "{} {}", + colors::italic(err.to_string()), + colors::red_bold("(resolve error)") + ), + ), + _ => Ok(()), + } +} + +#[cfg(test)] +mod tests { + use deno_graph::source::CacheInfo; + use deno_graph::source::MemoryLoader; + use deno_graph::source::Source; + use test_util::strip_ansi_codes; + + use super::*; + use std::path::PathBuf; + + #[tokio::test] + async fn test_info_graph() { + let mut loader = MemoryLoader::new( + vec![ + ( + "https://deno.land/x/example/a.ts", + Source::Module { + specifier: "https://deno.land/x/example/a.ts", + maybe_headers: Some(vec![( + "content-type", + "application/typescript", + )]), + content: r#"import * as b from "./b.ts"; + import type { F } from "./f.d.ts"; + import * as g from "./g.js"; + "#, + }, + ), + ( + "https://deno.land/x/example/b.ts", + Source::Module { + specifier: "https://deno.land/x/example/b.ts", + maybe_headers: Some(vec![( + "content-type", + "application/typescript", + )]), + content: r#" + // @deno-types="./c.d.ts" + import * as c from "./c.js"; + import * as d from "./d.ts";"#, + }, + ), + ( + "https://deno.land/x/example/c.js", + Source::Module { + specifier: "https://deno.land/x/example/c.js", + maybe_headers: Some(vec![( + "content-type", + "application/javascript", + )]), + content: r#"export const c = "c";"#, + }, + ), + ( + "https://deno.land/x/example/c.d.ts", + Source::Module { + specifier: "https://deno.land/x/example/c.d.ts", + maybe_headers: Some(vec![( + "content-type", + "application/typescript", + )]), + content: r#"export const c: "c";"#, + }, + ), + ( + "https://deno.land/x/example/d.ts", + Source::Module { + specifier: "https://deno.land/x/example/d.ts", + maybe_headers: Some(vec![( + "content-type", + "application/typescript", + )]), + content: r#"import * as e from "./e.ts"; + export const d = "d";"#, + }, + ), + ( + "https://deno.land/x/example/e.ts", + Source::Module { + specifier: "https://deno.land/x/example/e.ts", + maybe_headers: Some(vec![( + "content-type", + "application/typescript", + )]), + content: r#"import * as b from "./b.ts"; + export const e = "e";"#, + }, + ), + ( + "https://deno.land/x/example/f.d.ts", + Source::Module { + specifier: "https://deno.land/x/example/f.d.ts", + maybe_headers: Some(vec![( + "content-type", + "application/typescript", + )]), + content: r#"export interface F { }"#, + }, + ), + ( + "https://deno.land/x/example/g.js", + Source::Module { + specifier: "https://deno.land/x/example/g.js", + maybe_headers: Some(vec![ + ("content-type", "application/javascript"), + ("x-typescript-types", "./g.d.ts"), + ]), + content: r#"export const g = "g";"#, + }, + ), + ( + "https://deno.land/x/example/g.d.ts", + Source::Module { + specifier: "https://deno.land/x/example/g.d.ts", + maybe_headers: Some(vec![( + "content-type", + "application/typescript", + )]), + content: r#"export const g: "g";"#, + }, + ), + ], + vec![( + "https://deno.land/x/example/a.ts", + CacheInfo { + local: Some(PathBuf::from( + "/cache/deps/https/deno.land/x/example/a.ts", + )), + emit: Some(PathBuf::from( + "/cache/deps/https/deno.land/x/example/a.js", + )), + ..Default::default() + }, + )], + ); + let root_specifier = + ModuleSpecifier::parse("https://deno.land/x/example/a.ts").unwrap(); + let graph = deno_graph::create_graph( + vec![(root_specifier, ModuleKind::Esm)], + &mut loader, + deno_graph::GraphOptions { + is_dynamic: false, + imports: None, + resolver: None, + locker: None, + module_analyzer: None, + reporter: None, + }, + ) + .await; + let mut output = String::new(); + fmt_module_graph(&graph, &mut output).unwrap(); + assert_eq!( + strip_ansi_codes(&output), + r#"local: /cache/deps/https/deno.land/x/example/a.ts +emit: /cache/deps/https/deno.land/x/example/a.js +type: TypeScript +dependencies: 8 unique (total 477B) + +https://deno.land/x/example/a.ts (129B) +├─┬ https://deno.land/x/example/b.ts (120B) +│ ├── https://deno.land/x/example/c.js (21B) +│ ├── https://deno.land/x/example/c.d.ts (20B) +│ └─┬ https://deno.land/x/example/d.ts (62B) +│ └─┬ https://deno.land/x/example/e.ts (62B) +│ └── https://deno.land/x/example/b.ts * +├── https://deno.land/x/example/f.d.ts (22B) +└─┬ https://deno.land/x/example/g.js (21B) + └── https://deno.land/x/example/g.d.ts (20B) +"# + ); + } + + #[tokio::test] + async fn test_info_graph_import_assertion() { + let mut loader = MemoryLoader::new( + vec![ + ( + "https://deno.land/x/example/a.ts", + Source::Module { + specifier: "https://deno.land/x/example/a.ts", + maybe_headers: Some(vec![( + "content-type", + "application/typescript", + )]), + content: r#"import b from "./b.json" assert { type: "json" }; + const c = await import("./c.json", { assert: { type: "json" } }); + "#, + }, + ), + ( + "https://deno.land/x/example/b.json", + Source::Module { + specifier: "https://deno.land/x/example/b.json", + maybe_headers: Some(vec![("content-type", "application/json")]), + content: r#"{"b":"c"}"#, + }, + ), + ( + "https://deno.land/x/example/c.json", + Source::Module { + specifier: "https://deno.land/x/example/c.json", + maybe_headers: Some(vec![("content-type", "application/json")]), + content: r#"{"c":1}"#, + }, + ), + ], + vec![( + "https://deno.land/x/example/a.ts", + CacheInfo { + local: Some(PathBuf::from( + "/cache/deps/https/deno.land/x/example/a.ts", + )), + emit: Some(PathBuf::from( + "/cache/deps/https/deno.land/x/example/a.js", + )), + ..Default::default() + }, + )], + ); + let root_specifier = + ModuleSpecifier::parse("https://deno.land/x/example/a.ts").unwrap(); + let graph = deno_graph::create_graph( + vec![(root_specifier, ModuleKind::Esm)], + &mut loader, + deno_graph::GraphOptions { + is_dynamic: false, + imports: None, + resolver: None, + locker: None, + module_analyzer: None, + reporter: None, + }, + ) + .await; + let mut output = String::new(); + fmt_module_graph(&graph, &mut output).unwrap(); + assert_eq!( + strip_ansi_codes(&output), + r#"local: /cache/deps/https/deno.land/x/example/a.ts +emit: /cache/deps/https/deno.land/x/example/a.js +type: TypeScript +dependencies: 2 unique (total 156B) + +https://deno.land/x/example/a.ts (140B) +├── https://deno.land/x/example/b.json (9B) +└── https://deno.land/x/example/c.json (7B) +"# + ); + } +} diff --git a/cli/tools/mod.rs b/cli/tools/mod.rs index 8ef30189e..b992a2e9e 100644 --- a/cli/tools/mod.rs +++ b/cli/tools/mod.rs @@ -5,6 +5,7 @@ pub mod check; pub mod coverage; pub mod doc; pub mod fmt; +pub mod info; pub mod init; pub mod installer; pub mod lint; |