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