From 918d1ceb45a40edcaa5f4bf3e20bbfe444b56386 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Thu, 13 Jun 2024 15:01:49 +0100 Subject: [PATCH 1/7] fix(nuxt): preserve route metadata assigned outside page --- packages/nuxt/src/pages/utils.ts | 17 ++-- packages/nuxt/test/page-metadata.test.ts | 121 +++++++++++++++++++++++ test/basic.test.ts | 15 +++ test/fixtures/basic/nuxt.config.ts | 12 +++ test/fixtures/basic/pages/meta.vue | 38 +++++++ 5 files changed, 193 insertions(+), 10 deletions(-) create mode 100644 packages/nuxt/test/page-metadata.test.ts create mode 100644 test/fixtures/basic/pages/meta.vue diff --git a/packages/nuxt/src/pages/utils.ts b/packages/nuxt/src/pages/utils.ts index 63bb9cacf9fd..0d9e82842524 100644 --- a/packages/nuxt/src/pages/utils.ts +++ b/packages/nuxt/src/pages/utils.ts @@ -173,7 +173,7 @@ const DYNAMIC_META_KEY = '__nuxt_dynamic_meta_key' as const const pageContentsCache: Record = {} const metaCache: Record>> = {} -async function getRouteMeta (contents: string, absolutePath: string): Promise>> { +export async function getRouteMeta (contents: string, absolutePath: string): Promise>> { // set/update pageContentsCache, invalidate metaCache on cache mismatch if (!(absolutePath in pageContentsCache) || pageContentsCache[absolutePath] !== contents) { pageContentsCache[absolutePath] = contents @@ -505,19 +505,20 @@ async function createClientPage(loader) { }`) } - if (route.children != null) { + if (route.children) { metaRoute.children = route.children } - if (overrideMeta) { - metaRoute.name = `${metaImportName}?.name` - metaRoute.path = `${metaImportName}?.path ?? ''` + if (route.meta) { + metaRoute.meta = `{ ...(${metaImportName} || {}), ...${route.meta} }` + } + if (overrideMeta) { // skip and retain fallback if marked dynamic // set to extracted value or fallback if none extracted for (const key of ['name', 'path'] satisfies NormalizedRouteKeys) { if (markedDynamic.has(key)) { continue } - metaRoute[key] = route[key] ?? metaRoute[key] + metaRoute[key] = route[key] ?? `${metaImportName}?.${key}` } // set to extracted value or delete if none extracted @@ -532,10 +533,6 @@ async function createClientPage(loader) { metaRoute[key] = route[key] } } else { - if (route.meta != null) { - metaRoute.meta = `{ ...(${metaImportName} || {}), ...${route.meta} }` - } - if (route.alias != null) { metaRoute.alias = `${route.alias}.concat(${metaImportName}?.alias || [])` } diff --git a/packages/nuxt/test/page-metadata.test.ts b/packages/nuxt/test/page-metadata.test.ts new file mode 100644 index 000000000000..fb28d22bac37 --- /dev/null +++ b/packages/nuxt/test/page-metadata.test.ts @@ -0,0 +1,121 @@ +import { describe, expect, it, vi } from 'vitest' +import { getRouteMeta, normalizeRoutes } from "../src/pages/utils" +import type { NuxtPage } from '../schema' + +const filePath = '/app/pages/index.vue' + +describe('page metadata', () => { + it('should not extract metadata from empty files', async () => { + expect(await getRouteMeta('', filePath)).toEqual({}) + expect(await getRouteMeta('', filePath)).toEqual({}) + }) + + it('should use and invalidate cache', async () => { + const fileContents = `` + const meta = await getRouteMeta(fileContents, filePath) + expect(meta === await getRouteMeta(fileContents, filePath)).toBeTruthy() + expect(meta === await getRouteMeta(fileContents, '/app/pages/other.vue')).toBeFalsy() + expect(meta === await getRouteMeta('' + fileContents, filePath)).toBeFalsy() + }) + + it('should extract serialisable metadata', async () => { + const meta = await getRouteMeta(` + + `, filePath) + + expect(meta).toMatchInlineSnapshot(` + { + "meta": { + "__nuxt_dynamic_meta_key": Set { + "meta", + }, + }, + "name": "some-custom-name", + "path": "/some-custom-path", + } + `) + }) +}) + +describe('normalizeRoutes', () => { + it('should produce valid route objects when used with extracted meta', async () => { + const page: NuxtPage = { path: '/', file: filePath } + Object.assign(page, await getRouteMeta(` + + `, filePath)) + + page.meta ||= {} + page.meta.layout = 'test' + page.meta.foo = 'bar' + + const { routes, imports } = normalizeRoutes([page], new Set(), true) + expect({ routes, imports }).toMatchInlineSnapshot(` + { + "imports": Set { + "import { default as indexN6pT4Un8hYMeta } from "/app/pages/index.vue?macro=true";", + }, + "routes": "[ + { + name: "some-custom-name", + path: indexN6pT4Un8hYMeta?.path ?? "/", + meta: { ...(indexN6pT4Un8hYMeta || {}), ...{"layout":"test","foo":"bar"} }, + redirect: "/", + component: () => import("/app/pages/index.vue").then(m => m.default || m) + } + ]", + } + `) + }) + + it('should produce valid route objects when used without extracted meta', async () => { + const page: NuxtPage = { path: '/', file: filePath } + page.meta ||= {} + page.meta.layout = 'test' + page.meta.foo = 'bar' + + const { routes, imports } = normalizeRoutes([page], new Set()) + expect({ routes, imports }).toMatchInlineSnapshot(` + { + "imports": Set { + "import { default as indexN6pT4Un8hYMeta } from "/app/pages/index.vue?macro=true";", + }, + "routes": "[ + { + name: indexN6pT4Un8hYMeta?.name ?? undefined, + path: indexN6pT4Un8hYMeta?.path ?? "/", + meta: { ...(indexN6pT4Un8hYMeta || {}), ...{"layout":"test","foo":"bar"} }, + alias: indexN6pT4Un8hYMeta?.alias || [], + redirect: indexN6pT4Un8hYMeta?.redirect, + component: () => import("/app/pages/index.vue").then(m => m.default || m) + } + ]", + } + `) + }) +}) + diff --git a/test/basic.test.ts b/test/basic.test.ts index 1e4db83c89a7..8bc37201f335 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -166,6 +166,21 @@ describe('pages', () => { expect(res.headers.get('x-extend')).toEqual('added in pages:extend') }) + it('preserves page metadata added in pages:extend hook', async () => { + const html = await $fetch('/some-custom-path') + expect (html.match(/
([^<]*)<\/pre>/)?.[1].trim().replace(/"/g, '"').replace(/>/g, '>')).toMatchInlineSnapshot(`
+      "{
+        "name": "some-custom-name",
+        "path": "/some-custom-path",
+        "validate": "() => true",
+        "middleware": [
+          "() => {\\n      console.log(\\"hey\\");\\n    }"
+        ],
+        "otherValue": "{\\"foo\\":\\"bar\\"}"
+      }"
+    `)
+  })
+
   it('validates routes', async () => {
     const { status, headers } = await fetch('/forbidden')
     expect(status).toEqual(404)
diff --git a/test/fixtures/basic/nuxt.config.ts b/test/fixtures/basic/nuxt.config.ts
index d312d55944b0..3af4dad44f06 100644
--- a/test/fixtures/basic/nuxt.config.ts
+++ b/test/fixtures/basic/nuxt.config.ts
@@ -33,6 +33,7 @@ export default defineNuxtConfig({
   buildDir: process.env.NITRO_BUILD_DIR,
   builder: process.env.TEST_BUILDER as 'webpack' | 'vite' ?? 'vite',
   appId: 'nuxt-app-basic',
+  devtools: { enabled: true },
   build: {
     transpile: [
       (ctx) => {
@@ -151,6 +152,17 @@ export default defineNuxtConfig({
         internalParent!.children = newPages
       })
     },
+    function (_options, nuxt) {
+      // to check that page metadata is preserved
+      nuxt.hook('pages:extend', (pages) => {
+        const customName = pages.find(page => page.name === 'some-custom-name')
+        if (!customName) { throw new Error('Page with custom name not found') }
+        if (customName.path !== '/some-custom-path') { throw new Error('Page path not extracted') }
+
+        customName.meta ||= {}
+        customName.meta.someProp = true
+      })
+    },
     // To test falsy module values
     undefined,
   ],
diff --git a/test/fixtures/basic/pages/meta.vue b/test/fixtures/basic/pages/meta.vue
new file mode 100644
index 000000000000..53e829f40095
--- /dev/null
+++ b/test/fixtures/basic/pages/meta.vue
@@ -0,0 +1,38 @@
+
+
+

From 77a8713f09b6f96351c78454d0d616bb5eb10336 Mon Sep 17 00:00:00 2001
From: Daniel Roe 
Date: Thu, 13 Jun 2024 15:17:42 +0100
Subject: [PATCH 2/7] chore: lint + add test case for options api

---
 packages/nuxt/test/page-metadata.test.ts | 59 +++++++++++++++++-------
 1 file changed, 43 insertions(+), 16 deletions(-)

diff --git a/packages/nuxt/test/page-metadata.test.ts b/packages/nuxt/test/page-metadata.test.ts
index fb28d22bac37..8e1d33b35d8e 100644
--- a/packages/nuxt/test/page-metadata.test.ts
+++ b/packages/nuxt/test/page-metadata.test.ts
@@ -1,5 +1,5 @@
-import { describe, expect, it, vi } from 'vitest'
-import { getRouteMeta, normalizeRoutes } from "../src/pages/utils"
+import { describe, expect, it } from 'vitest'
+import { getRouteMeta, normalizeRoutes } from '../src/pages/utils'
 import type { NuxtPage } from '../schema'
 
 const filePath = '/app/pages/index.vue'
@@ -47,6 +47,34 @@ describe('page metadata', () => {
       }
     `)
   })
