@faststore/core 3.0.58 → 3.0.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +4 -4
  3. package/.next/build-manifest.json +31 -31
  4. package/.next/cache/.tsbuildinfo +1 -1
  5. package/.next/cache/config.json +3 -3
  6. package/.next/cache/eslint/.cache_1gneedd +1 -1
  7. package/.next/cache/fetch-cache/50912854cb7c781522a6ff8792d714e549515fcbbbfd660761961b06afe01c07 +1 -1
  8. package/.next/cache/webpack/client-production/0.pack +0 -0
  9. package/.next/cache/webpack/client-production/index.pack +0 -0
  10. package/.next/cache/webpack/server-production/0.pack +0 -0
  11. package/.next/cache/webpack/server-production/index.pack +0 -0
  12. package/.next/next-minimal-server.js.nft.json +1 -1
  13. package/.next/next-server.js.nft.json +1 -1
  14. package/.next/prerender-manifest.js +1 -1
  15. package/.next/prerender-manifest.json +1 -1
  16. package/.next/react-loadable-manifest.json +3 -3
  17. package/.next/routes-manifest.json +1 -1
  18. package/.next/server/app/_not-found.html +2 -2
  19. package/.next/server/app/_not-found.js +1 -1
  20. package/.next/server/app/_not-found.js.nft.json +1 -1
  21. package/.next/server/app/_not-found.rsc +4 -4
  22. package/.next/server/app/_not-found_client-reference-manifest.js +1 -1
  23. package/.next/server/app/fs-next-update/page.js +1 -1
  24. package/.next/server/app/fs-next-update/page.js.nft.json +1 -1
  25. package/.next/server/app/fs-next-update/page_client-reference-manifest.js +1 -1
  26. package/.next/server/app/fs-next-update.html +2 -2
  27. package/.next/server/app/fs-next-update.rsc +4 -4
  28. package/.next/server/chunks/1481.js +2 -2
  29. package/.next/server/chunks/{2317.js → 1889.js} +2 -2
  30. package/.next/server/chunks/2133.js +5 -5
  31. package/.next/server/chunks/{6112.js → 2381.js} +1 -1
  32. package/.next/server/middleware-build-manifest.js +1 -1
  33. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  34. package/.next/server/pages/404.html +2 -2
  35. package/.next/server/pages/404.js.nft.json +1 -1
  36. package/.next/server/pages/500.js.nft.json +1 -1
  37. package/.next/server/pages/[...slug].js +2 -2
  38. package/.next/server/pages/[...slug].js.nft.json +1 -1
  39. package/.next/server/pages/[slug]/p.js +1 -1
  40. package/.next/server/pages/[slug]/p.js.nft.json +1 -1
  41. package/.next/server/pages/_app.js.nft.json +1 -1
  42. package/.next/server/pages/_document.js.nft.json +1 -1
  43. package/.next/server/pages/_error.js.nft.json +1 -1
  44. package/.next/server/pages/account.js.nft.json +1 -1
  45. package/.next/server/pages/api/graphql.js.nft.json +1 -1
  46. package/.next/server/pages/api/health/live.js.nft.json +1 -1
  47. package/.next/server/pages/api/health/ready.js.nft.json +1 -1
  48. package/.next/server/pages/api/preview.js.nft.json +1 -1
  49. package/.next/server/pages/checkout.js.nft.json +1 -1
  50. package/.next/server/pages/en-US/404.html +2 -2
  51. package/.next/server/pages/en-US/404.json +1 -1
  52. package/.next/server/pages/en-US/500.html +2 -2
  53. package/.next/server/pages/en-US/500.json +1 -1
  54. package/.next/server/pages/en-US/account.html +2 -2
  55. package/.next/server/pages/en-US/account.json +1 -1
  56. package/.next/server/pages/en-US/checkout.html +2 -2
  57. package/.next/server/pages/en-US/checkout.json +1 -1
  58. package/.next/server/pages/en-US/login.html +2 -2
  59. package/.next/server/pages/en-US/login.json +1 -1
  60. package/.next/server/pages/en-US/s.html +2 -2
  61. package/.next/server/pages/en-US/s.json +1 -1
  62. package/.next/server/pages/en-US.html +2 -2
  63. package/.next/server/pages/en-US.json +1 -1
  64. package/.next/server/pages/index.js.nft.json +1 -1
  65. package/.next/server/pages/login.js.nft.json +1 -1
  66. package/.next/server/pages/s.js.nft.json +1 -1
  67. package/.next/server/pages-manifest.json +1 -1
  68. package/.next/static/6RpWP2aATNq_ljgOww_7v/_buildManifest.js +1 -0
  69. package/.next/static/chunks/{432-ea0bb3dcebbaab7b.js → 432-292475e2dafbb79e.js} +1 -1
  70. package/.next/static/chunks/{590-2e7ce772420d1e1a.js → 590-da547057f2ae283b.js} +1 -1
  71. package/.next/static/chunks/{667.2cacb6e5980b1219.js → 667.83389641ca332aef.js} +1 -1
  72. package/.next/static/chunks/{722-36fe06b9c486056e.js → 722-ed59bc6f32bb342f.js} +1 -1
  73. package/.next/static/chunks/{724.39212f642dc0d0bf.js → 724.78d40a7affe1d40b.js} +1 -1
  74. package/.next/static/chunks/{853.50e13fd0510fbf80.js → 853.a3933410052d23c8.js} +1 -1
  75. package/.next/static/chunks/app/{layout-aa18388c0586ed1f.js → layout-91b2430897aac937.js} +2 -2
  76. package/.next/static/chunks/pages/[slug]/{p-25a61a48b78901e5.js → p-d7c7044639459d13.js} +1 -1
  77. package/.next/static/chunks/pages/{_app-00cee674a20813ff.js → _app-1930798899758fda.js} +1 -1
  78. package/.next/static/chunks/{webpack-d7ccbda472189c19.js → webpack-a45dc84c9a0ef3fd.js} +1 -1
  79. package/.next/trace +62 -62
  80. package/.turbo/turbo-build.log +4 -4
  81. package/.turbo/turbo-test.log +6 -5
  82. package/package.json +2 -2
  83. package/src/pages/[...slug].tsx +3 -1
  84. package/src/server/cms/index.ts +30 -57
  85. package/src/server/cms/pdp.ts +1 -1
  86. package/src/server/cms/plp.ts +33 -14
  87. package/src/utils/multipleTemplates.ts +178 -0
  88. package/src/utils/utilities.ts +0 -118
  89. package/test/server/cms/index.test.ts +2 -32
  90. package/test/utils/multipleTemplates.test.ts +66 -0
  91. package/.next/static/2K9MWkRja381SNNuLNTU8/_buildManifest.js +0 -1
  92. /package/.next/static/{2K9MWkRja381SNNuLNTU8 → 6RpWP2aATNq_ljgOww_7v}/_ssgManifest.js +0 -0
