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 ..Default::default()
106 }
107 .cell(),
108 ResolveOptionsContext {
109 enable_node_native_modules: true,
110 enable_node_modules: Some(input_dir),
111 custom_conditions: vec![rcstr!("node")],
112 module_sync: ConditionValue::Unknown,
113 enable_node_externals: true,
114 loose_errors: true,
115 collect_affecting_sources: true,
116 ..Default::default()
117 }
118 .cell(),
119 Layer::new(rcstr!("externals-tracing")),
120 );
121 let module = module_asset_context
122 .process(Vc::upcast(source), ReferenceType::Undefined)
123 .module();
124
125 Ok(Vc::cell(if graph {
126 to_graph(module, max_depth.unwrap_or(usize::MAX)).await?
127 } else {
128 to_list(module).await?
129 }))
130}
131
132async fn to_list(asset: Vc<Box<dyn Module>>) -> Result<Vec<RcStr>> {
133 let mut assets = vec![];
134
135 let mut visited = FxHashSet::default();
136 let mut queue = Vec::new();
137 queue.push(asset);
138
139 while let Some(asset) = queue.pop() {
140 let references = referenced_modules_and_affecting_sources(asset, false).await?;
141 let path = &asset.ident().await?.path;
142 if visited.insert(asset) {
143 for (_, references) in references.iter().rev() {
144 for asset in references.modules.iter() {
145 queue.push(**asset);
146 }
147 }
148 }
149 assets.push(path.path.clone());
150 }
151
152 assets.sort();
153 assets.dedup();
154
155 Ok(assets)
156}
157
158async fn to_graph(asset: Vc<Box<dyn Module>>, max_depth: usize) -> Result<Vec<RcStr>> {
159 let mut visited = FxHashSet::default();
160 let mut queue = Vec::new();
161 queue.push((0, asset));
162
163 let mut result = vec![];
164 while let Some((depth, asset)) = queue.pop() {
165 let references = referenced_modules_and_affecting_sources(asset, false).await?;
166 let mut indent = String::new();
167 for _ in 0..depth {
168 indent.push_str(" ");
169 }
170 let path = &asset.ident().await?.path;
171 if visited.insert(asset) {
172 if depth < max_depth {
173 for (_, references) in references.iter().rev() {
174 for asset in references.modules.iter() {
175 queue.push((depth + 1, **asset));
176 }
177 }
178 }
179 result.push(turbofmt!("{indent}{}", path.path).await?);
180 } else if references.is_empty() {
181 result.push(turbofmt!("{indent}{} *", path.path).await?);
182 } else {
183 result.push(turbofmt!("{indent}{} *...", path.path).await?);
184 }
185 }
186 result.push("".into());
187 result.push("* : revisited and no references".into());
188 result.push("*... : revisited and references were already printed".into());
189 Ok(result)
190}