summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/display.rs28
-rw-r--r--cli/main.rs125
-rw-r--r--cli/tools/doc.rs4
-rw-r--r--cli/tools/info.rs684
-rw-r--r--cli/tools/mod.rs1
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;