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