next_api/
project.rs

1use std::time::Duration;
2
3use anyhow::{Context, Result, bail};
4use bincode::{Decode, Encode};
5use indexmap::map::Entry;
6use next_core::{
7    app_structure::find_app_dir,
8    emit_assets, get_edge_chunking_context, get_edge_chunking_context_with_client_assets,
9    get_edge_compile_time_info, get_edge_resolve_options_context,
10    instrumentation::instrumentation_files,
11    middleware::middleware_files,
12    mode::NextMode,
13    next_client::{
14        ClientChunkingContextOptions, get_client_chunking_context, get_client_compile_time_info,
15    },
16    next_config::{ModuleIds as ModuleIdStrategyConfig, NextConfig},
17    next_edge::context::EdgeChunkingContextOptions,
18    next_server::{
19        ServerChunkingContextOptions, ServerContextType, get_server_chunking_context,
20        get_server_chunking_context_with_client_assets, get_server_compile_time_info,
21        get_server_module_options_context, get_server_resolve_options_context,
22    },
23    next_telemetry::NextFeatureTelemetry,
24    parse_segment_config_from_source,
25    segment_config::ParseSegmentMode,
26    util::{NextRuntime, OptionEnvMap},
27};
28use serde::{Deserialize, Serialize};
29use tracing::Instrument;
30use turbo_rcstr::{RcStr, rcstr};
31use turbo_tasks::{
32    Completion, Completions, FxIndexMap, IntoTraitRef, NonLocalValue, OperationValue, OperationVc,
33    ReadRef, ResolvedVc, State, TaskInput, TransientInstance, TryFlatJoinIterExt, Vc,
34    debug::ValueDebugFormat, fxindexmap, mark_root, trace::TraceRawVcs,
35};
36use turbo_tasks_env::{EnvMap, ProcessEnv};
37use turbo_tasks_fs::{
38    DiskFileSystem, FileContent, FileSystem, FileSystemPath, VirtualFileSystem, invalidation,
39};
40use turbo_unix_path::{join_path, unix_to_sys};
41use turbopack::{
42    ModuleAssetContext, evaluate_context::node_build_environment,
43    global_module_ids::get_global_module_id_strategy, transition::TransitionOptions,
44};
45use turbopack_core::{
46    PROJECT_FILESYSTEM_NAME,
47    changed::content_changed,
48    chunk::{
49        ChunkingContext, EvaluatableAssets,
50        module_id_strategies::{DevModuleIdStrategy, ModuleIdStrategy},
51    },
52    compile_time_info::CompileTimeInfo,
53    context::AssetContext,
54    diagnostics::DiagnosticExt,
55    environment::NodeJsVersion,
56    file_source::FileSource,
57    ident::Layer,
58    issue::{
59        CollectibleIssuesExt, Issue, IssueExt, IssueSeverity, IssueStage, OptionStyledString,
60        StyledString,
61    },
62    module::Module,
63    module_graph::{
64        GraphEntries, ModuleGraph, SingleModuleGraph, VisitedModules,
65        binding_usage_info::{
66            BindingUsageInfo, OptionBindingUsageInfo, compute_binding_usage_info,
67        },
68        chunk_group_info::ChunkGroupEntry,
69    },
70    output::{
71        ExpandOutputAssetsInput, ExpandedOutputAssets, OutputAsset, OutputAssets,
72        expand_output_assets,
73    },
74    reference::all_assets_from_entries,
75    resolve::{FindContextFileResult, find_context_file},
76    version::{
77        NotFoundVersion, OptionVersionedContent, Update, Version, VersionState, VersionedContent,
78    },
79};
80use turbopack_node::execution_context::ExecutionContext;
81use turbopack_nodejs::NodeJsChunkingContext;
82
83use crate::{
84    app::{AppProject, OptionAppProject},
85    empty::EmptyEndpoint,
86    entrypoints::Entrypoints,
87    instrumentation::InstrumentationEndpoint,
88    middleware::MiddlewareEndpoint,
89    pages::PagesProject,
90    route::{
91        Endpoint, EndpointGroup, EndpointGroupEntry, EndpointGroupKey, EndpointGroups, Endpoints,
92        Route,
93    },
94    versioned_content_map::VersionedContentMap,
95};
96
97#[derive(
98    Debug,
99    Serialize,
100    Deserialize,
101    Clone,
102    TaskInput,
103    PartialEq,
104    Eq,
105    Hash,
106    TraceRawVcs,
107    NonLocalValue,
108    OperationValue,
109    Encode,
110    Decode,
111)]
112#[serde(rename_all = "camelCase")]
113pub struct DraftModeOptions {
114    pub preview_mode_id: RcStr,
115    pub preview_mode_encryption_key: RcStr,
116    pub preview_mode_signing_key: RcStr,
117}
118
119#[derive(
120    Debug,
121    Default,
122    Serialize,
123    Deserialize,
124    Copy,
125    Clone,
126    TaskInput,
127    PartialEq,
128    Eq,
129    Hash,
130    TraceRawVcs,
131    NonLocalValue,
132    OperationValue,
133    Encode,
134    Decode,
135)]
136#[serde(rename_all = "camelCase")]
137pub struct WatchOptions {
138    /// Whether to watch the filesystem for file changes.
139    pub enable: bool,
140
141    /// Enable polling at a certain interval if the native file watching doesn't work (e.g.
142    /// docker).
143    pub poll_interval: Option<Duration>,
144}
145
146#[derive(
147    Debug,
148    Serialize,
149    Deserialize,
150    Clone,
151    TaskInput,
152    PartialEq,
153    Eq,
154    Hash,
155    TraceRawVcs,
156    NonLocalValue,
157    OperationValue,
158    Encode,
159    Decode,
160)]
161#[serde(rename_all = "camelCase")]
162pub struct ProjectOptions {
163    /// An absolute root path (Unix or Windows path) from which all files must be nested under.
164    /// Trying to access a file outside this root will fail, so think of this as a chroot.
165    /// E.g. `/home/user/projects/my-repo`.
166    pub root_path: RcStr,
167
168    /// A path which contains the app/pages directories, relative to [`Project::root_path`], always
169    /// Unix path. E.g. `apps/my-app`
170    pub project_path: RcStr,
171
172    /// The contents of next.config.js, serialized to JSON.
173    pub next_config: RcStr,
174
175    /// A map of environment variables to use when compiling code.
176    pub env: Vec<(RcStr, RcStr)>,
177
178    /// A map of environment variables which should get injected at compile
179    /// time.
180    pub define_env: DefineEnv,
181
182    /// Filesystem watcher options.
183    pub watch: WatchOptions,
184
185    /// The mode in which Next.js is running.
186    pub dev: bool,
187
188    /// The server actions encryption key.
189    pub encryption_key: RcStr,
190
191    /// The build id.
192    pub build_id: RcStr,
193
194    /// Options for draft mode.
195    pub preview_props: DraftModeOptions,
196
197    /// The browserslist query to use for targeting browsers.
198    pub browserslist_query: RcStr,
199
200    /// When the code is minified, this opts out of the default mangling of
201    /// local names for variables, functions etc., which can be useful for
202    /// debugging/profiling purposes.
203    pub no_mangling: bool,
204
205    /// Whether to write the route hashes manifest.
206    pub write_routes_hashes_manifest: bool,
207
208    /// The version of Node.js that is available/currently running.
209    pub current_node_js_version: RcStr,
210}
211
212pub struct PartialProjectOptions {
213    /// A root path from which all files must be nested under. Trying to access
214    /// a file outside this root will fail. Think of this as a chroot.
215    pub root_path: Option<RcStr>,
216
217    /// A path inside the root_path which contains the app/pages directories.
218    pub project_path: Option<RcStr>,
219
220    /// The contents of next.config.js, serialized to JSON.
221    pub next_config: Option<RcStr>,
222
223    /// A map of environment variables to use when compiling code.
224    pub env: Option<Vec<(RcStr, RcStr)>>,
225
226    /// A map of environment variables which should get injected at compile
227    /// time.
228    pub define_env: Option<DefineEnv>,
229
230    /// Filesystem watcher options.
231    pub watch: Option<WatchOptions>,
232
233    /// The mode in which Next.js is running.
234    pub dev: Option<bool>,
235
236    /// The server actions encryption key.
237    pub encryption_key: Option<RcStr>,
238
239    /// The build id.
240    pub build_id: Option<RcStr>,
241
242    /// Options for draft mode.
243    pub preview_props: Option<DraftModeOptions>,
244
245    /// The browserslist query to use for targeting browsers.
246    pub browserslist_query: Option<RcStr>,
247
248    /// When the code is minified, this opts out of the default mangling of
249    /// local names for variables, functions etc., which can be useful for
250    /// debugging/profiling purposes.
251    pub no_mangling: Option<bool>,
252
253    /// Whether to write the route hashes manifest.
254    pub write_routes_hashes_manifest: Option<bool>,
255}
256
257#[derive(
258    Debug,
259    Serialize,
260    Deserialize,
261    Clone,
262    TaskInput,
263    PartialEq,
264    Eq,
265    Hash,
266    TraceRawVcs,
267    NonLocalValue,
268    OperationValue,
269    Encode,
270    Decode,
271)]
272#[serde(rename_all = "camelCase")]
273pub struct DefineEnv {
274    pub client: Vec<(RcStr, Option<RcStr>)>,
275    pub edge: Vec<(RcStr, Option<RcStr>)>,
276    pub nodejs: Vec<(RcStr, Option<RcStr>)>,
277}
278
279#[derive(TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue, Encode, Decode)]
280pub struct Middleware {
281    pub endpoint: ResolvedVc<Box<dyn Endpoint>>,
282    pub is_proxy: bool,
283}
284
285#[derive(TraceRawVcs, PartialEq, Eq, ValueDebugFormat, NonLocalValue, Encode, Decode)]
286pub struct Instrumentation {
287    pub node_js: ResolvedVc<Box<dyn Endpoint>>,
288    pub edge: ResolvedVc<Box<dyn Endpoint>>,
289}
290
291#[turbo_tasks::value]
292pub struct ProjectContainer {
293    name: RcStr,
294    options_state: State<Option<ProjectOptions>>,
295    versioned_content_map: Option<ResolvedVc<VersionedContentMap>>,
296}
297
298#[turbo_tasks::value_impl]
299impl ProjectContainer {
300    #[turbo_tasks::function]
301    pub fn new(name: RcStr, dev: bool) -> Result<Vc<Self>> {
302        Ok(ProjectContainer {
303            name,
304            // we only need to enable versioning in dev mode, since build
305            // is assumed to be operating over a static snapshot
306            versioned_content_map: if dev {
307                Some(VersionedContentMap::new())
308            } else {
309                None
310            },
311            options_state: State::new(None),
312        }
313        .cell())
314    }
315}
316
317#[turbo_tasks::function(operation)]
318fn project_operation(project: ResolvedVc<ProjectContainer>) -> Vc<Project> {
319    project.project()
320}
321
322#[turbo_tasks::function(operation)]
323fn project_fs_operation(project: ResolvedVc<Project>) -> Vc<DiskFileSystem> {
324    project.project_fs()
325}
326
327#[turbo_tasks::function(operation)]
328fn output_fs_operation(project: ResolvedVc<Project>) -> Vc<DiskFileSystem> {
329    project.project_fs()
330}
331
332impl ProjectContainer {
333    #[tracing::instrument(level = "info", name = "initialize project", skip_all)]
334    pub async fn initialize(self: ResolvedVc<Self>, options: ProjectOptions) -> Result<()> {
335        let watch = options.watch;
336
337        self.await?.options_state.set(Some(options));
338
339        let project = self.project().to_resolved().await?;
340        let project_fs = project_fs_operation(project)
341            .read_strongly_consistent()
342            .await?;
343        if watch.enable {
344            project_fs
345                .start_watching_with_invalidation_reason(watch.poll_interval)
346                .await?;
347        } else {
348            project_fs.invalidate_with_reason(|path| invalidation::Initialize {
349                // this path is just used for display purposes
350                path: RcStr::from(path.to_string_lossy()),
351            });
352        }
353        let output_fs = output_fs_operation(project)
354            .read_strongly_consistent()
355            .await?;
356        output_fs.invalidate_with_reason(|path| invalidation::Initialize {
357            path: RcStr::from(path.to_string_lossy()),
358        });
359        Ok(())
360    }
361
362    #[tracing::instrument(level = "info", name = "update project options", skip_all)]
363    pub async fn update(self: Vc<Self>, options: PartialProjectOptions) -> Result<()> {
364        let PartialProjectOptions {
365            root_path,
366            project_path,
367            next_config,
368            env,
369            define_env,
370            watch,
371            dev,
372            encryption_key,
373            build_id,
374            preview_props,
375            browserslist_query,
376            no_mangling,
377            write_routes_hashes_manifest,
378        } = options;
379
380        let resolved_self = self.to_resolved().await?;
381        let this = resolved_self.await?;
382
383        let mut new_options = this
384            .options_state
385            .get()
386            .clone()
387            .context("ProjectContainer need to be initialized with initialize()")?;
388
389        if let Some(root_path) = root_path {
390            new_options.root_path = root_path;
391        }
392        if let Some(project_path) = project_path {
393            new_options.project_path = project_path;
394        }
395        if let Some(next_config) = next_config {
396            new_options.next_config = next_config;
397        }
398        if let Some(env) = env {
399            new_options.env = env;
400        }
401        if let Some(define_env) = define_env {
402            new_options.define_env = define_env;
403        }
404        if let Some(watch) = watch {
405            new_options.watch = watch;
406        }
407        if let Some(dev) = dev {
408            new_options.dev = dev;
409        }
410        if let Some(encryption_key) = encryption_key {
411            new_options.encryption_key = encryption_key;
412        }
413        if let Some(build_id) = build_id {
414            new_options.build_id = build_id;
415        }
416        if let Some(preview_props) = preview_props {
417            new_options.preview_props = preview_props;
418        }
419        if let Some(browserslist_query) = browserslist_query {
420            new_options.browserslist_query = browserslist_query;
421        }
422        if let Some(no_mangling) = no_mangling {
423            new_options.no_mangling = no_mangling;
424        }
425        if let Some(write_routes_hashes_manifest) = write_routes_hashes_manifest {
426            new_options.write_routes_hashes_manifest = write_routes_hashes_manifest;
427        }
428
429        // TODO: Handle mode switch, should prevent mode being switched.
430        let watch = new_options.watch;
431
432        let project = project_operation(resolved_self)
433            .resolve_strongly_consistent()
434            .await?;
435        let prev_project_fs = project_fs_operation(project)
436            .read_strongly_consistent()
437            .await?;
438        let prev_output_fs = output_fs_operation(project)
439            .read_strongly_consistent()
440            .await?;
441
442        this.options_state.set(Some(new_options));
443        let project = project_operation(resolved_self)
444            .resolve_strongly_consistent()
445            .await?;
446        let project_fs = project_fs_operation(project)
447            .read_strongly_consistent()
448            .await?;
449        let output_fs = output_fs_operation(project)
450            .read_strongly_consistent()
451            .await?;
452
453        if !ReadRef::ptr_eq(&prev_project_fs, &project_fs) {
454            if watch.enable {
455                // TODO stop watching: prev_project_fs.stop_watching()?;
456                project_fs
457                    .start_watching_with_invalidation_reason(watch.poll_interval)
458                    .await?;
459            } else {
460                project_fs.invalidate_with_reason(|path| invalidation::Initialize {
461                    // this path is just used for display purposes
462                    path: RcStr::from(path.to_string_lossy()),
463                });
464            }
465        }
466        if !ReadRef::ptr_eq(&prev_output_fs, &output_fs) {
467            prev_output_fs.invalidate_with_reason(|path| invalidation::Initialize {
468                path: RcStr::from(path.to_string_lossy()),
469            });
470        }
471
472        Ok(())
473    }
474}
475
476#[turbo_tasks::value_impl]
477impl ProjectContainer {
478    #[turbo_tasks::function]
479    pub async fn project(&self) -> Result<Vc<Project>> {
480        let env_map: Vc<EnvMap>;
481        let next_config;
482        let define_env;
483        let root_path;
484        let project_path;
485        let watch;
486        let dev;
487        let encryption_key;
488        let build_id;
489        let preview_props;
490        let browserslist_query;
491        let no_mangling;
492        let write_routes_hashes_manifest;
493        let current_node_js_version;
494        {
495            let options = self.options_state.get();
496            let options = options
497                .as_ref()
498                .context("ProjectContainer need to be initialized with initialize()")?;
499            env_map = Vc::cell(options.env.iter().cloned().collect());
500            define_env = ProjectDefineEnv {
501                client: ResolvedVc::cell(options.define_env.client.iter().cloned().collect()),
502                edge: ResolvedVc::cell(options.define_env.edge.iter().cloned().collect()),
503                nodejs: ResolvedVc::cell(options.define_env.nodejs.iter().cloned().collect()),
504            }
505            .cell();
506            next_config = NextConfig::from_string(Vc::cell(options.next_config.clone()));
507            root_path = options.root_path.clone();
508            project_path = options.project_path.clone();
509            watch = options.watch;
510            dev = options.dev;
511            encryption_key = options.encryption_key.clone();
512            build_id = options.build_id.clone();
513            preview_props = options.preview_props.clone();
514            browserslist_query = options.browserslist_query.clone();
515            no_mangling = options.no_mangling;
516            write_routes_hashes_manifest = options.write_routes_hashes_manifest;
517            current_node_js_version = options.current_node_js_version.clone();
518        }
519
520        let dist_dir = next_config.dist_dir().owned().await?;
521        let dist_dir_root = next_config.dist_dir_root().owned().await?;
522        Ok(Project {
523            root_path,
524            project_path,
525            watch,
526            next_config: next_config.to_resolved().await?,
527            dist_dir,
528            dist_dir_root,
529            env: ResolvedVc::upcast(env_map.to_resolved().await?),
530            define_env: define_env.to_resolved().await?,
531            browserslist_query,
532            mode: if dev {
533                NextMode::Development.resolved_cell()
534            } else {
535                NextMode::Build.resolved_cell()
536            },
537            versioned_content_map: self.versioned_content_map,
538            build_id,
539            encryption_key,
540            preview_props,
541            no_mangling,
542            write_routes_hashes_manifest,
543            current_node_js_version,
544        }
545        .cell())
546    }
547
548    /// See [Project::entrypoints].
549    #[turbo_tasks::function]
550    pub fn entrypoints(self: Vc<Self>) -> Vc<Entrypoints> {
551        self.project().entrypoints()
552    }
553
554    /// See [Project::hmr_identifiers].
555    #[turbo_tasks::function]
556    pub fn hmr_identifiers(self: Vc<Self>) -> Vc<Vec<RcStr>> {
557        self.project().hmr_identifiers()
558    }
559
560    /// Gets a source map for a particular `file_path`. If `dev` mode is disabled, this will always
561    /// return [`FileContent::NotFound`].
562    #[turbo_tasks::function]
563    pub fn get_source_map(
564        &self,
565        file_path: FileSystemPath,
566        section: Option<RcStr>,
567    ) -> Vc<FileContent> {
568        if let Some(map) = self.versioned_content_map {
569            map.get_source_map(file_path, section)
570        } else {
571            FileContent::NotFound.cell()
572        }
573    }
574}
575
576#[derive(Clone)]
577#[turbo_tasks::value]
578pub struct Project {
579    /// An absolute root path (Windows or Unix path) from which all files must be nested under.
580    /// Trying to access a file outside this root will fail, so think of this as a chroot.
581    /// E.g. `/home/user/projects/my-repo`.
582    root_path: RcStr,
583
584    /// A path which contains the app/pages directories, relative to [`Project::root_path`], always
585    /// a Unix path.
586    /// E.g. `apps/my-app`
587    project_path: RcStr,
588
589    /// A path where to emit the build outputs, relative to [`Project::project_path`], always a
590    /// Unix path. Corresponds to next.config.js's `distDir`.
591    /// E.g. `.next`
592    dist_dir: RcStr,
593
594    /// The root directory of the distDir. Generally the same as `distDir` but when
595    /// `isolatedDevBuild` is true it is the parent directory of `distDir`.  This is used to
596    /// ensure that the bundler doesn't traverse into the output directory.
597    dist_dir_root: RcStr,
598
599    /// Filesystem watcher options.
600    watch: WatchOptions,
601
602    /// Next config.
603    next_config: ResolvedVc<NextConfig>,
604
605    /// A map of environment variables to use when compiling code.
606    env: ResolvedVc<Box<dyn ProcessEnv>>,
607
608    /// A map of environment variables which should get injected at compile
609    /// time.
610    define_env: ResolvedVc<ProjectDefineEnv>,
611
612    /// The browserslist query to use for targeting browsers.
613    browserslist_query: RcStr,
614
615    mode: ResolvedVc<NextMode>,
616
617    versioned_content_map: Option<ResolvedVc<VersionedContentMap>>,
618
619    build_id: RcStr,
620
621    encryption_key: RcStr,
622
623    preview_props: DraftModeOptions,
624
625    /// When the code is minified, this opts out of the default mangling of
626    /// local names for variables, functions etc., which can be useful for
627    /// debugging/profiling purposes.
628    no_mangling: bool,
629
630    /// Whether to write the route hashes manifest.
631    write_routes_hashes_manifest: bool,
632
633    current_node_js_version: RcStr,
634}
635
636#[turbo_tasks::value]
637pub struct ProjectDefineEnv {
638    client: ResolvedVc<OptionEnvMap>,
639    edge: ResolvedVc<OptionEnvMap>,
640    nodejs: ResolvedVc<OptionEnvMap>,
641}
642
643#[turbo_tasks::value_impl]
644impl ProjectDefineEnv {
645    #[turbo_tasks::function]
646    pub fn client(&self) -> Vc<OptionEnvMap> {
647        *self.client
648    }
649
650    #[turbo_tasks::function]
651    pub fn edge(&self) -> Vc<OptionEnvMap> {
652        *self.edge
653    }
654
655    #[turbo_tasks::function]
656    pub fn nodejs(&self) -> Vc<OptionEnvMap> {
657        *self.nodejs
658    }
659}
660
661#[turbo_tasks::value(shared)]
662struct ConflictIssue {
663    path: FileSystemPath,
664    title: ResolvedVc<StyledString>,
665    description: ResolvedVc<StyledString>,
666    severity: IssueSeverity,
667}
668
669#[turbo_tasks::value_impl]
670impl Issue for ConflictIssue {
671    #[turbo_tasks::function]
672    fn stage(&self) -> Vc<IssueStage> {
673        IssueStage::AppStructure.cell()
674    }
675
676    fn severity(&self) -> IssueSeverity {
677        self.severity
678    }
679
680    #[turbo_tasks::function]
681    fn file_path(&self) -> Vc<FileSystemPath> {
682        self.path.clone().cell()
683    }
684
685    #[turbo_tasks::function]
686    fn title(&self) -> Vc<StyledString> {
687        *self.title
688    }
689
690    #[turbo_tasks::function]
691    fn description(&self) -> Vc<OptionStyledString> {
692        Vc::cell(Some(self.description))
693    }
694}
695
696#[turbo_tasks::value_impl]
697impl Project {
698    #[turbo_tasks::function]
699    pub async fn app_project(self: Vc<Self>) -> Result<Vc<OptionAppProject>> {
700        let app_dir = find_app_dir(self.project_path().owned().await?).await?;
701
702        Ok(match &*app_dir {
703            Some(app_dir) => Vc::cell(Some(
704                AppProject::new(self, app_dir.clone()).to_resolved().await?,
705            )),
706            None => Vc::cell(None),
707        })
708    }
709
710    #[turbo_tasks::function]
711    pub fn pages_project(self: Vc<Self>) -> Vc<PagesProject> {
712        PagesProject::new(self)
713    }
714
715    #[turbo_tasks::function]
716    pub fn project_fs(&self) -> Result<Vc<DiskFileSystem>> {
717        let denied_path = match join_path(&self.project_path, &self.dist_dir_root) {
718            Some(dist_dir_root) => dist_dir_root.into(),
719            None => {
720                bail!(
721                    "Invalid distDirRoot: {:?}. distDirRoot should not navigate out of the \
722                     projectPath.",
723                    self.dist_dir_root
724                );
725            }
726        };
727
728        Ok(DiskFileSystem::new_with_denied_path(
729            rcstr!(PROJECT_FILESYSTEM_NAME),
730            self.root_path.clone(),
731            denied_path,
732        ))
733    }
734
735    #[turbo_tasks::function]
736    pub fn client_fs(self: Vc<Self>) -> Vc<Box<dyn FileSystem>> {
737        let virtual_fs = VirtualFileSystem::new_with_name(rcstr!("client-fs"));
738        Vc::upcast(virtual_fs)
739    }
740
741    #[turbo_tasks::function]
742    pub fn output_fs(&self) -> Vc<DiskFileSystem> {
743        DiskFileSystem::new(rcstr!("output"), self.root_path.clone())
744    }
745
746    #[turbo_tasks::function]
747    pub fn dist_dir_absolute(&self) -> Result<Vc<RcStr>> {
748        Ok(Vc::cell(
749            format!(
750                "{}{}{}",
751                self.root_path,
752                std::path::MAIN_SEPARATOR,
753                unix_to_sys(
754                    &join_path(&self.project_path, &self.dist_dir)
755                        .context("expected project_path to be inside of root_path")?
756                )
757            )
758            .into(),
759        ))
760    }
761
762    #[turbo_tasks::function]
763    pub async fn node_root(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
764        let this = self.await?;
765        Ok(self
766            .output_fs()
767            .root()
768            .await?
769            .join(&this.project_path)?
770            .join(&this.dist_dir)?
771            .cell())
772    }
773
774    #[turbo_tasks::function]
775    pub fn client_root(self: Vc<Self>) -> Vc<FileSystemPath> {
776        self.client_fs().root()
777    }
778
779    #[turbo_tasks::function]
780    pub fn project_root_path(self: Vc<Self>) -> Vc<FileSystemPath> {
781        self.project_fs().root()
782    }
783
784    #[turbo_tasks::function]
785    pub async fn client_relative_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
786        let next_config = self.next_config();
787        Ok(self
788            .client_root()
789            .await?
790            .join(&format!(
791                "{}/_next",
792                next_config
793                    .base_path()
794                    .await?
795                    .as_deref()
796                    .unwrap_or_default(),
797            ))?
798            .cell())
799    }
800
801    /// Returns the relative path from the node root to the output root.
802    /// E.g. from `[project]/test/e2e/app-dir/non-root-project-monorepo/apps/web/app/
803    /// import-meta-url-ssr/page.tsx` to `[project]/`.
804    #[turbo_tasks::function]
805    pub async fn node_root_to_root_path(self: Vc<Self>) -> Result<Vc<RcStr>> {
806        Ok(Vc::cell(
807            self.node_root()
808                .await?
809                .get_relative_path_to(&*self.output_fs().root().await?)
810                .context("Expected node root to be inside of output fs")?,
811        ))
812    }
813
814    #[turbo_tasks::function]
815    pub async fn project_path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
816        let this = self.await?;
817        let root = self.project_root_path().await?;
818        Ok(root.join(&this.project_path)?.cell())
819    }
820
821    #[turbo_tasks::function]
822    pub(super) fn env(&self) -> Vc<Box<dyn ProcessEnv>> {
823        *self.env
824    }
825
826    #[turbo_tasks::function]
827    pub async fn ci_has_next_support(&self) -> Result<Vc<bool>> {
828        Ok(Vc::cell(
829            self.env.read(rcstr!("NOW_BUILDER")).await?.is_some(),
830        ))
831    }
832
833    #[turbo_tasks::function]
834    pub(super) fn current_node_js_version(&self) -> Vc<NodeJsVersion> {
835        NodeJsVersion::Static(ResolvedVc::cell(self.current_node_js_version.clone())).cell()
836    }
837
838    #[turbo_tasks::function]
839    pub fn next_config(&self) -> Vc<NextConfig> {
840        *self.next_config
841    }
842
843    #[turbo_tasks::function]
844    pub(super) fn next_mode(&self) -> Vc<NextMode> {
845        *self.mode
846    }
847
848    #[turbo_tasks::function]
849    pub(super) fn is_watch_enabled(&self) -> Result<Vc<bool>> {
850        Ok(Vc::cell(self.watch.enable))
851    }
852
853    #[turbo_tasks::function]
854    pub(super) fn should_write_routes_hashes_manifest(&self) -> Result<Vc<bool>> {
855        Ok(Vc::cell(self.write_routes_hashes_manifest))
856    }
857
858    #[turbo_tasks::function]
859    pub(super) async fn per_page_module_graph(&self) -> Result<Vc<bool>> {
860        Ok(Vc::cell(*self.mode.await? == NextMode::Development))
861    }
862
863    #[turbo_tasks::function]
864    pub(super) fn encryption_key(&self) -> Vc<RcStr> {
865        Vc::cell(self.encryption_key.clone())
866    }
867
868    #[turbo_tasks::function]
869    pub(super) fn no_mangling(&self) -> Vc<bool> {
870        Vc::cell(self.no_mangling)
871    }
872
873    #[turbo_tasks::function]
874    pub(super) async fn should_create_webpack_stats(&self) -> Result<Vc<bool>> {
875        Ok(Vc::cell(
876            self.env.read(rcstr!("TURBOPACK_STATS")).await?.is_some(),
877        ))
878    }
879
880    #[turbo_tasks::function]
881    pub(super) async fn execution_context(self: Vc<Self>) -> Result<Vc<ExecutionContext>> {
882        let node_root = self.node_root().owned().await?;
883        let next_mode = self.next_mode().await?;
884
885        let node_execution_chunking_context = Vc::upcast(
886            NodeJsChunkingContext::builder(
887                self.project_root_path().owned().await?,
888                node_root.join("build")?,
889                self.node_root_to_root_path().owned().await?,
890                node_root.join("build")?,
891                node_root.join("build/chunks")?,
892                node_root.join("build/assets")?,
893                node_build_environment().to_resolved().await?,
894                next_mode.runtime_type(),
895            )
896            .source_maps(*self.next_config().server_source_maps().await?)
897            .build(),
898        );
899
900        Ok(ExecutionContext::new(
901            self.project_path().owned().await?,
902            node_execution_chunking_context,
903            self.env(),
904        ))
905    }
906
907    #[turbo_tasks::function]
908    pub(super) fn client_compile_time_info(&self) -> Vc<CompileTimeInfo> {
909        get_client_compile_time_info(self.browserslist_query.clone(), self.define_env.client())
910    }
911
912    #[turbo_tasks::function]
913    pub async fn get_all_endpoint_groups(
914        self: Vc<Self>,
915        app_dir_only: bool,
916    ) -> Result<Vc<EndpointGroups>> {
917        let mut endpoint_groups = Vec::new();
918
919        let entrypoints = self.entrypoints().await?;
920        let mut add_pages_entries = false;
921
922        if let Some(middleware) = &entrypoints.middleware {
923            endpoint_groups.push((
924                EndpointGroupKey::Middleware,
925                EndpointGroup::from(middleware.endpoint),
926            ));
927        }
928
929        if let Some(instrumentation) = &entrypoints.instrumentation {
930            endpoint_groups.push((
931                EndpointGroupKey::Instrumentation,
932                EndpointGroup::from(instrumentation.node_js),
933            ));
934            endpoint_groups.push((
935                EndpointGroupKey::InstrumentationEdge,
936                EndpointGroup::from(instrumentation.edge),
937            ));
938        }
939
940        for (key, route) in entrypoints.routes.iter() {
941            match route {
942                Route::Page {
943                    html_endpoint,
944                    data_endpoint,
945                } => {
946                    if !app_dir_only {
947                        endpoint_groups.push((
948                            EndpointGroupKey::Route(key.clone()),
949                            EndpointGroup {
950                                primary: vec![EndpointGroupEntry {
951                                    endpoint: *html_endpoint,
952                                    sub_name: None,
953                                }],
954                                // This only exists in development mode for HMR
955                                additional: data_endpoint
956                                    .iter()
957                                    .map(|endpoint| EndpointGroupEntry {
958                                        endpoint: *endpoint,
959                                        sub_name: None,
960                                    })
961                                    .collect(),
962                            },
963                        ));
964                        add_pages_entries = true;
965                    }
966                }
967                Route::PageApi { endpoint } => {
968                    if !app_dir_only {
969                        endpoint_groups.push((
970                            EndpointGroupKey::Route(key.clone()),
971                            EndpointGroup::from(*endpoint),
972                        ));
973                        add_pages_entries = true;
974                    }
975                }
976                Route::AppPage(page_routes) => {
977                    endpoint_groups.push((
978                        EndpointGroupKey::Route(key.clone()),
979                        EndpointGroup {
980                            primary: page_routes
981                                .iter()
982                                .map(|r| EndpointGroupEntry {
983                                    endpoint: r.html_endpoint,
984                                    sub_name: Some(r.original_name.clone()),
985                                })
986                                .collect(),
987                            additional: Vec::new(),
988                        },
989                    ));
990                }
991                Route::AppRoute {
992                    original_name: _,
993                    endpoint,
994                } => {
995                    endpoint_groups.push((
996                        EndpointGroupKey::Route(key.clone()),
997                        EndpointGroup::from(*endpoint),
998                    ));
999                }
1000                Route::Conflict => {
1001                    tracing::info!("WARN: conflict");
1002                }
1003            }
1004        }
1005
1006        if add_pages_entries {
1007            endpoint_groups.push((
1008                EndpointGroupKey::PagesError,
1009                EndpointGroup::from(entrypoints.pages_error_endpoint),
1010            ));
1011            endpoint_groups.push((
1012                EndpointGroupKey::PagesApp,
1013                EndpointGroup::from(entrypoints.pages_app_endpoint),
1014            ));
1015            endpoint_groups.push((
1016                EndpointGroupKey::PagesDocument,
1017                EndpointGroup::from(entrypoints.pages_document_endpoint),
1018            ));
1019        }
1020
1021        Ok(Vc::cell(endpoint_groups))
1022    }
1023
1024    #[turbo_tasks::function]
1025    pub async fn get_all_endpoints(self: Vc<Self>, app_dir_only: bool) -> Result<Vc<Endpoints>> {
1026        let mut endpoints = Vec::new();
1027        for (_key, group) in self.get_all_endpoint_groups(app_dir_only).await?.iter() {
1028            for entry in group.primary.iter() {
1029                endpoints.push(entry.endpoint);
1030            }
1031            for entry in group.additional.iter() {
1032                endpoints.push(entry.endpoint);
1033            }
1034        }
1035
1036        Ok(Vc::cell(endpoints))
1037    }
1038
1039    #[turbo_tasks::function]
1040    pub async fn get_all_entries(self: Vc<Self>) -> Result<Vc<GraphEntries>> {
1041        let mut modules = self
1042            .get_all_endpoints(false)
1043            .await?
1044            .iter()
1045            .map(async |endpoint| Ok(endpoint.entries().owned().await?))
1046            .try_flat_join()
1047            .await?;
1048        modules.extend(self.client_main_modules().await?.iter().cloned());
1049        Ok(Vc::cell(modules))
1050    }
1051
1052    #[turbo_tasks::function]
1053    pub async fn get_all_additional_entries(
1054        self: Vc<Self>,
1055        graphs: Vc<ModuleGraph>,
1056    ) -> Result<Vc<GraphEntries>> {
1057        let modules = self
1058            .get_all_endpoints(false)
1059            .await?
1060            .iter()
1061            .map(async |endpoint| Ok(endpoint.additional_entries(graphs).owned().await?))
1062            .try_flat_join()
1063            .await?;
1064        Ok(Vc::cell(modules))
1065    }
1066
1067    #[turbo_tasks::function]
1068    pub async fn module_graph(
1069        self: Vc<Self>,
1070        entry: ResolvedVc<Box<dyn Module>>,
1071    ) -> Result<Vc<ModuleGraph>> {
1072        Ok(if *self.per_page_module_graph().await? {
1073            let is_production = self.next_mode().await?.is_production();
1074            ModuleGraph::from_entry_module(*entry, is_production, is_production)
1075        } else {
1076            *self.whole_app_module_graphs().await?.full
1077        })
1078    }
1079
1080    #[turbo_tasks::function]
1081    pub async fn module_graph_for_modules(
1082        self: Vc<Self>,
1083        evaluatable_assets: Vc<EvaluatableAssets>,
1084    ) -> Result<Vc<ModuleGraph>> {
1085        Ok(if *self.per_page_module_graph().await? {
1086            let is_production = self.next_mode().await?.is_production();
1087            let entries = evaluatable_assets
1088                .await?
1089                .iter()
1090                .copied()
1091                .map(ResolvedVc::upcast)
1092                .collect();
1093            ModuleGraph::from_modules(
1094                Vc::cell(vec![ChunkGroupEntry::Entry(entries)]),
1095                is_production,
1096                is_production,
1097            )
1098        } else {
1099            *self.whole_app_module_graphs().await?.full
1100        })
1101    }
1102
1103    #[turbo_tasks::function]
1104    pub async fn whole_app_module_graphs(
1105        self: ResolvedVc<Self>,
1106    ) -> Result<Vc<BaseAndFullModuleGraph>> {
1107        async move {
1108            let module_graphs_op = whole_app_module_graph_operation(self);
1109            let module_graphs_vc = if self.next_mode().await?.is_production() {
1110                module_graphs_op.connect()
1111            } else {
1112                // In development mode, we need to to take and drop the issues, otherwise every
1113                // route will report all issues.
1114                let vc = module_graphs_op.resolve_strongly_consistent().await?;
1115                module_graphs_op.drop_issues();
1116                *vc
1117            };
1118
1119            // At this point all modules have been computed and we can get rid of the node.js
1120            // process pools
1121            if *self.is_watch_enabled().await? {
1122                turbopack_node::evaluate::scale_down();
1123            } else {
1124                turbopack_node::evaluate::scale_zero();
1125            }
1126
1127            Ok(module_graphs_vc)
1128        }
1129        .instrument(tracing::info_span!("module graph for app"))
1130        .await
1131    }
1132
1133    #[turbo_tasks::function]
1134    pub(super) async fn server_compile_time_info(self: Vc<Self>) -> Result<Vc<CompileTimeInfo>> {
1135        let this = self.await?;
1136        Ok(get_server_compile_time_info(
1137            // `/ROOT` corresponds to `[project]/`, so we need exactly the `path` part.
1138            self.project_path(),
1139            this.define_env.nodejs(),
1140            self.current_node_js_version(),
1141        ))
1142    }
1143
1144    #[turbo_tasks::function]
1145    pub(super) async fn edge_compile_time_info(self: Vc<Self>) -> Result<Vc<CompileTimeInfo>> {
1146        let this = self.await?;
1147        Ok(get_edge_compile_time_info(
1148            self.project_path().owned().await?,
1149            this.define_env.edge(),
1150            self.current_node_js_version(),
1151        ))
1152    }
1153
1154    #[turbo_tasks::function]
1155    pub(super) fn edge_env(&self) -> Vc<EnvMap> {
1156        let edge_env = fxindexmap! {
1157            rcstr!("__NEXT_BUILD_ID") => self.build_id.clone(),
1158            rcstr!("NEXT_SERVER_ACTIONS_ENCRYPTION_KEY") => self.encryption_key.clone(),
1159            rcstr!("__NEXT_PREVIEW_MODE_ID") => self.preview_props.preview_mode_id.clone(),
1160            rcstr!("__NEXT_PREVIEW_MODE_ENCRYPTION_KEY") => self.preview_props.preview_mode_encryption_key.clone(),
1161            rcstr!("__NEXT_PREVIEW_MODE_SIGNING_KEY") => self.preview_props.preview_mode_signing_key.clone(),
1162        };
1163        Vc::cell(edge_env)
1164    }
1165
1166    #[turbo_tasks::function]
1167    pub(super) async fn client_chunking_context(
1168        self: Vc<Self>,
1169    ) -> Result<Vc<Box<dyn ChunkingContext>>> {
1170        Ok(get_client_chunking_context(ClientChunkingContextOptions {
1171            mode: self.next_mode(),
1172            root_path: self.project_root_path().owned().await?,
1173            client_root: self.client_relative_path().owned().await?,
1174            client_root_to_root_path: rcstr!("/ROOT"),
1175            asset_prefix: self.next_config().computed_asset_prefix(),
1176            environment: self.client_compile_time_info().environment(),
1177            module_id_strategy: self.module_ids(),
1178            export_usage: self.export_usage(),
1179            unused_references: self.unused_references(),
1180            minify: self.next_config().turbo_minify(self.next_mode()),
1181            source_maps: self.next_config().client_source_maps(self.next_mode()),
1182            no_mangling: self.no_mangling(),
1183            scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()),
1184            nested_async_chunking: self
1185                .next_config()
1186                .turbo_nested_async_chunking(self.next_mode(), true),
1187            debug_ids: self.next_config().turbopack_debug_ids(),
1188            should_use_absolute_url_references: self.next_config().inline_css(),
1189        }))
1190    }
1191
1192    #[turbo_tasks::function]
1193    pub(super) async fn server_chunking_context(
1194        self: Vc<Self>,
1195        client_assets: bool,
1196    ) -> Result<Vc<NodeJsChunkingContext>> {
1197        let options = ServerChunkingContextOptions {
1198            mode: self.next_mode(),
1199            root_path: self.project_root_path().owned().await?,
1200            node_root: self.node_root().owned().await?,
1201            node_root_to_root_path: self.node_root_to_root_path().owned().await?,
1202            environment: self.server_compile_time_info().environment(),
1203            module_id_strategy: self.module_ids(),
1204            export_usage: self.export_usage(),
1205            unused_references: self.unused_references(),
1206            minify: self.next_config().turbo_minify(self.next_mode()),
1207            source_maps: self.next_config().server_source_maps(),
1208            no_mangling: self.no_mangling(),
1209            scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()),
1210            nested_async_chunking: self
1211                .next_config()
1212                .turbo_nested_async_chunking(self.next_mode(), false),
1213            debug_ids: self.next_config().turbopack_debug_ids(),
1214            client_root: self.client_relative_path().owned().await?,
1215            asset_prefix: self.next_config().computed_asset_prefix().owned().await?,
1216        };
1217        Ok(if client_assets {
1218            get_server_chunking_context_with_client_assets(options)
1219        } else {
1220            get_server_chunking_context(options)
1221        })
1222    }
1223
1224    #[turbo_tasks::function]
1225    pub(super) async fn edge_chunking_context(
1226        self: Vc<Self>,
1227        client_assets: bool,
1228    ) -> Result<Vc<Box<dyn ChunkingContext>>> {
1229        let options = EdgeChunkingContextOptions {
1230            mode: self.next_mode(),
1231            root_path: self.project_root_path().owned().await?,
1232            node_root: self.node_root().owned().await?,
1233            output_root_to_root_path: self.node_root_to_root_path(),
1234            environment: self.edge_compile_time_info().environment(),
1235            module_id_strategy: self.module_ids(),
1236            export_usage: self.export_usage(),
1237            unused_references: self.unused_references(),
1238            turbo_minify: self.next_config().turbo_minify(self.next_mode()),
1239            turbo_source_maps: self.next_config().server_source_maps(),
1240            no_mangling: self.no_mangling(),
1241            scope_hoisting: self.next_config().turbo_scope_hoisting(self.next_mode()),
1242            nested_async_chunking: self
1243                .next_config()
1244                .turbo_nested_async_chunking(self.next_mode(), false),
1245            client_root: self.client_relative_path().owned().await?,
1246            asset_prefix: self.next_config().computed_asset_prefix().owned().await?,
1247        };
1248        Ok(if client_assets {
1249            get_edge_chunking_context_with_client_assets(options)
1250        } else {
1251            get_edge_chunking_context(options)
1252        })
1253    }
1254
1255    #[turbo_tasks::function]
1256    pub(super) fn runtime_chunking_context(
1257        self: Vc<Self>,
1258        client_assets: bool,
1259        runtime: NextRuntime,
1260    ) -> Vc<Box<dyn ChunkingContext>> {
1261        match runtime {
1262            NextRuntime::Edge => self.edge_chunking_context(client_assets),
1263            NextRuntime::NodeJs => Vc::upcast(self.server_chunking_context(client_assets)),
1264        }
1265    }
1266
1267    /// Emit a telemetry event corresponding to [webpack configuration telemetry](https://github.com/vercel/next.js/blob/9da305fe320b89ee2f8c3cfb7ecbf48856368913/packages/next/src/build/webpack-config.ts#L2516)
1268    /// to detect which feature is enabled.
1269    #[turbo_tasks::function]
1270    async fn collect_project_feature_telemetry(self: Vc<Self>) -> Result<Vc<()>> {
1271        let emit_event = |feature_name: &str, enabled: bool| {
1272            NextFeatureTelemetry::new(feature_name.into(), enabled)
1273                .resolved_cell()
1274                .emit();
1275        };
1276
1277        // First, emit an event for the binary target triple.
1278        // This is different to webpack-config; when this is being called,
1279        // it is always using SWC so we don't check swc here.
1280        emit_event(env!("VERGEN_CARGO_TARGET_TRIPLE"), true);
1281
1282        // Go over config and report enabled features.
1283        // [TODO]: useSwcLoader is not being reported as it is not directly corresponds (it checks babel config existence)
1284        // need to confirm what we'll do with turbopack.
1285        let config = self.next_config();
1286
1287        emit_event(
1288            "skipProxyUrlNormalize",
1289            *config.skip_proxy_url_normalize().await?,
1290        );
1291
1292        emit_event(
1293            "skipTrailingSlashRedirect",
1294            *config.skip_trailing_slash_redirect().await?,
1295        );
1296        emit_event(
1297            "persistentCaching",
1298            *config.persistent_caching_enabled().await?,
1299        );
1300
1301        emit_event(
1302            "modularizeImports",
1303            !config.modularize_imports().await?.is_empty(),
1304        );
1305        emit_event(
1306            "transpilePackages",
1307            !config.transpile_packages().await?.is_empty(),
1308        );
1309        emit_event("turbotrace", false);
1310
1311        // compiler options
1312        let compiler_options = config.compiler().await?;
1313        let swc_relay_enabled = compiler_options.relay.is_some();
1314        let styled_components_enabled = compiler_options
1315            .styled_components
1316            .as_ref()
1317            .map(|sc| sc.is_enabled())
1318            .unwrap_or_default();
1319        let react_remove_properties_enabled = compiler_options
1320            .react_remove_properties
1321            .as_ref()
1322            .map(|rc| rc.is_enabled())
1323            .unwrap_or_default();
1324        let remove_console_enabled = compiler_options
1325            .remove_console
1326            .as_ref()
1327            .map(|rc| rc.is_enabled())
1328            .unwrap_or_default();
1329        let emotion_enabled = compiler_options
1330            .emotion
1331            .as_ref()
1332            .map(|e| e.is_enabled())
1333            .unwrap_or_default();
1334
1335        emit_event("swcRelay", swc_relay_enabled);
1336        emit_event("swcStyledComponents", styled_components_enabled);
1337        emit_event("swcReactRemoveProperties", react_remove_properties_enabled);
1338        emit_event("swcRemoveConsole", remove_console_enabled);
1339        emit_event("swcEmotion", emotion_enabled);
1340
1341        Ok(Default::default())
1342    }
1343
1344    /// Scans the app/pages directories for entry points files (matching the
1345    /// provided page_extensions).
1346    #[turbo_tasks::function]
1347    pub async fn entrypoints(self: Vc<Self>) -> Result<Vc<Entrypoints>> {
1348        self.collect_project_feature_telemetry().await?;
1349
1350        let mut routes = FxIndexMap::default();
1351        let app_project = self.app_project();
1352        let pages_project = self.pages_project();
1353
1354        if let Some(app_project) = &*app_project.await? {
1355            let app_routes = app_project.routes();
1356            routes.extend(
1357                app_routes
1358                    .await?
1359                    .iter()
1360                    .map(|(k, v)| (k.clone(), v.clone())),
1361            );
1362        }
1363
1364        for (pathname, page_route) in pages_project.routes().await?.iter() {
1365            match routes.entry(pathname.clone()) {
1366                Entry::Occupied(mut entry) => {
1367                    ConflictIssue {
1368                        path: self.project_path().owned().await?,
1369                        title: StyledString::Text(
1370                            format!("App Router and Pages Router both match path: {pathname}")
1371                                .into(),
1372                        )
1373                        .resolved_cell(),
1374                        description: StyledString::Text(
1375                            "Next.js does not support having both App Router and Pages Router \
1376                             routes matching the same path. Please remove one of the conflicting \
1377                             routes."
1378                                .into(),
1379                        )
1380                        .resolved_cell(),
1381                        severity: IssueSeverity::Error,
1382                    }
1383                    .resolved_cell()
1384                    .emit();
1385                    *entry.get_mut() = Route::Conflict;
1386                }
1387                Entry::Vacant(entry) => {
1388                    entry.insert(page_route.clone());
1389                }
1390            }
1391        }
1392
1393        let pages_document_endpoint = self
1394            .pages_project()
1395            .document_endpoint()
1396            .to_resolved()
1397            .await?;
1398        let pages_app_endpoint = self.pages_project().app_endpoint().to_resolved().await?;
1399        let pages_error_endpoint = self.pages_project().error_endpoint().to_resolved().await?;
1400
1401        let middleware = self.find_middleware();
1402        let middleware = if let FindContextFileResult::Found(fs_path, _) = &*middleware.await? {
1403            let is_proxy = fs_path.file_stem() == Some("proxy");
1404            Some(Middleware {
1405                endpoint: self.middleware_endpoint().to_resolved().await?,
1406                is_proxy,
1407            })
1408        } else {
1409            None
1410        };
1411
1412        let instrumentation = self.find_instrumentation();
1413        let instrumentation = if let FindContextFileResult::Found(..) = *instrumentation.await? {
1414            Some(Instrumentation {
1415                node_js: self.instrumentation_endpoint(false).to_resolved().await?,
1416                edge: self.instrumentation_endpoint(true).to_resolved().await?,
1417            })
1418        } else {
1419            None
1420        };
1421
1422        Ok(Entrypoints {
1423            routes,
1424            middleware,
1425            instrumentation,
1426            pages_document_endpoint,
1427            pages_app_endpoint,
1428            pages_error_endpoint,
1429        }
1430        .cell())
1431    }
1432
1433    #[turbo_tasks::function]
1434    async fn edge_middleware_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
1435        let mut transitions = vec![];
1436
1437        let app_dir = find_app_dir(self.project_path().owned().await?)
1438            .owned()
1439            .await?;
1440        let app_project = *self.app_project().await?;
1441
1442        let ecmascript_client_reference_transition_name =
1443            app_project.map(|_| AppProject::client_transition_name());
1444
1445        if let Some(app_project) = app_project {
1446            transitions.push((
1447                AppProject::client_transition_name(),
1448                app_project
1449                    .edge_ecmascript_client_reference_transition()
1450                    .to_resolved()
1451                    .await?,
1452            ));
1453        }
1454
1455        Ok(Vc::upcast(ModuleAssetContext::new(
1456            TransitionOptions {
1457                named_transitions: transitions.clone().into_iter().collect(),
1458                ..Default::default()
1459            }
1460            .cell(),
1461            self.edge_compile_time_info(),
1462            get_server_module_options_context(
1463                self.project_path().owned().await?,
1464                self.execution_context(),
1465                ServerContextType::Middleware {
1466                    app_dir: app_dir.clone(),
1467                    ecmascript_client_reference_transition_name:
1468                        ecmascript_client_reference_transition_name.clone(),
1469                },
1470                self.next_mode(),
1471                self.next_config(),
1472                NextRuntime::Edge,
1473                self.encryption_key(),
1474                self.edge_compile_time_info().environment(),
1475                self.client_compile_time_info().environment(),
1476            ),
1477            get_edge_resolve_options_context(
1478                self.project_path().owned().await?,
1479                ServerContextType::Middleware {
1480                    app_dir: app_dir.clone(),
1481                    ecmascript_client_reference_transition_name:
1482                        ecmascript_client_reference_transition_name.clone(),
1483                },
1484                self.next_mode(),
1485                self.next_config(),
1486                self.execution_context(),
1487                None, // root params can't be used in middleware
1488            ),
1489            Layer::new_with_user_friendly_name(
1490                rcstr!("middleware-edge"),
1491                rcstr!("Edge Middleware"),
1492            ),
1493        )))
1494    }
1495
1496    #[turbo_tasks::function]
1497    async fn node_middleware_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
1498        let mut transitions = vec![];
1499
1500        let app_dir = find_app_dir(self.project_path().owned().await?)
1501            .owned()
1502            .await?;
1503        let app_project = *self.app_project().await?;
1504
1505        let ecmascript_client_reference_transition_name =
1506            app_project.map(|_| AppProject::client_transition_name());
1507
1508        if let Some(app_project) = app_project {
1509            transitions.push((
1510                AppProject::client_transition_name(),
1511                app_project
1512                    .edge_ecmascript_client_reference_transition()
1513                    .to_resolved()
1514                    .await?,
1515            ));
1516        }
1517
1518        Ok(Vc::upcast(ModuleAssetContext::new(
1519            TransitionOptions {
1520                named_transitions: transitions.clone().into_iter().collect(),
1521                ..Default::default()
1522            }
1523            .cell(),
1524            self.server_compile_time_info(),
1525            get_server_module_options_context(
1526                self.project_path().owned().await?,
1527                self.execution_context(),
1528                ServerContextType::Middleware {
1529                    app_dir: app_dir.clone(),
1530                    ecmascript_client_reference_transition_name:
1531                        ecmascript_client_reference_transition_name.clone(),
1532                },
1533                self.next_mode(),
1534                self.next_config(),
1535                NextRuntime::NodeJs,
1536                self.encryption_key(),
1537                self.server_compile_time_info().environment(),
1538                self.client_compile_time_info().environment(),
1539            ),
1540            get_server_resolve_options_context(
1541                self.project_path().owned().await?,
1542                ServerContextType::Middleware {
1543                    app_dir: app_dir.clone(),
1544                    ecmascript_client_reference_transition_name,
1545                },
1546                self.next_mode(),
1547                self.next_config(),
1548                self.execution_context(),
1549                None, // root params can't be used in middleware
1550            ),
1551            Layer::new_with_user_friendly_name(rcstr!("middleware"), rcstr!("Middleware")),
1552        )))
1553    }
1554
1555    #[turbo_tasks::function]
1556    async fn find_middleware(self: Vc<Self>) -> Result<Vc<FindContextFileResult>> {
1557        Ok(find_context_file(
1558            self.project_path().owned().await?,
1559            middleware_files(self.next_config().page_extensions()),
1560            // our callers do not care about affecting sources
1561            false,
1562        ))
1563    }
1564
1565    #[turbo_tasks::function]
1566    async fn middleware_endpoint(self: Vc<Self>) -> Result<Vc<Box<dyn Endpoint>>> {
1567        let middleware = self.find_middleware();
1568        let FindContextFileResult::Found(fs_path, _) = &*middleware.await? else {
1569            return Ok(Vc::upcast(EmptyEndpoint::new()));
1570        };
1571        let source = Vc::upcast(FileSource::new(fs_path.clone()));
1572        let app_dir = find_app_dir(self.project_path().owned().await?)
1573            .owned()
1574            .await?;
1575        let ecmascript_client_reference_transition_name = (*self.app_project().await?)
1576            .as_ref()
1577            .map(|_| AppProject::client_transition_name());
1578
1579        let is_proxy = fs_path.file_stem() == Some("proxy");
1580        let config = parse_segment_config_from_source(
1581            source,
1582            if is_proxy {
1583                ParseSegmentMode::Proxy
1584            } else {
1585                ParseSegmentMode::Base
1586            },
1587        );
1588        let runtime = config.await?.runtime.unwrap_or(if is_proxy {
1589            NextRuntime::NodeJs
1590        } else {
1591            NextRuntime::Edge
1592        });
1593
1594        let middleware_asset_context = match runtime {
1595            NextRuntime::NodeJs => self.node_middleware_context(),
1596            NextRuntime::Edge => self.edge_middleware_context(),
1597        };
1598
1599        Ok(Vc::upcast(MiddlewareEndpoint::new(
1600            self,
1601            middleware_asset_context,
1602            source,
1603            app_dir.clone(),
1604            ecmascript_client_reference_transition_name,
1605            config,
1606            runtime,
1607        )))
1608    }
1609
1610    #[turbo_tasks::function]
1611    async fn node_instrumentation_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
1612        let mut transitions = vec![];
1613
1614        let app_dir = find_app_dir(self.project_path().owned().await?)
1615            .owned()
1616            .await?;
1617        let app_project = &*self.app_project().await?;
1618
1619        let ecmascript_client_reference_transition_name = app_project
1620            .as_ref()
1621            .map(|_| AppProject::client_transition_name());
1622
1623        if let Some(app_project) = app_project {
1624            transitions.push((
1625                AppProject::client_transition_name(),
1626                app_project
1627                    .ecmascript_client_reference_transition()
1628                    .to_resolved()
1629                    .await?,
1630            ));
1631        }
1632
1633        Ok(Vc::upcast(ModuleAssetContext::new(
1634            TransitionOptions {
1635                named_transitions: transitions.into_iter().collect(),
1636                ..Default::default()
1637            }
1638            .cell(),
1639            self.server_compile_time_info(),
1640            get_server_module_options_context(
1641                self.project_path().owned().await?,
1642                self.execution_context(),
1643                ServerContextType::Instrumentation {
1644                    app_dir: app_dir.clone(),
1645                    ecmascript_client_reference_transition_name:
1646                        ecmascript_client_reference_transition_name.clone(),
1647                },
1648                self.next_mode(),
1649                self.next_config(),
1650                NextRuntime::NodeJs,
1651                self.encryption_key(),
1652                self.server_compile_time_info().environment(),
1653                self.client_compile_time_info().environment(),
1654            ),
1655            get_server_resolve_options_context(
1656                self.project_path().owned().await?,
1657                ServerContextType::Instrumentation {
1658                    app_dir: app_dir.clone(),
1659                    ecmascript_client_reference_transition_name,
1660                },
1661                self.next_mode(),
1662                self.next_config(),
1663                self.execution_context(),
1664                None, // root params can't be used in instrumentation
1665            ),
1666            Layer::new_with_user_friendly_name(
1667                rcstr!("instrumentation"),
1668                rcstr!("Instrumentation"),
1669            ),
1670        )))
1671    }
1672
1673    #[turbo_tasks::function]
1674    async fn edge_instrumentation_context(self: Vc<Self>) -> Result<Vc<Box<dyn AssetContext>>> {
1675        let mut transitions = vec![];
1676
1677        let app_dir = find_app_dir(self.project_path().owned().await?)
1678            .owned()
1679            .await?;
1680        let app_project = &*self.app_project().await?;
1681
1682        let ecmascript_client_reference_transition_name = app_project
1683            .as_ref()
1684            .map(|_| AppProject::client_transition_name());
1685
1686        if let Some(app_project) = app_project {
1687            transitions.push((
1688                AppProject::client_transition_name(),
1689                app_project
1690                    .edge_ecmascript_client_reference_transition()
1691                    .to_resolved()
1692                    .await?,
1693            ));
1694        }
1695
1696        Ok(Vc::upcast(ModuleAssetContext::new(
1697            TransitionOptions {
1698                named_transitions: transitions.into_iter().collect(),
1699                ..Default::default()
1700            }
1701            .cell(),
1702            self.edge_compile_time_info(),
1703            get_server_module_options_context(
1704                self.project_path().owned().await?,
1705                self.execution_context(),
1706                ServerContextType::Instrumentation {
1707                    app_dir: app_dir.clone(),
1708                    ecmascript_client_reference_transition_name:
1709                        ecmascript_client_reference_transition_name.clone(),
1710                },
1711                self.next_mode(),
1712                self.next_config(),
1713                NextRuntime::Edge,
1714                self.encryption_key(),
1715                self.edge_compile_time_info().environment(),
1716                self.client_compile_time_info().environment(),
1717            ),
1718            get_edge_resolve_options_context(
1719                self.project_path().owned().await?,
1720                ServerContextType::Instrumentation {
1721                    app_dir: app_dir.clone(),
1722                    ecmascript_client_reference_transition_name,
1723                },
1724                self.next_mode(),
1725                self.next_config(),
1726                self.execution_context(),
1727                None, // root params can't be used in instrumentation
1728            ),
1729            Layer::new_with_user_friendly_name(
1730                rcstr!("instrumentation-edge"),
1731                rcstr!("Edge Instrumentation"),
1732            ),
1733        )))
1734    }
1735
1736    #[turbo_tasks::function]
1737    async fn find_instrumentation(self: Vc<Self>) -> Result<Vc<FindContextFileResult>> {
1738        Ok(find_context_file(
1739            self.project_path().owned().await?,
1740            instrumentation_files(self.next_config().page_extensions()),
1741            // our callers do not care about affecting sources
1742            false,
1743        ))
1744    }
1745
1746    #[turbo_tasks::function]
1747    async fn instrumentation_endpoint(
1748        self: Vc<Self>,
1749        is_edge: bool,
1750    ) -> Result<Vc<Box<dyn Endpoint>>> {
1751        let instrumentation = self.find_instrumentation();
1752        let FindContextFileResult::Found(fs_path, _) = &*instrumentation.await? else {
1753            return Ok(Vc::upcast(EmptyEndpoint::new()));
1754        };
1755        let source = Vc::upcast(FileSource::new(fs_path.clone()));
1756        let app_dir = find_app_dir(self.project_path().owned().await?)
1757            .owned()
1758            .await?;
1759        let ecmascript_client_reference_transition_name = (*self.app_project().await?)
1760            .as_ref()
1761            .map(|_| AppProject::client_transition_name());
1762
1763        let instrumentation_asset_context = if is_edge {
1764            self.edge_instrumentation_context()
1765        } else {
1766            self.node_instrumentation_context()
1767        };
1768
1769        Ok(Vc::upcast(InstrumentationEndpoint::new(
1770            self,
1771            instrumentation_asset_context,
1772            source,
1773            is_edge,
1774            app_dir.clone(),
1775            ecmascript_client_reference_transition_name,
1776        )))
1777    }
1778
1779    #[turbo_tasks::function]
1780    pub async fn emit_all_output_assets(
1781        self: Vc<Self>,
1782        output_assets: OperationVc<OutputAssets>,
1783    ) -> Result<()> {
1784        let span = tracing::info_span!("emitting");
1785        async move {
1786            let all_output_assets = all_assets_from_entries_operation(output_assets);
1787
1788            let client_relative_path = self.client_relative_path().owned().await?;
1789            let node_root = self.node_root().owned().await?;
1790
1791            if let Some(map) = self.await?.versioned_content_map {
1792                map.insert_output_assets(
1793                    all_output_assets,
1794                    node_root.clone(),
1795                    client_relative_path.clone(),
1796                    node_root.clone(),
1797                )
1798                .as_side_effect()
1799                .await?;
1800
1801                Ok(())
1802            } else {
1803                emit_assets(
1804                    all_output_assets.connect(),
1805                    node_root.clone(),
1806                    client_relative_path.clone(),
1807                    node_root.clone(),
1808                )
1809                .as_side_effect()
1810                .await?;
1811
1812                Ok(())
1813            }
1814        }
1815        .instrument(span)
1816        .await
1817    }
1818
1819    #[turbo_tasks::function]
1820    async fn hmr_content(self: Vc<Self>, identifier: RcStr) -> Result<Vc<OptionVersionedContent>> {
1821        if let Some(map) = self.await?.versioned_content_map {
1822            let content = map.get(self.client_relative_path().await?.join(&identifier)?);
1823            Ok(content)
1824        } else {
1825            bail!("must be in dev mode to hmr")
1826        }
1827    }
1828
1829    #[turbo_tasks::function]
1830    async fn hmr_version(self: Vc<Self>, identifier: RcStr) -> Result<Vc<Box<dyn Version>>> {
1831        let content = self.hmr_content(identifier).await?;
1832        if let Some(content) = &*content {
1833            Ok(content.version())
1834        } else {
1835            Ok(Vc::upcast(NotFoundVersion::new()))
1836        }
1837    }
1838
1839    /// Get the version state for a session. Initialized with the first seen
1840    /// version in that session.
1841    #[turbo_tasks::function]
1842    pub async fn hmr_version_state(
1843        self: Vc<Self>,
1844        identifier: RcStr,
1845        session: TransientInstance<()>,
1846    ) -> Result<Vc<VersionState>> {
1847        let version = self.hmr_version(identifier);
1848
1849        // The session argument is important to avoid caching this function between
1850        // sessions.
1851        let _ = session;
1852
1853        // INVALIDATION: This is intentionally untracked to avoid invalidating this
1854        // function completely. We want to initialize the VersionState with the
1855        // first seen version of the session.
1856        let state = VersionState::new(
1857            version
1858                .into_trait_ref()
1859                .strongly_consistent()
1860                .untracked()
1861                .await?,
1862        )
1863        .await?;
1864        Ok(state)
1865    }
1866
1867    /// Emits opaque HMR events whenever a change is detected in the chunk group
1868    /// internally known as `identifier`.
1869    #[turbo_tasks::function]
1870    pub async fn hmr_update(
1871        self: Vc<Self>,
1872        identifier: RcStr,
1873        from: Vc<VersionState>,
1874    ) -> Result<Vc<Update>> {
1875        let from = from.get();
1876        let content = self.hmr_content(identifier).await?;
1877        if let Some(content) = *content {
1878            Ok(content.update(from))
1879        } else {
1880            Ok(Update::Missing.cell())
1881        }
1882    }
1883
1884    /// Gets a list of all HMR identifiers that can be subscribed to. This is
1885    /// only needed for testing purposes and isn't used in real apps.
1886    #[turbo_tasks::function]
1887    pub async fn hmr_identifiers(self: Vc<Self>) -> Result<Vc<Vec<RcStr>>> {
1888        if let Some(map) = self.await?.versioned_content_map {
1889            Ok(map.keys_in_path(self.client_relative_path().owned().await?))
1890        } else {
1891            bail!("must be in dev mode to hmr")
1892        }
1893    }
1894
1895    /// Completion when server side changes are detected in output assets
1896    /// referenced from the roots
1897    #[turbo_tasks::function]
1898    pub async fn server_changed(self: Vc<Self>, roots: Vc<OutputAssets>) -> Result<Vc<Completion>> {
1899        let path = self.node_root().owned().await?;
1900        Ok(any_output_changed(roots, path, true))
1901    }
1902
1903    /// Completion when client side changes are detected in output assets
1904    /// referenced from the roots
1905    #[turbo_tasks::function]
1906    pub async fn client_changed(self: Vc<Self>, roots: Vc<OutputAssets>) -> Result<Vc<Completion>> {
1907        let path = self.client_root().owned().await?;
1908        Ok(any_output_changed(roots, path, false))
1909    }
1910
1911    #[turbo_tasks::function]
1912    pub async fn client_main_modules(self: Vc<Self>) -> Result<Vc<GraphEntries>> {
1913        let pages_project = self.pages_project();
1914        let mut modules = vec![ChunkGroupEntry::Entry(vec![
1915            pages_project.client_main_module().to_resolved().await?,
1916        ])];
1917
1918        if let Some(app_project) = *self.app_project().await? {
1919            modules.push(ChunkGroupEntry::Entry(vec![
1920                app_project.client_main_module().to_resolved().await?,
1921            ]));
1922        }
1923
1924        Ok(Vc::cell(modules))
1925    }
1926
1927    /// Gets the module id strategy for the project.
1928    #[turbo_tasks::function]
1929    pub async fn module_ids(self: Vc<Self>) -> Result<Vc<Box<dyn ModuleIdStrategy>>> {
1930        let module_id_strategy = *self.next_config().module_ids(self.next_mode()).await?;
1931        match module_id_strategy {
1932            ModuleIdStrategyConfig::Named => Ok(Vc::upcast(DevModuleIdStrategy::new())),
1933            ModuleIdStrategyConfig::Deterministic => {
1934                let module_graphs = self.whole_app_module_graphs().await?;
1935                Ok(Vc::upcast(get_global_module_id_strategy(
1936                    *module_graphs.full,
1937                )))
1938            }
1939        }
1940    }
1941
1942    /// Compute the used exports and unused imports for each module.
1943    #[turbo_tasks::function]
1944    async fn binding_usage_info(self: Vc<Self>) -> Result<Vc<BindingUsageInfo>> {
1945        let remove_unused_imports = *self
1946            .next_config()
1947            .turbopack_remove_unused_imports(self.next_mode())
1948            .await?;
1949
1950        let module_graphs = self.whole_app_module_graphs().await?;
1951        Ok(*compute_binding_usage_info(
1952            module_graphs.full_with_unused_references,
1953            remove_unused_imports,
1954        )
1955        // As a performance optimization, we resolve strongly consistently
1956        .resolve_strongly_consistent()
1957        .await?)
1958    }
1959
1960    /// Compute the used exports for each module.
1961    #[turbo_tasks::function]
1962    pub async fn export_usage(self: Vc<Self>) -> Result<Vc<OptionBindingUsageInfo>> {
1963        if *self
1964            .next_config()
1965            .turbopack_remove_unused_exports(self.next_mode())
1966            .await?
1967        {
1968            Ok(Vc::cell(Some(
1969                self.binding_usage_info().to_resolved().await?,
1970            )))
1971        } else {
1972            Ok(Vc::cell(None))
1973        }
1974    }
1975
1976    /// Compute the unused references that were removed (inner graph tree shaking).
1977    #[turbo_tasks::function]
1978    pub async fn unused_references(self: Vc<Self>) -> Result<Vc<OptionBindingUsageInfo>> {
1979        if *self
1980            .next_config()
1981            .turbopack_remove_unused_imports(self.next_mode())
1982            .await?
1983        {
1984            Ok(Vc::cell(Some(
1985                self.binding_usage_info().to_resolved().await?,
1986            )))
1987        } else {
1988            Ok(Vc::cell(None))
1989        }
1990    }
1991
1992    #[turbo_tasks::function]
1993    pub async fn with_next_config(&self, next_config: Vc<NextConfig>) -> Result<Vc<Self>> {
1994        Ok(Self {
1995            next_config: next_config.to_resolved().await?,
1996            ..(*self).clone()
1997        }
1998        .cell())
1999    }
2000}
2001
2002// This is a performance optimization. This function is a root aggregation function that
2003// aggregates over the whole subgraph.
2004#[turbo_tasks::function(operation)]
2005async fn whole_app_module_graph_operation(
2006    project: ResolvedVc<Project>,
2007) -> Result<Vc<BaseAndFullModuleGraph>> {
2008    mark_root();
2009
2010    let next_mode = project.next_mode();
2011    let next_mode_ref = next_mode.await?;
2012    let should_trace = next_mode_ref.is_production();
2013    let should_read_binding_usage = next_mode_ref.is_production();
2014    let base_single_module_graph = SingleModuleGraph::new_with_entries(
2015        project.get_all_entries(),
2016        should_trace,
2017        should_read_binding_usage,
2018    );
2019    let base_visited_modules = VisitedModules::from_graph(base_single_module_graph);
2020
2021    let base = ModuleGraph::from_single_graph(base_single_module_graph);
2022
2023    let turbopack_remove_unused_imports = *project
2024        .next_config()
2025        .turbopack_remove_unused_imports(next_mode)
2026        .await?;
2027
2028    let base = if turbopack_remove_unused_imports {
2029        // TODO suboptimal that we do compute_binding_usage_info twice (once for the base graph
2030        // and later for the full graph)
2031        base.without_unused_references(
2032            *compute_binding_usage_info(base.to_resolved().await?, true)
2033                .resolve_strongly_consistent()
2034                .await?,
2035        )
2036    } else {
2037        base
2038    };
2039
2040    let additional_entries = project.get_all_additional_entries(base);
2041
2042    let additional_module_graph = SingleModuleGraph::new_with_entries_visited(
2043        additional_entries,
2044        base_visited_modules,
2045        should_trace,
2046        should_read_binding_usage,
2047    );
2048
2049    let full_with_unused_references =
2050        ModuleGraph::from_graphs(vec![base_single_module_graph, additional_module_graph])
2051            .to_resolved()
2052            .await?;
2053
2054    let full = if turbopack_remove_unused_imports {
2055        full_with_unused_references
2056            .without_unused_references(
2057                *compute_binding_usage_info(full_with_unused_references, true)
2058                    .resolve_strongly_consistent()
2059                    .await?,
2060            )
2061            .to_resolved()
2062            .await?
2063    } else {
2064        full_with_unused_references
2065    };
2066
2067    Ok(BaseAndFullModuleGraph {
2068        base: base.to_resolved().await?,
2069        full_with_unused_references,
2070        full,
2071    }
2072    .cell())
2073}
2074
2075#[turbo_tasks::value(shared)]
2076pub struct BaseAndFullModuleGraph {
2077    /// The base module graph generated from the entry points.
2078    pub base: ResolvedVc<ModuleGraph>,
2079    /// The base graph plus any modules that were generated from additional entries (for which the
2080    /// base graph is needed).
2081    pub full_with_unused_references: ResolvedVc<ModuleGraph>,
2082    /// `full_with_unused_references` but with unused references removed.
2083    pub full: ResolvedVc<ModuleGraph>,
2084}
2085
2086#[turbo_tasks::function]
2087async fn any_output_changed(
2088    roots: Vc<OutputAssets>,
2089    path: FileSystemPath,
2090    server: bool,
2091) -> Result<Vc<Completion>> {
2092    let all_assets = expand_output_assets(
2093        roots
2094            .await?
2095            .into_iter()
2096            .map(|&a| ExpandOutputAssetsInput::Asset(a)),
2097        true,
2098    )
2099    .await?;
2100    let completions = all_assets
2101        .into_iter()
2102        .map(|m| {
2103            let path = path.clone();
2104
2105            async move {
2106                let asset_path = m.path().await?;
2107                if !asset_path.path.ends_with(".map")
2108                    && (!server || !asset_path.path.ends_with(".css"))
2109                    && asset_path.is_inside_ref(&path)
2110                {
2111                    anyhow::Ok(Some(
2112                        content_changed(*ResolvedVc::upcast(m))
2113                            .to_resolved()
2114                            .await?,
2115                    ))
2116                } else {
2117                    Ok(None)
2118                }
2119            }
2120        })
2121        .try_flat_join()
2122        .await?;
2123
2124    Ok(Vc::<Completions>::cell(completions).completed())
2125}
2126
2127#[turbo_tasks::function(operation)]
2128fn all_assets_from_entries_operation(
2129    operation: OperationVc<OutputAssets>,
2130) -> Result<Vc<ExpandedOutputAssets>> {
2131    let assets = operation.connect();
2132    Ok(all_assets_from_entries(assets))
2133}
2134
2135#[turbo_tasks::function]
2136fn stable_endpoint(endpoint: Vc<Box<dyn Endpoint>>) -> Vc<Box<dyn Endpoint>> {
2137    endpoint
2138}