1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-21 13:00:36 -05:00

fix(compile): output contents of embedded file system (#27302)

This commit is contained in:
David Sherret 2024-12-11 09:40:50 -05:00 committed by GitHub
parent 9df6be8916
commit c6fa62896d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 344 additions and 136 deletions

View file

@ -87,6 +87,7 @@ use super::serialization::DenoCompileModuleData;
use super::serialization::DeserializedDataSection; use super::serialization::DeserializedDataSection;
use super::serialization::RemoteModulesStore; use super::serialization::RemoteModulesStore;
use super::serialization::RemoteModulesStoreBuilder; use super::serialization::RemoteModulesStoreBuilder;
use super::virtual_fs::output_vfs;
use super::virtual_fs::FileBackedVfs; use super::virtual_fs::FileBackedVfs;
use super::virtual_fs::VfsBuilder; use super::virtual_fs::VfsBuilder;
use super::virtual_fs::VfsFileSubDataKind; use super::virtual_fs::VfsFileSubDataKind;
@ -367,6 +368,16 @@ pub fn extract_standalone(
})) }))
} }
pub struct WriteBinOptions<'a> {
pub writer: File,
pub display_output_filename: &'a str,
pub graph: &'a ModuleGraph,
pub root_dir_url: StandaloneRelativeFileBaseUrl<'a>,
pub entrypoint: &'a ModuleSpecifier,
pub include_files: &'a [ModuleSpecifier],
pub compile_flags: &'a CompileFlags,
}
pub struct DenoCompileBinaryWriter<'a> { pub struct DenoCompileBinaryWriter<'a> {
cjs_tracker: &'a CjsTracker, cjs_tracker: &'a CjsTracker,
cli_options: &'a CliOptions, cli_options: &'a CliOptions,
@ -407,18 +418,14 @@ impl<'a> DenoCompileBinaryWriter<'a> {
pub async fn write_bin( pub async fn write_bin(
&self, &self,
writer: File, options: WriteBinOptions<'_>,
graph: &ModuleGraph,
root_dir_url: StandaloneRelativeFileBaseUrl<'_>,
entrypoint: &ModuleSpecifier,
include_files: &[ModuleSpecifier],
compile_flags: &CompileFlags,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
// Select base binary based on target // Select base binary based on target
let mut original_binary = self.get_base_binary(compile_flags).await?; let mut original_binary =
self.get_base_binary(options.compile_flags).await?;
if compile_flags.no_terminal { if options.compile_flags.no_terminal {
let target = compile_flags.resolve_target(); let target = options.compile_flags.resolve_target();
if !target.contains("windows") { if !target.contains("windows") {
bail!( bail!(
"The `--no-terminal` flag is only available when targeting Windows (current: {})", "The `--no-terminal` flag is only available when targeting Windows (current: {})",
@ -428,8 +435,8 @@ impl<'a> DenoCompileBinaryWriter<'a> {
set_windows_binary_to_gui(&mut original_binary) set_windows_binary_to_gui(&mut original_binary)
.context("Setting windows binary to GUI.")?; .context("Setting windows binary to GUI.")?;
} }
if compile_flags.icon.is_some() { if options.compile_flags.icon.is_some() {
let target = compile_flags.resolve_target(); let target = options.compile_flags.resolve_target();
if !target.contains("windows") { if !target.contains("windows") {
bail!( bail!(
"The `--icon` flag is only available when targeting Windows (current: {})", "The `--icon` flag is only available when targeting Windows (current: {})",
@ -437,17 +444,7 @@ impl<'a> DenoCompileBinaryWriter<'a> {
) )
} }
} }
self self.write_standalone_binary(options, original_binary).await
.write_standalone_binary(
writer,
original_binary,
graph,
root_dir_url,
entrypoint,
include_files,
compile_flags,
)
.await
} }
async fn get_base_binary( async fn get_base_binary(
@ -552,14 +549,18 @@ impl<'a> DenoCompileBinaryWriter<'a> {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
async fn write_standalone_binary( async fn write_standalone_binary(
&self, &self,
writer: File, options: WriteBinOptions<'_>,
original_bin: Vec<u8>, original_bin: Vec<u8>,
graph: &ModuleGraph,
root_dir_url: StandaloneRelativeFileBaseUrl<'_>,
entrypoint: &ModuleSpecifier,
include_files: &[ModuleSpecifier],
compile_flags: &CompileFlags,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let WriteBinOptions {
writer,
display_output_filename,
graph,
root_dir_url,
entrypoint,
include_files,
compile_flags,
} = options;
let ca_data = match self.cli_options.ca_data() { let ca_data = match self.cli_options.ca_data() {
Some(CaData::File(ca_file)) => Some( Some(CaData::File(ca_file)) => Some(
std::fs::read(ca_file).with_context(|| format!("Reading {ca_file}"))?, std::fs::read(ca_file).with_context(|| format!("Reading {ca_file}"))?,
@ -784,6 +785,8 @@ impl<'a> DenoCompileBinaryWriter<'a> {
otel_config: self.cli_options.otel_config(), otel_config: self.cli_options.otel_config(),
}; };
output_vfs(&vfs, display_output_filename);
write_binary_bytes( write_binary_bytes(
writer, writer,
original_bin, original_bin,

View file

@ -31,6 +31,7 @@ use serde::Serialize;
use thiserror::Error; use thiserror::Error;
use crate::util; use crate::util;
use crate::util::display::DisplayTreeNode;
use crate::util::fs::canonicalize_path; use crate::util::fs::canonicalize_path;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@ -51,6 +52,7 @@ pub struct StripRootError {
target: PathBuf, target: PathBuf,
} }
#[derive(Debug)]
pub struct VfsBuilder { pub struct VfsBuilder {
root_path: PathBuf, root_path: PathBuf,
root_dir: VirtualDirectory, root_dir: VirtualDirectory,
@ -364,6 +366,125 @@ impl VfsBuilder {
} }
} }
pub fn output_vfs(builder: &VfsBuilder, executable_name: &str) {
if !log::log_enabled!(log::Level::Info) {
return; // no need to compute if won't output
}
if builder.root_dir.entries.is_empty() {
return; // nothing to output
}
let mut text = String::new();
let display_tree = vfs_as_display_tree(builder, executable_name);
display_tree.print(&mut text).unwrap(); // unwrap ok because it's writing to a string
log::info!(
"\n{}\n",
deno_terminal::colors::bold("Embedded File System")
);
log::info!("{}\n", text.trim());
}
fn vfs_as_display_tree(
builder: &VfsBuilder,
executable_name: &str,
) -> DisplayTreeNode {
enum EntryOutput<'a> {
All,
Subset(Vec<DirEntryOutput<'a>>),
File,
Symlink(&'a [String]),
}
impl<'a> EntryOutput<'a> {
pub fn as_display_tree(&self, name: String) -> DisplayTreeNode {
DisplayTreeNode {
text: match self {
EntryOutput::All | EntryOutput::Subset(_) | EntryOutput::File => name,
EntryOutput::Symlink(parts) => {
format!("{} --> {}", name, parts.join("/"))
}
},
children: match self {
EntryOutput::All => vec![DisplayTreeNode::from_text("*".to_string())],
EntryOutput::Subset(vec) => vec
.iter()
.map(|e| e.output.as_display_tree(e.name.to_string()))
.collect(),
EntryOutput::File | EntryOutput::Symlink(_) => vec![],
},
}
}
}
pub struct DirEntryOutput<'a> {
name: &'a str,
output: EntryOutput<'a>,
}
fn include_all_entries<'a>(
dir: &Path,
vfs_dir: &'a VirtualDirectory,
) -> EntryOutput<'a> {
EntryOutput::Subset(
vfs_dir
.entries
.iter()
.map(|entry| DirEntryOutput {
name: entry.name(),
output: analyze_entry(&dir.join(entry.name()), entry),
})
.collect(),
)
}
fn analyze_entry<'a>(path: &Path, entry: &'a VfsEntry) -> EntryOutput<'a> {
match entry {
VfsEntry::Dir(virtual_directory) => analyze_dir(path, virtual_directory),
VfsEntry::File(_) => EntryOutput::File,
VfsEntry::Symlink(virtual_symlink) => {
EntryOutput::Symlink(&virtual_symlink.dest_parts)
}
}
}
fn analyze_dir<'a>(
dir: &Path,
vfs_dir: &'a VirtualDirectory,
) -> EntryOutput<'a> {
let real_entry_count = std::fs::read_dir(dir)
.ok()
.map(|entries| entries.flat_map(|e| e.ok()).count())
.unwrap_or(0);
if real_entry_count == vfs_dir.entries.len() {
let children = vfs_dir
.entries
.iter()
.map(|entry| DirEntryOutput {
name: entry.name(),
output: analyze_entry(&dir.join(entry.name()), entry),
})
.collect::<Vec<_>>();
if children
.iter()
.all(|c| !matches!(c.output, EntryOutput::Subset(_)))
{
EntryOutput::All
} else {
EntryOutput::Subset(children)
}
} else {
include_all_entries(dir, vfs_dir)
}
}
// always include all the entries for the root directory, otherwise the
// user might not have context about what's being shown
let output = include_all_entries(&builder.root_path, &builder.root_dir);
output
.as_display_tree(deno_terminal::colors::italic(executable_name).to_string())
}
#[derive(Debug)] #[derive(Debug)]
enum VfsEntryRef<'a> { enum VfsEntryRef<'a> {
Dir(&'a VirtualDirectory), Dir(&'a VirtualDirectory),
@ -1013,6 +1134,7 @@ impl FileBackedVfs {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use console_static_text::ansi::strip_ansi_codes;
use std::io::Write; use std::io::Write;
use test_util::TempDir; use test_util::TempDir;
@ -1299,4 +1421,50 @@ mod test {
.unwrap(); .unwrap();
assert_eq!(all_buf.to_vec(), b"123456789"); assert_eq!(all_buf.to_vec(), b"123456789");
} }
#[test]
fn test_vfs_as_display_tree() {
let temp_dir = TempDir::new();
temp_dir.write("root.txt", "");
temp_dir.create_dir_all("a");
temp_dir.write("a/a.txt", "");
temp_dir.write("a/b.txt", "");
temp_dir.create_dir_all("b");
temp_dir.write("b/a.txt", "");
temp_dir.write("b/b.txt", "");
temp_dir.create_dir_all("c");
temp_dir.write("c/a.txt", "contents");
temp_dir.symlink_file("c/a.txt", "c/b.txt");
assert_eq!(temp_dir.read_to_string("c/b.txt"), "contents"); // ensure the symlink works
let mut vfs_builder =
VfsBuilder::new(temp_dir.path().to_path_buf()).unwrap();
// full dir
vfs_builder
.add_dir_recursive(temp_dir.path().join("a").as_path())
.unwrap();
// part of the dir
vfs_builder
.add_file_at_path(temp_dir.path().join("b/a.txt").as_path())
.unwrap();
// symlink
vfs_builder
.add_dir_recursive(temp_dir.path().join("c").as_path())
.unwrap();
temp_dir.write("c/c.txt", ""); // write an extra file so it shows the whole directory
let node = vfs_as_display_tree(&vfs_builder, "executable");
let mut text = String::new();
node.print(&mut text).unwrap();
assert_eq!(
strip_ansi_codes(&text),
r#"executable
a
*
b
a.txt
c
a.txt
b.txt --> c/a.txt
"#
);
}
} }

View file

@ -6,6 +6,7 @@ use crate::args::Flags;
use crate::factory::CliFactory; use crate::factory::CliFactory;
use crate::http_util::HttpClientProvider; use crate::http_util::HttpClientProvider;
use crate::standalone::binary::StandaloneRelativeFileBaseUrl; use crate::standalone::binary::StandaloneRelativeFileBaseUrl;
use crate::standalone::binary::WriteBinOptions;
use crate::standalone::is_standalone_binary; use crate::standalone::is_standalone_binary;
use deno_ast::MediaType; use deno_ast::MediaType;
use deno_ast::ModuleSpecifier; use deno_ast::ModuleSpecifier;
@ -15,6 +16,7 @@ use deno_core::error::generic_error;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::resolve_url_or_path; use deno_core::resolve_url_or_path;
use deno_graph::GraphKind; use deno_graph::GraphKind;
use deno_path_util::url_from_file_path;
use deno_terminal::colors; use deno_terminal::colors;
use rand::Rng; use rand::Rng;
use std::path::Path; use std::path::Path;
@ -93,7 +95,16 @@ pub async fn compile(
.and_then(|p| ModuleSpecifier::from_directory_path(p).ok()) .and_then(|p| ModuleSpecifier::from_directory_path(p).ok())
.iter(), .iter(),
) )
.chain(include_files.iter()), .chain(include_files.iter())
.chain(
// sometimes the import map path is outside the root dir
cli_options
.workspace()
.to_import_map_path()
.ok()
.and_then(|p| p.and_then(|p| url_from_file_path(&p).ok()))
.iter(),
),
); );
log::debug!("Binary root dir: {}", root_dir_url); log::debug!("Binary root dir: {}", root_dir_url);
log::info!( log::info!(
@ -120,14 +131,18 @@ pub async fn compile(
})?; })?;
let write_result = binary_writer let write_result = binary_writer
.write_bin( .write_bin(WriteBinOptions {
file, writer: file,
&graph, display_output_filename: &output_path
StandaloneRelativeFileBaseUrl::from(&root_dir_url), .file_name()
.unwrap()
.to_string_lossy(),
graph: &graph,
root_dir_url: StandaloneRelativeFileBaseUrl::from(&root_dir_url),
entrypoint, entrypoint,
&include_files, include_files: &include_files,
&compile_flags, compile_flags: &compile_flags,
) })
.await .await
.with_context(|| { .with_context(|| {
format!( format!(
@ -368,17 +383,6 @@ fn resolve_root_dir_from_specifiers<'a>(
} }
} }
} }
let found_dir = if is_file_system_root(found_dir) {
found_dir
} else {
// include the parent dir name because it helps create some context
found_dir
.strip_suffix('/')
.unwrap_or(found_dir)
.rfind('/')
.map(|i| &found_dir[..i + 1])
.unwrap_or(found_dir)
};
ModuleSpecifier::parse(found_dir).unwrap() ModuleSpecifier::parse(found_dir).unwrap()
} }
@ -473,14 +477,17 @@ mod test {
.to_string() .to_string()
} }
assert_eq!(resolve("file:///a/b/c", &["file:///a/b/c/d"]), "file:///a/");
assert_eq!( assert_eq!(
resolve("file:///a/b/c/", &["file:///a/b/c/d"]), resolve("file:///a/b/e", &["file:///a/b/c/d"]),
"file:///a/b/" "file:///a/b/"
); );
assert_eq!(
resolve("file:///a/b/c/", &["file:///a/b/c/d"]),
"file:///a/b/c/"
);
assert_eq!( assert_eq!(
resolve("file:///a/b/c/", &["file:///a/b/c/d", "file:///a/b/c/e"]), resolve("file:///a/b/c/", &["file:///a/b/c/d", "file:///a/b/c/e"]),
"file:///a/b/" "file:///a/b/c/"
); );
assert_eq!(resolve("file:///", &["file:///a/b/c/d"]), "file:///"); assert_eq!(resolve("file:///", &["file:///a/b/c/d"]), "file:///");
if cfg!(windows) { if cfg!(windows) {
@ -488,7 +495,7 @@ mod test {
// this will ignore the other one because it's on a separate drive // this will ignore the other one because it's on a separate drive
assert_eq!( assert_eq!(
resolve("file:///c:/a/b/c/", &["file:///v:/a/b/c/d"]), resolve("file:///c:/a/b/c/", &["file:///v:/a/b/c/d"]),
"file:///c:/a/b/" "file:///c:/a/b/c/"
); );
} }
} }

View file

@ -2,7 +2,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt;
use std::fmt::Write; use std::fmt::Write;
use std::sync::Arc; use std::sync::Arc;
@ -35,6 +34,7 @@ use crate::graph_util::graph_exit_integrity_errors;
use crate::npm::CliNpmResolver; use crate::npm::CliNpmResolver;
use crate::npm::ManagedCliNpmResolver; use crate::npm::ManagedCliNpmResolver;
use crate::util::checksum; use crate::util::checksum;
use crate::util::display::DisplayTreeNode;
const JSON_SCHEMA_VERSION: u8 = 1; const JSON_SCHEMA_VERSION: u8 = 1;
@ -342,76 +342,6 @@ fn add_npm_packages_to_json(
json.insert("npmPackages".to_string(), json_packages.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. /// Precached information about npm packages that are used in deno info.
#[derive(Default)] #[derive(Default)]
struct NpmInfo { struct NpmInfo {
@ -568,7 +498,7 @@ impl<'a> GraphDisplayContext<'a> {
)?; )?;
writeln!(writer)?; writeln!(writer)?;
let root_node = self.build_module_info(root, false); let root_node = self.build_module_info(root, false);
print_tree_node(&root_node, writer)?; root_node.print(writer)?;
Ok(()) Ok(())
} }
Err(err) => { Err(err) => {
@ -584,7 +514,7 @@ impl<'a> GraphDisplayContext<'a> {
} }
} }
fn build_dep_info(&mut self, dep: &Dependency) -> Vec<TreeNode> { fn build_dep_info(&mut self, dep: &Dependency) -> Vec<DisplayTreeNode> {
let mut children = Vec::with_capacity(2); let mut children = Vec::with_capacity(2);
if !dep.maybe_code.is_none() { if !dep.maybe_code.is_none() {
if let Some(child) = self.build_resolved_info(&dep.maybe_code, false) { if let Some(child) = self.build_resolved_info(&dep.maybe_code, false) {
@ -599,7 +529,11 @@ impl<'a> GraphDisplayContext<'a> {
children children
} }
fn build_module_info(&mut self, module: &Module, type_dep: bool) -> TreeNode { fn build_module_info(
&mut self,
module: &Module,
type_dep: bool,
) -> DisplayTreeNode {
enum PackageOrSpecifier { enum PackageOrSpecifier {
Package(Box<NpmResolutionPackage>), Package(Box<NpmResolutionPackage>),
Specifier(ModuleSpecifier), Specifier(ModuleSpecifier),
@ -645,7 +579,7 @@ impl<'a> GraphDisplayContext<'a> {
format!("{} {}", header_text, maybe_size_to_text(maybe_size)) format!("{} {}", header_text, maybe_size_to_text(maybe_size))
}; };
let mut tree_node = TreeNode::from_text(header_text); let mut tree_node = DisplayTreeNode::from_text(header_text);
if !was_seen { if !was_seen {
match &package_or_specifier { match &package_or_specifier {
@ -683,14 +617,14 @@ impl<'a> GraphDisplayContext<'a> {
fn build_npm_deps( fn build_npm_deps(
&mut self, &mut self,
package: &NpmResolutionPackage, package: &NpmResolutionPackage,
) -> Vec<TreeNode> { ) -> Vec<DisplayTreeNode> {
let mut deps = package.dependencies.values().collect::<Vec<_>>(); let mut deps = package.dependencies.values().collect::<Vec<_>>();
deps.sort(); deps.sort();
let mut children = Vec::with_capacity(deps.len()); let mut children = Vec::with_capacity(deps.len());
for dep_id in deps.into_iter() { for dep_id in deps.into_iter() {
let maybe_size = self.npm_info.package_sizes.get(dep_id).cloned(); let maybe_size = self.npm_info.package_sizes.get(dep_id).cloned();
let size_str = maybe_size_to_text(maybe_size); let size_str = maybe_size_to_text(maybe_size);
let mut child = TreeNode::from_text(format!( let mut child = DisplayTreeNode::from_text(format!(
"npm:/{} {}", "npm:/{} {}",
dep_id.as_serialized(), dep_id.as_serialized(),
size_str size_str
@ -715,7 +649,7 @@ impl<'a> GraphDisplayContext<'a> {
&mut self, &mut self,
err: &ModuleError, err: &ModuleError,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> TreeNode { ) -> DisplayTreeNode {
self.seen.insert(specifier.to_string()); self.seen.insert(specifier.to_string());
match err { match err {
ModuleError::InvalidTypeAssertion { .. } => { ModuleError::InvalidTypeAssertion { .. } => {
@ -758,8 +692,8 @@ impl<'a> GraphDisplayContext<'a> {
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
error_msg: &str, error_msg: &str,
) -> TreeNode { ) -> DisplayTreeNode {
TreeNode::from_text(format!( DisplayTreeNode::from_text(format!(
"{} {}", "{} {}",
colors::red(specifier), colors::red(specifier),
colors::red_bold(error_msg) colors::red_bold(error_msg)
@ -770,7 +704,7 @@ impl<'a> GraphDisplayContext<'a> {
&mut self, &mut self,
resolution: &Resolution, resolution: &Resolution,
type_dep: bool, type_dep: bool,
) -> Option<TreeNode> { ) -> Option<DisplayTreeNode> {
match resolution { match resolution {
Resolution::Ok(resolved) => { Resolution::Ok(resolved) => {
let specifier = &resolved.specifier; let specifier = &resolved.specifier;
@ -778,14 +712,14 @@ impl<'a> GraphDisplayContext<'a> {
Some(match self.graph.try_get(resolved_specifier) { Some(match self.graph.try_get(resolved_specifier) {
Ok(Some(module)) => self.build_module_info(module, type_dep), Ok(Some(module)) => self.build_module_info(module, type_dep),
Err(err) => self.build_error_info(err, resolved_specifier), Err(err) => self.build_error_info(err, resolved_specifier),
Ok(None) => TreeNode::from_text(format!( Ok(None) => DisplayTreeNode::from_text(format!(
"{} {}", "{} {}",
colors::red(specifier), colors::red(specifier),
colors::red_bold("(missing)") colors::red_bold("(missing)")
)), )),
}) })
} }
Resolution::Err(err) => Some(TreeNode::from_text(format!( Resolution::Err(err) => Some(DisplayTreeNode::from_text(format!(
"{} {}", "{} {}",
colors::italic(err.to_string()), colors::italic(err.to_string()),
colors::red_bold("(resolve error)") colors::red_bold("(resolve error)")

View file

@ -2,6 +2,7 @@
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::serde_json; use deno_core::serde_json;
use deno_runtime::colors;
use std::io::Write; use std::io::Write;
/// A function that converts a float to a string the represents a human /// A function that converts a float to a string the represents a human
@ -85,6 +86,78 @@ where
Ok(()) Ok(())
} }
pub struct DisplayTreeNode {
pub text: String,
pub children: Vec<DisplayTreeNode>,
}
impl DisplayTreeNode {
pub fn from_text(text: String) -> Self {
Self {
text,
children: Default::default(),
}
}
pub fn print<TWrite: std::fmt::Write>(
&self,
writer: &mut TWrite,
) -> std::fmt::Result {
fn print_children<TWrite: std::fmt::Write>(
writer: &mut TWrite,
prefix: &str,
children: &[DisplayTreeNode],
) -> std::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, "{}", self.text)?;
print_children(writer, "", &self.children)?;
Ok(())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -1119,6 +1119,11 @@ Compile file:///[WILDCARD]/main.ts to [WILDCARD]
Warning Failed resolving symlink. Ignoring. Warning Failed resolving symlink. Ignoring.
Path: [WILDCARD] Path: [WILDCARD]
Message: [WILDCARD]) Message: [WILDCARD])
Embedded File System
[WILDCARD]
"#, "#,
); );

View file

@ -2,3 +2,9 @@ Warning Parsing failed within the specified environment file: environment.env at
Check [WILDCARD]main.ts Check [WILDCARD]main.ts
Compile [WILDCARD]main.ts to out[WILDCARD] Compile [WILDCARD]main.ts to out[WILDCARD]
Warning Environment variables from the file "environment.env" were embedded in the generated executable file Warning Environment variables from the file "environment.env" were embedded in the generated executable file
Embedded File System
out[WILDLINE]
└── main.ts

View file

@ -1,2 +1,6 @@
Check file:///[WILDLINE]/main.js Check file:///[WILDLINE]/main.js
Compile file:///[WILDLINE] Compile file:///[WILDLINE]
Embedded File System
[WILDCARD]

View file

@ -1,2 +1,6 @@
Compile file:///[WILDCARD]/node_modules_symlink_outside/main.ts to [WILDCARD] Compile file:///[WILDCARD]/node_modules_symlink_outside/main.ts to [WILDCARD]
Warning Symlink target is outside '[WILDCARD]compile'. Inlining symlink at '[WILDCARD]node_modules_symlink_outside[WILDCARD]node_modules[WILDCARD]test.txt' to '[WILDCARD]target.txt' as file. Warning Symlink target is outside '[WILDCARD]node_modules_symlink_outside'. Inlining symlink at '[WILDCARD]node_modules_symlink_outside[WILDCARD]node_modules[WILDCARD]test.txt' to '[WILDCARD]target.txt' as file.
Embedded File System
[WILDCARD]

View file

@ -3,4 +3,8 @@ Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz
Initialize @denotest/esm-basic@1.0.0 Initialize @denotest/esm-basic@1.0.0
Check file:///[WILDCARD]/node_modules_symlink_outside/main.ts Check file:///[WILDCARD]/node_modules_symlink_outside/main.ts
Compile file:///[WILDCARD]/node_modules_symlink_outside/main.ts to [WILDLINE] Compile file:///[WILDCARD]/node_modules_symlink_outside/main.ts to [WILDLINE]
Warning Symlink target is outside '[WILDLINE]compile'. Excluding symlink at '[WILDLINE]node_modules_symlink_outside[WILDLINE]node_modules[WILDLINE]symlink_dir' with target '[WILDLINE]some_folder'. Warning Symlink target is outside '[WILDLINE]node_modules_symlink_outside'. Excluding symlink at '[WILDLINE]node_modules_symlink_outside[WILDLINE]node_modules[WILDLINE]symlink_dir' with target '[WILDLINE]some_folder'.
Embedded File System
[WILDCARD]