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::{
10 CssOptionsContext, EcmascriptOptionsContext, ModuleOptionsContext,
11 TypescriptTransformOptions,
12 },
13};
14use turbopack_cli_utils::issue::{ConsoleUi, LogOptions};
15use turbopack_core::{
16 compile_time_info::CompileTimeInfo,
17 context::AssetContext,
18 environment::{Environment, ExecutionEnvironment, NodeJsEnvironment},
19 file_source::FileSource,
20 ident::Layer,
21 issue::{IssueReporter, IssueSeverity, handle_issues},
22 output::{OutputAsset, OutputAssetsReference},
23 reference::all_assets_from_entries,
24 reference_type::ReferenceType,
25 traced_asset::TracedAsset,
26};
27use turbopack_ecmascript::AnalyzeMode;
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_without_replace_externals(
80 Default::default(),
81 CompileTimeInfo::new(environment),
87 ModuleOptionsContext {
88 ecmascript: EcmascriptOptionsContext {
89 enable_typescript_transform: Some(
90 TypescriptTransformOptions::default().resolved_cell(),
91 ),
92 ..Default::default()
93 },
94 css: CssOptionsContext {
95 enable_raw_css: true,
96 ..Default::default()
97 },
98 environment: None,
101 analyze_mode: AnalyzeMode::Tracing,
102 tree_shaking_mode: None,
105 ..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 enable_node_externals: true,
113 loose_errors: true,
114 collect_affecting_sources: true,
115 ..Default::default()
116 }
117 .cell(),
118 Layer::new(rcstr!("externals-tracing")),
119 );
120 let module = module_asset_context
121 .process(Vc::upcast(source), ReferenceType::Undefined)
122 .module();
123
124 let asset = TracedAsset::new(module).to_resolved().await?;
125
126 Ok(Vc::cell(if graph {
127 to_graph(ResolvedVc::upcast(asset), max_depth.unwrap_or(usize::MAX)).await?
128 } else {
129 to_list(ResolvedVc::upcast(asset)).await?
130 }))
131}
132
133async fn to_list(asset: ResolvedVc<Box<dyn OutputAsset>>) -> Result<Vec<RcStr>> {
134 let mut assets = all_assets_from_entries(Vc::cell(vec![asset]))
135 .await?
136 .iter()
137 .map(async |a| Ok(a.path().await?.path.clone()))
138 .try_join()
139 .await?;
140 assets.sort();
141 assets.dedup();
142
143 Ok(assets)
144}
145
146async fn to_graph(asset: ResolvedVc<Box<dyn OutputAsset>>, max_depth: usize) -> Result<Vec<RcStr>> {
147 let mut visited = HashSet::new();
148 let mut queue = Vec::new();
149 queue.push((0, asset));
150
151 let mut result = vec![];
152 while let Some((depth, asset)) = queue.pop() {
153 let references = asset.references().all_assets().await?;
154 let mut indent = String::new();
155 for _ in 0..depth {
156 indent.push_str(" ");
157 }
158 if visited.insert(asset) {
159 if depth < max_depth {
160 for &asset in references.iter().rev() {
161 queue.push((depth + 1, asset));
162 }
163 }
164 result.push(format!("{}{}", indent, asset.path().to_string().await?).into());
165 } else if references.is_empty() {
166 result.push(format!("{}{} *", indent, asset.path().to_string().await?).into());
167 } else {
168 result.push(format!("{}{} *...", indent, asset.path().to_string().await?).into());
169 }
170 }
171 result.push("".into());
172 result.push("* : revisited and no references".into());
173 result.push("*... : revisited and references were already printed".into());
174 Ok(result)
175}