1use std::{collections::HashSet, env::current_dir, path::PathBuf};
2
3use anyhow::Result;
4use turbo_rcstr::{RcStr, rcstr};
5use turbo_tasks::{ResolvedVc, TransientInstance, TryJoinIterExt, ValueToString, Vc};
6use turbo_tasks_fs::{DiskFileSystem, FileSystem};
7use turbopack::{
8 ModuleAssetContext,
9 module_options::{CssOptionsContext, EcmascriptOptionsContext, ModuleOptionsContext},
10};
11use turbopack_cli_utils::issue::{ConsoleUi, LogOptions};
12use turbopack_core::{
13 compile_time_info::CompileTimeInfo,
14 context::AssetContext,
15 environment::{Environment, ExecutionEnvironment, NodeJsEnvironment},
16 file_source::FileSource,
17 ident::Layer,
18 issue::{IssueReporter, IssueSeverity, handle_issues},
19 output::OutputAsset,
20 reference::all_assets_from_entries,
21 reference_type::ReferenceType,
22 traced_asset::TracedAsset,
23};
24use turbopack_resolve::resolve_options_context::ResolveOptionsContext;
25
26pub async fn node_file_trace(
27 project_root: RcStr,
28 input: RcStr,
29 graph: bool,
30 show_issues: bool,
31 max_depth: Option<usize>,
32) -> Result<()> {
33 let op = node_file_trace_operation(project_root.clone(), input.clone(), graph, max_depth);
34 let result = op.resolve_strongly_consistent().await?;
35
36 if show_issues {
37 let issue_reporter: Vc<Box<dyn IssueReporter>> =
38 Vc::upcast(ConsoleUi::new(TransientInstance::new(LogOptions {
39 project_dir: PathBuf::from(project_root),
40 current_dir: current_dir().unwrap(),
41 show_all: true,
42 log_detail: false,
43 log_level: IssueSeverity::Hint,
44 })));
45
46 handle_issues(op, issue_reporter, IssueSeverity::Error, None, None).await?;
47 }
48
49 println!("FILELIST:");
50 for a in result.await? {
51 println!("{a}");
52 }
53
54 Ok(())
55}
56
57#[turbo_tasks::function(operation)]
58async fn node_file_trace_operation(
59 project_root: RcStr,
60 input: RcStr,
61 graph: bool,
62 max_depth: Option<usize>,
63) -> Result<Vc<Vec<RcStr>>> {
64 let workspace_fs: Vc<Box<dyn FileSystem>> = Vc::upcast(DiskFileSystem::new(
65 rcstr!("workspace"),
66 project_root.clone(),
67 ));
68 let input_dir = workspace_fs.root().owned().await?;
69 let input = input_dir.join(&format!("{input}"))?;
70
71 let source = FileSource::new(input);
72 let environment = Environment::new(ExecutionEnvironment::NodeJsLambda(
73 NodeJsEnvironment::default().resolved_cell(),
74 ));
75 let module_asset_context = ModuleAssetContext::new(
76 Default::default(),
77 CompileTimeInfo::new(environment),
81 ModuleOptionsContext {
82 ecmascript: EcmascriptOptionsContext {
83 ..Default::default()
84 },
85 css: CssOptionsContext {
86 enable_raw_css: true,
87 ..Default::default()
88 },
89 environment: None,
92 is_tracing: true,
93 ..Default::default()
94 }
95 .cell(),
96 ResolveOptionsContext {
97 enable_node_native_modules: true,
98 enable_node_modules: Some(input_dir.clone()),
99 custom_conditions: vec![rcstr!("node")],
100 loose_errors: true,
101 ..Default::default()
102 }
103 .cell(),
104 Layer::new(rcstr!("externals-tracing")),
105 );
106 let module = module_asset_context
107 .process(Vc::upcast(source), ReferenceType::Undefined)
108 .module();
109
110 let asset = TracedAsset::new(module).to_resolved().await?;
111
112 Ok(Vc::cell(if graph {
113 to_graph(ResolvedVc::upcast(asset), max_depth.unwrap_or(usize::MAX)).await?
114 } else {
115 to_list(ResolvedVc::upcast(asset)).await?
116 }))
117}
118
119async fn to_list(asset: ResolvedVc<Box<dyn OutputAsset>>) -> Result<Vec<RcStr>> {
120 let mut assets = all_assets_from_entries(Vc::cell(vec![asset]))
121 .await?
122 .iter()
123 .map(async |a| Ok(a.path().await?.path.clone()))
124 .try_join()
125 .await?;
126 assets.sort();
127 assets.dedup();
128
129 Ok(assets)
130}
131
132async fn to_graph(asset: ResolvedVc<Box<dyn OutputAsset>>, max_depth: usize) -> Result<Vec<RcStr>> {
133 let mut visited = HashSet::new();
134 let mut queue = Vec::new();
135 queue.push((0, asset));
136
137 let mut result = vec![];
138 while let Some((depth, asset)) = queue.pop() {
139 let references = asset.references().await?;
140 let mut indent = String::new();
141 for _ in 0..depth {
142 indent.push_str(" ");
143 }
144 if visited.insert(asset) {
145 if depth < max_depth {
146 for &asset in references.iter().rev() {
147 queue.push((depth + 1, asset));
148 }
149 }
150 result.push(format!("{}{}", indent, asset.path().to_string().await?).into());
151 } else if references.is_empty() {
152 result.push(format!("{}{} *", indent, asset.path().to_string().await?).into());
153 } else {
154 result.push(format!("{}{} *...", indent, asset.path().to_string().await?).into());
155 }
156 }
157 result.push("".into());
158 result.push("* : revisited and no references".into());
159 result.push("*... : revisited and references were already printed".into());
160 Ok(result)
161}