Skip to main content

next_api/
project.rs

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