Skip to main content

next_api/
project.rs

1use std::{path::Path, time::Duration};
2
3use anyhow::{Context, Result, bail};
4use async_trait::async_trait;
5use bincode::{Decode, Encode};
6use indexmap::map::Entry;
7use next_core::{
8    app_structure::find_app_dir,
9    emit_assets, get_edge_chunking_context, get_edge_chunking_context_with_client_assets,
10    get_edge_compile_time_info, get_edge_resolve_options_context,
11    instrumentation::instrumentation_files,
12    middleware::middleware_files,
13    mode::NextMode,
14    next_app::{AppPage, AppPath},
15    next_client::{
16        ClientChunkingContextOptions, ClientContextType, ServiceWorkerChunkingContextOptions,
17        get_client_chunking_context, get_client_compile_time_info,
18        get_client_module_options_context, get_client_resolve_options_context,
19        get_service_worker_chunking_context,
20    },
21    next_config::{
22        DIST_PROFILES_DIR_NAME, ModuleIds as ModuleIdStrategyConfig, NextConfig, OutputType,
23        TurbopackPluginRuntimeStrategy,
24    },
25    next_edge::context::EdgeChunkingContextOptions,
26    next_server::{
27        ServerChunkingContextOptions, ServerContextType, get_server_chunking_context,
28        get_server_chunking_context_with_client_assets, get_server_compile_time_info,
29        get_server_module_options_context, get_server_resolve_options_context,
30        get_tracing_compile_time_info,
31    },
32    next_telemetry::ProjectFeatureUsageSummary,
33    parse_segment_config_from_source,
34    segment_config::ParseSegmentMode,
35    util::{NextRuntime, OptionEnvMap},
36};
37use rustc_hash::{FxHashMap, FxHashSet};
38use serde::{Deserialize, Serialize};
39use tracing::{Instrument, field::Empty};
40use turbo_rcstr::{RcStr, rcstr};
41use turbo_tasks::{
42    Completion, Completions, FxIndexMap, NonLocalValue, OperationValue, OperationVc, ReadRef,
43    ResolvedVc, State, TransientInstance, TryFlatJoinIterExt, TryJoinIterExt, Vc,
44    debug::ValueDebugFormat, fxindexmap, trace::TraceRawVcs,
45};
46use turbo_tasks_env::{EnvMap, ProcessEnv};
47use turbo_tasks_fs::{
48    DiskFileSystem, FileContent, FileSystem, FileSystemPath, VirtualFileSystem,
49    canonicalize_to_rcstr, invalidation,
50};
51use turbo_unix_path::join_path;
52use turbopack::{
53    ModuleAssetContext, evaluate_context::node_build_environment, externals_tracing_module_context,
54    global_module_ids::get_global_module_id_strategy, transition::TransitionOptions,
55};
56use turbopack_core::{
57    PROJECT_FILESYSTEM_NAME,
58    changed::content_changed,
59    chunk::{
60        ChunkingContext, EvaluatableAssets, UnusedReferences,
61        chunk_id_strategy::{ModuleIdFallback, ModuleIdStrategy},
62    },
63    compile_time_info::CompileTimeInfo,
64    context::AssetContext,
65    environment::NodeJsVersion,
66    file_source::FileSource,
67    ident::Layer,
68    issue::{
69        CollectibleIssuesExt, Issue, IssueExt, IssueFilter, IssueSeverity, IssueStage, StyledString,
70    },
71    module::{Module, Modules},
72    module_graph::{
73        GraphEntries, ModuleGraph, SingleModuleGraph, VisitedModules,
74        binding_usage_info::{
75            BindingUsageInfo, OptionBindingUsageInfo, compute_binding_usage_info,
76        },
77        chunk_group_info::{ChunkGroupEntry, EntryHeuristics},
78    },
79    output::{
80        ExpandOutputAssetsInput, ExpandedOutputAssets, OutputAsset, OutputAssets,
81        expand_output_assets,
82    },
83    reference::all_assets_from_entries,
84    reference_type::{CommonJsReferenceSubType, ReferenceType},
85    resolve::{FindContextFileResult, find_context_file},
86    version::{
87        NotFoundVersion, OptionVersionedContent, Update, Version, VersionState, VersionedContent,
88    },
89};
90#[cfg(feature = "process_pool")]
91use turbopack_node::child_process_backend;
92use turbopack_node::execution_context::ExecutionContext;
93#[cfg(feature = "worker_pool")]
94use turbopack_node::worker_threads_backend;
95use turbopack_nodejs::NodeJsChunkingContext;
96
97use crate::{
98    app::{AppProject, OptionAppProject},
99    empty::EmptyEndpoint,
100    entrypoints::Entrypoints,
101    instrumentation::InstrumentationEndpoint,
102    middleware::MiddlewareEndpoint,
103    pages::PagesProject,
104    route::{
105        Endpoint, EndpointGroup, EndpointGroupEntry, EndpointGroupKey, EndpointGroups, Endpoints,
106        Route,
107    },
108    versioned_content_map::VersionedContentMap,
109};
110
111#[turbo_tasks::task_input]
112#[derive(
113    Debug,
114    Serialize,
115    Deserialize,
116    Clone,
117    PartialEq,
118    Eq,
119    Hash,
120    TraceRawVcs,
121    OperationValue,
122    Encode,
123    Decode,
124)]
125#[serde(rename_all = "camelCase")]
126pub struct DraftModeOptions {
127    pub preview_mode_id: RcStr,
128    pub preview_mode_encryption_key: RcStr,
129    pub preview_mode_signing_key: RcStr,
130}
131
132#[turbo_tasks::task_input]
133#[derive(
134    Debug,
135    Default,
136    Serialize,
137    Deserialize,
138    Copy,
139    Clone,
140    PartialEq,
141    Eq,
142    Hash,
143    TraceRawVcs,
144    OperationValue,
145    Encode,
146    Decode,
147)]
148#[serde(rename_all = "camelCase")]
149pub struct WatchOptions {
150    /// Whether to watch the filesystem for file changes.
151    pub enable: bool,
152
153    /// Enable polling at a certain interval if the native file watching doesn't work (e.g.
154    /// docker).
155    pub poll_interval: Option<Duration>,
156}
157
158#[turbo_tasks::task_input]
159#[derive(
160    Debug,
161    Default,
162    Serialize,
163    Deserialize,
164    Clone,
165    PartialEq,
166    Eq,
167    Hash,
168    TraceRawVcs,
169    OperationValue,
170    Encode,
171    Decode,
172)]
173#[serde(rename_all = "camelCase")]
174pub struct DebugBuildPaths {
175    pub app: Vec<RcStr>,
176    pub pages: Vec<RcStr>,
177}
178
179/// Target for HMR operations - client-side (browser) or server-side (Node.js).
180#[turbo_tasks::task_input]
181#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash, TraceRawVcs, Encode, Decode)]
182pub enum HmrTarget {
183    #[default]
184    Client,
185    Server,
186}
187
188impl std::fmt::Display for HmrTarget {
189    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190        match self {
191            HmrTarget::Client => write!(f, "client"),
192            HmrTarget::Server => write!(f, "server"),
193        }
194    }
195}
196
197impl std::str::FromStr for HmrTarget {
198    type Err = String;
199
200    fn from_str(s: &str) -> Result<Self, Self::Err> {
201        match s {
202            "client" => Ok(HmrTarget::Client),
203            "server" => Ok(HmrTarget::Server),
204            _ => Err(format!(
205                "Invalid HMR target: '{}'. Expected 'client' or 'server'",
206                s
207            )),
208        }
209    }
210}
211
212/// Pre-converted route keys from debug build paths for O(1) lookups.
213struct DebugBuildPathsRouteKeys {
214    app: FxHashSet<RcStr>,
215    pages: FxHashSet<RcStr>,
216}
217
218impl DebugBuildPathsRouteKeys {
219    fn app_route_key_from_debug_path(path: &str) -> Result<RcStr> {
220        let mut segments = path
221            .trim_start_matches('/')
222            .split('/')
223            .filter(|segment| !segment.is_empty())
224            .collect::<Vec<_>>();
225
226        if let Some(last_segment) = segments.last()
227            && (*last_segment == "page"
228                || last_segment.starts_with("page.")
229                || *last_segment == "route"
230                || last_segment.starts_with("route."))
231        {
232            segments.pop();
233        }
234
235        let normalized_path = segments.join("/");
236        Ok(AppPath::from(AppPage::parse(&normalized_path)?)
237            .to_string()
238            .into())
239    }
240
241    fn pages_route_key_from_debug_path(path: &RcStr) -> Result<RcStr> {
242        // Strip extension: "/foo.tsx" -> "/foo"
243        // Catch-all routes like "/foo/[...slug]" contain dots in the segment name;
244        // only treat the suffix as an extension when it is a plain alphanumeric token.
245        let file_name = path.rsplit('/').next().unwrap_or(path);
246        let result = if let Some(dot_idx) = file_name.rfind('.') {
247            let ext = &file_name[dot_idx + 1..];
248            if !ext.is_empty() && ext.chars().all(|c| c.is_ascii_alphanumeric()) {
249                let trimmed_len = path.len() - (file_name.len() - dot_idx);
250                path[..trimmed_len].into()
251            } else {
252                path.clone()
253            }
254        } else {
255            path.clone()
256        };
257
258        // Strip index suffix: "/foo/index.tsx" -> "/foo"
259        Ok(if let Some(stripped) = result.strip_suffix("/index") {
260            if stripped.is_empty() {
261                "/".into()
262            } else {
263                stripped.into()
264            }
265        } else {
266            result
267        })
268    }
269
270    fn from_debug_build_paths(paths: &DebugBuildPaths) -> Result<Self> {
271        Ok(Self {
272            app: paths
273                .app
274                .iter()
275                .map(|path| Self::app_route_key_from_debug_path(path))
276                .collect::<Result<_>>()?,
277            pages: paths
278                .pages
279                .iter()
280                .map(Self::pages_route_key_from_debug_path)
281                .collect::<Result<_>>()?,
282        })
283    }
284
285    fn should_include_app_route(&self, route_key: &RcStr) -> bool {
286        // Special app router framework routes
287        if matches!(route_key.as_str(), "/_not-found" | "/_global-error") {
288            return true;
289        }
290        self.app.contains(route_key)
291    }
292
293    fn should_include_pages_route(&self, route_key: &RcStr) -> bool {
294        // Special pages router framework routes
295        if matches!(route_key.as_str(), "/_error" | "/_document" | "/_app") {
296            return true;
297        }
298        self.pages.contains(route_key)
299    }
300}
301
302#[derive(
303    Debug,
304    Serialize,
305    Deserialize,
306    Clone,
307    PartialEq,
308    Eq,
309    TraceRawVcs,
310    NonLocalValue,
311    OperationValue,
312    Encode,
313    Decode,
314)]
315#[serde(rename_all = "camelCase")]
316pub struct ProjectOptions {
317    /// An absolute root path (Unix or Windows path) from which all files must be nested under.
318    /// Trying to access a file outside this root will fail, so think of this as a chroot.
319    /// E.g. `/home/user/projects/my-repo`.
320    pub root_path: RcStr,
321
322    /// A path which contains the app/pages directories, relative to [`Project::project_path`],
323    /// always Unix path. E.g. `apps/my-app`
324    pub project_path: RcStr,
325
326    /// The contents of next.config.js, serialized to JSON.
327    pub next_config: RcStr,
328
329    /// A map of environment variables to use when compiling code.
330    pub env: Vec<(RcStr, RcStr)>,
331
332    /// A map of environment variables which should get injected at compile
333    /// time.
334    pub define_env: DefineEnv,
335
336    /// Filesystem watcher options.
337    pub watch: WatchOptions,
338
339    /// The mode in which Next.js is running.
340    pub dev: bool,
341
342    /// The server actions encryption key.
343    pub encryption_key: RcStr,
344
345    /// The build id.
346    pub build_id: RcStr,
347
348    /// Options for draft mode.
349    pub preview_props: DraftModeOptions,
350
351    /// The browserslist query to use for targeting browsers.
352    pub browserslist_query: RcStr,
353
354    /// When the code is minified, this opts out of the default mangling of
355    /// local names for variables, functions etc., which can be useful for
356    /// debugging/profiling purposes.
357    pub no_mangling: bool,
358
359    /// Whether to write the route hashes manifest.
360    pub write_routes_hashes_manifest: bool,
361
362    /// The version of Node.js that is available/currently running.
363    pub current_node_js_version: RcStr,
364
365    /// Debug build paths for selective builds.
366    /// When set, only routes matching these paths will be included in the build.
367    pub debug_build_paths: Option<DebugBuildPaths>,
368
369    /// App-router page routes that should be built after non-deferred routes.
370    pub deferred_entries: Option<Vec<RcStr>>,
371
372    /// Whether to enable persistent caching
373    pub is_persistent_caching_enabled: bool,
374
375    /// The version of Next.js that is running.
376    pub next_version: RcStr,
377
378    /// Whether server-side HMR is enabled (disabled with --no-server-fast-refresh).
379    pub server_hmr: bool,
380}
381
382#[derive(Default)]
383pub struct PartialProjectOptions {
384    /// A root path from which all files must be nested under. Trying to access
385    /// a file outside this root will fail. Think of this as a chroot.
386    pub root_path: Option<RcStr>,
387
388    /// A path inside the root_path which contains the app/pages directories.
389    pub project_path: Option<RcStr>,
390
391    /// The contents of next.config.js, serialized to JSON.
392    pub next_config: Option<RcStr>,
393
394    /// A map of environment variables to use when compiling code.
395    pub env: Option<Vec<(RcStr, RcStr)>>,
396
397    /// A map of environment variables which should get injected at compile
398    /// time.
399    pub define_env: Option<DefineEnv>,
400
401    /// Filesystem watcher options.
402    pub watch: Option<WatchOptions>,
403
404    /// The mode in which Next.js is running.
405    pub dev: Option<bool>,
406
407    /// The server actions encryption key.
408    pub encryption_key: Option<RcStr>,
409
410    /// The build id.
411    pub build_id: Option<RcStr>,
412
413    /// Options for draft mode.
414    pub preview_props: Option<DraftModeOptions>,
415
416    /// The browserslist query to use for targeting browsers.
417    pub browserslist_query: Option<RcStr>,
418
419    /// When the code is minified, this opts out of the default mangling of
420    /// local names for variables, functions etc., which can be useful for
421    /// debugging/profiling purposes.
422    pub no_mangling: Option<bool>,
423
424    /// Whether to write the route hashes manifest.
425    pub write_routes_hashes_manifest: Option<bool>,
426
427    /// Debug build paths for selective builds.
428    /// When set, only routes matching these paths will be included in the build.
429    pub debug_build_paths: Option<DebugBuildPaths>,
430}
431
432#[turbo_tasks::task_input]
433#[derive(
434    Debug,
435    Serialize,
436    Deserialize,
437    Clone,
438    PartialEq,
439    Eq,
440    Hash,
441    TraceRawVcs,
442    OperationValue,
443    Encode,
444    Decode,
445)]
446#[serde(rename_all = "camelCase")]
447pub struct DefineEnv {
448    pub client: Vec<(RcStr, Option<RcStr>)>,
449    pub edge: Vec<(RcStr, Option<RcStr>)>,
450    pub nodejs: Vec<(RcStr, Option<RcStr>)>,
451}
452
453#[derive(TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue, Encode, Decode)]
454pub struct Middleware {
455    pub endpoint: ResolvedVc<Box<dyn Endpoint>>,
456    pub is_proxy: bool,
457}
458
459#[derive(TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue, Encode, Decode)]
460pub struct Instrumentation {
461    pub node_js: ResolvedVc<Box<dyn Endpoint>>,
462    pub edge: ResolvedVc<Box<dyn Endpoint>>,
463}
464
465#[turbo_tasks::value]
466pub struct ProjectContainer {
467    name: RcStr,
468    options_state: State<Option<ProjectOptions>>,
469    versioned_content_map: Option<ResolvedVc<VersionedContentMap>>,
470}
471
472#[turbo_tasks::value_impl]
473impl ProjectContainer {
474    #[turbo_tasks::function(operation, root)]
475    pub fn new_operation(name: RcStr, dev: bool) -> Result<Vc<Self>> {
476        Ok(ProjectContainer {
477            name,
478            // we only need to enable versioning in dev mode, since build
479            // is assumed to be operating over a static snapshot
480            versioned_content_map: if dev {
481                Some(VersionedContentMap::new())
482            } else {
483                None
484            },
485            options_state: State::new(None),
486        }
487        .cell())
488    }
489}
490
491#[turbo_tasks::function(operation, root)]
492fn project_operation(project: ResolvedVc<ProjectContainer>) -> Vc<Project> {
493    project.project()
494}
495
496#[turbo_tasks::function(operation, root)]
497fn project_fs_operation(project: ResolvedVc<Project>) -> Vc<DiskFileSystem> {
498    project.project_fs()
499}
500
501#[turbo_tasks::function(operation, root)]
502fn output_fs_operation(project: ResolvedVc<Project>) -> Vc<DiskFileSystem> {
503    project.project_fs()
504}
505
506enum EnvDiffType {
507    Added,
508    Removed,
509    Modified,
510}
511
512fn env_diff(
513    old: &[(RcStr, Option<RcStr>)],
514    new: &[(RcStr, Option<RcStr>)],
515) -> Vec<(RcStr, EnvDiffType)> {
516    let mut diffs = Vec::new();
517    let mut old_map: FxHashMap<_, _> = old.iter().cloned().collect();
518
519    for (key, new_value) in new.iter() {
520        match old_map.remove(key) {
521            Some(old_value) => {
522                if &old_value != new_value {
523                    diffs.push((key.clone(), EnvDiffType::Modified));
524                }
525            }
526            None => {
527                diffs.push((key.clone(), EnvDiffType::Added));
528            }
529        }
530    }
531
532    for (key, _) in old.iter() {
533        if old_map.contains_key(key) {
534            diffs.push((key.clone(), EnvDiffType::Removed));
535        }
536    }
537
538    diffs
539}
540
541fn env_diff_report(old: &[(RcStr, Option<RcStr>)], new: &[(RcStr, Option<RcStr>)]) -> String {
542    use std::fmt::Write;
543
544    let diff = env_diff(old, new);
545
546    let mut report = String::new();
547    for (key, diff_type) in diff {
548        let symbol = match diff_type {
549            EnvDiffType::Added => "+",
550            EnvDiffType::Removed => "-",
551            EnvDiffType::Modified => "*",
552        };
553        if !report.is_empty() {
554            report.push_str(", ");
555        }
556        write!(report, "{}{}", symbol, key).unwrap();
557    }
558    report
559}
560
561fn define_env_diff_report(old: &DefineEnv, new: &DefineEnv) -> String {
562    use std::fmt::Write;
563
564    let mut report = String::new();
565    for (name, old, new) in [
566        ("client", &old.client, &new.client),
567        ("edge", &old.edge, &new.edge),
568        ("nodejs", &old.nodejs, &new.nodejs),
569    ] {
570        let diff = env_diff_report(old, new);
571        if !diff.is_empty() {
572            if !report.is_empty() {
573                report.push_str(", ");
574            }
575            write!(report, "{name}: {{ {diff} }}").unwrap();
576        }
577    }
578    report
579}
580
581impl ProjectContainer {
582    /// Set up filesystems, watchers, and construct the [`Project`] instance inside the container.
583    ///
584    /// This function is intended to be called inside of [`turbo_tasks::TurboTasks::run`], but not
585    /// part of a [`turbo_tasks::function`]. We don't want it to be possibly re-executed.
586    ///
587    /// This is an associated function instead of a method because we don't currently implement
588    /// [`std::ops::Receiver`] on [`OperationVc`].
589    pub async fn initialize(this_op: OperationVc<Self>, options: ProjectOptions) -> Result<()> {
590        let this = this_op.read_strongly_consistent().await?;
591        let span = tracing::info_span!(
592            "initialize project",
593            project_name = %this.name,
594            version = options.next_version.as_str(),
595            node_version = options.current_node_js_version.as_str(),
596            os = std::env::consts::OS,
597            arch = std::env::consts::ARCH,
598            turbo_tasks_available_parallelism =
599                turbo_tasks::parallel::available_parallelism().map(|n| n.get()).unwrap_or(0),
600            std_thread_available_parallelism =
601                std::thread::available_parallelism().map(|n| n.get()).unwrap_or(0),
602            dev = options.dev,
603            env_diff = Empty
604        );
605        let span_clone = span.clone();
606        async move {
607            let watch = options.watch;
608
609            if let Some(old_options) = &*this.options_state.get_untracked() {
610                span.record(
611                    "env_diff",
612                    define_env_diff_report(&old_options.define_env, &options.define_env).as_str(),
613                );
614            }
615            this.options_state.set(Some(options));
616
617            #[turbo_tasks::function(operation, root)]
618            fn project_from_container_operation(
619                container: OperationVc<ProjectContainer>,
620            ) -> Vc<Project> {
621                container.connect().project()
622            }
623            let project = project_from_container_operation(this_op)
624                .resolve()
625                .strongly_consistent()
626                .await?;
627            let project_fs = project_fs_operation(project)
628                .read_strongly_consistent()
629                .await?;
630            if watch.enable {
631                project_fs
632                    .start_watching_with_invalidation_reason(watch.poll_interval)
633                    .await?;
634            } else {
635                project_fs.invalidate_with_reason(|path| invalidation::Initialize {
636                    // this path is just used for display purposes
637                    path: RcStr::from(path.to_string_lossy()),
638                });
639            }
640            let output_fs = output_fs_operation(project)
641                .read_strongly_consistent()
642                .await?;
643            output_fs.invalidate_with_reason(|path| invalidation::Initialize {
644                path: RcStr::from(path.to_string_lossy()),
645            });
646            Ok(())
647        }
648        .instrument(span_clone)
649        .await
650    }
651
652    pub async fn update(self: ResolvedVc<Self>, options: PartialProjectOptions) -> Result<()> {
653        let span = tracing::info_span!(
654            "update project options",
655            project_name = %self.await?.name,
656            env_diff = Empty
657        );
658        let span_clone = span.clone();
659        async move {
660            // HACK: `update` is called from a top-level function. Top-level functions are not
661            // allowed to perform eventually consistent reads. Create a stub operation
662            // to upgrade the `ResolvedVc` to an `OperationVc`. This is mostly okay
663            // because we can assume the `ProjectContainer` was originally resolved with
664            // strong consistency, and is rarely updated.
665            #[turbo_tasks::function(operation, root)]
666            fn project_container_operation_hack(
667                container: ResolvedVc<ProjectContainer>,
668            ) -> Vc<ProjectContainer> {
669                *container
670            }
671            let this = project_container_operation_hack(self)
672                .read_strongly_consistent()
673                .await?;
674            let PartialProjectOptions {
675                root_path,
676                project_path,
677                next_config,
678                env,
679                define_env,
680                watch,
681                dev,
682                encryption_key,
683                build_id,
684                preview_props,
685                browserslist_query,
686                no_mangling,
687                write_routes_hashes_manifest,
688                debug_build_paths,
689            } = options;
690
691            let mut new_options = this
692                .options_state
693                .get()
694                .clone()
695                .context("ProjectContainer need to be initialized with initialize()")?;
696
697            if let Some(root_path) = root_path {
698                new_options.root_path = canonicalize_to_rcstr(Path::new(&*root_path))?;
699            }
700            if let Some(project_path) = project_path {
701                new_options.project_path = project_path;
702            }
703            if let Some(next_config) = next_config {
704                new_options.next_config = next_config;
705            }
706            if let Some(env) = env {
707                new_options.env = env;
708            }
709            if let Some(define_env) = define_env {
710                new_options.define_env = define_env;
711            }
712            if let Some(watch) = watch {
713                new_options.watch = watch;
714            }
715            if let Some(dev) = dev {
716                new_options.dev = dev;
717            }
718            if let Some(encryption_key) = encryption_key {
719                new_options.encryption_key = encryption_key;
720            }
721            if let Some(build_id) = build_id {
722                new_options.build_id = build_id;
723            }
724            if let Some(preview_props) = preview_props {
725                new_options.preview_props = preview_props;
726            }
727            if let Some(browserslist_query) = browserslist_query {
728                new_options.browserslist_query = browserslist_query;
729            }
730            if let Some(no_mangling) = no_mangling {
731                new_options.no_mangling = no_mangling;
732            }
733            if let Some(write_routes_hashes_manifest) = write_routes_hashes_manifest {
734                new_options.write_routes_hashes_manifest = write_routes_hashes_manifest;
735            }
736            if let Some(debug_build_paths) = debug_build_paths {
737                new_options.debug_build_paths = Some(debug_build_paths);
738            }
739
740            // TODO: Handle mode switch, should prevent mode being switched.
741            let watch = new_options.watch;
742
743            let project = project_operation(self)
744                .resolve()
745                .strongly_consistent()
746                .await?;
747            let prev_project_fs = project_fs_operation(project)
748                .read_strongly_consistent()
749                .await?;
750            let prev_output_fs = output_fs_operation(project)
751                .read_strongly_consistent()
752                .await?;
753
754            if let Some(old_options) = &*this.options_state.get_untracked() {
755                span.record(
756                    "env_diff",
757                    define_env_diff_report(&old_options.define_env, &new_options.define_env)
758                        .as_str(),
759                );
760            }
761            this.options_state.set(Some(new_options));
762            let project = project_operation(self)
763                .resolve()
764                .strongly_consistent()
765                .await?;
766            let project_fs = project_fs_operation(project)
767                .read_strongly_consistent()
768                .await?;
769            let output_fs = output_fs_operation(project)
770                .read_strongly_consistent()
771                .await?;
772
773            if !ReadRef::ptr_eq(&prev_project_fs, &project_fs) {
774                if watch.enable {
775                    // TODO stop watching: prev_project_fs.stop_watching()?;
776                    project_fs
777                        .start_watching_with_invalidation_reason(watch.poll_interval)
778                        .await?;
779                } else {
780                    project_fs.invalidate_with_reason(|path| invalidation::Initialize {
781                        // this path is just used for display purposes
782                        path: RcStr::from(path.to_string_lossy()),
783                    });
784                }
785            }
786            if !ReadRef::ptr_eq(&prev_output_fs, &output_fs) {
787                prev_output_fs.invalidate_with_reason(|path| invalidation::Initialize {
788                    path: RcStr::from(path.to_string_lossy()),
789                });
790            }
791
792            Ok(())
793        }
794        .instrument(span_clone)
795        .await
796    }
797}
798
799#[turbo_tasks::value_impl]
800impl ProjectContainer {
801    #[turbo_tasks::function]
802    pub async fn project(&self) -> Result<Vc<Project>> {
803        let env_map: Vc<EnvMap>;
804        let next_config;
805        let define_env;
806        let root_path_str: RcStr;
807        let project_path;
808        let watch;
809        let dev;
810        let encryption_key;
811        let build_id;
812        let preview_props;
813        let browserslist_query;
814        let no_mangling;
815        let write_routes_hashes_manifest;
816        let current_node_js_version;
817        let debug_build_paths;
818        let deferred_entries;
819        let is_persistent_caching_enabled;
820        let server_hmr;
821        {
822            let options = self.options_state.get();
823            let options = options
824                .as_ref()
825                .context("ProjectContainer need to be initialized with initialize()")?;
826            env_map = Vc::cell(options.env.iter().cloned().collect());
827            define_env = ProjectDefineEnv {
828                client: ResolvedVc::cell(options.define_env.client.iter().cloned().collect()),
829                edge: ResolvedVc::cell(options.define_env.edge.iter().cloned().collect()),
830                nodejs: ResolvedVc::cell(options.define_env.nodejs.iter().cloned().collect()),
831            }
832            .cell();
833            next_config = NextConfig::from_string(Vc::cell(options.next_config.clone()));
834            root_path_str = options.root_path.clone();
835            project_path = options.project_path.clone();
836            watch = options.watch;
837            dev = options.dev;
838            encryption_key = options.encryption_key.clone();
839            build_id = options.build_id.clone();
840            preview_props = options.preview_props.clone();
841            browserslist_query = options.browserslist_query.clone();
842            no_mangling = options.no_mangling;
843            write_routes_hashes_manifest = options.write_routes_hashes_manifest;
844            current_node_js_version = options.current_node_js_version.clone();
845            debug_build_paths = options.debug_build_paths.clone();
846            deferred_entries = options.deferred_entries.clone().unwrap_or_default();
847            is_persistent_caching_enabled = options.is_persistent_caching_enabled;
848            server_hmr = options.server_hmr;
849        }
850
851        let root_path = ResolvedVc::cell(root_path_str);
852        let dist_dir = next_config.dist_dir().owned().await?;
853        let dist_dir_root = next_config.dist_dir_root().owned().await?;
854        Ok(Project {
855            root_path,
856            project_path,
857            watch,
858            next_config: next_config.to_resolved().await?,
859            dist_dir,
860            dist_dir_root,
861            env: ResolvedVc::upcast(env_map.to_resolved().await?),
862            define_env: define_env.to_resolved().await?,
863            browserslist_query,
864            mode: if dev {
865                NextMode::Development.resolved_cell()
866            } else {
867                NextMode::Build.resolved_cell()
868            },
869            versioned_content_map: self.versioned_content_map,
870            build_id,
871            encryption_key,
872            preview_props,
873            no_mangling,
874            write_routes_hashes_manifest,
875            current_node_js_version,
876            debug_build_paths,
877            deferred_entries,
878            is_persistent_caching_enabled,
879            server_hmr,
880        }
881        .cell())
882    }
883
884    /// See [Project::entrypoints].
885    #[turbo_tasks::function]
886    pub fn entrypoints(self: Vc<Self>) -> Vc<Entrypoints> {
887        self.project().entrypoints()
888    }
889
890    /// See [`Project::hmr_chunk_names`].
891    #[turbo_tasks::function]
892    pub fn hmr_chunk_names(self: Vc<Self>, target: HmrTarget) -> Vc<Vec<RcStr>> {
893        self.project().hmr_chunk_names(target)
894    }
895
896    /// Gets a source map for a particular `file_path`. If `dev` mode is disabled, this will always
897    /// return [`FileContent::NotFound`].
898    #[turbo_tasks::function]
899    pub fn get_source_map(
900        &self,
901        file_path: FileSystemPath,
902        section: Option<RcStr>,
903    ) -> Vc<FileContent> {
904        if let Some(map) = self.versioned_content_map {
905            map.get_source_map(file_path, section)
906        } else {
907            FileContent::NotFound.cell()
908        }
909    }
910}
911
912#[derive(Clone)]
913#[turbo_tasks::value]
914pub struct Project {
915    /// An absolute root path (Windows or Unix path) from which all files must be nested under.
916    /// Trying to access a file outside this root will fail, so think of this as a chroot.
917    /// E.g. `/home/user/projects/my-repo`.
918    root_path: ResolvedVc<RcStr>,
919
920    /// A path which contains the app/pages directories, relative to [`Project::root_path`], always
921    /// a Unix path.
922    /// E.g. `apps/my-app`
923    project_path: RcStr,
924
925    /// A path where to emit the build outputs, relative to [`Project::project_path`], always a
926    /// Unix path. Corresponds to next.config.js's `distDir`.
927    /// E.g. `.next`
928    dist_dir: RcStr,
929
930    /// The root directory of the distDir. In development mode, this is the parent directory of
931    /// `distDir` since development builds use `{distDir}/dev`. This is used to ensure that the
932    /// bundler doesn't traverse into the output directory.
933    dist_dir_root: RcStr,
934
935    /// Filesystem watcher options.
936    watch: WatchOptions,
937
938    /// Next config.
939    next_config: ResolvedVc<NextConfig>,
940
941    /// A map of environment variables to use when compiling code.
942    env: ResolvedVc<Box<dyn ProcessEnv>>,
943
944    /// A map of environment variables which should get injected at compile
945    /// time.
946    define_env: ResolvedVc<ProjectDefineEnv>,
947
948    /// The browserslist query to use for targeting browsers.
949    browserslist_query: RcStr,
950
951    mode: ResolvedVc<NextMode>,
952
953    versioned_content_map: Option<ResolvedVc<VersionedContentMap>>,
954
955    build_id: RcStr,
956
957    encryption_key: RcStr,
958
959    preview_props: DraftModeOptions,
960
961    /// When the code is minified, this opts out of the default mangling of
962    /// local names for variables, functions etc., which can be useful for
963    /// debugging/profiling purposes.
964    no_mangling: bool,
965
966    /// Whether to write the route hashes manifest.
967    write_routes_hashes_manifest: bool,
968
969    current_node_js_version: RcStr,
970
971    /// Debug build paths for selective builds.
972    /// When set, only routes matching these paths will be included in the build.
973    debug_build_paths: Option<DebugBuildPaths>,
974
975    /// App-router page routes that should be built after non-deferred routes.
976    deferred_entries: Vec<RcStr>,
977
978    /// Whether to enable persistent caching
979    is_persistent_caching_enabled: bool,
980
981    /// Whether server-side HMR is enabled (disabled with --no-server-fast-refresh).
982    server_hmr: bool,
983}
984
985#[turbo_tasks::value]
986pub struct ProjectDefineEnv {
987    client: ResolvedVc<OptionEnvMap>,
988    edge: ResolvedVc<OptionEnvMap>,
989    nodejs: ResolvedVc<OptionEnvMap>,
990}
991
992#[turbo_tasks::value_impl]
993impl ProjectDefineEnv {
994    #[turbo_tasks::function]
995    pub fn client(&self) -> Vc<OptionEnvMap> {
996        *self.client
997    }
998
999    #[turbo_tasks::function]
1000    pub fn edge(&self) -> Vc<OptionEnvMap> {
1001        *self.edge
1002    }
1003
1004    #[turbo_tasks::function]
1005    pub fn nodejs(&self) -> Vc<OptionEnvMap> {
1006        *self.nodejs
1007    }
1008}
1009
1010#[turbo_tasks::value(shared)]
1011struct ConflictIssue {
1012    path: FileSystemPath,
1013    title: ResolvedVc<StyledString>,
1014    description: ResolvedVc<StyledString>,
1015    severity: IssueSeverity,
1016}
1017
1018#[async_trait]
1019#[turbo_tasks::value_impl]
1020impl Issue for ConflictIssue {
1021    fn stage(&self) -> IssueStage {
1022        IssueStage::AppStructure
1023    }
1024
1025    fn severity(&self) -> IssueSeverity {
1026        self.severity
1027    }
1028
1029    async fn file_path(&self) -> Result<FileSystemPath> {
1030        Ok(self.path.clone())
1031    }
1032
1033    async fn title(&self) -> Result<StyledString> {
1034        self.title.owned().await
1035    }
1036
1037    async fn description(&self) -> Result<Option<StyledString>> {
1038        Ok(Some(self.description.owned().await?))
1039    }
1040}
1041
1042#[turbo_tasks::value_impl]
1043impl Project {
1044    #[turbo_tasks::function]
1045    pub async fn app_project(self: Vc<Self>) -> Result<Vc<OptionAppProject>> {
1046        let app_dir = find_app_dir(self.project_path().owned().await?).await?;
1047
1048        Ok(match &*app_dir {
1049            Some(app_dir) => Vc::cell(Some(
1050                AppProject::new(self, app_dir.clone()).to_resolved().await?,
1051            )),
1052            None => Vc::cell(None),
1053        })
1054    }
1055
1056    #[turbo_tasks::function]
1057    pub fn pages_project(self: Vc<Self>) -> Vc<PagesProject> {
1058        PagesProject::new(self)
1059    }
1060
1061    #[turbo_tasks::function]
1062    pub fn project_fs(&self) -> Result<Vc<DiskFileSystem>> {
1063        let denied_path = match join_path(&self.project_path, &self.dist_dir_root) {
1064            Some(dist_dir_root) => dist_dir_root.into(),
1065            None => {
1066                bail!(
1067                    "Invalid distDirRoot: {:?}. distDirRoot should not navigate out of the \
1068                     projectPath.",
1069                    self.dist_dir_root
1070                );
1071            }
1072        };
1073
1074        // CPU profiles are written to `.next-profiles/` at the project root (see `--cpu-prof`).
1075        // Deny access to it so the bundler doesn't traverse into the profiling output directory.
1076        let denied_profiles_path = join_path(&self.project_path, DIST_PROFILES_DIR_NAME)
1077            .unwrap()
1078            .into();
1079
1080        Ok(DiskFileSystem::new_with_denied_paths(
1081            PROJECT_FILESYSTEM_NAME,
1082            *self.root_path,
1083            vec![denied_path, denied_profiles_path],
1084        ))
1085    }
1086
1087    #[turbo_tasks::function]
1088    pub fn client_fs(self: Vc<Self>) -> Vc<Box<dyn FileSystem>> {
1089        let virtual_fs = VirtualFileSystem::new_with_name(rcstr!("client-fs"));
1090        Vc::upcast(virtual_fs)
1091    }
1092
1093    #[turbo_tasks::function]
1094    pub fn output_fs(&self) -> Vc<DiskFileSystem> {
1095        DiskFileSystem::new(rcstr!("output"), *self.root_path)
1096    }
1097
1098    #[turbo_tasks::function]
1099    pub async fn node_root(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1100        let this = self.await?;
1101        Ok(self
1102            .output_fs()
1103            .root()
1104            .await?
1105            .join(&this.project_path)?
1106            .join(&this.dist_dir)?
1107            .cell())
1108    }
1109
1110    #[turbo_tasks::function]
1111    pub fn client_root(self: Vc<Self>) -> Vc<FileSystemPath> {
1112        self.client_fs().root()
1113    }
1114
1115    #[turbo_tasks::function]
1116    pub fn project_root_path(self: Vc<Self>) -> Vc<FileSystemPath> {
1117        self.project_fs().root()
1118    }
1119
1120    #[turbo_tasks::function]
1121    pub async fn client_relative_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1122        let next_config = self.next_config();
1123        Ok(self
1124            .client_root()
1125            .await?
1126            .join(&format!(
1127                "{}/_next",
1128                next_config
1129                    .base_path()
1130                    .await?
1131                    .as_deref()
1132                    .unwrap_or_default(),
1133            ))?
1134            .cell())
1135    }
1136
1137    /// Returns the relative path from the node root to the output root.
1138    /// E.g. from `[project]/test/e2e/app-dir/non-root-project-monorepo/apps/web/app/
1139    /// import-meta-url-ssr/page.tsx` to `[project]/`.
1140    #[turbo_tasks::function]
1141    pub async fn node_root_to_root_path(self: Vc<Self>) -> Result<Vc<RcStr>> {
1142        Ok(Vc::cell(
1143            self.node_root()
1144                .await?
1145                .get_relative_path_to(&*self.output_fs().root().await?)
1146                .context("Expected node root to be inside of output fs")?,
1147        ))
1148    }
1149
1150    #[turbo_tasks::function]
1151    pub async fn project_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
1152        let this = self.await?;
1153        let root = self.project_root_path().await?;
1154        Ok(root.join(&this.project_path)?.cell())
1155    }
1156
1157    #[turbo_tasks::function]
1158    pub(super) fn env(&self) -> Vc<Box<dyn ProcessEnv>> {
1159        *self.env
1160    }
1161
1162    #[turbo_tasks::function]
1163    pub async fn ci_has_next_support(&self) -> Result<Vc<bool>> {
1164        Ok(Vc::cell(
1165            self.env.read(rcstr!("NOW_BUILDER")).await?.is_some(),
1166        ))
1167    }
1168
1169    #[turbo_tasks::function]
1170    pub(super) fn current_node_js_version(&self) -> Vc<NodeJsVersion> {
1171        NodeJsVersion::Static(ResolvedVc::cell(self.current_node_js_version.clone())).cell()
1172    }
1173
1174    #[turbo_tasks::function]
1175    pub fn next_config(&self) -> Vc<NextConfig> {
1176        *self.next_config
1177    }
1178
1179    /// Build the `IssueFilter` for this project, incorporating any
1180    /// `turbopack.ignoreIssue` rules from the Next.js config.
1181    #[turbo_tasks::function]
1182    pub async fn issue_filter(self: Vc<Self>) -> Result<Vc<IssueFilter>> {
1183        let ignore_rules = self.next_config().turbopack_ignore_issue_rules().await?;
1184        Ok(IssueFilter::warnings_and_foreign_errors()
1185            .with_ignore_rules(ReadRef::into_owned(ignore_rules))
1186            .cell())
1187    }
1188
1189    #[turbo_tasks::function]
1190    pub(super) fn is_persistent_caching_enabled(&self) -> Vc<bool> {
1191        Vc::cell(self.is_persistent_caching_enabled)
1192    }
1193
1194    #[turbo_tasks::function]
1195    pub(super) fn next_mode(&self) -> Vc<NextMode> {
1196        *self.mode
1197    }
1198
1199    #[turbo_tasks::function]
1200    pub(super) fn is_watch_enabled(&self) -> Result<Vc<bool>> {
1201        Ok(Vc::cell(self.watch.enable))
1202    }
1203
1204    #[turbo_tasks::function]
1205    pub(super) fn should_write_routes_hashes_manifest(&self) -> Result<Vc<bool>> {
1206        Ok(Vc::cell(self.write_routes_hashes_manifest))
1207    }
1208
1209    #[turbo_tasks::function]
1210    pub(super) async fn should_write_nft_manifests(&self) -> Result<Vc<bool>> {
1211        Ok(Vc::cell(
1212            self.mode.await?.is_production()
1213                && *self.next_config.output().await? != Some(OutputType::Export),
1214        ))
1215    }
1216
1217    #[turbo_tasks::function]
1218    pub fn deferred_entries(&self) -> Vc<Vec<RcStr>> {
1219        Vc::cell(self.deferred_entries.clone())
1220    }
1221
1222    #[turbo_tasks::function]
1223    pub(super) async fn per_page_module_graph(&self) -> Result<Vc<bool>> {
1224        Ok(Vc::cell(*self.mode.await? == NextMode::Development))
1225    }
1226
1227    #[turbo_tasks::function]
1228    pub(super) fn encryption_key(&self) -> Vc<RcStr> {
1229        Vc::cell(self.encryption_key.clone())
1230    }
1231
1232    #[turbo_tasks::function]
1233    pub(super) fn no_mangling(&self) -> Vc<bool> {
1234        Vc::cell(self.no_mangling)
1235    }
1236
1237    #[turbo_tasks::function]
1238    pub(super) async fn execution_context(self: Vc<Self>) -> Result<Vc<ExecutionContext>> {
1239        let node_root = self.node_root().owned().await?;
1240        let next_mode = self.next_mode().await?;
1241        let strategy = *self
1242            .next_config()
1243            .turbopack_plugin_runtime_strategy()
1244            .await?;
1245        let node_backend = match strategy {
1246            #[cfg(feature = "worker_pool")]
1247            TurbopackPluginRuntimeStrategy::WorkerThreads => worker_threads_backend(),
1248            #[cfg(feature = "process_pool")]
1249            TurbopackPluginRuntimeStrategy::ChildProcesses => child_process_backend(),
1250        };
1251
1252        let node_execution_chunking_context = Vc::upcast(
1253            NodeJsChunkingContext::builder(
1254                self.project_root_path().owned().await?,
1255                node_root.join("build")?,
1256                self.node_root_to_root_path().owned().await?,
1257                node_root.join("build")?,
1258                node_root.join("build/chunks")?,
1259                node_root.join("build/assets")?,
1260                node_build_environment().to_resolved().await?,
1261                next_mode.runtime_type(),
1262            )
1263            .source_maps(*self.next_config().server_source_maps().await?)
1264            .build(),
1265        );
1266
1267        Ok(ExecutionContext::new(
1268            self.project_path().owned().await?,
1269            node_execution_chunking_context,
1270            self.env(),
1271            node_backend,
1272        ))
1273    }
1274
1275    #[turbo_tasks::function]
1276    pub(super) async fn client_compile_time_info(&self) -> Result<Vc<CompileTimeInfo>> {
1277        let next_mode = self.mode.await?;
1278        Ok(get_client_compile_time_info(
1279            self.browserslist_query.clone(),
1280            self.define_env.client(),
1281            self.next_config.report_system_env_inlining(),
1282            next_mode.is_development(),
1283        ))
1284    }
1285
1286    #[turbo_tasks::function]
1287    pub async fn get_all_endpoint_groups(
1288        self: Vc<Self>,
1289        app_dir_only: bool,
1290    ) -> Result<Vc<EndpointGroups>> {
1291        Ok(self.get_all_endpoint_groups_with_app_route_filter(app_dir_only, None))
1292    }
1293
1294    #[turbo_tasks::function]
1295    pub async fn get_all_endpoint_groups_with_app_route_filter(
1296        self: Vc<Self>,
1297        app_dir_only: bool,
1298        app_route_filter: Option<Vec<RcStr>>,
1299    ) -> Result<Vc<EndpointGroups>> {
1300        let mut endpoint_groups = Vec::new();
1301
1302        let entrypoints = self
1303            .entrypoints_with_app_route_filter(app_route_filter)
1304            .await?;
1305        let mut add_pages_entries = false;
1306
1307        if let Some(middleware) = &entrypoints.middleware {
1308            endpoint_groups.push((
1309                EndpointGroupKey::Middleware,
1310                EndpointGroup::from(middleware.endpoint),
1311            ));
1312        }
1313
1314        if let Some(instrumentation) = &entrypoints.instrumentation {
1315            endpoint_groups.push((
1316                EndpointGroupKey::Instrumentation,
1317                EndpointGroup::from(instrumentation.node_js),
1318            ));
1319            endpoint_groups.push((
1320                EndpointGroupKey::InstrumentationEdge,
1321                EndpointGroup::from(instrumentation.edge),
1322            ));
1323        }
1324
1325        for (key, route) in entrypoints.routes.iter() {
1326            match route {
1327                Route::Page {
1328                    html_endpoint,
1329                    data_endpoint,
1330                } => {
1331                    if !app_dir_only {
1332                        endpoint_groups.push((
1333                            EndpointGroupKey::Route(key.clone()),
1334                            EndpointGroup {
1335                                primary: vec![EndpointGroupEntry {
1336                                    endpoint: *html_endpoint,
1337                                    sub_name: None,
1338                                }],
1339                                // This only exists in development mode for HMR
1340                                additional: data_endpoint
1341                                    .iter()
1342                                    .map(|endpoint| EndpointGroupEntry {
1343                                        endpoint: *endpoint,
1344                                        sub_name: None,
1345                                    })
1346                                    .collect(),
1347                            },
1348                        ));
1349                        add_pages_entries = true;
1350                    }
1351                }
1352                Route::PageApi { endpoint } => {
1353                    if !app_dir_only {
1354                        endpoint_groups.push((
1355                            EndpointGroupKey::Route(key.clone()),
1356                            EndpointGroup::from(*endpoint),
1357                        ));
1358                        add_pages_entries = true;
1359                    }
1360                }
1361                Route::AppPage(page_routes) => {
1362                    endpoint_groups.push((
1363                        EndpointGroupKey::Route(key.clone()),
1364                        EndpointGroup {
1365                            primary: page_routes
1366                                .iter()
1367                                .map(|r| EndpointGroupEntry {
1368                                    endpoint: r.html_endpoint,
1369                                    sub_name: Some(r.original_name.clone()),
1370                                })
1371                                .collect(),
1372                            additional: Vec::new(),
1373                        },
1374                    ));
1375                }
1376                Route::AppRoute {
1377                    original_name: _,
1378                    endpoint,
1379                } => {
1380                    endpoint_groups.push((
1381                        EndpointGroupKey::Route(key.clone()),
1382                        EndpointGroup::from(*endpoint),
1383                    ));
1384                }
1385                Route::Conflict => {
1386                    tracing::info!("WARN: conflict");
1387                }
1388            }
1389        }
1390
1391        if add_pages_entries {
1392            endpoint_groups.push((
1393                EndpointGroupKey::PagesError,
1394                EndpointGroup::from(entrypoints.pages_error_endpoint),
1395            ));
1396            endpoint_groups.push((
1397                EndpointGroupKey::PagesApp,
1398                EndpointGroup::from(entrypoints.pages_app_endpoint),
1399            ));
1400            endpoint_groups.push((
1401                EndpointGroupKey::PagesDocument,
1402                EndpointGroup::from(entrypoints.pages_document_endpoint),
1403            ));
1404        }
1405
1406        Ok(Vc::cell(endpoint_groups))
1407    }
1408
1409    #[turbo_tasks::function]
1410    pub async fn get_all_endpoints(self: Vc<Self>, app_dir_only: bool) -> Result<Vc<Endpoints>> {
1411        let mut endpoints = Vec::new();
1412        for (_key, group) in self.get_all_endpoint_groups(app_dir_only).await?.iter() {
1413            for entry in group.primary.iter() {
1414                endpoints.push(entry.endpoint);
1415            }
1416            for entry in group.additional.iter() {
1417                endpoints.push(entry.endpoint);
1418            }
1419        }
1420
1421        Ok(Vc::cell(endpoints))
1422    }
1423
1424    #[turbo_tasks::function]
1425    pub async fn get_all_entries(self: Vc<Self>) -> Result<Vc<GraphEntries>> {
1426        let endpoint_entries = self
1427            .get_all_endpoints(false)
1428            .await?
1429            .iter()
1430            .map(|endpoint| endpoint.entries().owned())
1431            .try_join()
1432            .await?;
1433
1434        let result = GraphEntries::concatenate(
1435            endpoint_entries
1436                .into_iter()
1437                .chain(std::iter::once(self.client_main_modules().owned().await?))
1438                .chain(std::iter::once(GraphEntries::new(
1439                    vec![],
1440                    self.additional_traced_modules().owned().await?,
1441                ))),
1442        );
1443
1444        Ok(result.cell())
1445    }
1446
1447    #[turbo_tasks::function]
1448    pub async fn get_all_additional_entries(
1449        self: Vc<Self>,
1450        graphs: Vc<ModuleGraph>,
1451    ) -> Result<Vc<GraphEntries>> {
1452        let result = GraphEntries::concatenate(
1453            self.get_all_endpoints(false)
1454                .await?
1455                .iter()
1456                .map(|endpoint| endpoint.additional_entries(graphs).owned())
1457                .try_join()
1458                .await?,
1459        );
1460        Ok(result.cell())
1461    }
1462
1463    #[turbo_tasks::function]
1464    pub async fn module_graph(
1465        self: Vc<Self>,
1466        entry: ResolvedVc<Box<dyn Module>>,
1467    ) -> Result<Vc<ModuleGraph>> {
1468        Ok(if *self.per_page_module_graph().await? {
1469            ModuleGraph::from_graphs(
1470                vec![SingleModuleGraph::new_with_entry(
1471                    ChunkGroupEntry::Entry {
1472                        modules: vec![entry],
1473                        heuristics: EntryHeuristics::default(),
1474                    },
1475                    /* include_traced */ *self.should_write_nft_manifests().await?,
1476                    /* include_binding_usage */ self.next_mode().await?.is_production(),
1477                )],
1478                None,
1479            )
1480            .connect()
1481        } else {
1482            *self.whole_app_module_graphs().await?.full
1483        })
1484    }
1485
1486    #[turbo_tasks::function]
1487    pub async fn module_graph_for_modules(
1488        self: Vc<Self>,
1489        evaluatable_assets: Vc<EvaluatableAssets>,
1490    ) -> Result<Vc<ModuleGraph>> {
1491        Ok(if *self.per_page_module_graph().await? {
1492            let entries = evaluatable_assets
1493                .await?
1494                .iter()
1495                .copied()
1496                .map(ResolvedVc::upcast)
1497                .collect();
1498            ModuleGraph::from_graphs(
1499                vec![SingleModuleGraph::new_with_entries(
1500                    GraphEntries::from_chunk_groups(vec![ChunkGroupEntry::Entry {
1501                        modules: entries,
1502                        heuristics: EntryHeuristics::default(),
1503                    }])
1504                    .resolved_cell(),
1505                    /* include_traced */ *self.should_write_nft_manifests().await?,
1506                    /* include_binding_usage */ self.next_mode().await?.is_production(),
1507                )],
1508                None,
1509            )
1510            .connect()
1511        } else {
1512            *self.whole_app_module_graphs().await?.full
1513        })
1514    }
1515
1516    /// Computes the whole app module graph without dropping issues.
1517    ///
1518    /// Use this instead of [Self::whole_app_module_graphs] when you need to collect issues from
1519    /// the computation (e.g. for the `get_compilation_issues` MCP tool).
1520    #[turbo_tasks::function]
1521    pub async fn whole_app_module_graphs_without_dropping_issues(
1522        self: ResolvedVc<Self>,
1523    ) -> Result<Vc<BaseAndFullModuleGraph>> {
1524        let module_graphs_op = whole_app_module_graph_operation(self);
1525        let module_graphs_vc = module_graphs_op.connect();
1526        scale_down_node_pool(self).await?;
1527        Ok(module_graphs_vc)
1528    }
1529
1530    /// Computes the whole app module graph, dropping issues in development mode so that
1531    /// individual routes don't each report every issue from the shared graph.
1532    #[turbo_tasks::function(root)]
1533    pub async fn whole_app_module_graphs(
1534        self: ResolvedVc<Self>,
1535    ) -> Result<Vc<BaseAndFullModuleGraph>> {
1536        let module_graphs_op = whole_app_module_graph_operation(self);
1537        let module_graphs_vc = if self.next_mode().await?.is_production() {
1538            module_graphs_op.connect()
1539        } else {
1540            let vc = module_graphs_op.resolve().strongly_consistent().await?;
1541            module_graphs_op.drop_issues();
1542            *vc
1543        };
1544        scale_down_node_pool(self).await?;
1545        Ok(module_graphs_vc)
1546    }
1547
1548    #[turbo_tasks::function]
1549    pub(super) async fn server_compile_time_info(self: Vc<Self>) -> Result<Vc<CompileTimeInfo>> {
1550        let this = self.await?;
1551        Ok(get_server_compile_time_info(
1552            // `/ROOT` corresponds to `[project]/`, so we need exactly the `path` part.
1553            self.project_path(),
1554            this.define_env.nodejs(),
1555            self.current_node_js_version(),
1556            this.next_config.report_system_env_inlining(),
1557            this.server_hmr,
1558        ))
1559    }
1560
1561    #[turbo_tasks::function]
1562    pub(super) async fn edge_compile_time_info(self: Vc<Self>) -> Result<Vc<CompileTimeInfo>> {
1563        let this = self.await?;
1564        Ok(get_edge_compile_time_info(
1565            self.project_path().owned().await?,
1566            this.define_env.edge(),
1567            self.current_node_js_version(),
1568            this.next_config.report_system_env_inlining(),
1569        ))
1570    }
1571
1572    #[turbo_tasks::function]
1573    pub(super) fn edge_env(&self) -> Vc<EnvMap> {
1574        let edge_env = fxindexmap! {
1575            rcstr!("__NEXT_BUILD_ID") => self.build_id.clone(),
1576            rcstr!("NEXT_SERVER_ACTIONS_ENCRYPTION_KEY") => self.encryption_key.clone(),
1577            rcstr!("__NEXT_PREVIEW_MODE_ID") => self.preview_props.preview_mode_id.clone(),
1578            rcstr!("__NEXT_PREVIEW_MODE_ENCRYPTION_KEY") => self.preview_props.preview_mode_encryption_key.clone(),
1579            rcstr!("__NEXT_PREVIEW_MODE_SIGNING_KEY") => self.preview_props.preview_mode_signing_key.clone(),
1580        };
1581        Vc::cell(edge_env)
1582    }
1583
1584    #[turbo_tasks::function]
1585    pub(super) async fn client_chunking_context(
1586        self: Vc<Self>,
1587    ) -> Result<Vc<Box<dyn ChunkingContext>>> {
1588        let css_url_suffix = self.next_config().asset_suffix_path();
1589        let chunking_heuristics = self.next_config().chunking_heuristics().await?;
1590        Ok(get_client_chunking_context(ClientChunkingContextOptions {
1591            mode: self.next_mode(),
1592            root_path: self.project_root_path().owned().await?,
1593            client_root: self.client_relative_path().owned().await?,
1594            client_root_to_root_path: rcstr!("/ROOT"),
1595            client_static_folder_name: self
1596                .next_config()
1597                .client_static_folder_name()
1598                .owned()
1599                .await?,
1600            asset_prefix: self.next_config().computed_asset_prefix(),
1601            service_worker_scope_base_path: self.next_config().base_path(),
1602            environment: self.client_compile_time_info().environment(),
1603            module_id_strategy: self.module_ids(),
1604            export_usage: self.export_usage(),
1605            unused_references: self.unused_references(),
1606            minify: self.next_config().turbo_minify(self.next_mode()),
1607            source_maps: self.next_config().client_source_maps(self.next_mode()),
1608            no_mangling: self.no_mangling(),
1609            scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()),
1610            nested_async_chunking: self
1611                .next_config()
1612                .turbo_nested_async_chunking(self.next_mode(), true),
1613            shared_runtime: self.next_config().turbo_shared_runtime(self.next_mode()),
1614            debug_ids: self.next_config().turbopack_debug_ids(),
1615            worker_asset_prefix: self.next_config().turbopack_worker_asset_prefix(),
1616            should_use_absolute_url_references: self.next_config().inline_css(),
1617            css_url_suffix,
1618            hash_salt: self.next_config().output_hash_salt().to_resolved().await?,
1619            cross_origin: self.next_config().cross_origin(),
1620            chunk_loading_global: self.next_config().turbopack_chunk_loading_global(),
1621            style_groups_algorithm: self.next_config().css_chunking().owned().await?,
1622            chunking_first_page_load_priority: chunking_heuristics.first_page_load_priority,
1623            chunking_priority_boost_percent: chunking_heuristics.priority_boost_percent,
1624            chunking_request_cost: chunking_heuristics.request_cost,
1625            generate_component_chunks: self.next_config().turbopack_generate_component_chunks(),
1626        }))
1627    }
1628
1629    #[turbo_tasks::function]
1630    pub(super) async fn service_worker_chunking_context(
1631        self: Vc<Self>,
1632    ) -> Result<Vc<Box<dyn ChunkingContext>>> {
1633        Ok(get_service_worker_chunking_context(
1634            ServiceWorkerChunkingContextOptions {
1635                mode: self.next_mode(),
1636                root_path: self.project_root_path().owned().await?,
1637                output_root: self.node_root().owned().await?,
1638                output_root_to_root_path: self.node_root_to_root_path().owned().await?,
1639                environment: self.client_compile_time_info().environment(),
1640                minify: self.next_config().turbo_minify(self.next_mode()),
1641                source_maps: self.next_config().client_source_maps(self.next_mode()),
1642                no_mangling: self.no_mangling(),
1643                hash_salt: self.next_config().output_hash_salt().to_resolved().await?,
1644            },
1645        ))
1646    }
1647
1648    #[turbo_tasks::function]
1649    pub(super) async fn service_worker_asset_context(
1650        self: Vc<Self>,
1651    ) -> Result<Vc<Box<dyn AssetContext>>> {
1652        Ok(Vc::upcast(ModuleAssetContext::new(
1653            TransitionOptions::default().cell(),
1654            self.client_compile_time_info(),
1655            get_client_module_options_context(
1656                self.project_path().owned().await?,
1657                self.execution_context(),
1658                self.client_compile_time_info().environment(),
1659                ClientContextType::Other,
1660                self.next_mode(),
1661                self.next_config(),
1662                self.encryption_key(),
1663            ),
1664            get_client_resolve_options_context(
1665                self.project_path().owned().await?,
1666                ClientContextType::Other,
1667                self.next_mode(),
1668                self.next_config(),
1669                self.execution_context(),
1670            ),
1671            Layer::new_with_user_friendly_name(rcstr!("service-worker"), rcstr!("Service Worker")),
1672        )))
1673    }
1674
1675    #[turbo_tasks::function]
1676    pub(super) async fn server_chunking_context(
1677        self: Vc<Self>,
1678        client_assets: bool,
1679    ) -> Result<Vc<NodeJsChunkingContext>> {
1680        let css_url_suffix = self.next_config().asset_suffix_path();
1681        let options = ServerChunkingContextOptions {
1682            mode: self.next_mode(),
1683            root_path: self.project_root_path().owned().await?,
1684            node_root: self.node_root().owned().await?,
1685            node_root_to_root_path: self.node_root_to_root_path().owned().await?,
1686            environment: self.server_compile_time_info().environment(),
1687            module_id_strategy: self.module_ids(),
1688            export_usage: self.export_usage(),
1689            unused_references: self.unused_references(),
1690            minify: self.next_config().turbo_minify(self.next_mode()),
1691            source_maps: self.next_config().server_source_maps(),
1692            no_mangling: self.no_mangling(),
1693            scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()),
1694            nested_async_chunking: self
1695                .next_config()
1696                .turbo_nested_async_chunking(self.next_mode(), false),
1697            debug_ids: self.next_config().turbopack_debug_ids(),
1698            client_root: self.client_relative_path().owned().await?,
1699            client_static_folder_name: self
1700                .next_config()
1701                .client_static_folder_name()
1702                .owned()
1703                .await?,
1704            asset_prefix: self.next_config().computed_asset_prefix().owned().await?,
1705            css_url_suffix,
1706            hash_salt: self.next_config().output_hash_salt().to_resolved().await?,
1707            style_groups_algorithm: self.next_config().css_chunking().owned().await?,
1708        };
1709        Ok(if client_assets {
1710            get_server_chunking_context_with_client_assets(options)
1711        } else {
1712            get_server_chunking_context(options)
1713        })
1714    }
1715
1716    #[turbo_tasks::function]
1717    pub(super) async fn edge_chunking_context(
1718        self: Vc<Self>,
1719        client_assets: bool,
1720    ) -> Result<Vc<Box<dyn ChunkingContext>>> {
1721        let css_url_suffix = self.next_config().asset_suffix_path();
1722        let options = EdgeChunkingContextOptions {
1723            mode: self.next_mode(),
1724            root_path: self.project_root_path().owned().await?,
1725            node_root: self.node_root().owned().await?,
1726            output_root_to_root_path: self.node_root_to_root_path(),
1727            environment: self.edge_compile_time_info().environment(),
1728            module_id_strategy: self.module_ids(),
1729            export_usage: self.export_usage(),
1730            unused_references: self.unused_references(),
1731            turbo_minify: self.next_config().turbo_minify(self.next_mode()),
1732            turbo_source_maps: self.next_config().server_source_maps(),
1733            no_mangling: self.no_mangling(),
1734            scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()),
1735            nested_async_chunking: self
1736                .next_config()
1737                .turbo_nested_async_chunking(self.next_mode(), false),
1738            client_root: self.client_relative_path().owned().await?,
1739            client_static_folder_name: self
1740                .next_config()
1741                .client_static_folder_name()
1742                .owned()
1743                .await?,
1744            asset_prefix: self.next_config().computed_asset_prefix().owned().await?,
1745            css_url_suffix,
1746            hash_salt: self.next_config().output_hash_salt().to_resolved().await?,
1747            cross_origin: self.next_config().cross_origin(),
1748            style_groups_algorithm: self.next_config().css_chunking().owned().await?,
1749        };
1750        Ok(if client_assets {
1751            get_edge_chunking_context_with_client_assets(options)
1752        } else {
1753            get_edge_chunking_context(options)
1754        })
1755    }
1756
1757    #[turbo_tasks::function]
1758    pub(super) fn runtime_chunking_context(
1759        self: Vc<Self>,
1760        client_assets: bool,
1761        runtime: NextRuntime,
1762    ) -> Vc<Box<dyn ChunkingContext>> {
1763        match runtime {
1764            NextRuntime::Edge => self.edge_chunking_context(client_assets),
1765            NextRuntime::NodeJs => Vc::upcast(self.server_chunking_context(client_assets)),
1766        }
1767    }
1768
1769    /// Computes the project's feature-usage telemetry summary.
1770    ///
1771    /// Includes:
1772    /// - The SWC target triple (`swc/target/...`, always on).
1773    /// - Boolean config and compiler-option flags, mirroring the webpack [`TelemetryPlugin`](https://github.com/vercel/next.js/blob/9da305fe320b89ee2f8c3cfb7ecbf48856368913/packages/next/src/build/webpack-config.ts#L2516)
1774    ///   shape.
1775    /// - Per-feature-module import counts (e.g. `next/image`, `next/font/google`) computed by
1776    ///   walking the whole-app module graph and counting **unique importing modules** per feature.
1777    ///   This replaces an earlier `before_resolve` plugin that emitted telemetry per resolve;
1778    ///   because Turbopack caches resolves, the earlier approach under-counted to at most one per
1779    ///   feature.
1780    ///
1781    /// Returns `bail!` if the project is not in build mode — `whole_app_module_graphs` drops
1782    /// issues in development and the graph may not reflect the full project, so reporting
1783    /// telemetry from dev would produce misleading counts.
1784    ///
1785    /// The returned summary is sorted by feature name for determinism.
1786    #[turbo_tasks::function]
1787    pub async fn project_feature_usage(
1788        self: ResolvedVc<Self>,
1789    ) -> Result<Vc<ProjectFeatureUsageSummary>> {
1790        if !self.next_mode().await?.is_production() {
1791            bail!("project_feature_usage() may only be called during `next build`");
1792        }
1793
1794        // (public feature specifier, path suffix) pairs. The suffix identifies the resolved
1795        // feature module; we match via `module.ident().path.path.ends_with(suffix)`. Mirrors
1796        // the webpack `FEATURE_MODULE_MAP` + `FEATURE_MODULE_REGEXP_MAP` in
1797        // `packages/next/src/build/webpack/plugins/telemetry-plugin/telemetry-plugin.ts`.
1798        //
1799        // Font specifiers (`next/font/*`, `@next/font/*`) are matched against the synthesized
1800        // `target.css` virtual module produced by the Next.js font loader transform
1801        // (`crates/next-custom-transforms/src/transforms/fonts`). That transform rewrites
1802        // `import { Inter } from 'next/font/google'` into
1803        // `import inter from 'next/font/google/target.css?{...}'` — the original specifier never
1804        // appears in the module graph, but the synthesized `target.css` module's path suffix does.
1805        // `ident.path.path` does not include the query string (that lives on `ident.query`), so
1806        // `ends_with` is the correct matcher here.
1807        static FEATURE_MODULE_PATH_SUFFIXES: &[(&str, &str)] = &[
1808            ("next/image", "/next/image.js"),
1809            ("next/future/image", "/next/future/image.js"),
1810            ("next/legacy/image", "/next/legacy/image.js"),
1811            ("next/script", "/next/script.js"),
1812            ("next/dynamic", "/next/dynamic.js"),
1813            ("next/font/google", "/next/font/google/target.css"),
1814            ("next/font/local", "/next/font/local/target.css"),
1815            ("@next/font/google", "/@next/font/google/target.css"),
1816            ("@next/font/local", "/@next/font/local/target.css"),
1817        ];
1818
1819        // TODO: useSwcLoader is not being reported as it is not directly corresponds (it checks
1820        // babel config existence) — need to confirm what we'll do with turbopack.
1821        let config = self.next_config();
1822        let compiler_options = config.compiler().await?;
1823        let mut features: Vec<(RcStr, u32)> = vec![
1824            // SWC target triple is prefixed with `swc/target/` to match the webpack
1825            // `swc/target/${SWC_TARGET_TRIPLE}` variant in `EventBuildFeatureUsage`.
1826            (
1827                format!("swc/target/{}", env!("VERGEN_CARGO_TARGET_TRIPLE")).into(),
1828                1,
1829            ),
1830            (
1831                rcstr!("skipProxyUrlNormalize"),
1832                (*config.skip_proxy_url_normalize().await?) as u32,
1833            ),
1834            (
1835                rcstr!("skipTrailingSlashRedirect"),
1836                (*config.skip_trailing_slash_redirect().await?) as u32,
1837            ),
1838            (
1839                rcstr!("modularizeImports"),
1840                !config.modularize_imports().await?.is_empty() as u32,
1841            ),
1842            (
1843                rcstr!("transpilePackages"),
1844                !config.transpile_packages().await?.is_empty() as u32,
1845            ),
1846            (rcstr!("swcRelay"), compiler_options.relay.is_some() as u32),
1847            (
1848                rcstr!("swcStyledComponents"),
1849                compiler_options
1850                    .styled_components
1851                    .as_ref()
1852                    .is_some_and(|sc| sc.is_enabled()) as u32,
1853            ),
1854            (
1855                rcstr!("swcReactRemoveProperties"),
1856                compiler_options
1857                    .react_remove_properties
1858                    .as_ref()
1859                    .is_some_and(|rc| rc.is_enabled()) as u32,
1860            ),
1861            (
1862                rcstr!("swcRemoveConsole"),
1863                compiler_options
1864                    .remove_console
1865                    .as_ref()
1866                    .is_some_and(|rc| rc.is_enabled()) as u32,
1867            ),
1868            (
1869                rcstr!("swcEmotion"),
1870                compiler_options
1871                    .emotion
1872                    .as_ref()
1873                    .is_some_and(|e| e.is_enabled()) as u32,
1874            ),
1875        ];
1876
1877        // Module-usage counts: two passes over the module graph.
1878        //  1. Iterate all nodes, classify each in parallel, keep only feature-module matches.
1879        //  2. Walk edges, for each edge whose target is a classified feature module, add the parent
1880        //     to that feature's unique-importer set.
1881        let module_graph = self.whole_app_module_graphs().await?.full.await?;
1882
1883        let matching: FxHashMap<ResolvedVc<Box<dyn Module>>, &'static str> = module_graph
1884            .iter_nodes()
1885            .map(async |node| {
1886                let ident = node.ident().await?;
1887                let path = &ident.path.path;
1888                for &(feature, suffix) in FEATURE_MODULE_PATH_SUFFIXES {
1889                    if path.ends_with(suffix) {
1890                        return Ok(Some((node, feature)));
1891                    }
1892                }
1893                Ok(None)
1894            })
1895            .try_flat_join()
1896            .await?
1897            .into_iter()
1898            .collect();
1899
1900        // Collect (feature, parent) pairs for every edge whose target is a feature module.
1901        //
1902        // We count every such edge regardless of whether the import is eventually tree-shaken.
1903        // This matches webpack's `TelemetryPlugin`, which hooks `finishModules` (before DCE).
1904        // We could filter via `BindingUsageInfo` to only count edges that survive tree-shaking,
1905        // but staying parallel to webpack lets dashboards compare counts across the two bundlers
1906        // directly.
1907        let mut pairs: FxHashSet<(&'static str, ResolvedVc<Box<dyn Module>>)> =
1908            FxHashSet::default();
1909        module_graph.traverse_edges_unordered(|parent, node| {
1910            if let Some((parent_node, _)) = parent
1911                && let Some(&feature) = matching.get(&node)
1912            {
1913                pairs.insert((feature, parent_node));
1914            }
1915            Ok(())
1916        })?;
1917
1918        // Dedupe parents by their source location (path + query + fragment), ignoring
1919        // `ident().layer` and other modifiers. In Turbopack the same user file often appears as
1920        // separate modules per layer (e.g. SSR, client, edge), but webpack counts one "importer"
1921        // per source file — this matches that semantics.
1922        let parent_source_keys = pairs
1923            .into_iter()
1924            .map(async |(feature, parent)| {
1925                let ident = parent.ident().await?;
1926                let key = (
1927                    ident.path.path.clone(),
1928                    ident.query.clone(),
1929                    ident.fragment.clone(),
1930                );
1931                Ok((feature, key))
1932            })
1933            .try_join()
1934            .await?;
1935
1936        let mut importers: FxHashMap<&'static str, FxHashSet<(RcStr, RcStr, RcStr)>> =
1937            FxHashMap::default();
1938        for (feature, key) in parent_source_keys {
1939            importers.entry(feature).or_default().insert(key);
1940        }
1941        for (feature, unique_sources) in importers {
1942            features.push((RcStr::from(feature), unique_sources.len() as u32));
1943        }
1944
1945        features.sort_by(|a, b| a.0.cmp(&b.0));
1946        Ok(ProjectFeatureUsageSummary { features }.cell())
1947    }
1948
1949    /// Scans the app/pages directories for entry points files (matching the
1950    /// provided page_extensions).
1951    #[turbo_tasks::function]
1952    pub async fn entrypoints(self: Vc<Self>) -> Result<Vc<Entrypoints>> {
1953        Ok(self.entrypoints_with_app_route_filter(None))
1954    }
1955
1956    #[turbo_tasks::function]
1957    pub async fn entrypoints_with_app_route_filter(
1958        self: Vc<Self>,
1959        app_route_filter: Option<Vec<RcStr>>,
1960    ) -> Result<Vc<Entrypoints>> {
1961        let this = self.await?;
1962        let mut routes = FxIndexMap::default();
1963        let app_project = self.app_project();
1964        let pages_project = self.pages_project();
1965
1966        // Convert debug build paths to route keys once for O(1) lookups
1967        let debug_build_paths_route_keys = this
1968            .debug_build_paths
1969            .as_ref()
1970            .map(DebugBuildPathsRouteKeys::from_debug_build_paths)
1971            .transpose()?;
1972
1973        if let Some(app_project) = &*app_project.await? {
1974            let app_routes = app_project.routes_with_filter(app_route_filter);
1975            routes.extend(
1976                app_routes
1977                    .await?
1978                    .iter()
1979                    .filter(|(k, _)| {
1980                        debug_build_paths_route_keys
1981                            .as_ref()
1982                            .is_none_or(|keys| keys.should_include_app_route(k))
1983                    })
1984                    .map(|(k, v)| (k.clone(), v.clone())),
1985            );
1986        }
1987
1988        for (pathname, page_route) in &pages_project.routes().await? {
1989            if debug_build_paths_route_keys
1990                .as_ref()
1991                .is_some_and(|keys| !keys.should_include_pages_route(pathname))
1992            {
1993                continue;
1994            }
1995
1996            match routes.entry(pathname.clone()) {
1997                Entry::Occupied(mut entry) => {
1998                    ConflictIssue {
1999                        path: self.project_path().owned().await?,
2000                        title: StyledString::Text(
2001                            format!("App Router and Pages Router both match path: {pathname}")
2002                                .into(),
2003                        )
2004                        .resolved_cell(),
2005                        description: StyledString::Text(
2006                            "Next.js does not support having both App Router and Pages Router \
2007                             routes matching the same path. Please remove one of the conflicting \
2008                             routes."
2009                                .into(),
2010                        )
2011                        .resolved_cell(),
2012                        severity: IssueSeverity::Error,
2013                    }
2014                    .resolved_cell()
2015                    .emit();
2016                    *entry.get_mut() = Route::Conflict;
2017                }
2018                Entry::Vacant(entry) => {
2019                    entry.insert(page_route.clone());
2020                }
2021            }
2022        }
2023
2024        let pages_document_endpoint = self
2025            .pages_project()
2026            .document_endpoint()
2027            .to_resolved()
2028            .await?;
2029        let pages_app_endpoint = self.pages_project().app_endpoint().to_resolved().await?;
2030        let pages_error_endpoint = self.pages_project().error_endpoint().to_resolved().await?;
2031
2032        let middleware = self.find_middleware();
2033        let middleware = if let FindContextFileResult::Found(fs_path, _) = &*middleware.await? {
2034            let is_proxy = fs_path.file_stem() == Some("proxy");
2035            Some(Middleware {
2036                endpoint: self.middleware_endpoint().to_resolved().await?,
2037                is_proxy,
2038            })
2039        } else {
2040            None
2041        };
2042
2043        let instrumentation = self.find_instrumentation();
2044        let instrumentation = if let FindContextFileResult::Found(..) = *instrumentation.await? {
2045            Some(Instrumentation {
2046                node_js: self.instrumentation_endpoint(false).to_resolved().await?,
2047                edge: self.instrumentation_endpoint(true).to_resolved().await?,
2048            })
2049        } else {
2050            None
2051        };
2052
2053        Ok(Entrypoints {
2054            routes,
2055            middleware,
2056            instrumentation,
2057            pages_document_endpoint,
2058            pages_app_endpoint,
2059            pages_error_endpoint,
2060        }
2061        .cell())
2062    }
2063
2064    #[turbo_tasks::function]
2065    async fn edge_middleware_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
2066        let mut transitions = vec![];
2067
2068        let app_dir = find_app_dir(self.project_path().owned().await?)
2069            .owned()
2070            .await?;
2071        let app_project = *self.app_project().await?;
2072
2073        let ecmascript_client_reference_transition_name =
2074            app_project.map(|_| AppProject::client_transition_name());
2075
2076        if let Some(app_project) = app_project {
2077            transitions.push((
2078                AppProject::client_transition_name(),
2079                app_project
2080                    .edge_ecmascript_client_reference_transition()
2081                    .to_resolved()
2082                    .await?,
2083            ));
2084        }
2085
2086        Ok(Vc::upcast(ModuleAssetContext::new(
2087            TransitionOptions {
2088                named_transitions: transitions.clone().into_iter().collect(),
2089                ..Default::default()
2090            }
2091            .cell(),
2092            self.edge_compile_time_info(),
2093            get_server_module_options_context(
2094                self.project_path().owned().await?,
2095                self.execution_context(),
2096                ServerContextType::Middleware {
2097                    app_dir: app_dir.clone(),
2098                    ecmascript_client_reference_transition_name:
2099                        ecmascript_client_reference_transition_name.clone(),
2100                },
2101                self.next_mode(),
2102                self.next_config(),
2103                NextRuntime::Edge,
2104                self.encryption_key(),
2105                self.edge_compile_time_info().environment(),
2106                self.client_compile_time_info().environment(),
2107                // There is no NFT on edge
2108                false,
2109            ),
2110            get_edge_resolve_options_context(
2111                self.project_path().owned().await?,
2112                ServerContextType::Middleware {
2113                    app_dir: app_dir.clone(),
2114                    ecmascript_client_reference_transition_name:
2115                        ecmascript_client_reference_transition_name.clone(),
2116                },
2117                self.next_mode(),
2118                self.next_config(),
2119                self.execution_context(),
2120                None, // root params can't be used in middleware
2121            ),
2122            Layer::new_with_user_friendly_name(
2123                rcstr!("middleware-edge"),
2124                rcstr!("Edge Middleware"),
2125            ),
2126        )))
2127    }
2128
2129    #[turbo_tasks::function]
2130    async fn node_middleware_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
2131        let mut transitions = vec![];
2132
2133        let app_dir = find_app_dir(self.project_path().owned().await?)
2134            .owned()
2135            .await?;
2136        let app_project = *self.app_project().await?;
2137
2138        let ecmascript_client_reference_transition_name =
2139            app_project.map(|_| AppProject::client_transition_name());
2140
2141        if let Some(app_project) = app_project {
2142            transitions.push((
2143                AppProject::client_transition_name(),
2144                app_project
2145                    .edge_ecmascript_client_reference_transition()
2146                    .to_resolved()
2147                    .await?,
2148            ));
2149        }
2150
2151        Ok(Vc::upcast(ModuleAssetContext::new(
2152            TransitionOptions {
2153                named_transitions: transitions.clone().into_iter().collect(),
2154                ..Default::default()
2155            }
2156            .cell(),
2157            self.server_compile_time_info(),
2158            get_server_module_options_context(
2159                self.project_path().owned().await?,
2160                self.execution_context(),
2161                ServerContextType::Middleware {
2162                    app_dir: app_dir.clone(),
2163                    ecmascript_client_reference_transition_name:
2164                        ecmascript_client_reference_transition_name.clone(),
2165                },
2166                self.next_mode(),
2167                self.next_config(),
2168                NextRuntime::NodeJs,
2169                self.encryption_key(),
2170                self.server_compile_time_info().environment(),
2171                self.client_compile_time_info().environment(),
2172                *self.should_write_nft_manifests().await?,
2173            ),
2174            get_server_resolve_options_context(
2175                self.project_path().owned().await?,
2176                ServerContextType::Middleware {
2177                    app_dir: app_dir.clone(),
2178                    ecmascript_client_reference_transition_name,
2179                },
2180                self.next_mode(),
2181                self.next_config(),
2182                self.execution_context(),
2183                None, // root params can't be used in middleware
2184            ),
2185            Layer::new_with_user_friendly_name(rcstr!("middleware"), rcstr!("Middleware")),
2186        )))
2187    }
2188
2189    #[turbo_tasks::function]
2190    async fn find_middleware(self: Vc<Self>) -> Result<Vc<FindContextFileResult>> {
2191        Ok(find_context_file(
2192            self.project_path().owned().await?,
2193            middleware_files(self.next_config().page_extensions()),
2194            // our callers do not care about affecting sources
2195            false,
2196        ))
2197    }
2198
2199    #[turbo_tasks::function]
2200    async fn middleware_endpoint(self: Vc<Self>) -> Result<Vc<Box<dyn Endpoint>>> {
2201        let middleware = self.find_middleware();
2202        let FindContextFileResult::Found(fs_path, _) = &*middleware.await? else {
2203            return Ok(Vc::upcast(EmptyEndpoint::new(self)));
2204        };
2205        let source = Vc::upcast(FileSource::new(fs_path.clone()));
2206        let app_dir = find_app_dir(self.project_path().owned().await?)
2207            .owned()
2208            .await?;
2209        let ecmascript_client_reference_transition_name = (*self.app_project().await?)
2210            .as_ref()
2211            .map(|_| AppProject::client_transition_name());
2212
2213        let is_proxy = fs_path.file_stem() == Some("proxy");
2214        let config = parse_segment_config_from_source(
2215            source,
2216            if is_proxy {
2217                ParseSegmentMode::Proxy
2218            } else {
2219                ParseSegmentMode::Base
2220            },
2221        );
2222        let runtime = config.await?.runtime.unwrap_or(if is_proxy {
2223            NextRuntime::NodeJs
2224        } else {
2225            NextRuntime::Edge
2226        });
2227
2228        let middleware_asset_context = match runtime {
2229            NextRuntime::NodeJs => self.node_middleware_context(),
2230            NextRuntime::Edge => self.edge_middleware_context(),
2231        };
2232
2233        Ok(Vc::upcast(MiddlewareEndpoint::new(
2234            self,
2235            middleware_asset_context,
2236            source,
2237            app_dir.clone(),
2238            ecmascript_client_reference_transition_name,
2239            config,
2240            runtime,
2241        )))
2242    }
2243
2244    #[turbo_tasks::function]
2245    async fn node_instrumentation_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
2246        let mut transitions = vec![];
2247
2248        let app_dir = find_app_dir(self.project_path().owned().await?)
2249            .owned()
2250            .await?;
2251        let app_project = &*self.app_project().await?;
2252
2253        let ecmascript_client_reference_transition_name = app_project
2254            .as_ref()
2255            .map(|_| AppProject::client_transition_name());
2256
2257        if let Some(app_project) = app_project {
2258            transitions.push((
2259                AppProject::client_transition_name(),
2260                app_project
2261                    .ecmascript_client_reference_transition()
2262                    .to_resolved()
2263                    .await?,
2264            ));
2265        }
2266
2267        Ok(Vc::upcast(ModuleAssetContext::new(
2268            TransitionOptions {
2269                named_transitions: transitions.into_iter().collect(),
2270                ..Default::default()
2271            }
2272            .cell(),
2273            self.server_compile_time_info(),
2274            get_server_module_options_context(
2275                self.project_path().owned().await?,
2276                self.execution_context(),
2277                ServerContextType::Instrumentation {
2278                    app_dir: app_dir.clone(),
2279                    ecmascript_client_reference_transition_name:
2280                        ecmascript_client_reference_transition_name.clone(),
2281                },
2282                self.next_mode(),
2283                self.next_config(),
2284                NextRuntime::NodeJs,
2285                self.encryption_key(),
2286                self.server_compile_time_info().environment(),
2287                self.client_compile_time_info().environment(),
2288                *self.should_write_nft_manifests().await?,
2289            ),
2290            get_server_resolve_options_context(
2291                self.project_path().owned().await?,
2292                ServerContextType::Instrumentation {
2293                    app_dir: app_dir.clone(),
2294                    ecmascript_client_reference_transition_name,
2295                },
2296                self.next_mode(),
2297                self.next_config(),
2298                self.execution_context(),
2299                None, // root params can't be used in instrumentation
2300            ),
2301            Layer::new_with_user_friendly_name(
2302                rcstr!("instrumentation"),
2303                rcstr!("Instrumentation"),
2304            ),
2305        )))
2306    }
2307
2308    #[turbo_tasks::function]
2309    async fn edge_instrumentation_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
2310        let mut transitions = vec![];
2311
2312        let app_dir = find_app_dir(self.project_path().owned().await?)
2313            .owned()
2314            .await?;
2315        let app_project = &*self.app_project().await?;
2316
2317        let ecmascript_client_reference_transition_name = app_project
2318            .as_ref()
2319            .map(|_| AppProject::client_transition_name());
2320
2321        if let Some(app_project) = app_project {
2322            transitions.push((
2323                AppProject::client_transition_name(),
2324                app_project
2325                    .edge_ecmascript_client_reference_transition()
2326                    .to_resolved()
2327                    .await?,
2328            ));
2329        }
2330
2331        Ok(Vc::upcast(ModuleAssetContext::new(
2332            TransitionOptions {
2333                named_transitions: transitions.into_iter().collect(),
2334                ..Default::default()
2335            }
2336            .cell(),
2337            self.edge_compile_time_info(),
2338            get_server_module_options_context(
2339                self.project_path().owned().await?,
2340                self.execution_context(),
2341                ServerContextType::Instrumentation {
2342                    app_dir: app_dir.clone(),
2343                    ecmascript_client_reference_transition_name:
2344                        ecmascript_client_reference_transition_name.clone(),
2345                },
2346                self.next_mode(),
2347                self.next_config(),
2348                NextRuntime::Edge,
2349                self.encryption_key(),
2350                self.edge_compile_time_info().environment(),
2351                self.client_compile_time_info().environment(),
2352                // There is no NFT on edge
2353                false,
2354            ),
2355            get_edge_resolve_options_context(
2356                self.project_path().owned().await?,
2357                ServerContextType::Instrumentation {
2358                    app_dir: app_dir.clone(),
2359                    ecmascript_client_reference_transition_name,
2360                },
2361                self.next_mode(),
2362                self.next_config(),
2363                self.execution_context(),
2364                None, // root params can't be used in instrumentation
2365            ),
2366            Layer::new_with_user_friendly_name(
2367                rcstr!("instrumentation-edge"),
2368                rcstr!("Edge Instrumentation"),
2369            ),
2370        )))
2371    }
2372
2373    #[turbo_tasks::function]
2374    async fn find_instrumentation(self: Vc<Self>) -> Result<Vc<FindContextFileResult>> {
2375        Ok(find_context_file(
2376            self.project_path().owned().await?,
2377            instrumentation_files(self.next_config().page_extensions()),
2378            // our callers do not care about affecting sources
2379            false,
2380        ))
2381    }
2382
2383    #[turbo_tasks::function]
2384    async fn instrumentation_endpoint(
2385        self: Vc<Self>,
2386        is_edge: bool,
2387    ) -> Result<Vc<Box<dyn Endpoint>>> {
2388        let instrumentation = self.find_instrumentation();
2389        let FindContextFileResult::Found(fs_path, _) = &*instrumentation.await? else {
2390            return Ok(Vc::upcast(EmptyEndpoint::new(self)));
2391        };
2392        let source = Vc::upcast(FileSource::new(fs_path.clone()));
2393        let app_dir = find_app_dir(self.project_path().owned().await?)
2394            .owned()
2395            .await?;
2396        let ecmascript_client_reference_transition_name = (*self.app_project().await?)
2397            .as_ref()
2398            .map(|_| AppProject::client_transition_name());
2399
2400        let instrumentation_asset_context = if is_edge {
2401            self.edge_instrumentation_context()
2402        } else {
2403            self.node_instrumentation_context()
2404        };
2405
2406        Ok(Vc::upcast(InstrumentationEndpoint::new(
2407            self,
2408            instrumentation_asset_context,
2409            source,
2410            is_edge,
2411            app_dir.clone(),
2412            ecmascript_client_reference_transition_name,
2413        )))
2414    }
2415
2416    #[turbo_tasks::function]
2417    pub async fn emit_all_output_assets(
2418        self: Vc<Self>,
2419        output_assets: OperationVc<OutputAssets>,
2420    ) -> Result<()> {
2421        let span = tracing::info_span!("emitting");
2422        async move {
2423            let all_output_assets = all_assets_from_entries_operation(output_assets);
2424
2425            let client_relative_path = self.client_relative_path().owned().await?;
2426            let node_root = self.node_root().owned().await?;
2427
2428            if let Some(map) = self.await?.versioned_content_map {
2429                map.insert_output_assets(
2430                    all_output_assets,
2431                    node_root.clone(),
2432                    client_relative_path.clone(),
2433                    node_root.clone(),
2434                )
2435                .as_side_effect()
2436                .await?;
2437
2438                Ok(())
2439            } else {
2440                emit_assets(
2441                    all_output_assets.connect(),
2442                    node_root.clone(),
2443                    client_relative_path.clone(),
2444                    node_root.clone(),
2445                )
2446                .as_side_effect()
2447                .await?;
2448
2449                Ok(())
2450            }
2451        }
2452        .instrument(span)
2453        .await
2454    }
2455
2456    /// Returns the root path for HMR content based on the target.
2457    /// Client uses client_relative_path, Server uses node_root.
2458    #[turbo_tasks::function]
2459    async fn hmr_root_path(self: Vc<Self>, target: HmrTarget) -> Result<Vc<FileSystemPath>> {
2460        Ok(match target {
2461            HmrTarget::Client => self.client_relative_path(),
2462            HmrTarget::Server => self.node_root(),
2463        })
2464    }
2465
2466    /// Get HMR content by chunk_name for the specified target.
2467    #[turbo_tasks::function]
2468    async fn hmr_content(
2469        self: Vc<Self>,
2470        chunk_name: RcStr,
2471        target: HmrTarget,
2472    ) -> Result<Vc<OptionVersionedContent>> {
2473        if let Some(map) = self.await?.versioned_content_map {
2474            let content = map.get(self.hmr_root_path(target).await?.join(&chunk_name)?);
2475            Ok(content)
2476        } else {
2477            bail!("must be in dev mode to hmr")
2478        }
2479    }
2480
2481    /// Get the version state for an HMR session. Initialized with the first seen
2482    /// version in that session.
2483    #[turbo_tasks::function]
2484    pub async fn hmr_version_state(
2485        self: ResolvedVc<Self>,
2486        chunk_name: RcStr,
2487        target: HmrTarget,
2488        session: TransientInstance<()>,
2489    ) -> Result<Vc<VersionState>> {
2490        // The session argument is important to avoid caching this function between
2491        // sessions.
2492        let _ = session;
2493
2494        #[tracing::instrument(
2495            level = "info",
2496            name = "get HMR version",
2497            skip_all,
2498            fields(chunk_name = %chunk_name, target = %target),
2499        )]
2500        #[turbo_tasks::function(operation, root)]
2501        async fn hmr_version_operation(
2502            this: ResolvedVc<Project>,
2503            chunk_name: RcStr,
2504            target: HmrTarget,
2505        ) -> Result<Vc<Box<dyn Version>>> {
2506            tracing::info!(chunk_name = %chunk_name, target = %target, "hmr subscription");
2507            let content = this.hmr_content(chunk_name, target).await?;
2508            if let Some(content) = &*content {
2509                Ok(content.version())
2510            } else {
2511                Ok(Vc::upcast(NotFoundVersion::new()))
2512            }
2513        }
2514        let version_op = hmr_version_operation(self, chunk_name, target);
2515
2516        // INVALIDATION: This is intentionally untracked to avoid invalidating this
2517        // function completely. We want to initialize the VersionState with the
2518        // first seen version of the session.
2519        let state = VersionState::new(
2520            version_op
2521                .read_trait_strongly_consistent()
2522                .untracked()
2523                .await?,
2524        )
2525        .await?;
2526        Ok(state)
2527    }
2528
2529    /// Emits opaque HMR events whenever a change is detected in the chunk group
2530    /// internally known as `chunk_name` for the specified target.
2531    #[turbo_tasks::function]
2532    pub async fn hmr_update(
2533        self: Vc<Self>,
2534        chunk_name: RcStr,
2535        target: HmrTarget,
2536        from: Vc<VersionState>,
2537    ) -> Result<Vc<Update>> {
2538        let from = from.get();
2539        let content = self.hmr_content(chunk_name, target).await?;
2540        if let Some(content) = *content {
2541            Ok(content.update(from))
2542        } else {
2543            Ok(Update::Missing.cell())
2544        }
2545    }
2546
2547    /// Gets a list of all HMR chunk names that can be subscribed to for the
2548    /// specified target. Used by the dev server to set up server-side HMR
2549    /// subscriptions for all Node.js App Router entries (pages and route
2550    /// handlers).
2551    #[turbo_tasks::function]
2552    pub async fn hmr_chunk_names(self: Vc<Self>, target: HmrTarget) -> Result<Vc<Vec<RcStr>>> {
2553        if let Some(map) = self.await?.versioned_content_map {
2554            Ok(map.keys_in_path(self.hmr_root_path(target).owned().await?))
2555        } else {
2556            bail!("must be in dev mode to hmr")
2557        }
2558    }
2559
2560    /// Completion when server side changes are detected in output assets
2561    /// referenced from the roots
2562    #[turbo_tasks::function]
2563    pub async fn server_changed(self: Vc<Self>, roots: Vc<OutputAssets>) -> Result<Vc<Completion>> {
2564        let path = self.node_root().owned().await?;
2565        Ok(any_output_changed(roots, path, true))
2566    }
2567
2568    /// Completion when client side changes are detected in output assets
2569    /// referenced from the roots
2570    #[turbo_tasks::function]
2571    pub async fn client_changed(self: Vc<Self>, roots: Vc<OutputAssets>) -> Result<Vc<Completion>> {
2572        let path = self.client_root().owned().await?;
2573        Ok(any_output_changed(roots, path, false))
2574    }
2575
2576    #[turbo_tasks::function]
2577    pub async fn client_main_modules(self: Vc<Self>) -> Result<Vc<GraphEntries>> {
2578        let pages_project = self.pages_project();
2579        let mut chunk_groups = vec![ChunkGroupEntry::Entry {
2580            modules: vec![pages_project.client_main_module().to_resolved().await?],
2581            heuristics: EntryHeuristics::high_priority(),
2582        }];
2583
2584        if let Some(app_project) = *self.app_project().await? {
2585            chunk_groups.push(ChunkGroupEntry::Entry {
2586                modules: vec![app_project.client_main_module().to_resolved().await?],
2587                heuristics: EntryHeuristics::high_priority(),
2588            });
2589        }
2590
2591        Ok(GraphEntries::from_chunk_groups(chunk_groups).cell())
2592    }
2593
2594    /// Gets the module id strategy for the project.
2595    #[turbo_tasks::function]
2596    pub async fn module_ids(self: Vc<Self>) -> Result<Vc<ModuleIdStrategy>> {
2597        let module_id_strategy = *self.next_config().module_ids(self.next_mode()).await?;
2598        match module_id_strategy {
2599            ModuleIdStrategyConfig::Named => Ok(ModuleIdStrategy {
2600                module_id_map: None,
2601                fallback: ModuleIdFallback::Ident,
2602            }
2603            .cell()),
2604            ModuleIdStrategyConfig::Deterministic => {
2605                let module_graphs = self.whole_app_module_graphs().await?;
2606                Ok(get_global_module_id_strategy(*module_graphs.full))
2607            }
2608        }
2609    }
2610
2611    /// Compute the used exports and unused imports for each module.
2612    #[turbo_tasks::function]
2613    async fn binding_usage_info(self: Vc<Self>) -> Result<Vc<BindingUsageInfo>> {
2614        let module_graphs = self.whole_app_module_graphs().await?;
2615        Ok(module_graphs
2616            .binding_usage_info
2617            .context("No binding usage info")?
2618            .connect())
2619    }
2620
2621    /// Compute the used exports for each module.
2622    #[turbo_tasks::function]
2623    pub async fn export_usage(self: Vc<Self>) -> Result<Vc<OptionBindingUsageInfo>> {
2624        if *self
2625            .next_config()
2626            .turbopack_remove_unused_exports(self.next_mode())
2627            .await?
2628        {
2629            Ok(Vc::cell(Some(
2630                self.binding_usage_info().to_resolved().await?,
2631            )))
2632        } else {
2633            Ok(Vc::cell(None))
2634        }
2635    }
2636
2637    /// Compute the unused references that were removed (inner graph tree shaking).
2638    #[turbo_tasks::function]
2639    pub async fn unused_references(self: Vc<Self>) -> Result<Vc<UnusedReferences>> {
2640        if *self
2641            .next_config()
2642            .turbopack_remove_unused_imports(self.next_mode())
2643            .await?
2644        {
2645            Ok(self.binding_usage_info().unused_references())
2646        } else {
2647            Ok(Vc::cell(Default::default()))
2648        }
2649    }
2650
2651    #[turbo_tasks::function]
2652    pub async fn with_next_config(&self, next_config: Vc<NextConfig>) -> Result<Vc<Self>> {
2653        Ok(Self {
2654            next_config: next_config.to_resolved().await?,
2655            ..(*self).clone()
2656        }
2657        .cell())
2658    }
2659
2660    /// Returns any modules specified as `nextConfig.cacheHandler` and/or `nextConfig.cacheHandlers`
2661    #[turbo_tasks::function]
2662    pub async fn additional_traced_modules(self: Vc<Self>) -> Result<Vc<Modules>> {
2663        let project_path = self.project_path().owned().await?;
2664        let cache_handler = self
2665            .next_config()
2666            .cache_handler(project_path.clone())
2667            .await?;
2668        let cache_handlers = self
2669            .next_config()
2670            .cache_handlers(project_path.clone())
2671            .await?;
2672
2673        let asset_context =
2674            externals_tracing_module_context(get_tracing_compile_time_info(), false);
2675
2676        Ok(Vc::cell(
2677            cache_handler
2678                .iter()
2679                .chain(cache_handlers.iter())
2680                .map(|f| {
2681                    asset_context
2682                        .process(
2683                            Vc::upcast(FileSource::new(f.clone())),
2684                            ReferenceType::CommonJs(CommonJsReferenceSubType::Undefined),
2685                        )
2686                        .module()
2687                })
2688                .map(|m| m.to_resolved())
2689                .try_join()
2690                .await?,
2691        ))
2692    }
2693}
2694
2695/// Scales down or shuts down the Node.js process pool after module graph computation.
2696async fn scale_down_node_pool(project: ResolvedVc<Project>) -> Result<()> {
2697    let execution_context = project.execution_context().await?;
2698    let node_backend = execution_context.node_backend.into_trait_ref().await?;
2699    if *project.is_watch_enabled().await? {
2700        node_backend.scale_down()?;
2701    } else {
2702        node_backend.scale_zero()?;
2703    }
2704    Ok(())
2705}
2706
2707// This is a performance optimization. This function is a root aggregation function that
2708// aggregates over the whole subgraph.
2709#[turbo_tasks::function(operation, root)]
2710async fn whole_app_module_graph_operation(
2711    project: ResolvedVc<Project>,
2712) -> Result<Vc<BaseAndFullModuleGraph>> {
2713    let span = tracing::info_span!("whole app module graph", modules = Empty, edges = Empty);
2714    let span_clone = span.clone();
2715    async move {
2716        let next_mode = project.next_mode();
2717        let should_trace = *project.should_write_nft_manifests().await?;
2718        let should_read_binding_usage = next_mode.await?.is_production();
2719        let base_single_module_graph = SingleModuleGraph::new_with_entries(
2720            project.get_all_entries().to_resolved().await?,
2721            should_trace,
2722            should_read_binding_usage,
2723        );
2724        let base_visited_modules = VisitedModules::from_graph(base_single_module_graph);
2725
2726        let base = ModuleGraph::from_graphs(vec![base_single_module_graph], None);
2727
2728        let turbopack_remove_unused_imports = *project
2729            .next_config()
2730            .turbopack_remove_unused_imports(next_mode)
2731            .await?;
2732
2733        let base = if turbopack_remove_unused_imports {
2734            // TODO suboptimal that we do compute_binding_usage_info twice (once for the base
2735            // graph and later for the full graph)
2736            let binding_usage_info = compute_binding_usage_info(base, true);
2737            ModuleGraph::from_graphs(vec![base_single_module_graph], Some(binding_usage_info))
2738        } else {
2739            base
2740        };
2741
2742        let additional_entries = project
2743            .get_all_additional_entries(base.connect())
2744            .to_resolved()
2745            .await?;
2746
2747        let additional_module_graph = SingleModuleGraph::new_with_entries_visited(
2748            additional_entries,
2749            base_visited_modules,
2750            should_trace,
2751            should_read_binding_usage,
2752        );
2753
2754        if !span.is_disabled() {
2755            let base_module_count = base_single_module_graph
2756                .connect()
2757                .module_count()
2758                .untracked()
2759                .await?;
2760            let additional_module_count = additional_module_graph
2761                .connect()
2762                .module_count()
2763                .untracked()
2764                .await?;
2765            span.record("modules", *base_module_count + *additional_module_count);
2766            let base_edge_count = base_single_module_graph
2767                .connect()
2768                .edge_count()
2769                .untracked()
2770                .await?;
2771            let additional_edge_count = additional_module_graph
2772                .connect()
2773                .edge_count()
2774                .untracked()
2775                .await?;
2776            span.record("edges", *base_edge_count + *additional_edge_count);
2777        }
2778
2779        let graphs = vec![base_single_module_graph, additional_module_graph];
2780
2781        let (full, binding_usage_info) = if turbopack_remove_unused_imports {
2782            let full_with_unused_references = ModuleGraph::from_graphs(graphs.clone(), None);
2783            let binding_usage_info = compute_binding_usage_info(full_with_unused_references, true);
2784            (
2785                ModuleGraph::from_graphs(graphs, Some(binding_usage_info)),
2786                Some(binding_usage_info),
2787            )
2788        } else {
2789            (ModuleGraph::from_graphs(graphs, None), None)
2790        };
2791
2792        Ok(BaseAndFullModuleGraph {
2793            base: base.connect().to_resolved().await?,
2794            full: full.connect().to_resolved().await?,
2795            binding_usage_info,
2796        }
2797        .cell())
2798    }
2799    .instrument(span_clone)
2800    .await
2801}
2802
2803#[turbo_tasks::value(shared)]
2804pub struct BaseAndFullModuleGraph {
2805    /// The base module graph generated from the entry points.
2806    pub base: ResolvedVc<ModuleGraph>,
2807    /// `full_with_unused_references` but with unused references removed.
2808    pub full: ResolvedVc<ModuleGraph>,
2809    /// Information about binding usage in the module graph.
2810    pub binding_usage_info: Option<OperationVc<BindingUsageInfo>>,
2811}
2812
2813#[turbo_tasks::function]
2814async fn any_output_changed(
2815    roots: Vc<OutputAssets>,
2816    path: FileSystemPath,
2817    server: bool,
2818) -> Result<Vc<Completion>> {
2819    let all_assets = expand_output_assets(
2820        roots.await?.into_iter().map(ExpandOutputAssetsInput::Asset),
2821        true,
2822    )
2823    .await?;
2824    let completions = all_assets
2825        .into_iter()
2826        .map(|m| {
2827            let path = path.clone();
2828
2829            async move {
2830                let asset_path = m.path().await?;
2831                if !asset_path.path.ends_with(".map")
2832                    && (!server || !asset_path.path.ends_with(".css"))
2833                    && asset_path.is_inside_ref(&path)
2834                {
2835                    anyhow::Ok(Some(
2836                        content_changed(*ResolvedVc::upcast(m))
2837                            .to_resolved()
2838                            .await?,
2839                    ))
2840                } else {
2841                    Ok(None)
2842                }
2843            }
2844        })
2845        .try_flat_join()
2846        .await?;
2847
2848    Ok(Vc::<Completions>::cell(completions).completed())
2849}
2850
2851#[turbo_tasks::function(operation, root)]
2852fn all_assets_from_entries_operation(
2853    operation: OperationVc<OutputAssets>,
2854) -> Result<Vc<ExpandedOutputAssets>> {
2855    let assets = operation.connect();
2856    Ok(all_assets_from_entries(assets))
2857}