+
+  it('should extract serialisable metadata in options api', async () => {
+    const meta = await getRouteMeta(`
+    
+    `, filePath)
+
+    expect(meta).toMatchInlineSnapshot(`
+      {
+        "meta": {
+          "__nuxt_dynamic_meta_key": Set {
+            "meta",
+          },
+        },
+        "name": "some-custom-name",
+        "path": "/some-custom-path",
+      }
+    `)
+  })
 })
 
 describe('normalizeRoutes', () => {
@@ -69,12 +97,12 @@ describe('normalizeRoutes', () => {
       
       `, filePath))
 
-      page.meta ||= {}
-      page.meta.layout = 'test'
-      page.meta.foo = 'bar'
+    page.meta ||= {}
+    page.meta.layout = 'test'
+    page.meta.foo = 'bar'
 
-      const { routes, imports } = normalizeRoutes([page], new Set(), true)
-      expect({ routes, imports }).toMatchInlineSnapshot(`
+    const { routes, imports } = normalizeRoutes([page], new Set(), true)
+    expect({ routes, imports }).toMatchInlineSnapshot(`
         {
           "imports": Set {
             "import { default as indexN6pT4Un8hYMeta } from "/app/pages/index.vue?macro=true";",
@@ -90,16 +118,16 @@ describe('normalizeRoutes', () => {
         ]",
         }
       `)
-    })
+  })
 
