turbopack_node/
evaluate.rs

1use std::{
2    borrow::Cow, iter, ops::ControlFlow, sync::Arc, thread::available_parallelism, time::Duration,
3};
4
5use anyhow::{Result, anyhow, bail};
6use async_stream::try_stream as generator;
7use futures::{
8    SinkExt, StreamExt,
9    channel::mpsc::{UnboundedSender, unbounded},
10    pin_mut,
11};
12use futures_retry::{FutureRetry, RetryPolicy};
13use parking_lot::Mutex;
14use serde::{Deserialize, Serialize, de::DeserializeOwned};
15use serde_json::Value as JsonValue;
16use turbo_rcstr::rcstr;
17use turbo_tasks::{
18    Completion, Effects, FxIndexMap, NonLocalValue, OperationVc, RawVc, ReadRef, ResolvedVc,
19    TaskInput, TryJoinIterExt, Vc, VcValueType, duration_span, fxindexmap, get_effects,
20    mark_finished, prevent_gc, trace::TraceRawVcs, util::SharedError,
21};
22use turbo_tasks_bytes::{Bytes, Stream};
23use turbo_tasks_env::{EnvMap, ProcessEnv};
24use turbo_tasks_fs::{File, FileSystemPath, to_sys_path};
25use turbopack_core::{
26    asset::AssetContent,
27    changed::content_changed,
28    chunk::{ChunkingContext, ChunkingContextExt, EvaluatableAsset, EvaluatableAssets},
29    context::AssetContext,
30    error::PrettyPrintError,
31    file_source::FileSource,
32    issue::{
33        Issue, IssueExt, IssueSource, IssueStage, OptionIssueSource, OptionStyledString,
34        StyledString,
35    },
36    module::Module,
37    module_graph::{ModuleGraph, chunk_group_info::ChunkGroupEntry},
38    output::{OutputAsset, OutputAssets},
39    reference_type::{InnerAssets, ReferenceType},
40    source::Source,
41    virtual_source::VirtualSource,
42};
43
44use crate::{
45    AssetsForSourceMapping,
46    embed_js::embed_file_path,
47    emit, emit_package_json, internal_assets_for_source_mapping,
48    pool::{FormattingMode, NodeJsOperation, NodeJsPool},
49    source_map::StructuredError,
50};
51
52#[derive(Serialize)]
53#[serde(tag = "type", rename_all = "camelCase")]
54enum EvalJavaScriptOutgoingMessage<'a> {
55    #[serde(rename_all = "camelCase")]
56    Evaluate { args: Vec<&'a JsonValue> },
57    Result {
58        id: u64,
59        data: Option<JsonValue>,
60        error: Option<String>,
61    },
62}
63
64#[derive(Deserialize, Debug)]
65#[serde(tag = "type", rename_all = "camelCase")]
66enum EvalJavaScriptIncomingMessage {
67    Info { data: JsonValue },
68    Request { id: u64, data: JsonValue },
69    End { data: Option<String> },
70    Error(StructuredError),
71}
72
73type LoopResult = ControlFlow<Result<Option<String>, StructuredError>, String>;
74
75type EvaluationItem = Result<Bytes, SharedError>;
76type JavaScriptStream = Stream<EvaluationItem>;
77
78#[turbo_tasks::value(eq = "manual", cell = "new", serialization = "none")]
79pub struct JavaScriptStreamSender {
80    #[turbo_tasks(trace_ignore, debug_ignore)]
81    get: Box<dyn Fn() -> UnboundedSender<Result<Bytes, SharedError>> + Send + Sync>,
82}
83
84#[turbo_tasks::value(transparent)]
85#[derive(Clone, Debug)]
86pub struct JavaScriptEvaluation(#[turbo_tasks(trace_ignore)] JavaScriptStream);
87
88#[turbo_tasks::value]
89struct EmittedEvaluatePoolAssets {
90    bootstrap: ResolvedVc<Box<dyn OutputAsset>>,
91    output_root: FileSystemPath,
92    entrypoint: FileSystemPath,
93}
94
95#[turbo_tasks::function(operation)]
96async fn emit_evaluate_pool_assets_operation(
97    module_asset: ResolvedVc<Box<dyn Module>>,
98    asset_context: ResolvedVc<Box<dyn AssetContext>>,
99    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
100    runtime_entries: Option<ResolvedVc<EvaluatableAssets>>,
101) -> Result<Vc<EmittedEvaluatePoolAssets>> {
102    let runtime_asset = asset_context
103        .process(
104            Vc::upcast(FileSource::new(
105                embed_file_path(rcstr!("ipc/evaluate.ts")).owned().await?,
106            )),
107            ReferenceType::Internal(InnerAssets::empty().to_resolved().await?),
108        )
109        .module()
110        .to_resolved()
111        .await?;
112
113    let module_path = module_asset.ident().path().await?;
114    let file_name = module_path.file_name();
115    let file_name = if file_name.ends_with(".js") {
116        Cow::Borrowed(file_name)
117    } else if let Some(file_name) = file_name.strip_suffix(".ts") {
118        Cow::Owned(format!("{file_name}.js"))
119    } else {
120        Cow::Owned(format!("{file_name}.js"))
121    };
122    let entrypoint = chunking_context.output_root().await?.join(&file_name)?;
123    let entry_module = asset_context
124        .process(
125            Vc::upcast(VirtualSource::new(
126                runtime_asset.ident().path().await?.join("evaluate.js")?,
127                AssetContent::file(
128                    File::from("import { run } from 'RUNTIME'; run(() => import('INNER'))").into(),
129                ),
130            )),
131            ReferenceType::Internal(ResolvedVc::cell(fxindexmap! {
132                rcstr!("INNER") => module_asset,
133                rcstr!("RUNTIME") => runtime_asset
134            })),
135        )
136        .module()
137        .to_resolved()
138        .await?;
139
140    let runtime_entries = {
141        let globals_module = asset_context
142            .process(
143                Vc::upcast(FileSource::new(
144                    embed_file_path(rcstr!("globals.ts")).owned().await?,
145                )),
146                ReferenceType::Internal(InnerAssets::empty().to_resolved().await?),
147            )
148            .module();
149
150        let Some(globals_module) =
151            Vc::try_resolve_sidecast::<Box<dyn EvaluatableAsset>>(globals_module).await?
152        else {
153            bail!("Internal module is not evaluatable");
154        };
155
156        let mut entries = vec![globals_module.to_resolved().await?];
157        if let Some(runtime_entries) = runtime_entries {
158            for &entry in &*runtime_entries.await? {
159                entries.push(entry)
160            }
161        }
162        entries
163    };
164
165    let module_graph = ModuleGraph::from_modules(
166        Vc::cell(vec![ChunkGroupEntry::Entry(
167            iter::once(entry_module)
168                .chain(runtime_entries.iter().copied().map(ResolvedVc::upcast))
169                .collect(),
170        )]),
171        false,
172    );
173
174    let bootstrap = chunking_context.root_entry_chunk_group_asset(
175        entrypoint.clone(),
176        Vc::<EvaluatableAssets>::cell(runtime_entries)
177            .with_entry(*ResolvedVc::try_downcast(entry_module).unwrap()),
178        module_graph,
179        OutputAssets::empty(),
180        OutputAssets::empty(),
181    );
182
183    let output_root = chunking_context.output_root().owned().await?;
184    emit_package_json(output_root.clone())?
185        .as_side_effect()
186        .await?;
187    emit(bootstrap, output_root.clone())
188        .as_side_effect()
189        .await?;
190
191    Ok(EmittedEvaluatePoolAssets {
192        bootstrap: bootstrap.to_resolved().await?,
193        output_root,
194        entrypoint: entrypoint.clone(),
195    }
196    .cell())
197}
198
199#[turbo_tasks::value(serialization = "none")]
200struct EmittedEvaluatePoolAssetsWithEffects {
201    assets: ReadRef<EmittedEvaluatePoolAssets>,
202    effects: Arc<Effects>,
203}
204
205#[turbo_tasks::function(operation)]
206async fn emit_evaluate_pool_assets_with_effects_operation(
207    module_asset: ResolvedVc<Box<dyn Module>>,
208    asset_context: ResolvedVc<Box<dyn AssetContext>>,
209    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
210    runtime_entries: Option<ResolvedVc<EvaluatableAssets>>,
211) -> Result<Vc<EmittedEvaluatePoolAssetsWithEffects>> {
212    let operation = emit_evaluate_pool_assets_operation(
213        module_asset,
214        asset_context,
215        chunking_context,
216        runtime_entries,
217    );
218    let assets = operation.read_strongly_consistent().await?;
219    let effects = Arc::new(get_effects(operation).await?);
220    Ok(EmittedEvaluatePoolAssetsWithEffects { assets, effects }.cell())
221}
222
223#[derive(
224    Clone,
225    Copy,
226    Hash,
227    Debug,
228    PartialEq,
229    Eq,
230    Serialize,
231    Deserialize,
232    TaskInput,
233    NonLocalValue,
234    TraceRawVcs,
235)]
236pub enum EnvVarTracking {
237    WholeEnvTracked,
238    Untracked,
239}
240
241#[turbo_tasks::function(operation)]
242/// Pass the file you cared as `runtime_entries` to invalidate and reload the
243/// evaluated result automatically.
244pub async fn get_evaluate_pool(
245    module_asset: ResolvedVc<Box<dyn Module>>,
246    cwd: FileSystemPath,
247    env: ResolvedVc<Box<dyn ProcessEnv>>,
248    asset_context: ResolvedVc<Box<dyn AssetContext>>,
249    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
250    runtime_entries: Option<ResolvedVc<EvaluatableAssets>>,
251    additional_invalidation: ResolvedVc<Completion>,
252    debug: bool,
253    env_var_tracking: EnvVarTracking,
254) -> Result<Vc<NodeJsPool>> {
255    let operation = emit_evaluate_pool_assets_with_effects_operation(
256        module_asset,
257        asset_context,
258        chunking_context,
259        runtime_entries,
260    );
261    let EmittedEvaluatePoolAssetsWithEffects { assets, effects } =
262        &*operation.read_strongly_consistent().await?;
263    effects.apply().await?;
264
265    let EmittedEvaluatePoolAssets {
266        bootstrap,
267        output_root,
268        entrypoint,
269    } = &**assets;
270
271    let (Some(cwd), Some(entrypoint)) = (
272        to_sys_path(cwd.clone()).await?,
273        to_sys_path(entrypoint.clone()).await?,
274    ) else {
275        panic!("can only evaluate from a disk filesystem");
276    };
277
278    // Invalidate pool when code content changes
279    content_changed(Vc::upcast(**bootstrap)).await?;
280    let assets_for_source_mapping =
281        internal_assets_for_source_mapping(**bootstrap, output_root.clone())
282            .to_resolved()
283            .await?;
284    let env = match env_var_tracking {
285        EnvVarTracking::WholeEnvTracked => env.read_all().await?,
286        EnvVarTracking::Untracked => {
287            // We always depend on some known env vars that are used by Node.js
288            common_node_env(*env).await?;
289            for name in ["FORCE_COLOR", "NO_COLOR", "OPENSSL_CONF", "TZ"] {
290                env.read(name.into()).await?;
291            }
292
293            env.read_all().untracked().await?
294        }
295    };
296    let pool = NodeJsPool::new(
297        cwd,
298        entrypoint,
299        env.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
300        assets_for_source_mapping,
301        output_root.clone(),
302        chunking_context.root_path().owned().await?,
303        available_parallelism().map_or(1, |v| v.get()),
304        debug,
305    );
306    additional_invalidation.await?;
307    Ok(pool.cell())
308}
309
310#[turbo_tasks::function]
311async fn common_node_env(env: Vc<Box<dyn ProcessEnv>>) -> Result<Vc<EnvMap>> {
312    let mut filtered = FxIndexMap::default();
313    let env = env.read_all().await?;
314    for (key, value) in &*env {
315        let uppercase = key.to_uppercase();
316        for filter in &["NODE_", "UV_", "SSL_"] {
317            if uppercase.starts_with(filter) {
318                filtered.insert(key.clone(), value.clone());
319                break;
320            }
321        }
322    }
323    Ok(Vc::cell(filtered))
324}
325
326struct PoolErrorHandler;
327
328/// Number of attempts before we start slowing down the retry.
329const MAX_FAST_ATTEMPTS: usize = 5;
330/// Total number of attempts.
331const MAX_ATTEMPTS: usize = MAX_FAST_ATTEMPTS * 2;
332
333impl futures_retry::ErrorHandler<anyhow::Error> for PoolErrorHandler {
334    type OutError = anyhow::Error;
335
336    fn handle(&mut self, attempt: usize, err: anyhow::Error) -> RetryPolicy<Self::OutError> {
337        if attempt >= MAX_ATTEMPTS {
338            RetryPolicy::ForwardError(err)
339        } else if attempt >= MAX_FAST_ATTEMPTS {
340            RetryPolicy::WaitRetry(Duration::from_secs(1))
341        } else {
342            RetryPolicy::Repeat
343        }
344    }
345}
346
347pub trait EvaluateContext {
348    type InfoMessage: DeserializeOwned;
349    type RequestMessage: DeserializeOwned;
350    type ResponseMessage: Serialize;
351    type State: Default;
352
353    fn compute(self, sender: Vc<JavaScriptStreamSender>)
354    -> impl Future<Output = Result<()>> + Send;
355    fn pool(&self) -> OperationVc<NodeJsPool>;
356    fn keep_alive(&self) -> bool {
357        false
358    }
359    fn args(&self) -> &[ResolvedVc<JsonValue>];
360    fn cwd(&self) -> Vc<FileSystemPath>;
361    fn emit_error(
362        &self,
363        error: StructuredError,
364        pool: &NodeJsPool,
365    ) -> impl Future<Output = Result<()>> + Send;
366    fn info(
367        &self,
368        state: &mut Self::State,
369        data: Self::InfoMessage,
370        pool: &NodeJsPool,
371    ) -> impl Future<Output = Result<()>> + Send;
372    fn request(
373        &self,
374        state: &mut Self::State,
375        data: Self::RequestMessage,
376        pool: &NodeJsPool,
377    ) -> impl Future<Output = Result<Self::ResponseMessage>> + Send;
378    fn finish(
379        &self,
380        state: Self::State,
381        pool: &NodeJsPool,
382    ) -> impl Future<Output = Result<()>> + Send;
383}
384
385pub async fn custom_evaluate(
386    evaluate_context: impl EvaluateContext,
387) -> Result<Vc<JavaScriptEvaluation>> {
388    // TODO: The way we invoke compute_evaluate_stream as side effect is not
389    // GC-safe, so we disable GC for this task.
390    prevent_gc();
391
392    // Note the following code uses some hacks to create a child task that produces
393    // a stream that is returned by this task.
394
395    // We create a new cell in this task, which will be updated from the
396    // [compute_evaluate_stream] task.
397    let cell = turbo_tasks::macro_helpers::find_cell_by_type(
398        <JavaScriptEvaluation as VcValueType>::get_value_type_id(),
399    );
400
401    // We initialize the cell with a stream that is open, but has no values.
402    // The first [compute_evaluate_stream] pipe call will pick up that stream.
403    let (sender, receiver) = unbounded();
404    cell.update(JavaScriptEvaluation(JavaScriptStream::new_open(
405        vec![],
406        Box::new(receiver),
407    )));
408    let initial = Mutex::new(Some(sender));
409
410    // run the evaluation as side effect
411    evaluate_context
412        .compute(
413            JavaScriptStreamSender {
414                get: Box::new(move || {
415                    if let Some(sender) = initial.lock().take() {
416                        sender
417                    } else {
418                        // In cases when only [compute_evaluate_stream] is (re)executed, we need to
419                        // update the old stream with a new value.
420                        let (sender, receiver) = unbounded();
421                        cell.update(JavaScriptEvaluation(JavaScriptStream::new_open(
422                            vec![],
423                            Box::new(receiver),
424                        )));
425                        sender
426                    }
427                }),
428            }
429            .cell(),
430        )
431        .await?;
432
433    let raw: RawVc = cell.into();
434    Ok(raw.into())
435}
436
437/// Pass the file you cared as `runtime_entries` to invalidate and reload the
438/// evaluated result automatically.
439#[turbo_tasks::function]
440pub async fn evaluate(
441    module_asset: ResolvedVc<Box<dyn Module>>,
442    cwd: FileSystemPath,
443    env: ResolvedVc<Box<dyn ProcessEnv>>,
444    context_source_for_issue: ResolvedVc<Box<dyn Source>>,
445    asset_context: ResolvedVc<Box<dyn AssetContext>>,
446    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
447    runtime_entries: Option<ResolvedVc<EvaluatableAssets>>,
448    args: Vec<ResolvedVc<JsonValue>>,
449    additional_invalidation: ResolvedVc<Completion>,
450    debug: bool,
451) -> Result<Vc<JavaScriptEvaluation>> {
452    custom_evaluate(BasicEvaluateContext {
453        module_asset,
454        cwd,
455        env,
456        context_source_for_issue,
457        asset_context,
458        chunking_context,
459        runtime_entries,
460        args,
461        additional_invalidation,
462        debug,
463    })
464    .await
465}
466
467pub async fn compute(
468    evaluate_context: impl EvaluateContext,
469    sender: Vc<JavaScriptStreamSender>,
470) -> Result<Vc<()>> {
471    mark_finished();
472    let Ok(sender) = sender.await else {
473        // Impossible to handle the error in a good way.
474        return Ok(Default::default());
475    };
476
477    let stream = generator! {
478        let pool_op = evaluate_context.pool();
479        let mut state = Default::default();
480
481        // Read this strongly consistent, since we don't want to run inconsistent
482        // node.js code.
483        let pool = pool_op.read_strongly_consistent().await?;
484
485        let args = evaluate_context.args().iter().try_join().await?;
486        // Assume this is a one-off operation, so we can kill the process
487        // TODO use a better way to decide that.
488        let kill = !evaluate_context.keep_alive();
489
490        // Workers in the pool could be in a bad state that we didn't detect yet.
491        // The bad state might even be unnoticeable until we actually send the job to the
492        // worker. So we retry picking workers from the pools until we succeed
493        // sending the job.
494
495        let (mut operation, _) = FutureRetry::new(
496            || async {
497                let mut operation = pool.operation().await?;
498                operation
499                    .send(EvalJavaScriptOutgoingMessage::Evaluate {
500                        args: args.iter().map(|v| &**v).collect(),
501                    })
502                    .await?;
503                Ok(operation)
504            },
505            PoolErrorHandler,
506        )
507        .await
508        .map_err(|(e, _)| e)?;
509
510        // The evaluation sent an initial intermediate value without completing. We'll
511        // need to spawn a new thread to continually pull data out of the process,
512        // and ferry that along.
513        loop {
514            let output = pull_operation(&mut operation, &pool, &evaluate_context, &mut state).await?;
515
516            match output {
517                LoopResult::Continue(data) => {
518                    yield data.into();
519                }
520                LoopResult::Break(Ok(Some(data))) => {
521                    yield data.into();
522                    break;
523                }
524                LoopResult::Break(Err(e)) => {
525                    let error = print_error(e, &pool, &evaluate_context).await?;
526                    Err(anyhow!("Node.js evaluation failed: {}", error))?;
527                    break;
528                }
529                LoopResult::Break(Ok(None)) => {
530                    break;
531                }
532            }
533        }
534
535        evaluate_context.finish(state, &pool).await?;
536
537        if kill {
538            operation.wait_or_kill().await?;
539        }
540    };
541
542    let mut sender = (sender.get)();
543    pin_mut!(stream);
544    while let Some(value) = stream.next().await {
545        if sender.send(value).await.is_err() {
546            return Ok(Default::default());
547        }
548        if sender.flush().await.is_err() {
549            return Ok(Default::default());
550        }
551    }
552
553    Ok(Default::default())
554}
555
556/// Repeatedly pulls from the NodeJsOperation until we receive a
557/// value/error/end.
558async fn pull_operation<T: EvaluateContext>(
559    operation: &mut NodeJsOperation,
560    pool: &NodeJsPool,
561    evaluate_context: &T,
562    state: &mut T::State,
563) -> Result<LoopResult> {
564    let guard = duration_span!("Node.js evaluation");
565
566    let output = loop {
567        match operation.recv().await? {
568            EvalJavaScriptIncomingMessage::Error(error) => {
569                evaluate_context.emit_error(error, pool).await?;
570                // Do not reuse the process in case of error
571                operation.disallow_reuse();
572                // Issue emitted, we want to break but don't want to return an error
573                break ControlFlow::Break(Ok(None));
574            }
575            EvalJavaScriptIncomingMessage::End { data } => break ControlFlow::Break(Ok(data)),
576            EvalJavaScriptIncomingMessage::Info { data } => {
577                evaluate_context
578                    .info(state, serde_json::from_value(data)?, pool)
579                    .await?;
580            }
581            EvalJavaScriptIncomingMessage::Request { id, data } => {
582                match evaluate_context
583                    .request(state, serde_json::from_value(data)?, pool)
584                    .await
585                {
586                    Ok(response) => {
587                        operation
588                            .send(EvalJavaScriptOutgoingMessage::Result {
589                                id,
590                                error: None,
591                                data: Some(serde_json::to_value(response)?),
592                            })
593                            .await?;
594                    }
595                    Err(e) => {
596                        operation
597                            .send(EvalJavaScriptOutgoingMessage::Result {
598                                id,
599                                error: Some(PrettyPrintError(&e).to_string()),
600                                data: None,
601                            })
602                            .await?;
603                    }
604                }
605            }
606        }
607    };
608    drop(guard);
609
610    Ok(output)
611}
612
613#[turbo_tasks::function]
614async fn basic_compute(
615    evaluate_context: BasicEvaluateContext,
616    sender: Vc<JavaScriptStreamSender>,
617) -> Result<Vc<()>> {
618    compute(evaluate_context, sender).await
619}
620
621#[derive(Clone, PartialEq, Eq, Hash, TaskInput, Debug, Serialize, Deserialize, TraceRawVcs)]
622struct BasicEvaluateContext {
623    module_asset: ResolvedVc<Box<dyn Module>>,
624    cwd: FileSystemPath,
625    env: ResolvedVc<Box<dyn ProcessEnv>>,
626    context_source_for_issue: ResolvedVc<Box<dyn Source>>,
627    asset_context: ResolvedVc<Box<dyn AssetContext>>,
628    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
629    runtime_entries: Option<ResolvedVc<EvaluatableAssets>>,
630    args: Vec<ResolvedVc<JsonValue>>,
631    additional_invalidation: ResolvedVc<Completion>,
632    debug: bool,
633}
634
635impl EvaluateContext for BasicEvaluateContext {
636    type InfoMessage = ();
637    type RequestMessage = ();
638    type ResponseMessage = ();
639    type State = ();
640
641    async fn compute(self, sender: Vc<JavaScriptStreamSender>) -> Result<()> {
642        basic_compute(self, sender).as_side_effect().await
643    }
644
645    fn pool(&self) -> OperationVc<crate::pool::NodeJsPool> {
646        get_evaluate_pool(
647            self.module_asset,
648            self.cwd.clone(),
649            self.env,
650            self.asset_context,
651            self.chunking_context,
652            self.runtime_entries,
653            self.additional_invalidation,
654            self.debug,
655            EnvVarTracking::WholeEnvTracked,
656        )
657    }
658
659    fn args(&self) -> &[ResolvedVc<serde_json::Value>] {
660        &self.args
661    }
662
663    fn cwd(&self) -> Vc<turbo_tasks_fs::FileSystemPath> {
664        self.cwd.clone().cell()
665    }
666
667    fn keep_alive(&self) -> bool {
668        !self.args.is_empty()
669    }
670
671    async fn emit_error(&self, error: StructuredError, pool: &NodeJsPool) -> Result<()> {
672        EvaluationIssue {
673            error,
674            source: IssueSource::from_source_only(self.context_source_for_issue),
675            assets_for_source_mapping: pool.assets_for_source_mapping,
676            assets_root: pool.assets_root.clone(),
677            root_path: self.chunking_context.root_path().owned().await?,
678        }
679        .resolved_cell()
680        .emit();
681        Ok(())
682    }
683
684    async fn info(
685        &self,
686        _state: &mut Self::State,
687        _data: Self::InfoMessage,
688        _pool: &NodeJsPool,
689    ) -> Result<()> {
690        bail!("BasicEvaluateContext does not support info messages")
691    }
692
693    async fn request(
694        &self,
695        _state: &mut Self::State,
696        _data: Self::RequestMessage,
697        _pool: &NodeJsPool,
698    ) -> Result<Self::ResponseMessage> {
699        bail!("BasicEvaluateContext does not support request messages")
700    }
701
702    async fn finish(&self, _state: Self::State, _pool: &NodeJsPool) -> Result<()> {
703        Ok(())
704    }
705}
706
707pub fn scale_zero() {
708    NodeJsPool::scale_zero();
709}
710
711pub fn scale_down() {
712    NodeJsPool::scale_down();
713}
714
715async fn print_error(
716    error: StructuredError,
717    pool: &NodeJsPool,
718    evaluate_context: &impl EvaluateContext,
719) -> Result<String> {
720    error
721        .print(
722            *pool.assets_for_source_mapping,
723            pool.assets_root.clone(),
724            evaluate_context.cwd().owned().await?,
725            FormattingMode::Plain,
726        )
727        .await
728}
729/// An issue that occurred while evaluating node code.
730#[turbo_tasks::value(shared)]
731pub struct EvaluationIssue {
732    pub source: IssueSource,
733    pub error: StructuredError,
734    pub assets_for_source_mapping: ResolvedVc<AssetsForSourceMapping>,
735    pub assets_root: FileSystemPath,
736    pub root_path: FileSystemPath,
737}
738
739#[turbo_tasks::value_impl]
740impl Issue for EvaluationIssue {
741    #[turbo_tasks::function]
742    fn title(&self) -> Vc<StyledString> {
743        StyledString::Text(rcstr!("Error evaluating Node.js code")).cell()
744    }
745
746    #[turbo_tasks::function]
747    fn stage(&self) -> Vc<IssueStage> {
748        IssueStage::Transform.into()
749    }
750
751    #[turbo_tasks::function]
752    fn file_path(&self) -> Vc<FileSystemPath> {
753        self.source.file_path()
754    }
755
756    #[turbo_tasks::function]
757    async fn description(&self) -> Result<Vc<OptionStyledString>> {
758        Ok(Vc::cell(Some(
759            StyledString::Text(
760                self.error
761                    .print(
762                        *self.assets_for_source_mapping,
763                        self.assets_root.clone(),
764                        self.root_path.clone(),
765                        FormattingMode::Plain,
766                    )
767                    .await?
768                    .into(),
769            )
770            .resolved_cell(),
771        )))
772    }
773
774    #[turbo_tasks::function]
775    fn source(&self) -> Vc<OptionIssueSource> {
776        Vc::cell(Some(self.source))
777    }
778}