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