1use std::{env::current_dir, path::PathBuf};
2
3use anyhow::Result;
4use rustc_hash::FxHashSet;
5use turbo_rcstr::{RcStr, rcstr};
6use turbo_tasks::{TransientInstance, Vc, turbofmt};
7use turbo_tasks_fs::{DiskFileSystem, FileSystem};
8use turbopack::{
9 ModuleAssetContext,
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 module::Module,
24 reference::referenced_modules_and_affecting_sources,
25 reference_type::ReferenceType,
26 resolve::options::ConditionValue,
27};
28use turbopack_ecmascript::AnalyzeMode;
29use turbopack_resolve::resolve_options_context::ResolveOptionsContext;
30
31pub async fn node_file_trace(
32 project_root: RcStr,
33 input: RcStr,
34 graph: bool,
35 show_issues: bool,
36 max_depth: Option<usize>,
37) -> Result<()> {
38 let op = node_file_trace_operation(project_root.clone(), input.clone(), graph, max_depth);
39 let result = op.read_strongly_consistent().await?;
40
41 if show_issues {
42 let issue_reporter: Vc<Box<dyn IssueReporter>> =
43 Vc::upcast(ConsoleUi::new(TransientInstance::new(LogOptions {
44 project_dir: PathBuf::from(project_root),
45 current_dir: current_dir().unwrap(),
46 show_all: true,
47 log_detail: false,
48 log_level: IssueSeverity::Hint,
49 })));
50
51 handle_issues(op, issue_reporter, IssueSeverity::Error, None, None).await?;
52 }
53
54 println!("FILELIST:");
55 for a in &result {
56 println!("{a}");
57 }
58
59 Ok(())
60}
61
62#[turbo_tasks::function(operation)]
63async fn node_file_trace_operation(
64 project_root: RcStr,
65 input: RcStr,
66 graph: bool,
67 max_depth: Option<usize>,
68) -> Result<Vc<Vec<RcStr>>> {
69 let workspace_fs: Vc<Box<dyn FileSystem>> = Vc::upcast(DiskFileSystem::new(
70 rcstr!("workspace"),
71 Vc::cell(project_root.clone()),
72 ));
73 let input_dir = workspace_fs.root().owned().await?;
74 let input = input_dir.join(&format!("{input}"))?;
75
76 let source = FileSource::new(input);
77 let environment = Environment::new(ExecutionEnvironment::NodeJsLambda(
78 NodeJsEnvironment::default().resolved_cell(),
79 ));
80 let module_asset_context = ModuleAssetContext::new_without_replace_externals(
81 Default::default(),
82 CompileTimeInfo::new(environment),
88 ModuleOptionsContext {
89 ecmascript: EcmascriptOptionsContext {
90 enable_typescript_transform: Some(
91 TypescriptTransformOptions::default().resolved_cell(),
92 ),
93 ..Default::default()
94 },
95 css: CssOptionsContext {
96 enable_raw_css: true,
97 ..Default::default()
98 },
99 environment: None,
102 analyze_mode: AnalyzeMode::Tracing,
103 tree_shaking_mode: None,
106 ..Default::default()
107 }
108 .cell(),
109 ResolveOptionsContext {
110 enable_node_native_modules: true,
111 enable_node_modules: Some(input_dir),
112 custom_conditions: vec![rcstr!("node")],
113 module_sync: ConditionValue::Unknown,
114 enable_node_externals: true,
115 loose_errors: true,
116 collect_affecting_sources: true,
117 ..Default::default()
118 }
119 .cell(),
120 Layer::new(rcstr!("externals-tracing")),
121 );
122 let module = module_asset_context
123 .process(Vc::upcast(source), ReferenceType::Undefined)
124 .module();
125
126 Ok(Vc::cell(if graph {
127 to_graph(module, max_depth.unwrap_or(usize::MAX)).await?
128 } else {
129 to_list(module).await?
130 }))
131}
132
133async fn to_list(asset: Vc<Box<dyn Module>>) -> Result<Vec<RcStr>> {
134 let mut assets = vec![];
135
136 let mut visited = FxHashSet::default();
137 let mut queue = Vec::new();
138 queue.push(asset);
139
140 while let Some(asset) = queue.pop() {
141 let references = referenced_modules_and_affecting_sources(asset, false).await?;
142 let path = &asset.ident().await?.path;
143 if visited.insert(asset) {
144 for (_, references) in references.iter().rev() {
145 for asset in references.modules.iter() {
146 queue.push(**asset);
147 }
148 }
149 }
150 assets.push(path.path.clone());
151 }
152
153 assets.sort();
154 assets.dedup();
155
156 Ok(assets)
157}
158
159async fn to_graph(asset: Vc<Box<dyn Module>>, max_depth: usize) -> Result<Vec<RcStr>> {
160 let mut visited = FxHashSet::default();
161 let mut queue = Vec::new();
162 queue.push((0, asset));
163
164 let mut result = vec![];
165 while let Some((depth, asset)) = queue.pop() {
166 let references = referenced_modules_and_affecting_sources(asset, false).await?;
167 let mut indent = String::new();
168 for _ in 0..depth {
169 indent.push_str(" ");
170 }
171 let path = &asset.ident().await?.path;
172 if visited.insert(asset) {
173 if depth < max_depth {
174 for (_, references) in references.iter().rev() {
175 for asset in references.modules.iter() {
176 queue.push((depth + 1, **asset));
177 }
178 }
179 }
180 result.push(turbofmt!("{indent}{}", path.path).await?);
181 } else if references.is_empty() {
182 result.push(turbofmt!("{indent}{} *", path.path).await?);
183 } else {
184 result.push(turbofmt!("{indent}{} *...", path.path).await?);
185 }
186 }
187 result.push("".into());
188 result.push("* : revisited and no references".into());
189 result.push("*... : revisited and references were already printed".into());
190 Ok(result)
191}