-  it('should produce valid route objects when used without extracted meta', async () => {
+  it('should produce valid route objects when used without extracted meta', () => {
     const page: NuxtPage = { path: '/', file: filePath }
-      page.meta ||= {}
-      page.meta.layout = 'test'
-      page.meta.foo = 'bar'
+    page.meta ||= {}
+    page.meta.layout = 'test'
+    page.meta.foo = 'bar'
 
-      const { routes, imports } = normalizeRoutes([page], new Set())
-      expect({ routes, imports }).toMatchInlineSnapshot(`
+    const { routes, imports } = normalizeRoutes([page], new Set())
+    expect({ routes, imports }).toMatchInlineSnapshot(`
         {
           "imports": Set {
             "import { default as indexN6pT4Un8hYMeta } from "/app/pages/index.vue?macro=true";",
@@ -116,6 +144,5 @@ describe('normalizeRoutes', () => {
         ]",
         }
       `)
-    })
+  })
 })
-

From 6bced52c11a053daae5febccec267b2066f8f293 Mon Sep 17 00:00:00 2001
From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com>
Date: Thu, 13 Jun 2024 14:31:45 +0000
Subject: [PATCH 3/7] [autofix.ci] apply automated fixes

---
 .../test/__snapshots__/pages-override-meta-enabled.test.ts.snap | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/nuxt/test/__snapshots__/pages-override-meta-enabled.test.ts.snap b/packages/nuxt/test/__snapshots__/pages-override-meta-enabled.test.ts.snap
index 88989345d1bc..9fb308e7c11c 100644
--- a/packages/nuxt/test/__snapshots__/pages-override-meta-enabled.test.ts.snap
+++ b/packages/nuxt/test/__snapshots__/pages-override-meta-enabled.test.ts.snap
@@ -303,7 +303,7 @@
       "alias": "mockMeta?.alias || []",
       "component": "() => import("pages/index.vue").then(m => m.default || m)",
       "meta": "mockMeta || {}",
-      "name": "mockMeta?.name",
+      "name": "mockMeta?.name ?? "index"",
       "path": ""/"",
       "redirect": "mockMeta?.redirect",
     },

From 78c56c5a0bfe1eb2b5c2041f261c22457e33401f Mon Sep 17 00:00:00 2001
From: Daniel Roe 
Date: Thu, 13 Jun 2024 15:57:31 +0100
Subject: [PATCH 4/7] chore: remove devtools

Co-authored-by: Bobbie Goede 
---
 test/fixtures/basic/nuxt.config.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/test/fixtures/basic/nuxt.config.ts b/test/fixtures/basic/nuxt.config.ts
index 3af4dad44f06..08f427b2a65d 100644
--- a/test/fixtures/basic/nuxt.config.ts
+++ b/test/fixtures/basic/nuxt.config.ts
@@ -33,7 +33,6 @@ export default defineNuxtConfig({
   buildDir: process.env.NITRO_BUILD_DIR,
   builder: process.env.TEST_BUILDER as 'webpack' | 'vite' ?? 'vite',
   appId: 'nuxt-app-basic',
-  devtools: { enabled: true },
   build: {
     transpile: [
       (ctx) => {

From 7638f905d1e392afedee3c850ba87bf0322a7cc2 Mon Sep 17 00:00:00 2001
From: Daniel Roe 
Date: Thu, 13 Jun 2024 16:37:56 +0100
Subject: [PATCH 5/7] chore: update fixture

---
 test/basic.test.ts                 | 4 ++--
 test/fixtures/basic/pages/meta.vue | 4 +---
 2 files changed, 3 insertions(+), 5 deletions(-)

diff --git a/test/basic.test.ts b/test/basic.test.ts
index 8bc37201f335..f310d0f9c64b 100644
--- a/test/basic.test.ts
+++ b/test/basic.test.ts
@@ -168,13 +168,13 @@ describe('pages', () => {
 
   it('preserves page metadata added in pages:extend hook', async () => {
     const html = await $fetch('/some-custom-path')
-    expect (html.match(/
([^<]*)<\/pre>/)?.[1].trim().replace(/"/g, '"').replace(/>/g, '>')).toMatchInlineSnapshot(`
+    expect (html.match(/
([^<]*)<\/pre>/)?.[1]?.trim().replace(/"/g, '"').replace(/>/g, '>')).toMatchInlineSnapshot(`
       "{
         "name": "some-custom-name",
         "path": "/some-custom-path",
         "validate": "() => true",
         "middleware": [
-          "() => {\\n      console.log(\\"hey\\");\\n    }"
+          "() => true"
         ],
         "otherValue": "{\\"foo\\":\\"bar\\"}"
       }"
diff --git a/test/fixtures/basic/pages/meta.vue b/test/fixtures/basic/pages/meta.vue
index 53e829f40095..7983bde1eecb 100644
--- a/test/fixtures/basic/pages/meta.vue
+++ b/test/fixtures/basic/pages/meta.vue
@@ -3,9 +3,7 @@ definePageMeta({
   name: 'some-custom-name',
   path: '/some-custom-path',
   validate: () => true,
-  middleware: [
-    () => { console.log('hey') },
-  ],
+  middleware: [() => true],
   otherValue: {
     foo: 'bar',
   },

From 35282cb5e32de8188d84c102eafaf1ff143bd02c Mon Sep 17 00:00:00 2001
From: Daniel Roe 
Date: Thu, 13 Jun 2024 16:50:46 +0100
Subject: [PATCH 6/7] fix: handle `definePageMeta` nested within another
 function

---
 packages/nuxt/src/pages/utils.ts         | 106 ++++++++++++-----------
 packages/nuxt/test/page-metadata.test.ts |  10 +--
 2 files changed, 62 insertions(+), 54 deletions(-)

diff --git a/packages/nuxt/src/pages/utils.ts b/packages/nuxt/src/pages/utils.ts
index 0d9e82842524..602f63feea82 100644
--- a/packages/nuxt/src/pages/utils.ts
+++ b/packages/nuxt/src/pages/utils.ts
@@ -9,6 +9,7 @@ import { filename } from 'pathe/utils'
 import { hash } from 'ohash'
 import { transform } from 'esbuild'
 import { parse } from 'acorn'
+import { walk } from 'estree-walker'
 import type { CallExpression, ExpressionStatement, ObjectExpression, Program, Property } from 'estree'
 import type { NuxtPage } from 'nuxt/schema'
 
@@ -199,70 +200,77 @@ export async function getRouteMeta (contents: string, absolutePath: string): Pro
     ecmaVersion: 'latest',
     ranges: true,
   }) as unknown as Program
-  const pageMetaAST = ast.body.find(node => node.type === 'ExpressionStatement' && node.expression.type === 'CallExpression' && node.expression.callee.type === 'Identifier' && node.expression.callee.name === 'definePageMeta')
-  if (!pageMetaAST) {
-    metaCache[absolutePath] = {}
-    return {}
-  }
 
-  const pageMetaArgument = ((pageMetaAST as ExpressionStatement).expression as CallExpression).arguments[0] as ObjectExpression
   const extractedMeta = {} as Partial>
   const extractionKeys = ['name', 'path', 'alias', 'redirect'] as const
   const dynamicProperties = new Set()
 
-  for (const key of extractionKeys) {
-    const property = pageMetaArgument.properties.find(property => property.type === 'Property' && property.key.type === 'Identifier' && property.key.name === key) as Property
-    if (!property) { continue }
-
-    if (property.value.type === 'ObjectExpression') {
-      const valueString = js.code.slice(property.value.range![0], property.value.range![1])
-      try {
-        extractedMeta[key] = JSON.parse(runInNewContext(`JSON.stringify(${valueString})`, {}))
-      } catch {
-        console.debug(`[nuxt] Skipping extraction of \`${key}\` metadata as it is not JSON-serializable (reading \`${absolutePath}\`).`)
-        dynamicProperties.add(key)
-        continue
-      }
-    }
+  let foundMeta = false
+
+  walk(ast, {
+    enter(node) {
+      if (foundMeta) { return }
+
+      if (node.type !== 'ExpressionStatement' || node.expression.type !== 'CallExpression' || node.expression.callee.type !== 'Identifier' || node.expression.callee.name !== 'definePageMeta') { return }
+
+      foundMeta = true
+      const pageMetaArgument = ((node as ExpressionStatement).expression as CallExpression).arguments[0] as ObjectExpression
+
+      for (const key of extractionKeys) {
+        const property = pageMetaArgument.properties.find(property => property.type === 'Property' && property.key.type === 'Identifier' && property.key.name === key) as Property
+        if (!property) { continue }
+
+        if (property.value.type === 'ObjectExpression') {
+          const valueString = js.code.slice(property.value.range![0], property.value.range![1])
+          try {
+            extractedMeta[key] = JSON.parse(runInNewContext(`JSON.stringify(${valueString})`, {}))
+          } catch {
+            console.debug(`[nuxt] Skipping extraction of \`${key}\` metadata as it is not JSON-serializable (reading \`${absolutePath}\`).`)
+            dynamicProperties.add(key)
+            continue
+          }
+        }
 
-    if (property.value.type === 'ArrayExpression') {
-      const values = []
-      for (const element of property.value.elements) {
-        if (!element) {
+        if (property.value.type === 'ArrayExpression') {
+          const values = []
+          for (const element of property.value.elements) {
+            if (!element) {
+              continue
+            }
+            if (element.type !== 'Literal' || typeof element.value !== 'string') {
+              console.debug(`[nuxt] Skipping extraction of \`${key}\` metadata as it is not an array of string literals (reading \`${absolutePath}\`).`)
+              dynamicProperties.add(key)
+              continue
+            }
+            values.push(element.value)
+          }
+          extractedMeta[key] = values
           continue
         }
-        if (element.type !== 'Literal' || typeof element.value !== 'string') {
-          console.debug(`[nuxt] Skipping extraction of \`${key}\` metadata as it is not an array of string literals (reading \`${absolutePath}\`).`)
+
+        if (property.value.type !== 'Literal' || typeof property.value.value !== 'string') {
+          console.debug(`[nuxt] Skipping extraction of \`${key}\` metadata as it is not a string literal or array of string literals (reading \`${absolutePath}\`).`)
           dynamicProperties.add(key)
           continue
         }
-        values.push(element.value)
+        extractedMeta[key] = property.value.value
       }
-      extractedMeta[key] = values
-      continue
-    }
-
-    if (property.value.type !== 'Literal' || typeof property.value.value !== 'string') {
-      console.debug(`[nuxt] Skipping extraction of \`${key}\` metadata as it is not a string literal or array of string literals (reading \`${absolutePath}\`).`)
-      dynamicProperties.add(key)
-      continue
-    }
-    extractedMeta[key] = property.value.value
-  }
 
-  const extraneousMetaKeys = pageMetaArgument.properties
-    .filter(property => property.type === 'Property' && property.key.type === 'Identifier' && !(extractionKeys as unknown as string[]).includes(property.key.name))
-    // @ts-expect-error inferred types have been filtered out
-    .map(property => property.key.name)
+      const extraneousMetaKeys = pageMetaArgument.properties
+        .filter(property => property.type === 'Property' && property.key.type === 'Identifier' && !(extractionKeys as unknown as string[]).includes(property.key.name))
+        // @ts-expect-error inferred types have been filtered out
+        .map(property => property.key.name)
 
-  if (extraneousMetaKeys.length) {
-    dynamicProperties.add('meta')
-  }
+      if (extraneousMetaKeys.length) {
+        dynamicProperties.add('meta')
+      }
 
-  if (dynamicProperties.size) {
-    extractedMeta.meta ??= {}
-    extractedMeta.meta[DYNAMIC_META_KEY] = dynamicProperties
-  }
+      if (dynamicProperties.size) {
+        extractedMeta.meta ??= {}
+        extractedMeta.meta[DYNAMIC_META_KEY] = dynamicProperties
+      }
+    }
+  })
 
   metaCache[absolutePath] = extractedMeta
   return extractedMeta
diff --git a/packages/nuxt/test/page-metadata.test.ts b/packages/nuxt/test/page-metadata.test.ts
index 8e1d33b35d8e..4431b80f2f20 100644
--- a/packages/nuxt/test/page-metadata.test.ts
+++ b/packages/nuxt/test/page-metadata.test.ts
@@ -51,13 +51,13 @@ describe('page metadata', () => {
   it('should extract serialisable metadata in options api', async () => {
     const meta = await getRouteMeta(`
     

From ad7d8421c1f3e79858f6e559da5155e4efdf0464 Mon Sep 17 00:00:00 2001
From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com>
Date: Thu, 13 Jun 2024 16:08:24 +0000
Subject: [PATCH 7/7] [autofix.ci] apply automated fixes

---
 packages/nuxt/src/pages/utils.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/nuxt/src/pages/utils.ts b/packages/nuxt/src/pages/utils.ts
index 602f63feea82..1e713075dd34 100644
--- a/packages/nuxt/src/pages/utils.ts
+++ b/packages/nuxt/src/pages/utils.ts
@@ -208,7 +208,7 @@ export async function getRouteMeta (contents: string, absolutePath: string): Pro
   let foundMeta = false
 
   walk(ast, {
-    enter(node) {
+    enter (node) {
       if (foundMeta) { return }
 
       if (node.type !== 'ExpressionStatement' || node.expression.type !== 'CallExpression' || node.expression.callee.type !== 'Identifier' || node.expression.callee.name !== 'definePageMeta') { return }
@@ -269,7 +269,7 @@ export async function getRouteMeta (contents: string, absolutePath: string): Pro
         extractedMeta.meta ??= {}
         extractedMeta.meta[DYNAMIC_META_KEY] = dynamicProperties
       }
-    }
+    },
   })
 
   metaCache[absolutePath] = extractedMeta