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)]
242pub 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 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 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
328const MAX_FAST_ATTEMPTS: usize = 5;
330const 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 prevent_gc();
391
392 let cell = turbo_tasks::macro_helpers::find_cell_by_type(
398 <JavaScriptEvaluation as VcValueType>::get_value_type_id(),
399 );
400
401 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 evaluate_context
412 .compute(
413 JavaScriptStreamSender {
414 get: Box::new(move || {
415 if let Some(sender) = initial.lock().take() {
416 sender
417 } else {
418 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#[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 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 let pool = pool_op.read_strongly_consistent().await?;
484
485 let args = evaluate_context.args().iter().try_join().await?;
486 let kill = !evaluate_context.keep_alive();
489
490 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 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
556async 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 operation.disallow_reuse();
572 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#[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}