@@ -1,7 +1,7 @@
1
1
  $ yarn partytown & yarn generate && next build
2
- $ faststore generate-graphql -c
3
2
  $ partytown copylib ./public/~partytown
4
3
  Partytown lib copied to: /home/runner/work/faststore/faststore/packages/core/public/~partytown
4
+ $ faststore generate-graphql -c
5
5
  success - GraphQL schema, types, and optimizations successfully generated 🎉
6
6
  ⚠ No build cache found. Please configure build caching for faster rebuilds. Read more: https://nextjs.org/docs/messages/no-cache
7
7
  Attention: Next.js now collects completely anonymous telemetry regarding usage.
@@ -38,7 +38,7 @@ Route (app) Size First Load JS
38
38
  ├ chunks/472-369461a1f39981d5.js 28.4 kB
39
39
  ├ chunks/fd9d1056-43c43818840d7811.js 51.1 kB
40
40
  ├ chunks/main-app-e13fa67c2c3ceca5.js 230 B
41
- └ chunks/webpack-d7ccbda472189c19.js 2.43 kB
41
+ └ chunks/webpack-a45dc84c9a0ef3fd.js 2.43 kB
42
42
 
43
43
  Route (pages) Size First Load JS
44
44
  ┌ ● / 1.04 kB 148 kB
@@ -61,8 +61,8 @@ Route (pages) Size First Load JS
61
61
  + First Load JS shared by all 96.9 kB
62
62
  ├ chunks/framework-21e9365486ba23a6.js 45.4 kB
63
63
  ├ chunks/main-9c9c62c368c0a47e.js 34.8 kB
