1use std::{borrow::Cow, fmt::Display, str::FromStr};
2
3use anyhow::{Result, bail};
4use bincode::{Decode, Encode};
5use next_taskless::{expand_next_js_template, expand_next_js_template_no_imports};
6use serde::{Deserialize, de::DeserializeOwned};
7use turbo_rcstr::{RcStr, rcstr};
8use turbo_tasks::{FxIndexMap, NonLocalValue, Vc, fxindexset, trace::TraceRawVcs, turbobail};
9use turbo_tasks_fs::{File, FileContent, FileJsonContent, FileSystem, FileSystemPath, rope::Rope};
10use turbopack::module_options::RuleCondition;
11use turbopack_core::{
12 asset::AssetContent,
13 compile_time_info::{
14 CompileTimeDefineValue, CompileTimeDefines, DefinableNameSegment, FreeVarReference,
15 FreeVarReferences,
16 },
17 condition::ContextCondition,
18 issue::IssueSeverity,
19 source::Source,
20 virtual_source::VirtualSource,
21};
22
23use crate::{
24 embed_js::next_js_fs, next_config::NextConfig, next_import_map::get_next_package,
25 next_manifests::ProxyMatcher, next_shared::webpack_rules::WebpackLoaderBuiltinCondition,
26};
27
28const NEXT_TEMPLATE_PATH: &str = "dist/esm/build/templates";
29
30#[turbo_tasks::value(transparent)]
33pub struct OptionEnvMap(
34 #[turbo_tasks(trace_ignore)]
35 #[bincode(with = "turbo_bincode::indexmap")]
36 FxIndexMap<RcStr, Option<RcStr>>,
37);
38
39pub fn defines(define_env: &FxIndexMap<RcStr, Option<RcStr>>) -> CompileTimeDefines {
40 let mut defines = FxIndexMap::default();
41
42 for (k, v) in define_env {
43 defines
44 .entry(
45 k.split('.')
46 .map(|s| DefinableNameSegment::Name(s.into()))
47 .collect::<Vec<_>>(),
48 )
49 .or_insert_with(|| {
50 if let Some(v) = v {
51 let val = serde_json::Value::from_str(v);
52 match val {
53 Ok(v) => v.into(),
54 _ => CompileTimeDefineValue::Evaluate(v.clone()),
55 }
56 } else {
57 CompileTimeDefineValue::Undefined
58 }
59 });
60 }
61
62 CompileTimeDefines(defines)
63}
64
65pub fn free_var_references_with_vercel_system_env_warnings(
67 defines: CompileTimeDefines,
68 severity: IssueSeverity,
69) -> FreeVarReferences {
70 let entries = defines
107 .0
108 .into_iter()
109 .map(|(k, value)| (k, FreeVarReference::Value(value)));
110
111 fn wrap_report_next_public_usage(
112 public_env_var: &str,
113 inner: Option<Box<FreeVarReference>>,
114 severity: IssueSeverity,
115 ) -> FreeVarReference {
116 let message = match public_env_var {
117 "NEXT_PUBLIC_NEXT_DEPLOYMENT_ID" | "NEXT_PUBLIC_VERCEL_DEPLOYMENT_ID" => {
118 rcstr!(
119 "The deployment id is being inlined.\nThis variable changes frequently, \
120 causing slower deploy times and worse browser client-side caching. Use \
121 `process.env.NEXT_DEPLOYMENT_ID` instead to access the same value without \
122 inlining, for faster deploy times and better browser client-side caching."
123 )
124 }
125 "NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA" => {
126 rcstr!(
127 "The commit hash is being inlined.\nThis variable changes frequently, causing \
128 slower deploy times and worse browser client-side caching. Consider using \
129 `process.env.NEXT_DEPLOYMENT_ID` to identify a deployment. Alternatively, \
130 use `process.env.VERCEL_GIT_COMMIT_SHA` in server side code and for browser \
131 code, remove it."
132 )
133 }
134 "NEXT_PUBLIC_VERCEL_BRANCH_URL" | "NEXT_PUBLIC_VERCEL_URL" => format!(
135 "The deployment url system environment variable is being inlined.\nThis variable \
136 changes frequently, causing slower deploy times and worse browser client-side \
137 caching. For server-side code, replace with `process.env.{}` and for browser \
138 code, read `location.host` instead.",
139 public_env_var.strip_prefix("NEXT_PUBLIC_").unwrap(),
140 )
141 .into(),
142 _ => format!(
143 "A system environment variable is being inlined.\nThis variable changes \
144 frequently, causing slower deploy times and worse browser client-side caching. \
145 For server-side code, replace with `process.env.{}` and for browser code, try to \
146 remove it.",
147 public_env_var.strip_prefix("NEXT_PUBLIC_").unwrap(),
148 )
149 .into(),
150 };
151 FreeVarReference::ReportUsage {
152 message,
153 severity,
154 inner,
155 }
156 }
157
158 let mut list = fxindexset!(
159 "NEXT_PUBLIC_NEXT_DEPLOYMENT_ID",
160 "NEXT_PUBLIC_VERCEL_BRANCH_URL",
161 "NEXT_PUBLIC_VERCEL_DEPLOYMENT_ID",
162 "NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_LOGIN",
163 "NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_NAME",
164 "NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE",
165 "NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF",
166 "NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA",
167 "NEXT_PUBLIC_VERCEL_GIT_PREVIOUS_SHA",
168 "NEXT_PUBLIC_VERCEL_GIT_PULL_REQUEST_ID",
169 "NEXT_PUBLIC_VERCEL_URL",
170 );
171
172 let mut entries: FxIndexMap<_, _> = entries
173 .map(|(k, value)| {
174 let value = if let &[
175 DefinableNameSegment::Name(a),
176 DefinableNameSegment::Name(b),
177 DefinableNameSegment::Name(public_env_var),
178 ] = &&*k
179 && a == "process"
180 && b == "env"
181 && list.swap_remove(&**public_env_var)
182 {
183 wrap_report_next_public_usage(public_env_var, Some(Box::new(value)), severity)
184 } else {
185 value
186 };
187 (k, value)
188 })
189 .collect();
190
191 for public_env_var in list {
193 entries.insert(
194 vec![
195 rcstr!("process").into(),
196 rcstr!("env").into(),
197 DefinableNameSegment::Name(public_env_var.into()),
198 ],
199 wrap_report_next_public_usage(public_env_var, None, severity),
200 );
201 }
202
203 FreeVarReferences(entries)
204}
205
206#[turbo_tasks::task_input]
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TraceRawVcs, Encode, Decode)]
208pub enum PathType {
209 PagesPage,
210 PagesApi,
211 Data,
212}
213
214#[turbo_tasks::function]
216pub async fn pathname_for_path(
217 server_root: FileSystemPath,
218 server_path: FileSystemPath,
219 path_ty: PathType,
220) -> Result<Vc<RcStr>> {
221 let server_path_value = server_path.clone();
222 let path = if let Some(path) = server_root.get_path_to(&server_path_value) {
223 path
224 } else {
225 turbobail!("server_path ({server_path}) is not in server_root ({server_root})");
226 };
227 let path = match (path_ty, path) {
228 (PathType::Data, "") => rcstr!("/index"),
230 (_, path) => format!("/{path}").into(),
233 };
234
235 Ok(Vc::cell(path))
236}
237
238pub fn get_asset_prefix_from_pathname(pathname: &str) -> String {
242 if pathname == "/" {
243 "/index".to_string()
244 } else if pathname == "/index" || pathname.starts_with("/index/") {
245 format!("/index{pathname}")
246 } else {
247 pathname.to_string()
248 }
249}
250
251pub fn get_asset_path_from_pathname(pathname: &str, ext: &str) -> String {
253 format!("{}{}", get_asset_prefix_from_pathname(pathname), ext)
254}
255
256#[turbo_tasks::function]
257pub async fn get_transpiled_packages(
258 next_config: Vc<NextConfig>,
259 project_path: FileSystemPath,
260) -> Result<Vc<Vec<RcStr>>> {
261 let mut transpile_packages: Vec<RcStr> = next_config.transpile_packages().owned().await?;
262
263 let default_transpiled_packages: Vec<RcStr> = load_next_js_json_file(
264 project_path,
265 rcstr!("dist/lib/default-transpiled-packages.json"),
266 )
267 .await?;
268
269 transpile_packages.extend(default_transpiled_packages.iter().cloned());
270
271 Ok(Vc::cell(transpile_packages))
272}
273
274pub async fn foreign_code_context_condition(
275 next_config: Vc<NextConfig>,
276 project_path: FileSystemPath,
277) -> Result<ContextCondition> {
278 let transpiled_packages = get_transpiled_packages(next_config, project_path.clone()).await?;
279
280 let not_next_template_dir = ContextCondition::not(ContextCondition::InPath(
285 get_next_package(project_path.clone())
286 .await?
287 .join(NEXT_TEMPLATE_PATH)?,
288 ));
289
290 let result = ContextCondition::all(vec![
291 ContextCondition::InNodeModules,
292 not_next_template_dir,
293 ContextCondition::not(ContextCondition::any(
294 transpiled_packages
295 .iter()
296 .map(|package| ContextCondition::InDirectory(format!("node_modules/{package}")))
297 .collect(),
298 )),
299 ]);
300 Ok(result)
301}
302
303pub async fn internal_assets_conditions() -> Result<ContextCondition> {
310 Ok(ContextCondition::any(vec![
311 ContextCondition::InPath(next_js_fs().root().owned().await?),
312 ContextCondition::InPath(
313 turbopack_ecmascript_runtime::embed_fs()
314 .root()
315 .owned()
316 .await?,
317 ),
318 ContextCondition::InPath(turbopack_node::embed_js::embed_fs().root().owned().await?),
319 ]))
320}
321
322pub fn app_function_name(page: impl Display) -> String {
323 format!("app{page}")
324}
325pub fn pages_function_name(page: impl Display) -> String {
326 format!("pages{page}")
327}
328
329#[turbo_tasks::task_input]
330#[derive(
331 Default,
332 PartialEq,
333 Eq,
334 Clone,
335 Copy,
336 Debug,
337 TraceRawVcs,
338 Deserialize,
339 Hash,
340 PartialOrd,
341 Ord,
342 Encode,
343 Decode,
344)]
345#[serde(rename_all = "lowercase")]
346pub enum NextRuntime {
347 #[default]
348 NodeJs,
349 #[serde(alias = "experimental-edge")]
350 Edge,
351}
352
353impl NextRuntime {
354 pub fn webpack_loader_conditions(&self) -> impl Iterator<Item = WebpackLoaderBuiltinCondition> {
357 match self {
358 NextRuntime::NodeJs => [WebpackLoaderBuiltinCondition::Node],
359 NextRuntime::Edge => [WebpackLoaderBuiltinCondition::EdgeLight],
360 }
361 .into_iter()
362 }
363
364 pub fn custom_resolve_conditions(&self) -> impl Iterator<Item = RcStr> {
366 match self {
367 NextRuntime::NodeJs => [rcstr!("node")],
368 NextRuntime::Edge => [rcstr!("edge-light")],
369 }
370 .into_iter()
371 }
372}
373
374#[derive(PartialEq, Eq, Clone, Debug, TraceRawVcs, NonLocalValue, Encode, Decode)]
375pub enum MiddlewareMatcherKind {
376 Str(String),
377 Matcher(ProxyMatcher),
378}
379
380pub async fn load_next_js_template<'b>(
383 template_path: &'b str,
384 project_path: FileSystemPath,
385 replacements: impl IntoIterator<Item = (&'b str, &'b str)>,
386 injections: impl IntoIterator<Item = (&'b str, &'b str)>,
387 imports: impl IntoIterator<Item = (&'b str, Option<&'b str>)>,
388) -> Result<Vc<Box<dyn Source>>> {
389 let template_path = virtual_next_js_template_path(project_path.clone(), template_path).await?;
390
391 let content = file_content_rope(template_path.read()).await?;
392 let content = content.to_str()?;
393
394 let package_root = get_next_package(project_path).await?;
395
396 let content = expand_next_js_template(
397 &content,
398 &template_path.path,
399 &package_root.path,
400 replacements,
401 injections,
402 imports,
403 )?;
404
405 let file = File::from(content);
406 let source = VirtualSource::new(
407 template_path,
408 AssetContent::file(FileContent::Content(file).cell()),
409 );
410
411 Ok(Vc::upcast(source))
412}
413
414pub async fn load_next_js_template_no_imports(
418 template_path: &str,
419 project_path: FileSystemPath,
420 replacements: &[(&str, &str)],
421 injections: &[(&str, &str)],
422 imports: &[(&str, Option<&str>)],
423) -> Result<Vc<Box<dyn Source>>> {
424 let template_path = virtual_next_js_template_path(project_path.clone(), template_path).await?;
425
426 let content = file_content_rope(template_path.read()).await?;
427 let content = content.to_str()?;
428
429 let package_root = get_next_package(project_path).await?;
430
431 let content = expand_next_js_template_no_imports(
432 &content,
433 &template_path.path,
434 &package_root.path,
435 replacements.iter().copied(),
436 injections.iter().copied(),
437 imports.iter().copied(),
438 )?;
439
440 let file = File::from(content);
441 let source = VirtualSource::new(
442 template_path,
443 AssetContent::file(FileContent::Content(file).cell()),
444 );
445
446 Ok(Vc::upcast(source))
447}
448
449#[turbo_tasks::function]
450pub async fn file_content_rope(content: Vc<FileContent>) -> Result<Vc<Rope>> {
451 let content = &*content.await?;
452
453 let FileContent::Content(file) = content else {
454 bail!("Expected file content for file");
455 };
456
457 Ok(file.content().to_owned().cell())
458}
459
460async fn virtual_next_js_template_path(
461 project_path: FileSystemPath,
462 file: &str,
463) -> Result<FileSystemPath> {
464 debug_assert!(!file.contains('/'));
465 get_next_package(project_path)
466 .await?
467 .join(&format!("{NEXT_TEMPLATE_PATH}/{file}"))
468}
469
470pub async fn load_next_js_json_file<T: DeserializeOwned>(
471 project_path: FileSystemPath,
472 sub_path: RcStr,
473) -> Result<T> {
474 let file_path = get_next_package(project_path.clone())
475 .await?
476 .join(&sub_path)?;
477
478 let content = &*file_path.read().await?;
479
480 match content.parse_json_ref() {
481 FileJsonContent::Unparsable(e) => bail!("File is not valid JSON: {e}"),
482 FileJsonContent::NotFound => turbobail!("File not found: {file_path:?}",),
483 FileJsonContent::Content(value) => Ok(serde_json::from_value(value)?),
484 }
485}
486
487pub async fn load_next_js_jsonc_file<T: DeserializeOwned>(
488 project_path: FileSystemPath,
489 sub_path: RcStr,
490) -> Result<T> {
491 let file_path = get_next_package(project_path.clone())
492 .await?
493 .join(&sub_path)?;
494
495 let content = &*file_path.read().await?;
496
497 match content.parse_json_with_comments_ref() {
498 FileJsonContent::Unparsable(e) => turbobail!("File is not valid JSON: {e}"),
499 FileJsonContent::NotFound => turbobail!("File not found: {file_path}",),
500 FileJsonContent::Content(value) => Ok(serde_json::from_value(value)?),
501 }
502}
503
504pub fn styles_rule_condition() -> RuleCondition {
505 RuleCondition::any(vec![
506 RuleCondition::all(vec![
507 RuleCondition::ResourcePathEndsWith(".css".into()),
508 RuleCondition::not(RuleCondition::ResourcePathEndsWith(".module.css".into())),
509 ]),
510 RuleCondition::all(vec![
511 RuleCondition::ResourcePathEndsWith(".sass".into()),
512 RuleCondition::not(RuleCondition::ResourcePathEndsWith(".module.sass".into())),
513 ]),
514 RuleCondition::all(vec![
515 RuleCondition::ResourcePathEndsWith(".scss".into()),
516 RuleCondition::not(RuleCondition::ResourcePathEndsWith(".module.scss".into())),
517 ]),
518 RuleCondition::all(vec![
519 RuleCondition::ContentTypeStartsWith("text/css".into()),
520 RuleCondition::not(RuleCondition::ContentTypeStartsWith(
521 "text/css+module".into(),
522 )),
523 ]),
524 RuleCondition::all(vec![
525 RuleCondition::ContentTypeStartsWith("text/sass".into()),
526 RuleCondition::not(RuleCondition::ContentTypeStartsWith(
527 "text/sass+module".into(),
528 )),
529 ]),
530 RuleCondition::all(vec![
531 RuleCondition::ContentTypeStartsWith("text/scss".into()),
532 RuleCondition::not(RuleCondition::ContentTypeStartsWith(
533 "text/scss+module".into(),
534 )),
535 ]),
536 ])
537}
538pub fn module_styles_rule_condition() -> RuleCondition {
539 RuleCondition::any(vec![
540 RuleCondition::ResourcePathEndsWith(".module.css".into()),
541 RuleCondition::ResourcePathEndsWith(".module.scss".into()),
542 RuleCondition::ResourcePathEndsWith(".module.sass".into()),
543 RuleCondition::ContentTypeStartsWith("text/css+module".into()),
544 RuleCondition::ContentTypeStartsWith("text/sass+module".into()),
545 RuleCondition::ContentTypeStartsWith("text/scss+module".into()),
546 ])
547}
548
549pub fn worker_forwarded_globals() -> Vec<RcStr> {
553 vec![
554 rcstr!("NEXT_DEPLOYMENT_ID"),
555 rcstr!("NEXT_CLIENT_ASSET_SUFFIX"),
556 ]
557}
558
559pub fn relativize_glob<'a>(
565 glob: &'a str,
566 relative_to: &FileSystemPath,
567) -> Result<(&'a str, FileSystemPath)> {
568 let mut relative_to = Cow::Borrowed(relative_to);
569 let mut processed_glob = glob;
570 loop {
571 if let Some(stripped) = processed_glob.strip_prefix("../") {
572 if relative_to.path.is_empty() {
573 bail!(
574 "glob '{glob}' is invalid, it has a prefix that navigates out of the project \
575 root"
576 );
577 }
578 relative_to = Cow::Owned(relative_to.parent());
579 processed_glob = stripped;
580 } else if let Some(stripped) = processed_glob.strip_prefix("./") {
581 processed_glob = stripped;
582 } else {
583 break;
584 }
585 }
586 Ok((processed_glob, relative_to.into_owned()))
587}
588
589#[cfg(test)]
590mod tests {
591 use turbo_tasks::ResolvedVc;
592 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
593 use turbo_tasks_fs::{FileSystemPath, NullFileSystem};
594
595 use super::*;
596
597 fn create_test_fs_path(path: &str) -> FileSystemPath {
598 FileSystemPath {
599 fs: ResolvedVc::upcast(NullFileSystem {}.resolved_cell()),
600 path: path.into(),
601 }
602 }
603
604 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
605 async fn test_relativize_glob_normal_patterns() {
606 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
607 BackendOptions::default(),
608 noop_backing_storage(),
609 ));
610 tt.run_once(async {
611 let base_path = create_test_fs_path("project/src");
613
614 let (glob, path) = relativize_glob("*.js", &base_path).unwrap();
615 assert_eq!(glob, "*.js");
616 assert_eq!(path.path.as_str(), "project/src");
617
618 let (glob, path) = relativize_glob("components/**/*.tsx", &base_path).unwrap();
619 assert_eq!(glob, "components/**/*.tsx");
620 assert_eq!(path.path.as_str(), "project/src");
621
622 let (glob, path) = relativize_glob("lib/utils.ts", &base_path).unwrap();
623 assert_eq!(glob, "lib/utils.ts");
624 assert_eq!(path.path.as_str(), "project/src");
625 Ok(())
626 })
627 .await
628 .unwrap();
629 }
630
631 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
632 async fn test_relativize_glob_current_directory_prefix() {
633 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
634 BackendOptions::default(),
635 noop_backing_storage(),
636 ));
637 tt.run_once(async {
638 let base_path = create_test_fs_path("project/src");
639
640 let (glob, path) = relativize_glob("./components/*.tsx", &base_path).unwrap();
642 assert_eq!(glob, "components/*.tsx");
643 assert_eq!(path.path.as_str(), "project/src");
644
645 let (glob, path) = relativize_glob("././utils.js", &base_path).unwrap();
647 assert_eq!(glob, "utils.js");
648 assert_eq!(path.path.as_str(), "project/src");
649
650 let (glob, path) = relativize_glob("./lib/**/*.{js,ts}", &base_path).unwrap();
652 assert_eq!(glob, "lib/**/*.{js,ts}");
653 assert_eq!(path.path.as_str(), "project/src");
654 Ok(())
655 })
656 .await
657 .unwrap();
658 }
659
660 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
661 async fn test_relativize_glob_parent_directory_navigation() {
662 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
663 BackendOptions::default(),
664 noop_backing_storage(),
665 ));
666 tt.run_once(async {
667 let base_path = create_test_fs_path("project/src/components");
668
669 let (glob, path) = relativize_glob("../utils/*.js", &base_path).unwrap();
671 assert_eq!(glob, "utils/*.js");
672 assert_eq!(path.path.as_str(), "project/src");
673
674 let (glob, path) = relativize_glob("../../lib/*.ts", &base_path).unwrap();
676 assert_eq!(glob, "lib/*.ts");
677 assert_eq!(path.path.as_str(), "project");
678
679 let (glob, path) = relativize_glob("../../../external/**/*.json", &base_path).unwrap();
681 assert_eq!(glob, "external/**/*.json");
682 assert_eq!(path.path.as_str(), "");
683 Ok(())
684 })
685 .await
686 .unwrap();
687 }
688
689 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
690 async fn test_relativize_glob_mixed_prefixes() {
691 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
692 BackendOptions::default(),
693 noop_backing_storage(),
694 ));
695 tt.run_once(async {
696 let base_path = create_test_fs_path("project/src/components");
697
698 let (glob, path) = relativize_glob(".././utils/*.js", &base_path).unwrap();
700 assert_eq!(glob, "utils/*.js");
701 assert_eq!(path.path.as_str(), "project/src");
702
703 let (glob, path) = relativize_glob("./../lib/*.ts", &base_path).unwrap();
705 assert_eq!(glob, "lib/*.ts");
706 assert_eq!(path.path.as_str(), "project/src");
707
708 let (glob, path) = relativize_glob("././../.././external/*.json", &base_path).unwrap();
710 assert_eq!(glob, "external/*.json");
711 assert_eq!(path.path.as_str(), "project");
712 Ok(())
713 })
714 .await
715 .unwrap();
716 }
717
718 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
719 async fn test_relativize_glob_error_navigation_out_of_root() {
720 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
721 BackendOptions::default(),
722 noop_backing_storage(),
723 ));
724 tt.run_once(async {
725 let empty_path = create_test_fs_path("");
727 let result = relativize_glob("../outside.js", &empty_path);
728 assert!(result.is_err());
729 assert!(
730 result
731 .unwrap_err()
732 .to_string()
733 .contains("navigates out of the project root")
734 );
735
736 let shallow_path = create_test_fs_path("project");
738 let result = relativize_glob("../../outside.js", &shallow_path);
739 assert!(result.is_err());
740 assert!(
741 result
742 .unwrap_err()
743 .to_string()
744 .contains("navigates out of the project root")
745 );
746
747 let base_path = create_test_fs_path("a/b");
749 let result = relativize_glob("../../../outside.js", &base_path);
750 assert!(result.is_err());
751 assert!(
752 result
753 .unwrap_err()
754 .to_string()
755 .contains("navigates out of the project root")
756 );
757 Ok(())
758 })
759 .await
760 .unwrap();
761 }
762}