turbopack_nft/
nft.rs

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        // This config should be kept in sync with
82        // turbopack/crates/turbopack/tests/node-file-trace.rs and
83        // turbopack/crates/turbopack/src/lib.rs
84        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 is not passed in order to avoid downleveling JS / CSS for
97            // node-file-trace.
98            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}