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 );
181
182 let output_root = chunking_context.output_root().owned().await?;
183 emit_package_json(output_root.clone())?
184 .as_side_effect()
185 .await?;
186 emit(bootstrap, output_root.clone())
187 .as_side_effect()
188 .await?;
189
190 Ok(EmittedEvaluatePoolAssets {
191 bootstrap: bootstrap.to_resolved().await?,
192 output_root,
193 entrypoint: entrypoint.clone(),
194 }
195 .cell())
196}
197
198#[turbo_tasks::value(serialization = "none")]
199struct EmittedEvaluatePoolAssetsWithEffects {
200 assets: ReadRef<EmittedEvaluatePoolAssets>,
201 effects: Arc<Effects>,
202}
203
204#[turbo_tasks::function(operation)]
205async fn emit_evaluate_pool_assets_with_effects_operation(
206 module_asset: ResolvedVc<Box<dyn Module>>,
207 asset_context: ResolvedVc<Box<dyn AssetContext>>,
208 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
209 runtime_entries: Option<ResolvedVc<EvaluatableAssets>>,
210) -> Result<Vc<EmittedEvaluatePoolAssetsWithEffects>> {
211 let operation = emit_evaluate_pool_assets_operation(
212 module_asset,
213 asset_context,
214 chunking_context,
215 runtime_entries,
216 );
217 let assets = operation.read_strongly_consistent().await?;
218 let effects = Arc::new(get_effects(operation).await?);
219 Ok(EmittedEvaluatePoolAssetsWithEffects { assets, effects }.cell())
220}
221
222#[derive(
223 Clone,
224 Copy,
225 Hash,
226 Debug,
227 PartialEq,
228 Eq,
229 Serialize,
230 Deserialize,
231 TaskInput,
232 NonLocalValue,
233 TraceRawVcs,
234)]
235pub enum EnvVarTracking {
236 WholeEnvTracked,
237 Untracked,
238}
239
240#[turbo_tasks::function(operation)]
241pub async fn get_evaluate_pool(
244 module_asset: ResolvedVc<Box<dyn Module>>,
245 cwd: FileSystemPath,
246 env: ResolvedVc<Box<dyn ProcessEnv>>,
247 asset_context: ResolvedVc<Box<dyn AssetContext>>,
248 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
249 runtime_entries: Option<ResolvedVc<EvaluatableAssets>>,
250 additional_invalidation: ResolvedVc<Completion>,
251 debug: bool,
252 env_var_tracking: EnvVarTracking,
253) -> Result<Vc<NodeJsPool>> {
254 let operation = emit_evaluate_pool_assets_with_effects_operation(
255 module_asset,
256 asset_context,
257 chunking_context,
258 runtime_entries,
259 );
260 let EmittedEvaluatePoolAssetsWithEffects { assets, effects } =
261 &*operation.read_strongly_consistent().await?;
262 effects.apply().await?;
263
264 let EmittedEvaluatePoolAssets {
265 bootstrap,
266 output_root,
267 entrypoint,
268 } = &**assets;
269
270 let (Some(cwd), Some(entrypoint)) = (
271 to_sys_path(cwd.clone()).await?,
272 to_sys_path(entrypoint.clone()).await?,
273 ) else {
274 panic!("can only evaluate from a disk filesystem");
275 };
276
277 content_changed(Vc::upcast(**bootstrap)).await?;
279 let assets_for_source_mapping =
280 internal_assets_for_source_mapping(**bootstrap, output_root.clone())
281 .to_resolved()
282 .await?;
283 let env = match env_var_tracking {
284 EnvVarTracking::WholeEnvTracked => env.read_all().await?,
285 EnvVarTracking::Untracked => {
286 common_node_env(*env).await?;
288 for name in ["FORCE_COLOR", "NO_COLOR", "OPENSSL_CONF", "TZ"] {
289 env.read(name.into()).await?;
290 }
291
292 env.read_all().untracked().await?
293 }
294 };
295 let pool = NodeJsPool::new(
296 cwd,
297 entrypoint,
298 env.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
299 assets_for_source_mapping,
300 output_root.clone(),
301 chunking_context.root_path().owned().await?,
302 available_parallelism().map_or(1, |v| v.get()),
303 debug,
304 );
305 additional_invalidation.await?;
306 Ok(pool.cell())
307}
308
309#[turbo_tasks::function]
310async fn common_node_env(env: Vc<Box<dyn ProcessEnv>>) -> Result<Vc<EnvMap>> {
311 let mut filtered = FxIndexMap::default();
312 let env = env.read_all().await?;
313 for (key, value) in &*env {
314 let uppercase = key.to_uppercase();
315 for filter in &["NODE_", "UV_", "SSL_"] {
316 if uppercase.starts_with(filter) {
317 filtered.insert(key.clone(), value.clone());
318 break;
319 }
320 }
321 }
322 Ok(Vc::cell(filtered))
323}
324
325struct PoolErrorHandler;
326
327const MAX_FAST_ATTEMPTS: usize = 5;
329const MAX_ATTEMPTS: usize = MAX_FAST_ATTEMPTS * 2;
331
332impl futures_retry::ErrorHandler<anyhow::Error> for PoolErrorHandler {
333 type OutError = anyhow::Error;
334
335 fn handle(&mut self, attempt: usize, err: anyhow::Error) -> RetryPolicy<Self::OutError> {
336 if attempt >= MAX_ATTEMPTS {
337 RetryPolicy::ForwardError(err)
338 } else if attempt >= MAX_FAST_ATTEMPTS {
339 RetryPolicy::WaitRetry(Duration::from_secs(1))
340 } else {
341 RetryPolicy::Repeat
342 }
343 }
344}
345
346pub trait EvaluateContext {
347 type InfoMessage: DeserializeOwned;
348 type RequestMessage: DeserializeOwned;
349 type ResponseMessage: Serialize;
350 type State: Default;
351
352 fn compute(self, sender: Vc<JavaScriptStreamSender>)
353 -> impl Future<Output = Result<()>> + Send;
354 fn pool(&self) -> OperationVc<NodeJsPool>;
355 fn keep_alive(&self) -> bool {
356 false
357 }
358 fn args(&self) -> &[ResolvedVc<JsonValue>];
359 fn cwd(&self) -> Vc<FileSystemPath>;
360 fn emit_error(
361 &self,
362 error: StructuredError,
363 pool: &NodeJsPool,
364 ) -> impl Future<Output = Result<()>> + Send;
365 fn info(
366 &self,
367 state: &mut Self::State,
368 data: Self::InfoMessage,
369 pool: &NodeJsPool,
370 ) -> impl Future<Output = Result<()>> + Send;
371 fn request(
372 &self,
373 state: &mut Self::State,
374 data: Self::RequestMessage,
375 pool: &NodeJsPool,
376 ) -> impl Future<Output = Result<Self::ResponseMessage>> + Send;
377 fn finish(
378 &self,
379 state: Self::State,
380 pool: &NodeJsPool,
381 ) -> impl Future<Output = Result<()>> + Send;
382}
383
384pub async fn custom_evaluate(
385 evaluate_context: impl EvaluateContext,
386) -> Result<Vc<JavaScriptEvaluation>> {
387 prevent_gc();
390
391 let cell = turbo_tasks::macro_helpers::find_cell_by_type(
397 <JavaScriptEvaluation as VcValueType>::get_value_type_id(),
398 );
399
400 let (sender, receiver) = unbounded();
403 cell.update(JavaScriptEvaluation(JavaScriptStream::new_open(
404 vec![],
405 Box::new(receiver),
406 )));
407 let initial = Mutex::new(Some(sender));
408
409 evaluate_context
411 .compute(
412 JavaScriptStreamSender {
413 get: Box::new(move || {
414 if let Some(sender) = initial.lock().take() {
415 sender
416 } else {
417 let (sender, receiver) = unbounded();
420 cell.update(JavaScriptEvaluation(JavaScriptStream::new_open(
421 vec![],
422 Box::new(receiver),
423 )));
424 sender
425 }
426 }),
427 }
428 .cell(),
429 )
430 .await?;
431
432 let raw: RawVc = cell.into();
433 Ok(raw.into())
434}
435
436#[turbo_tasks::function]
439pub async fn evaluate(
440 module_asset: ResolvedVc<Box<dyn Module>>,
441 cwd: FileSystemPath,
442 env: ResolvedVc<Box<dyn ProcessEnv>>,
443 context_source_for_issue: ResolvedVc<Box<dyn Source>>,
444 asset_context: ResolvedVc<Box<dyn AssetContext>>,
445 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
446 runtime_entries: Option<ResolvedVc<EvaluatableAssets>>,
447 args: Vec<ResolvedVc<JsonValue>>,
448 additional_invalidation: ResolvedVc<Completion>,
449 debug: bool,
450) -> Result<Vc<JavaScriptEvaluation>> {
451 custom_evaluate(BasicEvaluateContext {
452 module_asset,
453 cwd,
454 env,
455 context_source_for_issue,
456 asset_context,
457 chunking_context,
458 runtime_entries,
459 args,
460 additional_invalidation,
461 debug,
462 })
463 .await
464}
465
466pub async fn compute(
467 evaluate_context: impl EvaluateContext,
468 sender: Vc<JavaScriptStreamSender>,
469) -> Result<Vc<()>> {
470 mark_finished();
471 let Ok(sender) = sender.await else {
472 return Ok(Default::default());
474 };
475
476 let stream = generator! {
477 let pool_op = evaluate_context.pool();
478 let mut state = Default::default();
479
480 let pool = pool_op.read_strongly_consistent().await?;
483
484 let args = evaluate_context.args().iter().try_join().await?;
485 let kill = !evaluate_context.keep_alive();
488
489 let (mut operation, _) = FutureRetry::new(
495 || async {
496 let mut operation = pool.operation().await?;
497 operation
498 .send(EvalJavaScriptOutgoingMessage::Evaluate {
499 args: args.iter().map(|v| &**v).collect(),
500 })
501 .await?;
502 Ok(operation)
503 },
504 PoolErrorHandler,
505 )
506 .await
507 .map_err(|(e, _)| e)?;
508
509 loop {
513 let output = pull_operation(&mut operation, &pool, &evaluate_context, &mut state).await?;
514
515 match output {
516 LoopResult::Continue(data) => {
517 yield data.into();
518 }
519 LoopResult::Break(Ok(Some(data))) => {
520 yield data.into();
521 break;
522 }
523 LoopResult::Break(Err(e)) => {
524 let error = print_error(e, &pool, &evaluate_context).await?;
525 Err(anyhow!("Node.js evaluation failed: {}", error))?;
526 break;
527 }
528 LoopResult::Break(Ok(None)) => {
529 break;
530 }
531 }
532 }
533
534 evaluate_context.finish(state, &pool).await?;
535
536 if kill {
537 operation.wait_or_kill().await?;
538 }
539 };
540
541 let mut sender = (sender.get)();
542 pin_mut!(stream);
543 while let Some(value) = stream.next().await {
544 if sender.send(value).await.is_err() {
545 return Ok(Default::default());
546 }
547 if sender.flush().await.is_err() {
548 return Ok(Default::default());
549 }
550 }
551
552 Ok(Default::default())
553}
554
555async fn pull_operation<T: EvaluateContext>(
558 operation: &mut NodeJsOperation,
559 pool: &NodeJsPool,
560 evaluate_context: &T,
561 state: &mut T::State,
562) -> Result<LoopResult> {
563 let guard = duration_span!("Node.js evaluation");
564
565 let output = loop {
566 match operation.recv().await? {
567 EvalJavaScriptIncomingMessage::Error(error) => {
568 evaluate_context.emit_error(error, pool).await?;
569 operation.disallow_reuse();
571 break ControlFlow::Break(Ok(None));
573 }
574 EvalJavaScriptIncomingMessage::End { data } => break ControlFlow::Break(Ok(data)),
575 EvalJavaScriptIncomingMessage::Info { data } => {
576 evaluate_context
577 .info(state, serde_json::from_value(data)?, pool)
578 .await?;
579 }
580 EvalJavaScriptIncomingMessage::Request { id, data } => {
581 match evaluate_context
582 .request(state, serde_json::from_value(data)?, pool)
583 .await
584 {
585 Ok(response) => {
586 operation
587 .send(EvalJavaScriptOutgoingMessage::Result {
588 id,
589 error: None,
590 data: Some(serde_json::to_value(response)?),
591 })
592 .await?;
593 }
594 Err(e) => {
595 operation
596 .send(EvalJavaScriptOutgoingMessage::Result {
597 id,
598 error: Some(PrettyPrintError(&e).to_string()),
599 data: None,
600 })
601 .await?;
602 }
603 }
604 }
605 }
606 };
607 drop(guard);
608
609 Ok(output)
610}
611
612#[turbo_tasks::function]
613async fn basic_compute(
614 evaluate_context: BasicEvaluateContext,
615 sender: Vc<JavaScriptStreamSender>,
616) -> Result<Vc<()>> {
617 compute(evaluate_context, sender).await
618}
619
620#[derive(Clone, PartialEq, Eq, Hash, TaskInput, Debug, Serialize, Deserialize, TraceRawVcs)]
621struct BasicEvaluateContext {
622 module_asset: ResolvedVc<Box<dyn Module>>,
623 cwd: FileSystemPath,
624 env: ResolvedVc<Box<dyn ProcessEnv>>,
625 context_source_for_issue: ResolvedVc<Box<dyn Source>>,
626 asset_context: ResolvedVc<Box<dyn AssetContext>>,
627 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
628 runtime_entries: Option<ResolvedVc<EvaluatableAssets>>,
629 args: Vec<ResolvedVc<JsonValue>>,
630 additional_invalidation: ResolvedVc<Completion>,
631 debug: bool,
632}
633
634impl EvaluateContext for BasicEvaluateContext {
635 type InfoMessage = ();
636 type RequestMessage = ();
637 type ResponseMessage = ();
638 type State = ();
639
640 async fn compute(self, sender: Vc<JavaScriptStreamSender>) -> Result<()> {
641 basic_compute(self, sender).as_side_effect().await
642 }
643
644 fn pool(&self) -> OperationVc<crate::pool::NodeJsPool> {
645 get_evaluate_pool(
646 self.module_asset,
647 self.cwd.clone(),
648 self.env,
649 self.asset_context,
650 self.chunking_context,
651 self.runtime_entries,
652 self.additional_invalidation,
653 self.debug,
654 EnvVarTracking::WholeEnvTracked,
655 )
656 }
657
658 fn args(&self) -> &[ResolvedVc<serde_json::Value>] {
659 &self.args
660 }
661
662 fn cwd(&self) -> Vc<turbo_tasks_fs::FileSystemPath> {
663 self.cwd.clone().cell()
664 }
665
666 fn keep_alive(&self) -> bool {
667 !self.args.is_empty()
668 }
669
670 async fn emit_error(&self, error: StructuredError, pool: &NodeJsPool) -> Result<()> {
671 EvaluationIssue {
672 error,
673 source: IssueSource::from_source_only(self.context_source_for_issue),
674 assets_for_source_mapping: pool.assets_for_source_mapping,
675 assets_root: pool.assets_root.clone(),
676 root_path: self.chunking_context.root_path().owned().await?,
677 }
678 .resolved_cell()
679 .emit();
680 Ok(())
681 }
682
683 async fn info(
684 &self,
685 _state: &mut Self::State,
686 _data: Self::InfoMessage,
687 _pool: &NodeJsPool,
688 ) -> Result<()> {
689 bail!("BasicEvaluateContext does not support info messages")
690 }
691
692 async fn request(
693 &self,
694 _state: &mut Self::State,
695 _data: Self::RequestMessage,
696 _pool: &NodeJsPool,
697 ) -> Result<Self::ResponseMessage> {
698 bail!("BasicEvaluateContext does not support request messages")
699 }
700
701 async fn finish(&self, _state: Self::State, _pool: &NodeJsPool) -> Result<()> {
702 Ok(())
703 }
704}
705
706pub fn scale_zero() {
707 NodeJsPool::scale_zero();
708}
709
710pub fn scale_down() {
711 NodeJsPool::scale_down();
712}
713
714async fn print_error(
715 error: StructuredError,
716 pool: &NodeJsPool,
717 evaluate_context: &impl EvaluateContext,
718) -> Result<String> {
719 error
720 .print(
721 *pool.assets_for_source_mapping,
722 pool.assets_root.clone(),
723 evaluate_context.cwd().owned().await?,
724 FormattingMode::Plain,
725 )
726 .await
727}
728#[turbo_tasks::value(shared)]
730pub struct EvaluationIssue {
731 pub source: IssueSource,
732 pub error: StructuredError,
733 pub assets_for_source_mapping: ResolvedVc<AssetsForSourceMapping>,
734 pub assets_root: FileSystemPath,
735 pub root_path: FileSystemPath,
736}
737
738#[turbo_tasks::value_impl]
739impl Issue for EvaluationIssue {
740 #[turbo_tasks::function]
741 fn title(&self) -> Vc<StyledString> {
742 StyledString::Text(rcstr!("Error evaluating Node.js code")).cell()
743 }
744
745 #[turbo_tasks::function]
746 fn stage(&self) -> Vc<IssueStage> {
747 IssueStage::Transform.into()
748 }
749
750 #[turbo_tasks::function]
751 fn file_path(&self) -> Vc<FileSystemPath> {
752 self.source.file_path()
753 }
754
755 #[turbo_tasks::function]
756 async fn description(&self) -> Result<Vc<OptionStyledString>> {
757 Ok(Vc::cell(Some(
758 StyledString::Text(
759 self.error
760 .print(
761 *self.assets_for_source_mapping,
762 self.assets_root.clone(),
763 self.root_path.clone(),
764 FormattingMode::Plain,
765 )
766 .await?
767 .into(),
768 )
769 .resolved_cell(),
770 )))
771 }
772
773 #[turbo_tasks::function]
774 fn source(&self) -> Vc<OptionIssueSource> {
775 Vc::cell(Some(self.source))
776 }
777}