64
- ├ chunks/pages/_app-00cee674a20813ff.js 11.2 kB
65
- ├ chunks/webpack-d7ccbda472189c19.js 2.43 kB
64
+ ├ chunks/pages/_app-1930798899758fda.js 11.2 kB
65
+ ├ chunks/webpack-a45dc84c9a0ef3fd.js 2.43 kB
66
66
  └ css/5d1f64b61ea581f4.css 3.05 kB
67
67
 
68
68
  λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
@@ -1,9 +1,10 @@
1
1
  $ jest
2
- PASS test/server/cms/index.test.ts (28.539 s)
3
- PASS test/server/index.test.ts (30.283 s)
2
+ PASS test/utils/multipleTemplates.test.ts (32.235 s)
3
+ PASS test/server/cms/index.test.ts (32.591 s)
4
+ PASS test/server/index.test.ts (34.429 s)
4
5
 
5
- Test Suites: 2 passed, 2 total
6
- Tests: 12 passed, 12 total
6
+ Test Suites: 3 passed, 3 total
7
+ Tests: 16 passed, 16 total
7
8
  Snapshots: 0 total
8
- Time: 31.156 s
9
+ Time: 35.349 s
9
10
  Ran all test suites.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "3.0.58",
3
+ "version": "3.0.60",
4
4
  "license": "MIT",
5
5
  "repository": "vtex/faststore",
6
6
  "browserslist": "supports es6-module and not dead",
@@ -125,5 +125,5 @@
125
125
  "node": "18.19.0",
126
126
  "yarn": "1.19.1"
127
127
  },
128
- "gitHead": "e1f415c74cfb74de617dc85669792439a1e6be29"
128
+ "gitHead": "19bd97e7ce1d842d390dbdd1e7e1859c1624b5fb"
129
129
  }
@@ -1,4 +1,5 @@
1
1
  import { isNotFoundError } from '@faststore/api'
2
+ import storeConfig from 'faststore.config'
2
3
  import type { GetStaticPaths, GetStaticProps } from 'next'
3
4
 
4
5
  import { gql } from '@generated'
@@ -86,6 +87,7 @@ export const getStaticProps: GetStaticProps<
86
87
  Locator
87
88
  > = async ({ params, previewData }) => {
88
89
  const slug = params?.slug.join('/') ?? ''
90
+ const rewrites = (await storeConfig.rewrites?.()) ?? []
89
91
 
90
92
  const [landingPagePromise, globalSectionsPromise] = [
91
93
  getLandingPageBySlug(slug, previewData),
@@ -114,7 +116,7 @@ export const getStaticProps: GetStaticProps<
114
116
  variables: { slug },
115
117
  operation: query,
116
118
  }),
117
- getPLP(slug, previewData),
119
+ getPLP(slug, previewData, rewrites),
118
120
  ])
119
121
 
120
122
  const notFound = errors.find(isNotFoundError)
@@ -5,14 +5,6 @@ import MissingContentError from 'src/sdk/error/MissingContentError'
5
5
  import MultipleContentError from 'src/sdk/error/MultipleContentError'
6
6
  import config from '../../../faststore.config'
7
7
 
8
- type Cache<T> = {
9
- [key: string]: { data: Array<T> }
10
- }
11
- type ExtraOptions = {
12
- cmsClient?: ClientCMS
13
- cache?: Cache<ContentData>
14
- }
15
-
16
8
  export type Options =
17
9
  | Locator
18
10
  | {
@@ -53,68 +45,49 @@ export const clientCMS = new ClientCMS({
53
45
  tenant: config.api.storeId,
54
46
  })
55
47
 
56
- /*
57
- * This in memory cache exists because for each page (think category or department)
58
- * we are fetching all the pages of the same content type from the headless CMS to
59
- * find the one that matches the slug.
60
- *
61
- * So instead of making multiple request for the Headless CMS API for each page we make
62
- * one for each content-type and reuse the results for the next page.
63
- *
64
- * Since we rebuild on a CMS publication the server will go away and will "invalidate"
65
- * the cache
66
- */
67
- const getCMSPageCache = {}
68
-
69
48
  export const getCMSPage = async (
70
49
  options: Options,
71
- extraOptions?: ExtraOptions
50
+ cmsClient: ClientCMS = clientCMS
72
51
  ) => {
73
- const cmsClient = extraOptions?.cmsClient ?? clientCMS
74
- const cache = extraOptions?.cache ?? getCMSPageCache
75
-
76
52
  if (isLocator(options)) {
77
53
  return await cmsClient
78
54
  .getCMSPage(options)
79
55
  .then((page) => ({ data: [page] }))
80
56
  }
81
57
 
82
- if (!cache[options.contentType]) {
83
- const pages = []
84
- let page = 1
85
- const perPage = 10
86
- const response = await cmsClient.getCMSPagesByContentType(
87
- options.contentType,
88
- { ...options.filters, page: page, perPage }
89
- )
90
-
91
- pages.push(...response.data)
92
-
93
- const totalPagesToFetch = Math.ceil(response.totalItems / perPage) // How many pages have content
94
- const pagesToFetch = Array.from(
95
- { length: totalPagesToFetch - 1 }, // We want all those pages minus the first one that we fetched
96
- (_, i) => i + 2 // + 1 because indices are 0 based, and + 1 because we already fetched the first
97
- )
98
-
99
- if (response.totalItems > pages.length) {
100
- const restOfPages = await Promise.all(
101
- pagesToFetch.map((i) =>
102
- cmsClient.getCMSPagesByContentType(options.contentType, {
103
- ...options.filters,
104
- page: i,
105
- perPage,
106
- })
107
- )
58
+ const pages = []
59
+ let page = 1
60
+ const perPage = 10
61
+ const response = await cmsClient.getCMSPagesByContentType(
62
+ options.contentType,
63
+ { ...options.filters, page: page, perPage }
64
+ )
65
+
66
+ pages.push(...response.data)
67
+
68
+ const totalPagesToFetch = Math.ceil(response.totalItems / perPage) // How many pages have content
69
+ const pagesToFetch = Array.from(
70
+ { length: totalPagesToFetch - 1 }, // We want all those pages minus the first one that we fetched
71
+ (_, i) => i + 2 // + 1 because indices are 0 based, and + 1 because we already fetched the first
72
+ )
73
+
74
+ if (response.totalItems > pages.length) {
75
+ const restOfPages = await Promise.all(
76
+ pagesToFetch.map((i) =>
77
+ cmsClient.getCMSPagesByContentType(options.contentType, {
78
+ ...options.filters,
79
+ page: i,
80
+ perPage,
81
+ })
108
82
  )
83
+ )
109
84
 
110
- restOfPages.forEach((response) => {
111
- pages.push(...response.data)
112
- })
113
- }
114
- cache[options.contentType] = { data: pages }
85
+ restOfPages.forEach((response) => {
86
+ pages.push(...response.data)
87
+ })
115
88
  }
116
89
 
117
- return cache[options.contentType]
90
+ return { data: pages }
118
91
  }
119
92
 
120
93
  export const getPage = async <T extends ContentData>(options: Options) => {
@@ -1,7 +1,7 @@
1
1
  import { ServerProductQueryQuery } from '@generated/graphql'
2
2
  import type { ContentData, Locator } from '@vtex/client-cms'
3
3
  import MissingContentError from 'src/sdk/error/MissingContentError'
4
- import { findBestPDPTemplate } from 'src/utils/utilities'
4
+ import { findBestPDPTemplate } from 'src/utils/multipleTemplates'
5
5
  import { Options, getCMSPage, getPage } from '.'
6
6
  import config from '../../../faststore.config'
7
7
 
@@ -1,6 +1,10 @@
1
1
  import { ContentData, Locator } from '@vtex/client-cms'
2
2
  import MissingContentError from 'src/sdk/error/MissingContentError'
3
- import { findBestPLPTemplate } from 'src/utils/utilities'
3
+ import {
4
+ Rewrite,
5
+ RewritesConfig,
6
+ findBestPLPTemplate,
7
+ } from 'src/utils/multipleTemplates'
4
8
  import config from '../../../faststore.config'
5
9
  import { Options, getCMSPage, getPage } from '../cms'
6
10
 
@@ -23,27 +27,41 @@ type PLPfromCmsEnvData = {
23
27
 
24
28
  export type PLPContentType = ContentData & PLPSettings
25
29
 
26
- export const getPLP = async (slug: string, previewData: Locator) => {
30
+ export const getPLP = async (
31
+ slug: string,
32
+ previewData: Locator,
33
+ rewrites: Rewrite[] | RewritesConfig
34
+ ) => {
27
35
  if (config.cms.data) {
28
36
  const cmsData = JSON.parse(config.cms.data)
29
37
  const allPLPsFromCmsEnvData: PLPfromCmsEnvData[] = cmsData['plp']
30
38
 
31
- return await getPLPFromCmsEnvData(`/${slug}/`, allPLPsFromCmsEnvData, {
32
- ...(previewData?.contentType === 'plp' ? previewData : null),
33
- contentType: 'plp',
34
- })
39
+ return await getPLPFromCmsEnvData(
40
+ `/${slug}/`,
41
+ allPLPsFromCmsEnvData,
42
+ {
43
+ ...(previewData?.contentType === 'plp' ? previewData : null),
44
+ contentType: 'plp',
45
+ },
46
+ rewrites
47
+ )
35
48
  }
36
49
 
37
- return (await getPLPFromCms(`/${slug}/`, {
38
- ...(previewData?.contentType === 'plp' ? previewData : null),
39
- contentType: 'plp',
40
- })) as PLPContentType
50
+ return (await getPLPFromCms(
51
+ `/${slug}/`,
52
+ {
53
+ ...(previewData?.contentType === 'plp' ? previewData : null),
54
+ contentType: 'plp',
55
+ },
56
+ rewrites
57
+ )) as PLPContentType
41
58
  }
42
59
 
43
60
  const getPLPFromCmsEnvData = async (
44
61
  slug: string,
45
62
  allPLPsFromCMSData: PLPfromCmsEnvData[],
46
- options: Options
63
+ options: Options,
64
+ rewrites: Rewrite[] | RewritesConfig
47
65
  ): Promise<PLPContentType> => {
48
66
  const pages: PLPfromCmsEnvData[] = allPLPsFromCMSData ?? []
49
67
 
@@ -51,7 +69,7 @@ const getPLPFromCmsEnvData = async (
51
69
  throw new MissingContentError(options)
52
70
  }
53
71
 
54
- const template = findBestPLPTemplate(pages, slug)
72
+ const template = findBestPLPTemplate(pages, slug, rewrites)
55
73
 
56
74
  return getPage<PLPContentType>({
57
75
  contentType: 'plp',
@@ -62,7 +80,8 @@ const getPLPFromCmsEnvData = async (
62
80
 
63
81
  export const getPLPFromCms = async (
64
82
  slug: string,
65
- options: Options
83
+ options: Options,
84
+ rewrites: Rewrite[] | RewritesConfig
66
85
  ): Promise<Partial<PLPContentType>> => {
67
86
  const pages = (await getCMSPage(options)).data
68
87
 
@@ -70,5 +89,5 @@ export const getPLPFromCms = async (
70
89
  throw new MissingContentError(options)
71
90
  }
72
91
 
73
- return findBestPLPTemplate(pages, slug)
92
+ return findBestPLPTemplate(pages, slug, rewrites)
74
93
  }
@@ -0,0 +1,178 @@
1
+ import { ServerProductQueryQuery } from '@generated/graphql'
2
+ import { PDPContentType } from 'src/server/cms/pdp'
3
+ import { PLPContentType } from 'src/server/cms/plp'
4
+
5
+ export type Rewrite = {
6
+ source: string
7
+ destination: string
8
+ }
9
+
10
+ export type RewritesConfig = {
11
+ beforeFiles?: Rewrite[]
12
+ afterFiles?: Rewrite[]
13
+ fallback?: Rewrite[]
14
+ }
15
+
16
+ export function normalizePLPSlug(slug: string) {
17
+ // Remove extra slashes at the beginning and end
18
+ let normalizedSlug = slug.replace(/^\/+|\/+$/g, '')
19
+
20
+ // Remove duplicate slashes and white spaces throughout the string
21
+ normalizedSlug = normalizedSlug.replace(/\/+/g, '/').replace(/\s+/g, '')
22
+
23
+ return '/' + normalizedSlug.toLowerCase()
24
+ }
25
+
26
+ function findPLPTemplateBySlug(
27
+ pages: Partial<PLPContentType>[],
28
+ slug: string,
29
+ rewrites: Rewrite[] | RewritesConfig
30
+ ) {
31
+ return pages.find((page) => {
32
+ // generic PLP template
33
+ if (!page.settings?.template?.value) return false
34
+
35
+ const templateValue = normalizePLPSlug(page.settings?.template?.value)
36
+ return (
37
+ templateValue === slug ||
38
+ hasRewritesConfigForSlug({ rewrites, templateValue, slug })
39
+ )
40
+ })
41
+ }
42
+
43
+ /**
44
+ * Normalizes the rewrites object, and returns:
45
+ * true if the templateValue from hCMS passed as param match the source value from the rewrites object AND the slug match the destination.
46
+ * see https://nextjs.org/docs/app/api-reference/next-config-js/rewrites
47
+ * @param rewrites
48
+ * @param templateValue
49
+ * @param slug
50
+ * @returns boolean indicating if the slug exist as a rewrite destination
51
+ */
52
+ export function hasRewritesConfigForSlug({
53
+ rewrites,
54
+ templateValue,
55
+ slug,
56
+ }: {
57
+ rewrites: Rewrite[] | RewritesConfig
58
+ templateValue: string
59
+ slug: string
60
+ }): boolean {
61
+ if (!rewrites) {
62
+ return false
63
+ }
64
+
65
+ let allRewrites: Rewrite[] = []
66
+
67
+ if (Array.isArray(rewrites)) {
68
+ allRewrites = rewrites
69
+ } else {
70
+ allRewrites = [
71
+ ...(rewrites?.beforeFiles || []),
72
+ ...(rewrites?.afterFiles || []),
73
+ ...(rewrites?.fallback || []),
74
+ ]
75
+ }
76
+
77
+ const foundRewrite = allRewrites.find(
78
+ (rewriteConfig) =>
79
+ rewriteConfig.source === templateValue &&
80
+ rewriteConfig.destination === slug
81
+ )
82
+ return Boolean(foundRewrite)
83
+ }
84
+
85
+ /**
86
+ * Find the best PLP template from the CMS based on the slug passed as param.
87
+ *
88
+ * This function iterates the slug until there is no slashes (/), prioritizing the following order:
89
+ * 1. A PLP template that matches the subcategory (e.g. slug = /department/category/subcategory).
90
+ * 2. A PLP template that matches the category (e.g. slug = /department/category).
91
+ * 3. A PLP template that matches the department (e.g. slug = /department).
92
+ * 4. If no matches are found, use the generic PLP template.
93
+ *
94
+ * @param pages
95
+ * @param originalSlug
96
+ * @returns The best PLP template page for the slug
97
+ */
98
+ export function findBestPLPTemplate(
99
+ pages: Partial<PLPContentType>[],
100
+ originalSlug: string,
101
+ rewrites: Rewrite[] | RewritesConfig
102
+ ) {
103
+ let slug = normalizePLPSlug(originalSlug)
104
+ let foundPageTemplate = findPLPTemplateBySlug(pages, slug, rewrites)
105
+
106
+ while (!foundPageTemplate && slug.lastIndexOf('/') !== -1) {
107
+ slug = slug.substring(0, slug.lastIndexOf('/'))
108
+ foundPageTemplate = findPLPTemplateBySlug(pages, slug, rewrites)
109
+ }
110
+
111
+ return (
112
+ foundPageTemplate ||
113
+ pages.find((page) => !page.settings?.template?.value) ||
114
+ pages[0]
115
+ )
116
+ }
117
+
118
+ export function normalizePDPTemplate(templateValue: string) {
119
+ // Remove extra slashes, white spaces at the beginning and end
120
+ let formattedValue = templateValue.trim().replace(/^\/+|\/+$/g, '')
121
+
122
+ // Add a slash at the beginning if not present
123
+ if (!formattedValue.startsWith('/')) {
124
+ formattedValue = '/' + formattedValue
125
+ }
126
+
127
+ // Add a slash at the end if not ending with '/p'
128
+ if (!formattedValue.endsWith('/p')) {
129
+ formattedValue += '/'
130
+ }
131
+
132
+ // Remove duplicate slashes and white spaces throughout the string
133
+ formattedValue = formattedValue.replace(/\/+/g, '/').replace(/\s+/g, '')
134
+
135
+ return formattedValue.toLowerCase()
136
+ }
137
+
138
+ /**
139
+ * Find the best PDP template from the CMS based on the slug or in the product category tree.
140
+ * Prioritizing the following order:
141
+ *
142
+ * 1. A PDP template that matches the page slug (e.g. slug = /apple-magic-mouse/p).
143
+ * 2. A PDP template that matches the product subcategory (e.g. /department/category/subcategory).
144
+ * 3. A PDP template that matches the product category (e.g. /department/category).
145
+ * 4. A PDP template that matches the product department (e.g. /department).
146
+ * 5. If no matches are found, use the generic PDP template.
147
+ *
148
+ * @param pages
149
+ * @param originalSlug
150
+ * @param product
151
+ * @returns The best PDP template page for the slug
152
+ */
153
+ export function findBestPDPTemplate(
154
+ pages: Partial<PDPContentType>[],
155
+ slug: string,
156
+ product: ServerProductQueryQuery['product']
157
+ ) {
158
+ // productSlugAndCategoryTree with the prioritized order. [slug, subcategory tree, category tree, department]
159
+ const productSlugAndCategoryTree = product?.breadcrumbList?.itemListElement
160
+ ? [...product?.breadcrumbList?.itemListElement]
161
+ .reverse()
162
+ .map(({ item }) => item)
163
+ : []
164
+ productSlugAndCategoryTree.unshift(slug)
165
+
166
+ for (const item of productSlugAndCategoryTree) {
167
+ for (const page of pages) {
168
+ if (!page.settings?.template?.value) continue
169
+
170
+ const templateValue = normalizePDPTemplate(page.settings.template.value)
171
+ if (templateValue === item) {
172
+ return page
173
+ }
174
+ }
175
+ }
176
+
177
+ return pages.find((page) => !page.settings?.template?.value) || pages[0]
178
+ }
@@ -1,7 +1,3 @@
1
- import { ServerProductQueryQuery } from '@generated/graphql'
2
- import { PDPContentType } from 'src/server/cms/pdp'
3
- import { PLPContentType } from 'src/server/cms/plp'
4
-
5
1
  //Input "Example Text!". Output: example-text
6
2
  export function textToKebabCase(text: string): string {
7
3
  // Replace spaces and special characters with hyphens
@@ -15,117 +11,3 @@ export function textToKebabCase(text: string): string {
15
11
 
16
12
  return kebabCase ?? ''
17
13
  }
18
-
19
- export function normalizePLPSlug(slug: string) {
20
- // Remove extra slashes at the beginning and end
21
- let normalizedSlug = slug.replace(/^\/+|\/+$/g, '')
22
-
23
- // Remove duplicate slashes and white spaces throughout the string
24
- normalizedSlug = normalizedSlug.replace(/\/+/g, '/').replace(/\s+/g, '')
25
-
26
- return '/' + normalizedSlug.toLowerCase()
27
- }
28
-
29
- function findPLPTemplateBySlug(pages: Partial<PLPContentType>[], slug: string) {
30
- return pages.find((page) => {
31
- // generic PLP template
32
- if (!page.settings?.template?.value) return false
33
-
34
- const templateValue = normalizePLPSlug(page.settings?.template?.value)
35
- return templateValue === slug
36
- })
37
- }
38
-
39
- /**
40
- * Find the best PLP template from the CMS based on the slug passed as param.
41
- *
42
- * This function iterates the slug until there is no slashes (/), prioritizing the following order:
43
- * 1. A PLP template that matches the subcategory (e.g. slug = /department/category/subcategory).
44
- * 2. A PLP template that matches the category (e.g. slug = /department/category).
45
- * 3. A PLP template that matches the department (e.g. slug = /department).
46
- * 4. If no matches are found, use the generic PLP template.
47
- *
48
- * @param pages
49
- * @param originalSlug
50
- * @returns The best PLP template page for the slug
51
- */
52
- export function findBestPLPTemplate(
53
- pages: Partial<PLPContentType>[],
54
- originalSlug: string
55
- ) {
56
- let slug = normalizePLPSlug(originalSlug)
57
- let foundPageTemplate = findPLPTemplateBySlug(pages, slug)
58
-
59
- while (!foundPageTemplate && slug.lastIndexOf('/') !== -1) {
60
- slug = slug.substring(0, slug.lastIndexOf('/'))
61
- foundPageTemplate = findPLPTemplateBySlug(pages, slug)
62
- }
63
-
64
- return (
65
- foundPageTemplate ||
66
- pages.find((page) => !page.settings?.template?.value) ||
67
- pages[0]
68
- )
69
- }
70
-
71
- export function normalizePDPTemplate(templateValue: string) {
72
- // Remove extra slashes, white spaces at the beginning and end
73
- let formattedValue = templateValue.trim().replace(/^\/+|\/+$/g, '')
74
-
75
- // Add a slash at the beginning if not present
76
- if (!formattedValue.startsWith('/')) {
77
- formattedValue = '/' + formattedValue
78
- }
79
-
80
- // Add a slash at the end if not ending with '/p'
81
- if (!formattedValue.endsWith('/p')) {
82
- formattedValue += '/'
83
- }
84
-
85
- // Remove duplicate slashes and white spaces throughout the string
86
- formattedValue = formattedValue.replace(/\/+/g, '/').replace(/\s+/g, '')
87
-
88
- return formattedValue.toLowerCase()
89
- }
90
-
91
- /**
92
- * Find the best PDP template from the CMS based on the slug or in the product category tree.
93
- * Prioritizing the following order:
94
- *
95
- * 1. A PDP template that matches the page slug (e.g. slug = /apple-magic-mouse/p).
96
- * 2. A PDP template that matches the product subcategory (e.g. /department/category/subcategory).
97
- * 3. A PDP template that matches the product category (e.g. /department/category).
98
- * 4. A PDP template that matches the product department (e.g. /department).
99
- * 5. If no matches are found, use the generic PDP template.
100
- *
101
- * @param pages
102
- * @param originalSlug
103
- * @param product
104
- * @returns The best PDP template page for the slug
105
- */
106
- export function findBestPDPTemplate(
107
- pages: Partial<PDPContentType>[],
108
- slug: string,
109
- product: ServerProductQueryQuery['product']
110
- ) {
111
- // productSlugAndCategoryTree with the prioritized order. [slug, subcategory tree, category tree, department]
112
- const productSlugAndCategoryTree = product?.breadcrumbList?.itemListElement
113
- ? [...product?.breadcrumbList?.itemListElement]
114
- .reverse()
115
- .map(({ item }) => item)
116
- : []
117
- productSlugAndCategoryTree.unshift(slug)
118
-
119
- for (const item of productSlugAndCategoryTree) {
120
- for (const page of pages) {
121
- if (!page.settings?.template?.value) continue
122
-
123
- const templateValue = normalizePDPTemplate(page.settings.template.value)
124
- if (templateValue === item) {
125
- return page
126
- }
127
- }
128
- }
129
-
130
- return pages.find((page) => !page.settings?.template?.value) || pages[0]
131
- }
@@ -29,10 +29,7 @@ describe('CMS Integration', () => {
29
29
  })
30
30
  clientCMS.getCMSPagesByContentType = mockFunction
31
31
 
32
- const result = await getCMSPage(
33
- { contentType: 'plp' },
34
- { cmsClient: clientCMS }
35
- )
32
+ const result = await getCMSPage({ contentType: 'plp' }, clientCMS)
36
33
 
37
34
  expect(mockFunction.mock.calls.length).toBe(1)
38
35
  expect(result.data.length).toBe(3)
@@ -59,37 +56,10 @@ describe('CMS Integration', () => {
59
56
 
60
57
  clientCMS.getCMSPagesByContentType = mockFunction
61
58
 
62
- const result = await getCMSPage(
63
- { contentType: 'plp' },
64
- { cmsClient: clientCMS, cache: {} }
65
- )
59
+ const result = await getCMSPage({ contentType: 'plp' }, clientCMS)
66
60
 
67
61
  expect(mockFunction.mock.calls.length).toBe(2)
68
62
  expect(result.data.length).toBe(15)
69
63
  })
70
-
71
- it('it makes no request if the cache is filled', async () => {
72
- const mockFunction: jest.Mock<typeof clientCMS.getCMSPagesByContentType> =
73
- jest.fn()
74
-
75
- mockFunction.mockImplementationOnce(() => {
76
- return Promise.resolve({
77
- data: mockData(10),
78
- hasNextPage: true,
79
- totalItems: 15,
80
- })
81
- })
82
-
83
- clientCMS.getCMSPagesByContentType = mockFunction
84
-
85
- const cache = { plp: { data: [] } }
86
- const result = await getCMSPage(
87
- { contentType: 'plp' },
88
- { cmsClient: clientCMS, cache: cache }
89
- )
90
-
91
- expect(mockFunction.mock.calls.length).toBe(0)
92
- expect(result.data.length).toBe(0)
93
- })
94
64
  })
95
65
  })