+
+## Frontmatter
+
+A single value bound to a component (`:src="featured"`):
+
+:img{:src="featured" alt="From frontmatter"}
+
+An array bound to a custom component (a gallery built from `gallery`):
+
+:content-gallery{:items="gallery"}
+
+## Image sizing
+
+By default an `aspect-ratio` is injected so images reserve their space before
+loading — no layout shift, no configuration:
+
+
+
+Switch to `width`/`height` attributes with `imageSize: 'attrs'`, encode the size
+in a `?width=&height=` query with `'src'`, or turn it off with `imageSize: ''`.
+
+## AST traversal
+
+Existing attributes are preserved and merged (`width`, `style`):
+
+:img{src="media/turkey-casserole.jpg" width="240" style="transform: rotate(-3deg);" alt="Rotated casserole"}
+
+Paths inside fenced code are never rewritten:
+
+```ts
+const src = 'media/turkey-casserole.jpg'
+```
+
+Components inside tables are processed too:
+
+| Label | Image |
+| ------ | ------------------------------------------------------------------ |
+| Recipe | :img{src="italian-bean-stew.jpg" style="width: 80px;" alt="Stew"} |
+
+## Ordering
+
+This page lives in `1.real-content/`, yet its assets are served without the
+numbering prefix: `media/pesto-salmon-lentils.jpg` is served at
+`/real-content/media/pesto-salmon-lentils.jpg`.
+
+## Nuxt Image
+
+Assets are served from the site root, so with `@nuxt/image` installed
+`:nuxt-img{src="media/turkey-casserole.jpg"}` resolves them through the default
+IPX provider.
diff --git a/playground/content/1.real-content/files/sample.pdf b/playground/content/1.real-content/files/sample.pdf
new file mode 100644
index 000000000..dbf091df9
Binary files /dev/null and b/playground/content/1.real-content/files/sample.pdf differ
diff --git a/playground/content/1.real-content/italian-bean-stew.jpg b/playground/content/1.real-content/italian-bean-stew.jpg
new file mode 100644
index 000000000..c14abf39b
Binary files /dev/null and b/playground/content/1.real-content/italian-bean-stew.jpg differ
diff --git a/playground/content/1.real-content/media/pesto-salmon-lentils.jpg b/playground/content/1.real-content/media/pesto-salmon-lentils.jpg
new file mode 100644
index 000000000..656979e69
Binary files /dev/null and b/playground/content/1.real-content/media/pesto-salmon-lentils.jpg differ
diff --git a/playground/content/1.real-content/media/sample.html b/playground/content/1.real-content/media/sample.html
new file mode 100644
index 000000000..19e5e0458
--- /dev/null
+++ b/playground/content/1.real-content/media/sample.html
@@ -0,0 +1,17 @@
+
+
+
+
+ Sample
+
+
+
+
+Yay, Assets!
+This page is displaying in an iframe
+
+
diff --git a/playground/content/1.real-content/media/sample.mp4 b/playground/content/1.real-content/media/sample.mp4
new file mode 100644
index 000000000..b11552f9c
Binary files /dev/null and b/playground/content/1.real-content/media/sample.mp4 differ
diff --git a/playground/content/1.real-content/media/turkey-casserole.jpg b/playground/content/1.real-content/media/turkey-casserole.jpg
new file mode 100644
index 000000000..237bfe653
Binary files /dev/null and b/playground/content/1.real-content/media/turkey-casserole.jpg differ
diff --git a/playground/content/shared/sicilian-fish-stew.jpg b/playground/content/shared/sicilian-fish-stew.jpg
new file mode 100644
index 000000000..2f10579b2
Binary files /dev/null and b/playground/content/shared/sicilian-fish-stew.jpg differ
diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts
index 5ada5df5c..f9b9396b8 100644
--- a/playground/nuxt.config.ts
+++ b/playground/nuxt.config.ts
@@ -37,6 +37,14 @@ export default defineNuxtConfig({
},
},
compatibilityDate: '2025-10-15',
+ vite: {
+ optimizeDeps: {
+ include: [
+ '@vue/devtools-core',
+ '@vue/devtools-kit',
+ ],
+ },
+ },
llms: {
domain: 'http://localhost:3000',
title: 'Content Playground',
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9c61a1a21..08c39683c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -65,6 +65,9 @@ importers:
hookable:
specifier: ^5.5.3
version: 5.5.3
+ image-size:
+ specifier: ^2.0.2
+ version: 2.0.2
isomorphic-git:
specifier: ^1.38.4
version: 1.38.5
@@ -290,19 +293,19 @@ importers:
version: 3.0.2(magicast@0.5.2)
'@vercel/analytics':
specifier: ^2.0.1
- version: 2.0.1(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vue@3.5.34(typescript@5.9.3))
+ version: 2.0.1(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vue@3.5.34(typescript@5.9.3))
'@vercel/speed-insights':
specifier: ^2.0.0
- version: 2.0.0(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vue@3.5.34(typescript@5.9.3))
+ version: 2.0.0(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vue@3.5.34(typescript@5.9.3))
'@vueuse/nuxt':
specifier: ^14.3.0
- version: 14.3.0(magicast@0.5.2)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vue@3.5.34(typescript@5.9.3))
+ version: 14.3.0(magicast@0.5.2)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vue@3.5.34(typescript@5.9.3))
better-sqlite3:
specifier: ^12.10.0
version: 12.10.0
docus:
specifier: ^5.11.0
- version: 5.11.0(7be736386eac822290d67dfa1d56f35d)
+ version: 5.11.0(ce604dd20a7a75c44e066ad05d4cf9aa)
drizzle-kit:
specifier: ^0.31.10
version: 0.31.10
@@ -314,7 +317,7 @@ importers:
version: 7.2.0
nuxt:
specifier: ^4.4.5
- version: 4.4.5(9fcd7314e3eecf945123399c7742c3cc)
+ version: 4.4.5(62a2f48613f90195bda1847309522735)
nuxt-studio:
specifier: ^1.7.0
version: 1.7.0(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(ioredis@5.10.1)(magicast@0.5.2)(vue@3.5.34(typescript@5.9.3))
@@ -388,6 +391,8 @@ importers:
specifier: ^1.1.0
version: 1.1.0
+ test/fixtures/assets: {}
+
test/fixtures/basic: {}
test/fixtures/csv: {}
@@ -713,9 +718,6 @@ packages:
cpu: [x64]
os: [win32]
- '@cloudflare/workers-types@4.20260518.1':
- resolution: {integrity: sha512-xXzGrbRi8RHRBNQFgXYkzrB4DgF0RXvmp8E1vCxoBmINpeitM/ZjVDd1CNC+N3uXjgcNjacoz4OgTa0rxgig1A==}
-
'@cloudflare/workers-types@4.20260617.1':
resolution: {integrity: sha512-HdbP3CNcdMZBwegitFDjWvzv+6wPkFXvV9gBXMnf6RjV2Cy3W8TJL3IhSEGul0S6F1DHjnucP7lrpIsvkzNEjA==}
@@ -5861,9 +5863,6 @@ packages:
citty@0.1.6:
resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==}
- citty@0.2.1:
- resolution: {integrity: sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==}
-
citty@0.2.2:
resolution: {integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==}
@@ -7320,6 +7319,11 @@ packages:
image-meta@0.2.2:
resolution: {integrity: sha512-3MOLanc3sb3LNGWQl1RlQlNWURE5g32aUphrDyFeCsxBTk08iE3VNe4CwsUZ0Qs1X+EfX0+r29Sxdpza4B+yRA==}
+ image-size@2.0.2:
+ resolution: {integrity: sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==}
+ engines: {node: '>=16.x'}
+ hasBin: true
+
import-without-cache@0.2.5:
resolution: {integrity: sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A==}
engines: {node: '>=20.19.0'}
@@ -8697,9 +8701,6 @@ packages:
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
- pkg-types@2.3.0:
- resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
-
pkg-types@2.3.1:
resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==}
@@ -10871,21 +10872,6 @@ packages:
pinia:
optional: true
- vue-router@5.0.7:
- resolution: {integrity: sha512-dqfk8kvRbCutmCOCj/XLDqDEYxc1wBdAOGLuVy5M93ifYMsBd5fIjfaPN4tQAbxr5IprdBDIox1gr4wYyOx/SA==}
- peerDependencies:
- '@pinia/colada': '>=0.21.2'
- '@vue/compiler-sfc': ^3.5.34
- pinia: ^3.0.4
- vue: ^3.5.34
- peerDependenciesMeta:
- '@pinia/colada':
- optional: true
- '@vue/compiler-sfc':
- optional: true
- pinia:
- optional: true
-
vue-router@5.1.0:
resolution: {integrity: sha512-HAbiLzLEHQwxPgvsbOJDAwtavszEgLwri6XfyrsPECIFez8+59xc9LofWVdc/HEaSRT822lJ8H9Ns38VVond5g==}
peerDependencies:
@@ -11264,7 +11250,7 @@ snapshots:
'@babel/code-frame@7.29.0':
dependencies:
- '@babel/helper-validator-identifier': 7.28.5
+ '@babel/helper-validator-identifier': 7.29.7
js-tokens: 4.0.0
picocolors: 1.1.1
@@ -11277,10 +11263,10 @@ snapshots:
'@babel/helper-compilation-targets': 7.28.6
'@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
'@babel/helpers': 7.28.6
- '@babel/parser': 7.29.3
+ '@babel/parser': 7.29.7
'@babel/template': 7.28.6
'@babel/traverse': 7.29.0
- '@babel/types': 7.29.0
+ '@babel/types': 7.29.7
'@jridgewell/remapping': 2.3.5
convert-source-map: 2.0.0
debug: 4.4.3
@@ -11292,8 +11278,8 @@ snapshots:
'@babel/generator@7.29.1':
dependencies:
- '@babel/parser': 7.29.3
- '@babel/types': 7.29.0
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
jsesc: 3.1.0
@@ -11309,7 +11295,7 @@ snapshots:
'@babel/helper-annotate-as-pure@7.27.3':
dependencies:
- '@babel/types': 7.29.0
+ '@babel/types': 7.29.7
'@babel/helper-compilation-targets@7.28.6':
dependencies:
@@ -11337,14 +11323,14 @@ snapshots:
'@babel/helper-member-expression-to-functions@7.28.5':
dependencies:
'@babel/traverse': 7.29.0
- '@babel/types': 7.29.0
+ '@babel/types': 7.29.7
transitivePeerDependencies:
- supports-color
'@babel/helper-module-imports@7.28.6':
dependencies:
'@babel/traverse': 7.29.0
- '@babel/types': 7.29.0
+ '@babel/types': 7.29.7
transitivePeerDependencies:
- supports-color
@@ -11352,14 +11338,14 @@ snapshots:
dependencies:
'@babel/core': 7.29.0
'@babel/helper-module-imports': 7.28.6
- '@babel/helper-validator-identifier': 7.28.5
+ '@babel/helper-validator-identifier': 7.29.7
'@babel/traverse': 7.29.0
transitivePeerDependencies:
- supports-color
'@babel/helper-optimise-call-expression@7.27.1':
dependencies:
- '@babel/types': 7.29.0
+ '@babel/types': 7.29.7
'@babel/helper-plugin-utils@7.28.6': {}
@@ -11375,7 +11361,7 @@ snapshots:
'@babel/helper-skip-transparent-expression-wrappers@7.27.1':
dependencies:
'@babel/traverse': 7.29.0
- '@babel/types': 7.29.0
+ '@babel/types': 7.29.7
transitivePeerDependencies:
- supports-color
@@ -11396,7 +11382,7 @@ snapshots:
'@babel/helpers@7.28.6':
dependencies:
'@babel/template': 7.28.6
- '@babel/types': 7.29.0
+ '@babel/types': 7.29.7
'@babel/parser@7.29.2':
dependencies:
@@ -11438,17 +11424,17 @@ snapshots:
'@babel/template@7.28.6':
dependencies:
'@babel/code-frame': 7.29.0
- '@babel/parser': 7.29.3
- '@babel/types': 7.29.0
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
'@babel/traverse@7.29.0':
dependencies:
'@babel/code-frame': 7.29.0
'@babel/generator': 7.29.1
'@babel/helper-globals': 7.28.0
- '@babel/parser': 7.29.3
+ '@babel/parser': 7.29.7
'@babel/template': 7.28.6
- '@babel/types': 7.29.0
+ '@babel/types': 7.29.7
debug: 4.4.3
transitivePeerDependencies:
- supports-color
@@ -11547,8 +11533,6 @@ snapshots:
'@cloudflare/workerd-windows-64@1.20260616.1':
optional: true
- '@cloudflare/workers-types@4.20260518.1': {}
-
'@cloudflare/workers-types@4.20260617.1': {}
'@colordx/core@5.4.3': {}
@@ -11557,7 +11541,7 @@ snapshots:
dependencies:
'@simple-libs/child-process-utils': 1.0.1
'@simple-libs/stream-utils': 1.2.0
- semver: 7.8.0
+ semver: 7.8.4
optionalDependencies:
conventional-commits-filter: 5.0.0
conventional-commits-parser: 6.3.0
@@ -11571,10 +11555,10 @@ snapshots:
'@dxup/nuxt@0.4.1(magicast@0.5.2)(typescript@5.9.3)':
dependencies:
'@dxup/unimport': 0.1.2
- '@nuxt/kit': 4.4.5(magicast@0.5.2)
+ '@nuxt/kit': 4.4.8(magicast@0.5.2)
chokidar: 5.0.0
pathe: 2.0.3
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
optionalDependencies:
typescript: 5.9.3
transitivePeerDependencies:
@@ -12457,7 +12441,7 @@ snapshots:
https-proxy-agent: 7.0.6
node-fetch: 2.7.0(encoding@0.1.13)
nopt: 8.1.0
- semver: 7.8.0
+ semver: 7.8.4
tar: 7.5.2
transitivePeerDependencies:
- encoding
@@ -12529,7 +12513,7 @@ snapshots:
'@npmcli/fs@1.1.1':
dependencies:
'@gar/promisify': 1.1.3
- semver: 7.8.0
+ semver: 7.8.4
optional: true
'@npmcli/move-file@1.1.2':
@@ -12541,7 +12525,7 @@ snapshots:
'@nuxt/cli@3.35.2(@nuxt/schema@4.4.5)(cac@6.7.14)(magicast@0.5.2)':
dependencies:
'@bomb.sh/tab': 0.0.15(cac@6.7.14)(citty@0.2.2)
- '@clack/prompts': 1.4.0
+ '@clack/prompts': 1.5.1
c12: 3.3.4(magicast@0.5.2)
citty: 0.2.2
confbox: 0.2.4
@@ -12554,18 +12538,18 @@ snapshots:
giget: 3.2.0
jiti: 2.7.0
listhen: 1.10.0
- nypm: 0.6.6
+ nypm: 0.6.7
ofetch: 1.5.1
ohash: 2.0.11
pathe: 2.0.3
perfect-debounce: 2.1.0
pkg-types: 2.3.1
scule: 1.3.0
- semver: 7.8.0
+ semver: 7.8.4
srvx: 0.11.15
std-env: 4.1.0
tinyclip: 0.1.12
- tinyexec: 1.1.2
+ tinyexec: 1.2.4
ufo: 1.6.4
youch: 4.1.1
optionalDependencies:
@@ -12579,7 +12563,7 @@ snapshots:
'@nuxt/cli@3.35.2(@nuxt/schema@4.4.8)(cac@6.7.14)(magicast@0.5.2)':
dependencies:
'@bomb.sh/tab': 0.0.15(cac@6.7.14)(citty@0.2.2)
- '@clack/prompts': 1.4.0
+ '@clack/prompts': 1.5.1
c12: 3.3.4(magicast@0.5.2)
citty: 0.2.2
confbox: 0.2.4
@@ -12592,18 +12576,18 @@ snapshots:
giget: 3.2.0
jiti: 2.7.0
listhen: 1.10.0
- nypm: 0.6.6
+ nypm: 0.6.7
ofetch: 1.5.1
ohash: 2.0.11
pathe: 2.0.3
perfect-debounce: 2.1.0
pkg-types: 2.3.1
scule: 1.3.0
- semver: 7.8.0
+ semver: 7.8.4
srvx: 0.11.15
std-env: 4.1.0
tinyclip: 0.1.12
- tinyexec: 1.1.2
+ tinyexec: 1.2.4
ufo: 1.6.4
youch: 4.1.1
optionalDependencies:
@@ -12626,7 +12610,7 @@ snapshots:
'@nuxt/devtools-kit@3.2.4(magicast@0.5.2)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))':
dependencies:
- '@nuxt/kit': 4.4.5(magicast@0.5.2)
+ '@nuxt/kit': 4.4.8(magicast@0.5.2)
execa: 8.0.1
vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0)
transitivePeerDependencies:
@@ -12634,8 +12618,8 @@ snapshots:
'@nuxt/devtools-kit@4.0.0-alpha.3(magicast@0.5.2)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))':
dependencies:
- '@nuxt/kit': 4.4.5(magicast@0.5.2)
- tinyexec: 1.1.2
+ '@nuxt/kit': 4.4.8(magicast@0.5.2)
+ tinyexec: 1.2.4
vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0)
transitivePeerDependencies:
- magicast
@@ -12649,7 +12633,7 @@ snapshots:
magicast: 0.5.2
pathe: 2.0.3
pkg-types: 2.3.1
- semver: 7.8.0
+ semver: 7.8.4
'@nuxt/devtools@3.2.4(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))':
dependencies:
@@ -12776,7 +12760,7 @@ snapshots:
'@nuxt/fonts@0.14.0(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(ioredis@5.10.1)(magicast@0.5.2)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))':
dependencies:
'@nuxt/devtools-kit': 3.2.4(magicast@0.5.2)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))
- '@nuxt/kit': 4.4.5(magicast@0.5.2)
+ '@nuxt/kit': 4.4.8(magicast@0.5.2)
consola: 3.4.2
defu: 6.1.7
fontless: 0.2.1(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(ioredis@5.10.1)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))
@@ -12785,11 +12769,11 @@ snapshots:
ofetch: 1.5.1
pathe: 2.0.3
sirv: 3.0.2
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
ufo: 1.6.4
unifont: 0.7.4
unplugin: 3.0.0
- unstorage: 1.17.4(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(ioredis@5.10.1)
+ unstorage: 1.17.5(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(ioredis@5.10.1)
transitivePeerDependencies:
- '@azure/app-configuration'
- '@azure/cosmos'
@@ -12820,15 +12804,15 @@ snapshots:
'@iconify/utils': 3.1.0
'@iconify/vue': 5.0.0(vue@3.5.34(typescript@5.9.3))
'@nuxt/devtools-kit': 3.2.4(magicast@0.5.2)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))
- '@nuxt/kit': 4.4.5(magicast@0.5.2)
+ '@nuxt/kit': 4.4.8(magicast@0.5.2)
consola: 3.4.2
- local-pkg: 1.1.2
+ local-pkg: 1.2.1
mlly: 1.8.2
ohash: 2.0.11
pathe: 2.0.3
picomatch: 4.0.4
std-env: 3.10.0
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
transitivePeerDependencies:
- magicast
- vite
@@ -12836,7 +12820,7 @@ snapshots:
'@nuxt/image@2.0.0(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(ioredis@5.10.1)(magicast@0.5.2)':
dependencies:
- '@nuxt/kit': 4.4.5(magicast@0.5.2)
+ '@nuxt/kit': 4.4.8(magicast@0.5.2)
consola: 3.4.2
defu: 6.1.7
h3: 1.15.11
@@ -12888,8 +12872,8 @@ snapshots:
pkg-types: 2.3.1
rc9: 3.0.1
scule: 1.3.0
- semver: 7.8.0
- tinyglobby: 0.2.16
+ semver: 7.8.4
+ tinyglobby: 0.2.17
ufo: 1.6.4
unctx: 2.5.0
untyped: 2.0.0
@@ -12913,8 +12897,8 @@ snapshots:
pkg-types: 2.3.1
rc9: 3.0.1
scule: 1.3.0
- semver: 7.8.0
- tinyglobby: 0.2.16
+ semver: 7.8.4
+ tinyglobby: 0.2.17
ufo: 1.6.4
unctx: 2.5.0
untyped: 2.0.0
@@ -12969,13 +12953,13 @@ snapshots:
- vue
- vue-tsc
- '@nuxt/nitro-server@4.4.5(@babel/core@7.29.0)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(encoding@0.1.13)(ioredis@5.10.1)(magicast@0.5.2)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(oxc-parser@0.128.0)(rolldown@1.0.0-beta.57)(sqlite3@5.1.7)(typescript@5.9.3)':
+ '@nuxt/nitro-server@4.4.5(@babel/core@7.29.0)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(encoding@0.1.13)(ioredis@5.10.1)(magicast@0.5.2)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(oxc-parser@0.128.0)(rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(sqlite3@5.1.7)(typescript@5.9.3)':
dependencies:
'@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0)
'@nuxt/devalue': 2.0.2
'@nuxt/kit': 4.4.5(magicast@0.5.2)
- '@unhead/vue': 2.1.15(vue@3.5.34(typescript@5.9.3))
- '@vue/shared': 3.5.34
+ '@unhead/vue': 2.1.15(vue@3.5.38(typescript@5.9.3))
+ '@vue/shared': 3.5.38
consola: 3.4.2
defu: 6.1.7
destr: 2.0.5
@@ -12987,9 +12971,9 @@ snapshots:
impound: 1.1.5
klona: 2.0.6
mocked-exports: 0.1.1
- nitropack: 2.13.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(encoding@0.1.13)(oxc-parser@0.128.0)(rolldown@1.0.0-beta.57)(sqlite3@5.1.7)
- nuxt: 4.4.5(9fcd7314e3eecf945123399c7742c3cc)
- nypm: 0.6.6
+ nitropack: 2.13.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(encoding@0.1.13)(oxc-parser@0.128.0)(rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(sqlite3@5.1.7)
+ nuxt: 4.4.5(62a2f48613f90195bda1847309522735)
+ nypm: 0.6.7
ohash: 2.0.11
pathe: 2.0.3
rou3: 0.8.1
@@ -12997,7 +12981,7 @@ snapshots:
ufo: 1.6.4
unctx: 2.5.0
unstorage: 1.17.5(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(ioredis@5.10.1)
- vue: 3.5.34(typescript@5.9.3)
+ vue: 3.5.38(typescript@5.9.3)
vue-bundle-renderer: 2.2.0
vue-devtools-stub: 0.1.0
transitivePeerDependencies:
@@ -13042,8 +13026,8 @@ snapshots:
'@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0)
'@nuxt/devalue': 2.0.2
'@nuxt/kit': 4.4.5(magicast@0.5.2)
- '@unhead/vue': 2.1.15(vue@3.5.34(typescript@5.9.3))
- '@vue/shared': 3.5.34
+ '@unhead/vue': 2.1.15(vue@3.5.38(typescript@5.9.3))
+ '@vue/shared': 3.5.38
consola: 3.4.2
defu: 6.1.7
destr: 2.0.5
@@ -13057,7 +13041,7 @@ snapshots:
mocked-exports: 0.1.1
nitropack: 2.13.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(encoding@0.1.13)(oxc-parser@0.128.0)(rolldown@1.0.3)(sqlite3@5.1.7)
nuxt: 4.4.5(baa66a6a16e24a1b54c16423ff94cee6)
- nypm: 0.6.6
+ nypm: 0.6.7
ohash: 2.0.11
pathe: 2.0.3
rou3: 0.8.1
@@ -13065,7 +13049,7 @@ snapshots:
ufo: 1.6.4
unctx: 2.5.0
unstorage: 1.17.5(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(ioredis@5.10.1)
- vue: 3.5.34(typescript@5.9.3)
+ vue: 3.5.38(typescript@5.9.3)
vue-bundle-renderer: 2.2.0
vue-devtools-stub: 0.1.0
transitivePeerDependencies:
@@ -13110,8 +13094,8 @@ snapshots:
'@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0)
'@nuxt/devalue': 2.0.2
'@nuxt/kit': 4.4.5(magicast@0.5.2)
- '@unhead/vue': 2.1.15(vue@3.5.34(typescript@5.9.3))
- '@vue/shared': 3.5.34
+ '@unhead/vue': 2.1.15(vue@3.5.38(typescript@5.9.3))
+ '@vue/shared': 3.5.38
consola: 3.4.2
defu: 6.1.7
destr: 2.0.5
@@ -13125,7 +13109,7 @@ snapshots:
mocked-exports: 0.1.1
nitropack: 2.13.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(encoding@0.1.13)(oxc-parser@0.128.0)(rolldown@1.0.3)(sqlite3@5.1.7)
nuxt: 4.4.5(c678397a5d359c68ec65d805733eddf4)
- nypm: 0.6.6
+ nypm: 0.6.7
ohash: 2.0.11
pathe: 2.0.3
rou3: 0.8.1
@@ -13133,7 +13117,7 @@ snapshots:
ufo: 1.6.4
unctx: 2.5.0
unstorage: 1.17.5(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(ioredis@5.10.1)
- vue: 3.5.34(typescript@5.9.3)
+ vue: 3.5.38(typescript@5.9.3)
vue-bundle-renderer: 2.2.0
vue-devtools-stub: 0.1.0
transitivePeerDependencies:
@@ -13192,7 +13176,7 @@ snapshots:
mocked-exports: 0.1.1
nitropack: 2.13.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(encoding@0.1.13)(oxc-parser@0.133.0)(rolldown@1.0.3)(sqlite3@5.1.7)
nuxt: 4.4.8(15f32171ed0ebfb0b2ff2e97d246f626)
- nypm: 0.6.6
+ nypm: 0.6.7
ohash: 2.0.11
pathe: 2.0.3
rou3: 0.8.1
@@ -13243,7 +13227,7 @@ snapshots:
'@nuxt/schema@4.4.5':
dependencies:
- '@vue/shared': 3.5.34
+ '@vue/shared': 3.5.38
defu: 6.1.7
pathe: 2.0.3
pkg-types: 2.3.1
@@ -13660,7 +13644,7 @@ snapshots:
- vue
- yjs
- '@nuxt/vite-builder@4.4.5(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@types/node@25.8.0)(eslint@10.5.0(jiti@2.7.0))(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(optionator@0.9.4)(rolldown@1.0.0-beta.57)(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.57)(rollup@4.60.4))(rollup@4.60.4)(terser@5.45.0)(tsx@4.21.0)(typescript@5.9.3)(vue-tsc@3.2.9(typescript@5.9.3))(vue@3.5.34(typescript@5.9.3))(yaml@2.9.0)':
+ '@nuxt/vite-builder@4.4.5(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@types/node@25.8.0)(eslint@10.5.0(jiti@2.7.0))(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(optionator@0.9.4)(rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup@4.60.4))(rollup@4.60.4)(terser@5.45.0)(tsx@4.21.0)(typescript@5.9.3)(vue-tsc@3.2.9(typescript@5.9.3))(vue@3.5.34(typescript@5.9.3))(yaml@2.9.0)':
dependencies:
'@nuxt/kit': 4.4.5(magicast@0.5.2)
'@rollup/plugin-replace': 6.0.3(rollup@4.60.4)
@@ -13678,8 +13662,8 @@ snapshots:
magic-string: 0.30.21
mlly: 1.8.2
mocked-exports: 0.1.1
- nuxt: 4.4.5(9fcd7314e3eecf945123399c7742c3cc)
- nypm: 0.6.6
+ nuxt: 4.4.5(62a2f48613f90195bda1847309522735)
+ nypm: 0.6.7
pathe: 2.0.3
pkg-types: 2.3.1
postcss: 8.5.14
@@ -13694,8 +13678,8 @@ snapshots:
vue-bundle-renderer: 2.2.0
optionalDependencies:
'@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0)
- rolldown: 1.0.0-beta.57
- rollup-plugin-visualizer: 7.0.1(rolldown@1.0.0-beta.57)(rollup@4.60.4)
+ rolldown: 1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
+ rollup-plugin-visualizer: 7.0.1(rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup@4.60.4)
transitivePeerDependencies:
- '@biomejs/biome'
- '@types/node'
@@ -13740,7 +13724,7 @@ snapshots:
mlly: 1.8.2
mocked-exports: 0.1.1
nuxt: 4.4.5(baa66a6a16e24a1b54c16423ff94cee6)
- nypm: 0.6.6
+ nypm: 0.6.7
pathe: 2.0.3
pkg-types: 2.3.1
postcss: 8.5.14
@@ -13801,7 +13785,7 @@ snapshots:
mlly: 1.8.2
mocked-exports: 0.1.1
nuxt: 4.4.5(c678397a5d359c68ec65d805733eddf4)
- nypm: 0.6.6
+ nypm: 0.6.7
pathe: 2.0.3
pkg-types: 2.3.1
postcss: 8.5.14
@@ -13862,7 +13846,7 @@ snapshots:
mlly: 1.8.2
mocked-exports: 0.1.1
nuxt: 4.4.8(15f32171ed0ebfb0b2ff2e97d246f626)
- nypm: 0.6.6
+ nypm: 0.6.7
pathe: 2.0.3
pkg-types: 2.3.1
postcss: 8.5.15
@@ -13904,12 +13888,12 @@ snapshots:
'@nuxthub/core@0.10.7(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(ioredis@5.10.1)(magicast@0.5.2)(typescript@5.9.3)(vue-tsc@3.2.9(typescript@5.9.3))':
dependencies:
- '@cloudflare/workers-types': 4.20260518.1
+ '@cloudflare/workers-types': 4.20260617.1
'@nuxt/kit': 4.4.5(magicast@0.5.2)
'@uploadthing/mime-types': 0.3.6
- c12: 3.3.3(magicast@0.5.2)
+ c12: 3.3.4(magicast@0.5.2)
chokidar: 5.0.0
- citty: 0.2.1
+ citty: 0.2.2
consola: 3.4.2
defu: 6.1.7
execa: 9.6.1
@@ -13920,14 +13904,14 @@ snapshots:
nypm: 0.6.6
ofetch: 1.5.1
pathe: 2.0.3
- pkg-types: 2.3.0
+ pkg-types: 2.3.1
scule: 1.3.0
std-env: 3.10.0
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
tsdown: 0.18.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(typescript@5.9.3)(vue-tsc@3.2.9(typescript@5.9.3))
- ufo: 1.6.3
+ ufo: 1.6.4
uncrypto: 0.1.3
- unstorage: 1.17.4(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(ioredis@5.10.1)
+ unstorage: 1.17.5(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(ioredis@5.10.1)
zod: 4.3.6
transitivePeerDependencies:
- '@arethetypeswrong/core'
@@ -13969,7 +13953,7 @@ snapshots:
'@nuxt/kit': 3.21.2(magicast@0.5.2)
pathe: 1.1.2
pkg-types: 1.3.1
- semver: 7.8.0
+ semver: 7.8.4
transitivePeerDependencies:
- magicast
@@ -14036,9 +14020,9 @@ snapshots:
'@nuxtjs/mcp-toolkit@0.13.4(h3@1.15.11)(magicast@0.5.2)(zod@4.3.6)':
dependencies:
'@modelcontextprotocol/sdk': 1.29.0(zod@4.3.6)
- '@nuxt/kit': 4.4.5(magicast@0.5.2)
+ '@nuxt/kit': 4.4.8(magicast@0.5.2)
h3: 1.15.11
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
zod: 4.3.6
transitivePeerDependencies:
- '@cfworker/json-schema'
@@ -14047,7 +14031,7 @@ snapshots:
'@nuxtjs/mdc@0.21.1(magicast@0.5.2)':
dependencies:
- '@nuxt/kit': 4.4.5(magicast@0.5.2)
+ '@nuxt/kit': 4.4.8(magicast@0.5.2)
'@shikijs/core': 4.0.2
'@shikijs/engine-javascript': 4.0.2
'@shikijs/langs': 4.0.2
@@ -14154,15 +14138,15 @@ snapshots:
transitivePeerDependencies:
- magicast
- '@nuxtjs/robots@6.0.8(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)':
+ '@nuxtjs/robots@6.0.8(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)':
dependencies:
'@fingerprintjs/botd': 2.0.0
- '@nuxt/kit': 4.4.5(magicast@0.5.2)
+ '@nuxt/kit': 4.4.8(magicast@0.5.2)
consola: 3.4.2
defu: 6.1.7
h3: 1.15.11
- nuxt-site-config: 4.0.8(@nuxt/schema@4.4.8)(magicast@0.5.2)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)
- nuxtseo-shared: 5.1.3(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt-site-config@4.0.8(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@3.25.76))(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)
+ nuxt-site-config: 4.0.8(@nuxt/schema@4.4.8)(magicast@0.5.2)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)
+ nuxtseo-shared: 5.1.3(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt-site-config@4.0.8(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@3.25.76))(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)
pathe: 2.0.3
pkg-types: 2.3.1
ufo: 1.6.4
@@ -15034,9 +15018,12 @@ snapshots:
'@rolldown/binding-openharmony-arm64@1.0.3':
optional: true
- '@rolldown/binding-wasm32-wasi@1.0.0-beta.57':
+ '@rolldown/binding-wasm32-wasi@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
dependencies:
- '@napi-rs/wasm-runtime': 1.1.1
+ '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
+ transitivePeerDependencies:
+ - '@emnapi/core'
+ - '@emnapi/runtime'
optional: true
'@rolldown/binding-wasm32-wasi@1.0.0-beta.60(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
@@ -16019,8 +16006,8 @@ snapshots:
'@typescript-eslint/visitor-keys': 8.57.2
debug: 4.4.3
minimatch: 10.2.5
- semver: 7.8.0
- tinyglobby: 0.2.16
+ semver: 7.8.4
+ tinyglobby: 0.2.17
ts-api-utils: 2.4.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
@@ -16034,8 +16021,8 @@ snapshots:
'@typescript-eslint/visitor-keys': 8.61.1
debug: 4.4.3
minimatch: 10.2.5
- semver: 7.8.0
- tinyglobby: 0.2.16
+ semver: 7.8.4
+ tinyglobby: 0.2.17
ts-api-utils: 2.5.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
@@ -16141,9 +16128,9 @@ snapshots:
dependencies:
valibot: 1.4.1(typescript@5.9.3)
- '@vercel/analytics@2.0.1(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vue@3.5.34(typescript@5.9.3))':
+ '@vercel/analytics@2.0.1(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vue@3.5.34(typescript@5.9.3))':
optionalDependencies:
- nuxt: 4.4.5(9fcd7314e3eecf945123399c7742c3cc)
+ nuxt: 4.4.5(62a2f48613f90195bda1847309522735)
vue: 3.5.34(typescript@5.9.3)
'@vercel/nft@1.5.0(encoding@0.1.13)(rollup@4.60.4)':
@@ -16167,9 +16154,9 @@ snapshots:
'@vercel/oidc@3.2.0': {}
- '@vercel/speed-insights@2.0.0(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vue@3.5.34(typescript@5.9.3))':
+ '@vercel/speed-insights@2.0.0(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vue@3.5.34(typescript@5.9.3))':
optionalDependencies:
- nuxt: 4.4.5(9fcd7314e3eecf945123399c7742c3cc)
+ nuxt: 4.4.5(62a2f48613f90195bda1847309522735)
vue: 3.5.34(typescript@5.9.3)
'@vitejs/plugin-vue-jsx@5.1.5(vite@7.3.3(@types/node@25.8.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))':
@@ -16263,9 +16250,9 @@ snapshots:
'@vue-macros/common@3.1.2(vue@3.5.34(typescript@5.9.3))':
dependencies:
- '@vue/compiler-sfc': 3.5.34
+ '@vue/compiler-sfc': 3.5.38
ast-kit: 2.2.0
- local-pkg: 1.1.2
+ local-pkg: 1.2.1
magic-string-ast: 1.0.3
unplugin-utils: 0.3.1
optionalDependencies:
@@ -16273,9 +16260,9 @@ snapshots:
'@vue-macros/common@3.1.2(vue@3.5.38(typescript@5.9.3))':
dependencies:
- '@vue/compiler-sfc': 3.5.34
+ '@vue/compiler-sfc': 3.5.38
ast-kit: 2.2.0
- local-pkg: 1.1.2
+ local-pkg: 1.2.1
magic-string-ast: 1.0.3
unplugin-utils: 0.3.1
optionalDependencies:
@@ -16290,10 +16277,10 @@ snapshots:
'@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0)
'@babel/template': 7.28.6
'@babel/traverse': 7.29.0
- '@babel/types': 7.29.0
+ '@babel/types': 7.29.7
'@vue/babel-helper-vue-transform-on': 2.0.1
'@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.29.0)
- '@vue/shared': 3.5.34
+ '@vue/shared': 3.5.38
optionalDependencies:
'@babel/core': 7.29.0
transitivePeerDependencies:
@@ -16305,8 +16292,8 @@ snapshots:
'@babel/core': 7.29.0
'@babel/helper-module-imports': 7.28.6
'@babel/helper-plugin-utils': 7.28.6
- '@babel/parser': 7.29.3
- '@vue/compiler-sfc': 3.5.34
+ '@babel/parser': 7.29.7
+ '@vue/compiler-sfc': 3.5.38
transitivePeerDependencies:
- supports-color
@@ -16363,14 +16350,14 @@ snapshots:
'@vue/compiler-sfc@3.5.34':
dependencies:
- '@babel/parser': 7.29.3
+ '@babel/parser': 7.29.7
'@vue/compiler-core': 3.5.34
'@vue/compiler-dom': 3.5.34
'@vue/compiler-ssr': 3.5.34
'@vue/shared': 3.5.34
estree-walker: 2.0.2
magic-string: 0.30.21
- postcss: 8.5.14
+ postcss: 8.5.15
source-map-js: 1.2.1
'@vue/compiler-sfc@3.5.38':
@@ -16443,8 +16430,8 @@ snapshots:
'@vue/language-core@3.2.9':
dependencies:
'@volar/language-core': 2.4.28
- '@vue/compiler-dom': 3.5.34
- '@vue/shared': 3.5.34
+ '@vue/compiler-dom': 3.5.38
+ '@vue/shared': 3.5.38
alien-signals: 3.2.1
muggle-string: 0.4.1
path-browserify: 1.0.1
@@ -16455,7 +16442,7 @@ snapshots:
dependencies:
'@volar/language-core': 2.4.28
'@vue/compiler-dom': 3.5.34
- '@vue/shared': 3.5.34
+ '@vue/shared': 3.5.38
alien-signals: 3.2.1
muggle-string: 0.4.1
path-browserify: 1.0.1
@@ -16465,7 +16452,7 @@ snapshots:
dependencies:
'@volar/language-core': 2.4.28
'@vue/compiler-dom': 3.5.34
- '@vue/shared': 3.5.34
+ '@vue/shared': 3.5.38
alien-signals: 3.2.1
muggle-string: 0.4.1
path-browserify: 1.0.1
@@ -16569,13 +16556,13 @@ snapshots:
'@vueuse/metadata@14.3.0': {}
- '@vueuse/nuxt@14.3.0(magicast@0.5.2)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vue@3.5.34(typescript@5.9.3))':
+ '@vueuse/nuxt@14.3.0(magicast@0.5.2)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vue@3.5.34(typescript@5.9.3))':
dependencies:
'@nuxt/kit': 4.4.5(magicast@0.5.2)
'@vueuse/core': 14.3.0(vue@3.5.34(typescript@5.9.3))
'@vueuse/metadata': 14.3.0
local-pkg: 1.1.2
- nuxt: 4.4.5(9fcd7314e3eecf945123399c7742c3cc)
+ nuxt: 4.4.5(62a2f48613f90195bda1847309522735)
vue: 3.5.34(typescript@5.9.3)
transitivePeerDependencies:
- magicast
@@ -16744,7 +16731,7 @@ snapshots:
ast-kit@2.2.0:
dependencies:
- '@babel/parser': 7.29.3
+ '@babel/parser': 7.29.7
pathe: 2.0.3
ast-types@0.13.4:
@@ -16758,8 +16745,8 @@ snapshots:
ast-walker-scope@0.9.0:
dependencies:
- '@babel/parser': 7.29.3
- '@babel/types': 7.29.0
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
ast-kit: 2.2.0
async-lock@1.4.1: {}
@@ -16881,7 +16868,7 @@ snapshots:
browserslist@4.28.1:
dependencies:
baseline-browser-mapping: 2.9.14
- caniuse-lite: 1.0.30001781
+ caniuse-lite: 1.0.30001793
electron-to-chromium: 1.5.267
node-releases: 2.0.27
update-browserslist-db: 1.2.3(browserslist@4.28.1)
@@ -16916,7 +16903,7 @@ snapshots:
bun-types@1.3.14:
dependencies:
- '@types/node': 25.8.0
+ '@types/node': 25.9.3
bundle-name@4.1.0:
dependencies:
@@ -17066,8 +17053,6 @@ snapshots:
dependencies:
consola: 3.4.2
- citty@0.2.1: {}
-
citty@0.2.2: {}
clean-git-ref@2.0.1: {}
@@ -17186,7 +17171,7 @@ snapshots:
conventional-commits-filter: 5.0.0
handlebars: 4.7.8
meow: 13.2.0
- semver: 7.8.0
+ semver: 7.8.4
conventional-changelog@7.2.0(conventional-commits-filter@5.0.0):
dependencies:
@@ -17296,7 +17281,7 @@ snapshots:
cssnano-preset-default@7.0.11(postcss@8.5.8):
dependencies:
- browserslist: 4.28.1
+ browserslist: 4.28.2
css-declaration-sorter: 7.3.1(postcss@8.5.8)
cssnano-utils: 5.0.1(postcss@8.5.8)
postcss: 8.5.8
@@ -17524,7 +17509,7 @@ snapshots:
diff@8.0.3: {}
- docus@5.11.0(7be736386eac822290d67dfa1d56f35d):
+ docus@5.11.0(ce604dd20a7a75c44e066ad05d4cf9aa):
dependencies:
'@ai-sdk/gateway': 3.0.115(zod@4.3.6)
'@ai-sdk/mcp': 1.0.42(zod@4.3.6)
@@ -17539,7 +17524,7 @@ snapshots:
'@nuxtjs/i18n': 10.3.0(@vue/compiler-dom@3.5.38)(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(eslint@10.5.0(jiti@2.7.0))(ioredis@5.10.1)(magicast@0.5.2)(rollup@4.60.4)(typescript@5.9.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))
'@nuxtjs/mcp-toolkit': 0.13.4(h3@1.15.11)(magicast@0.5.2)(zod@4.3.6)
'@nuxtjs/mdc': 0.21.1(magicast@0.5.2)
- '@nuxtjs/robots': 6.0.8(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)
+ '@nuxtjs/robots': 6.0.8(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)
'@shikijs/core': 4.0.2
'@shikijs/engine-javascript': 4.0.2
'@shikijs/langs': 4.0.2
@@ -17552,9 +17537,9 @@ snapshots:
exsolve: 1.0.8
git-url-parse: 16.1.0
motion-v: 2.2.1(@vueuse/core@14.2.1(vue@3.5.34(typescript@5.9.3)))(vue@3.5.34(typescript@5.9.3))
- nuxt: 4.4.5(9fcd7314e3eecf945123399c7742c3cc)
+ nuxt: 4.4.5(62a2f48613f90195bda1847309522735)
nuxt-llms: 0.2.0(magicast@0.5.2)
- nuxt-og-image: 6.5.0(40230ecdb76470dff0ccde617fd5a564)
+ nuxt-og-image: 6.5.0(86a9d2a5375bab160bd82d7aedcce194)
pathe: 2.0.3
pkg-types: 2.3.1
scule: 1.3.0
@@ -17702,7 +17687,7 @@ snapshots:
'@one-ini/wasm': 0.1.1
commander: 10.0.1
minimatch: 9.0.1
- semver: 7.8.0
+ semver: 7.8.4
ee-first@1.1.1: {}
@@ -17980,7 +17965,7 @@ snapshots:
eslint-import-context: 0.1.9(unrs-resolver@1.11.1)
is-glob: 4.0.3
minimatch: 10.2.5
- semver: 7.8.0
+ semver: 7.8.4
stable-hash-x: 0.2.0
unrs-resolver: 1.11.1
optionalDependencies:
@@ -18035,7 +18020,7 @@ snapshots:
jsesc: 3.1.0
pluralize: 8.0.0
regjsparser: 0.13.0
- semver: 7.8.0
+ semver: 7.8.4
strip-indent: 4.1.1
eslint-plugin-vue@10.9.2(@stylistic/eslint-plugin@5.10.0(eslint@10.5.0(jiti@2.7.0)))(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.5.0(jiti@2.7.0))(vue-eslint-parser@10.4.1(eslint@10.5.0(jiti@2.7.0))):
@@ -18045,7 +18030,7 @@ snapshots:
natural-compare: 1.4.0
nth-check: 2.1.1
postcss-selector-parser: 7.1.1
- semver: 7.8.0
+ semver: 7.8.4
vue-eslint-parser: 10.4.1(eslint@10.5.0(jiti@2.7.0))
xml-name-validator: 4.0.0
optionalDependencies:
@@ -18497,7 +18482,7 @@ snapshots:
consola: 3.4.2
defu: 6.1.7
node-fetch-native: 1.6.7
- nypm: 0.6.6
+ nypm: 0.6.7
pathe: 2.0.3
giget@3.2.0: {}
@@ -18872,6 +18857,8 @@ snapshots:
image-meta@0.2.2: {}
+ image-size@2.0.2: {}
+
import-without-cache@0.2.5: {}
impound@1.1.5:
@@ -19158,7 +19145,7 @@ snapshots:
acorn: 8.16.0
eslint-visitor-keys: 3.4.3
espree: 9.6.1
- semver: 7.8.0
+ semver: 7.8.4
keyv@4.5.4:
dependencies:
@@ -19397,8 +19384,8 @@ snapshots:
magicast@0.5.2:
dependencies:
- '@babel/parser': 7.29.3
- '@babel/types': 7.29.0
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
source-map-js: 1.2.1
make-fetch-happen@9.1.0:
@@ -19898,8 +19885,8 @@ snapshots:
pkg-types: 2.3.1
postcss: 8.5.8
postcss-nested: 7.0.2(postcss@8.5.8)
- semver: 7.8.0
- tinyglobby: 0.2.16
+ semver: 7.8.4
+ tinyglobby: 0.2.17
optionalDependencies:
typescript: 5.9.3
vue: 3.5.38(typescript@5.9.3)
@@ -19967,7 +19954,7 @@ snapshots:
dependencies:
type-fest: 2.19.0
- nitropack@2.13.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(encoding@0.1.13)(oxc-parser@0.128.0)(rolldown@1.0.0-beta.57)(sqlite3@5.1.7):
+ nitropack@2.13.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(encoding@0.1.13)(oxc-parser@0.128.0)(rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(sqlite3@5.1.7):
dependencies:
'@cloudflare/kv-asset-handler': 0.4.2
'@rollup/plugin-alias': 6.0.0(rollup@4.60.4)
@@ -20020,9 +20007,9 @@ snapshots:
pretty-bytes: 7.1.0
radix3: 1.1.2
rollup: 4.60.4
- rollup-plugin-visualizer: 7.0.1(rolldown@1.0.0-beta.57)(rollup@4.60.4)
+ rollup-plugin-visualizer: 7.0.1(rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup@4.60.4)
scule: 1.3.0
- semver: 7.8.0
+ semver: 7.8.4
serve-placeholder: 2.0.2
serve-static: 2.2.1
source-map: 0.7.6
@@ -20032,7 +20019,7 @@ snapshots:
uncrypto: 0.1.3
unctx: 2.5.0
unenv: 2.0.0-rc.24
- unimport: 6.3.0(oxc-parser@0.128.0)(rolldown@1.0.0-beta.57)
+ unimport: 6.3.0(oxc-parser@0.128.0)(rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))
unplugin-utils: 0.3.1
unstorage: 1.17.5(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(ioredis@5.10.1)
untyped: 2.0.0
@@ -20125,7 +20112,7 @@ snapshots:
rollup: 4.60.4
rollup-plugin-visualizer: 7.0.1(rolldown@1.0.3)(rollup@4.60.4)
scule: 1.3.0
- semver: 7.8.0
+ semver: 7.8.4
serve-placeholder: 2.0.2
serve-static: 2.2.1
source-map: 0.7.6
@@ -20228,7 +20215,7 @@ snapshots:
rollup: 4.60.4
rollup-plugin-visualizer: 7.0.1(rolldown@1.0.3)(rollup@4.60.4)
scule: 1.3.0
- semver: 7.8.0
+ semver: 7.8.4
serve-placeholder: 2.0.2
serve-static: 2.2.1
source-map: 0.7.6
@@ -20278,7 +20265,7 @@ snapshots:
node-abi@3.85.0:
dependencies:
- semver: 7.8.0
+ semver: 7.8.4
node-addon-api@7.1.1: {}
@@ -20313,7 +20300,7 @@ snapshots:
nopt: 5.0.0
npmlog: 6.0.2
rimraf: 3.0.2
- semver: 7.8.0
+ semver: 7.8.4
tar: 6.2.1
which: 2.0.2
transitivePeerDependencies:
@@ -20343,7 +20330,7 @@ snapshots:
normalize-package-data@7.0.1:
dependencies:
hosted-git-info: 8.1.0
- semver: 7.8.0
+ semver: 7.8.4
validate-npm-package-license: 3.0.4
normalize-path@3.0.0: {}
@@ -20390,10 +20377,10 @@ snapshots:
transitivePeerDependencies:
- magicast
- nuxt-og-image@6.5.0(40230ecdb76470dff0ccde617fd5a564):
+ nuxt-og-image@6.5.0(86a9d2a5375bab160bd82d7aedcce194):
dependencies:
'@clack/prompts': 1.4.0
- '@nuxt/kit': 4.4.5(magicast@0.5.2)
+ '@nuxt/kit': 4.4.8(magicast@0.5.2)
'@unhead/vue': 2.1.15(vue@3.5.34(typescript@5.9.3))
'@vue/compiler-sfc': 3.5.34
chrome-launcher: 1.2.1
@@ -20406,9 +20393,9 @@ snapshots:
magic-string: 0.30.21
magicast: 0.5.2
mocked-exports: 0.1.1
- nuxt-site-config: 4.0.8(@nuxt/schema@4.4.8)(magicast@0.5.2)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)
- nuxtseo-shared: 5.1.3(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt-site-config@4.0.8(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@3.25.76))(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)
- nypm: 0.6.6
+ nuxt-site-config: 4.0.8(@nuxt/schema@4.4.8)(magicast@0.5.2)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)
+ nuxtseo-shared: 5.1.3(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt-site-config@4.0.8(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@3.25.76))(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)
+ nypm: 0.6.7
ofetch: 1.5.1
ohash: 2.0.11
oxc-parser: 0.128.0
@@ -20419,7 +20406,7 @@ snapshots:
std-env: 4.1.0
strip-literal: 3.1.0
tinyexec: 1.1.2
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
ufo: 1.6.4
ultrahtml: 1.6.0
unplugin: 3.0.0
@@ -20440,7 +20427,7 @@ snapshots:
nuxt-site-config-kit@4.0.8(magicast@0.5.2)(vue@3.5.34(typescript@5.9.3)):
dependencies:
- '@nuxt/kit': 4.4.5(magicast@0.5.2)
+ '@nuxt/kit': 4.4.8(magicast@0.5.2)
site-config-stack: 4.0.8(vue@3.5.34(typescript@5.9.3))
std-env: 4.1.0
ufo: 1.6.4
@@ -20448,12 +20435,12 @@ snapshots:
- magicast
- vue
- nuxt-site-config@4.0.8(@nuxt/schema@4.4.8)(magicast@0.5.2)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6):
+ nuxt-site-config@4.0.8(@nuxt/schema@4.4.8)(magicast@0.5.2)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6):
dependencies:
- '@nuxt/kit': 4.4.5(magicast@0.5.2)
+ '@nuxt/kit': 4.4.8(magicast@0.5.2)
h3: 1.15.11
nuxt-site-config-kit: 4.0.8(magicast@0.5.2)(vue@3.5.34(typescript@5.9.3))
- nuxtseo-shared: 5.1.3(@nuxt/schema@4.4.8)(magicast@0.5.2)(nuxt-site-config@4.0.8(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@3.25.76))(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)
+ nuxtseo-shared: 5.1.3(@nuxt/schema@4.4.8)(magicast@0.5.2)(nuxt-site-config@4.0.8(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@3.25.76))(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)
pathe: 2.0.3
pkg-types: 2.3.1
site-config-stack: 4.0.8(vue@3.5.34(typescript@5.9.3))
@@ -20510,16 +20497,16 @@ snapshots:
- uploadthing
- vue
- nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc):
+ nuxt@4.4.5(62a2f48613f90195bda1847309522735):
dependencies:
'@dxup/nuxt': 0.4.1(magicast@0.5.2)(typescript@5.9.3)
'@nuxt/cli': 3.35.2(@nuxt/schema@4.4.5)(cac@6.7.14)(magicast@0.5.2)
'@nuxt/devtools': 3.2.4(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))
'@nuxt/kit': 4.4.5(magicast@0.5.2)
- '@nuxt/nitro-server': 4.4.5(@babel/core@7.29.0)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(encoding@0.1.13)(ioredis@5.10.1)(magicast@0.5.2)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(oxc-parser@0.128.0)(rolldown@1.0.0-beta.57)(sqlite3@5.1.7)(typescript@5.9.3)
+ '@nuxt/nitro-server': 4.4.5(@babel/core@7.29.0)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(db0@0.3.4(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(better-sqlite3@12.10.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(sqlite3@5.1.7))(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260617.1)(@electric-sql/pglite@0.4.2)(@libsql/client@0.17.3)(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(bun-types@1.3.14)(sqlite3@5.1.7))(encoding@0.1.13)(ioredis@5.10.1)(magicast@0.5.2)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(oxc-parser@0.128.0)(rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(sqlite3@5.1.7)(typescript@5.9.3)
'@nuxt/schema': 4.4.5
'@nuxt/telemetry': 2.8.0(@nuxt/kit@4.4.5(magicast@0.5.2))
- '@nuxt/vite-builder': 4.4.5(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@types/node@25.8.0)(eslint@10.5.0(jiti@2.7.0))(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(optionator@0.9.4)(rolldown@1.0.0-beta.57)(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.57)(rollup@4.60.4))(rollup@4.60.4)(terser@5.45.0)(tsx@4.21.0)(typescript@5.9.3)(vue-tsc@3.2.9(typescript@5.9.3))(vue@3.5.34(typescript@5.9.3))(yaml@2.9.0)
+ '@nuxt/vite-builder': 4.4.5(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@types/node@25.8.0)(eslint@10.5.0(jiti@2.7.0))(lightningcss@1.32.0)(magicast@0.5.2)(meow@13.2.0)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(optionator@0.9.4)(rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup@4.60.4))(rollup@4.60.4)(terser@5.45.0)(tsx@4.21.0)(typescript@5.9.3)(vue-tsc@3.2.9(typescript@5.9.3))(vue@3.5.34(typescript@5.9.3))(yaml@2.9.0)
'@unhead/vue': 2.1.15(vue@3.5.34(typescript@5.9.3))
'@vue/shared': 3.5.34
chokidar: 5.0.0
@@ -20561,12 +20548,12 @@ snapshots:
ultrahtml: 1.6.0
uncrypto: 0.1.3
unctx: 2.5.0
- unimport: 6.3.0(oxc-parser@0.128.0)(rolldown@1.0.0-beta.57)
+ unimport: 6.3.0(oxc-parser@0.128.0)(rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))
unplugin: 3.0.0
unrouting: 0.1.7
untyped: 2.0.0
vue: 3.5.34(typescript@5.9.3)
- vue-router: 5.0.7(@vue/compiler-sfc@3.5.38)(vue@3.5.34(typescript@5.9.3))
+ vue-router: 5.1.0(@vue/compiler-sfc@3.5.38)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))
optionalDependencies:
'@parcel/watcher': 2.5.6
'@types/node': 25.8.0
@@ -20694,7 +20681,7 @@ snapshots:
unrouting: 0.1.7
untyped: 2.0.0
vue: 3.5.34(typescript@5.9.3)
- vue-router: 5.0.7(@vue/compiler-sfc@3.5.31)(vue@3.5.34(typescript@5.9.3))
+ vue-router: 5.1.0(@vue/compiler-sfc@3.5.31)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))
optionalDependencies:
'@parcel/watcher': 2.5.6
'@types/node': 25.8.0
@@ -20822,7 +20809,7 @@ snapshots:
unrouting: 0.1.7
untyped: 2.0.0
vue: 3.5.34(typescript@5.9.3)
- vue-router: 5.0.7(@vue/compiler-sfc@3.5.38)(vue@3.5.34(typescript@5.9.3))
+ vue-router: 5.1.0(@vue/compiler-sfc@3.5.38)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))
optionalDependencies:
'@parcel/watcher': 2.5.6
'@types/node': 25.8.0
@@ -20924,7 +20911,7 @@ snapshots:
magic-string: 0.30.21
mlly: 1.8.2
nanotar: 0.3.0
- nypm: 0.6.6
+ nypm: 0.6.7
ofetch: 1.5.1
ohash: 2.0.11
on-change: 6.0.2
@@ -21021,16 +21008,16 @@ snapshots:
- xml2js
- yaml
- nuxtseo-shared@5.1.3(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt-site-config@4.0.8(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@3.25.76))(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6):
+ nuxtseo-shared@5.1.3(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt-site-config@4.0.8(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@3.25.76))(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6):
dependencies:
'@clack/prompts': 1.4.0
'@nuxt/devtools-kit': 4.0.0-alpha.3(magicast@0.5.2)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))
- '@nuxt/kit': 4.4.5(magicast@0.5.2)
+ '@nuxt/kit': 4.4.8(magicast@0.5.2)
'@nuxt/schema': 4.4.5
birpc: 4.0.0
consola: 3.4.2
defu: 6.1.7
- nuxt: 4.4.5(9fcd7314e3eecf945123399c7742c3cc)
+ nuxt: 4.4.5(62a2f48613f90195bda1847309522735)
ofetch: 1.5.1
pathe: 2.0.3
pkg-types: 2.3.1
@@ -21040,22 +21027,22 @@ snapshots:
ufo: 1.6.4
vue: 3.5.34(typescript@5.9.3)
optionalDependencies:
- nuxt-site-config: 4.0.8(@nuxt/schema@4.4.8)(magicast@0.5.2)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)
+ nuxt-site-config: 4.0.8(@nuxt/schema@4.4.8)(magicast@0.5.2)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)
zod: 4.3.6
transitivePeerDependencies:
- magicast
- vite
- nuxtseo-shared@5.1.3(@nuxt/schema@4.4.8)(magicast@0.5.2)(nuxt-site-config@4.0.8(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@3.25.76))(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6):
+ nuxtseo-shared@5.1.3(@nuxt/schema@4.4.8)(magicast@0.5.2)(nuxt-site-config@4.0.8(@nuxt/schema@4.4.5)(magicast@0.5.2)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@3.25.76))(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6):
dependencies:
'@clack/prompts': 1.4.0
'@nuxt/devtools-kit': 4.0.0-alpha.3(magicast@0.5.2)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))
- '@nuxt/kit': 4.4.5(magicast@0.5.2)
+ '@nuxt/kit': 4.4.8(magicast@0.5.2)
'@nuxt/schema': 4.4.8
birpc: 4.0.0
consola: 3.4.2
defu: 6.1.7
- nuxt: 4.4.5(9fcd7314e3eecf945123399c7742c3cc)
+ nuxt: 4.4.5(62a2f48613f90195bda1847309522735)
ofetch: 1.5.1
pathe: 2.0.3
pkg-types: 2.3.1
@@ -21065,7 +21052,7 @@ snapshots:
ufo: 1.6.4
vue: 3.5.34(typescript@5.9.3)
optionalDependencies:
- nuxt-site-config: 4.0.8(@nuxt/schema@4.4.8)(magicast@0.5.2)(nuxt@4.4.5(9fcd7314e3eecf945123399c7742c3cc))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)
+ nuxt-site-config: 4.0.8(@nuxt/schema@4.4.8)(magicast@0.5.2)(nuxt@4.4.5(62a2f48613f90195bda1847309522735))(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3))(zod@4.3.6)
zod: 4.3.6
transitivePeerDependencies:
- magicast
@@ -21527,12 +21514,6 @@ snapshots:
mlly: 1.8.2
pathe: 2.0.3
- pkg-types@2.3.0:
- dependencies:
- confbox: 0.2.4
- exsolve: 1.0.8
- pathe: 2.0.3
-
pkg-types@2.3.1:
dependencies:
confbox: 0.2.4
@@ -22486,24 +22467,24 @@ snapshots:
glob: 7.2.3
optional: true
- rolldown-plugin-dts@0.20.0(rolldown@1.0.0-beta.57)(typescript@5.9.3)(vue-tsc@3.2.9(typescript@5.9.3)):
+ rolldown-plugin-dts@0.20.0(rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(typescript@5.9.3)(vue-tsc@3.2.9(typescript@5.9.3)):
dependencies:
'@babel/generator': 7.29.1
- '@babel/parser': 7.29.2
- '@babel/types': 7.29.0
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
ast-kit: 2.2.0
birpc: 4.0.0
dts-resolver: 2.1.3
get-tsconfig: 4.13.0
obug: 2.1.1
- rolldown: 1.0.0-beta.57
+ rolldown: 1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
optionalDependencies:
typescript: 5.9.3
vue-tsc: 3.2.9(typescript@5.9.3)
transitivePeerDependencies:
- oxc-resolver
- rolldown@1.0.0-beta.57:
+ rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0):
dependencies:
'@oxc-project/types': 0.103.0
'@rolldown/pluginutils': 1.0.0-beta.57
@@ -22518,9 +22499,12 @@ snapshots:
'@rolldown/binding-linux-x64-gnu': 1.0.0-beta.57
'@rolldown/binding-linux-x64-musl': 1.0.0-beta.57
'@rolldown/binding-openharmony-arm64': 1.0.0-beta.57
- '@rolldown/binding-wasm32-wasi': 1.0.0-beta.57
+ '@rolldown/binding-wasm32-wasi': 1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
'@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.57
'@rolldown/binding-win32-x64-msvc': 1.0.0-beta.57
+ transitivePeerDependencies:
+ - '@emnapi/core'
+ - '@emnapi/runtime'
rolldown@1.0.0-beta.60(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0):
dependencies:
@@ -22573,14 +22557,14 @@ snapshots:
optionalDependencies:
'@babel/code-frame': 7.29.0
- rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.57)(rollup@4.60.4):
+ rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(rollup@4.60.4):
dependencies:
open: 11.0.0
picomatch: 4.0.4
source-map: 0.7.6
yargs: 18.0.0
optionalDependencies:
- rolldown: 1.0.0-beta.57
+ rolldown: 1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
rollup: 4.60.4
rollup-plugin-visualizer@7.0.1(rolldown@1.0.3)(rollup@4.60.4):
@@ -22756,7 +22740,7 @@ snapshots:
dependencies:
'@img/colour': 1.0.0
detect-libc: 2.1.2
- semver: 7.8.0
+ semver: 7.8.4
optionalDependencies:
'@img/sharp-darwin-arm64': 0.34.5
'@img/sharp-darwin-x64': 0.34.5
@@ -23076,13 +23060,13 @@ snapshots:
dependencies:
browserslist: 4.28.2
postcss: 8.5.14
- postcss-selector-parser: 7.1.1
+ postcss-selector-parser: 7.1.4
stylehacks@7.0.7(postcss@8.5.8):
dependencies:
browserslist: 4.28.2
postcss: 8.5.8
- postcss-selector-parser: 7.1.1
+ postcss-selector-parser: 7.1.4
stylehacks@8.0.1(postcss@8.5.15):
dependencies:
@@ -23266,15 +23250,15 @@ snapshots:
cac: 6.7.14
defu: 6.1.7
empathic: 2.0.0
- hookable: 6.1.0
+ hookable: 6.1.1
import-without-cache: 0.2.5
obug: 2.1.1
picomatch: 4.0.4
- rolldown: 1.0.0-beta.57
- rolldown-plugin-dts: 0.20.0(rolldown@1.0.0-beta.57)(typescript@5.9.3)(vue-tsc@3.2.9(typescript@5.9.3))
- semver: 7.8.0
- tinyexec: 1.1.2
- tinyglobby: 0.2.16
+ rolldown: 1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
+ rolldown-plugin-dts: 0.20.0(rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0))(typescript@5.9.3)(vue-tsc@3.2.9(typescript@5.9.3))
+ semver: 7.8.4
+ tinyexec: 1.2.4
+ tinyglobby: 0.2.17
tree-kill: 1.2.2
unconfig-core: 7.4.2
unrun: 0.2.25(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
@@ -23363,7 +23347,7 @@ snapshots:
rollup: 4.55.1
rollup-plugin-dts: 6.3.0(rollup@4.55.1)(typescript@5.9.3)
scule: 1.3.0
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
untyped: 2.0.0
optionalDependencies:
typescript: 5.9.3
@@ -23438,16 +23422,16 @@ snapshots:
pkg-types: 2.3.1
scule: 1.3.0
strip-literal: 3.1.0
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
unplugin: 2.3.11
unplugin-utils: 0.3.1
- unimport@6.3.0(oxc-parser@0.128.0)(rolldown@1.0.0-beta.57):
+ unimport@6.3.0(oxc-parser@0.128.0)(rolldown@1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)):
dependencies:
acorn: 8.16.0
escape-string-regexp: 5.0.0
estree-walker: 3.0.3
- local-pkg: 1.1.2
+ local-pkg: 1.2.1
magic-string: 0.30.21
mlly: 1.8.2
pathe: 2.0.3
@@ -23455,19 +23439,19 @@ snapshots:
pkg-types: 2.3.1
scule: 1.3.0
strip-literal: 3.1.0
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
unplugin: 3.0.0
unplugin-utils: 0.3.1
optionalDependencies:
oxc-parser: 0.128.0
- rolldown: 1.0.0-beta.57
+ rolldown: 1.0.0-beta.57(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
unimport@6.3.0(oxc-parser@0.128.0)(rolldown@1.0.3):
dependencies:
acorn: 8.16.0
escape-string-regexp: 5.0.0
estree-walker: 3.0.3
- local-pkg: 1.1.2
+ local-pkg: 1.2.1
magic-string: 0.30.21
mlly: 1.8.2
pathe: 2.0.3
@@ -23475,7 +23459,7 @@ snapshots:
pkg-types: 2.3.1
scule: 1.3.0
strip-literal: 3.1.0
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
unplugin: 3.0.0
unplugin-utils: 0.3.1
optionalDependencies:
@@ -23487,7 +23471,7 @@ snapshots:
acorn: 8.16.0
escape-string-regexp: 5.0.0
estree-walker: 3.0.3
- local-pkg: 1.1.2
+ local-pkg: 1.2.1
magic-string: 0.30.21
mlly: 1.8.2
pathe: 2.0.3
@@ -23495,7 +23479,7 @@ snapshots:
pkg-types: 2.3.1
scule: 1.3.0
strip-literal: 3.1.0
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
unplugin: 3.0.0
unplugin-utils: 0.3.1
optionalDependencies:
@@ -23592,7 +23576,7 @@ snapshots:
mlly: 1.8.2
obug: 2.1.1
picomatch: 4.0.4
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
unplugin: 3.0.0
unplugin-utils: 0.3.1
vue: 3.5.34(typescript@5.9.3)
@@ -23829,7 +23813,7 @@ snapshots:
picomatch: 4.0.4
proper-lockfile: 4.1.2
tiny-invariant: 1.3.3
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
vite: 7.3.3(@types/node@25.8.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0)
vscode-uri: 3.1.0
optionalDependencies:
@@ -23848,7 +23832,7 @@ snapshots:
picomatch: 4.0.4
proper-lockfile: 4.1.2
tiny-invariant: 1.3.3
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
vite: 7.3.3(@types/node@25.8.0)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0)
vscode-uri: 3.1.0
optionalDependencies:
@@ -23917,9 +23901,9 @@ snapshots:
esbuild: 0.27.3
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
- postcss: 8.5.14
+ postcss: 8.5.15
rollup: 4.60.4
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
optionalDependencies:
'@types/node': 25.8.0
fsevents: 2.3.3
@@ -23934,9 +23918,9 @@ snapshots:
esbuild: 0.27.3
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
- postcss: 8.5.14
+ postcss: 8.5.15
rollup: 4.60.4
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
optionalDependencies:
'@types/node': 25.9.3
fsevents: 2.3.3
@@ -24048,7 +24032,7 @@ snapshots:
eslint-visitor-keys: 5.0.1
espree: 11.2.0
esquery: 1.7.0
- semver: 7.8.0
+ semver: 7.8.4
transitivePeerDependencies:
- supports-color
@@ -24074,30 +24058,7 @@ snapshots:
pathe: 2.0.3
picomatch: 4.0.4
scule: 1.3.0
- tinyglobby: 0.2.16
- unplugin: 3.0.0
- unplugin-utils: 0.3.1
- vue: 3.5.34(typescript@5.9.3)
- yaml: 2.8.2
- optionalDependencies:
- '@vue/compiler-sfc': 3.5.31
-
- vue-router@5.0.7(@vue/compiler-sfc@3.5.31)(vue@3.5.34(typescript@5.9.3)):
- dependencies:
- '@babel/generator': 8.0.0-rc.5
- '@vue-macros/common': 3.1.2(vue@3.5.34(typescript@5.9.3))
- '@vue/devtools-api': 8.1.1
- ast-walker-scope: 0.8.3
- chokidar: 5.0.0
- json5: 2.2.3
- local-pkg: 1.1.2
- magic-string: 0.30.21
- mlly: 1.8.2
- muggle-string: 0.4.1
- pathe: 2.0.3
- picomatch: 4.0.4
- scule: 1.3.0
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
unplugin: 3.0.0
unplugin-utils: 0.3.1
vue: 3.5.34(typescript@5.9.3)
@@ -24105,29 +24066,6 @@ snapshots:
optionalDependencies:
'@vue/compiler-sfc': 3.5.31
- vue-router@5.0.7(@vue/compiler-sfc@3.5.38)(vue@3.5.34(typescript@5.9.3)):
- dependencies:
- '@babel/generator': 8.0.0-rc.5
- '@vue-macros/common': 3.1.2(vue@3.5.34(typescript@5.9.3))
- '@vue/devtools-api': 8.1.1
- ast-walker-scope: 0.8.3
- chokidar: 5.0.0
- json5: 2.2.3
- local-pkg: 1.1.2
- magic-string: 0.30.21
- mlly: 1.8.2
- muggle-string: 0.4.1
- pathe: 2.0.3
- picomatch: 4.0.4
- scule: 1.3.0
- tinyglobby: 0.2.16
- unplugin: 3.0.0
- unplugin-utils: 0.3.1
- vue: 3.5.34(typescript@5.9.3)
- yaml: 2.8.2
- optionalDependencies:
- '@vue/compiler-sfc': 3.5.38
-
vue-router@5.1.0(@vue/compiler-sfc@3.5.31)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3)):
dependencies:
'@babel/generator': 8.0.0-rc.5
@@ -24136,14 +24074,14 @@ snapshots:
ast-walker-scope: 0.9.0
chokidar: 5.0.0
json5: 2.2.3
- local-pkg: 1.1.2
+ local-pkg: 1.2.1
magic-string: 0.30.21
mlly: 1.8.2
muggle-string: 0.4.1
pathe: 2.0.3
picomatch: 4.0.4
scule: 1.3.0
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
unplugin: 3.0.0
unplugin-utils: 0.3.1
vue: 3.5.34(typescript@5.9.3)
@@ -24151,7 +24089,6 @@ snapshots:
optionalDependencies:
'@vue/compiler-sfc': 3.5.31
vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0)
- optional: true
vue-router@5.1.0(@vue/compiler-sfc@3.5.38)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.34(typescript@5.9.3)):
dependencies:
@@ -24161,14 +24098,14 @@ snapshots:
ast-walker-scope: 0.9.0
chokidar: 5.0.0
json5: 2.2.3
- local-pkg: 1.1.2
+ local-pkg: 1.2.1
magic-string: 0.30.21
mlly: 1.8.2
muggle-string: 0.4.1
pathe: 2.0.3
picomatch: 4.0.4
scule: 1.3.0
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
unplugin: 3.0.0
unplugin-utils: 0.3.1
vue: 3.5.34(typescript@5.9.3)
@@ -24176,7 +24113,6 @@ snapshots:
optionalDependencies:
'@vue/compiler-sfc': 3.5.38
vite: 8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0)
- optional: true
vue-router@5.1.0(@vue/compiler-sfc@3.5.38)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.0)(jiti@2.7.0)(terser@5.45.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.38(typescript@5.9.3)):
dependencies:
@@ -24186,14 +24122,14 @@ snapshots:
ast-walker-scope: 0.9.0
chokidar: 5.0.0
json5: 2.2.3
- local-pkg: 1.1.2
+ local-pkg: 1.2.1
magic-string: 0.30.21
mlly: 1.8.2
muggle-string: 0.4.1
pathe: 2.0.3
picomatch: 4.0.4
scule: 1.3.0
- tinyglobby: 0.2.16
+ tinyglobby: 0.2.17
unplugin: 3.0.0
unplugin-utils: 0.3.1
vue: 3.5.38(typescript@5.9.3)
diff --git a/src/module.ts b/src/module.ts
index e9860ee8c..f3cee20e9 100644
--- a/src/module.ts
+++ b/src/module.ts
@@ -1,4 +1,4 @@
-import { stat } from 'node:fs/promises'
+import { mkdir, stat } from 'node:fs/promises'
import {
defineNuxtModule,
createResolver,
@@ -33,6 +33,7 @@ import { findPreset } from './presets'
import type { Manifest } from './types/manifest'
import { setupPreview, setupPreviewWithAPI, shouldEnablePreview } from './utils/preview/module'
import { parseSourceBase } from './utils/source'
+import { createAfterParseHandler, DEFAULT_ASSET_EXTENSIONS, discoverAndCopyAssets, matchImageSizeHints, setAssetExtensions, type AssetIndex, type UnresolvedIndex } from './utils/assets'
import { databaseVersion, getLocalDatabase, refineDatabaseConfig, resolveDatabaseAdapter } from './utils/database'
import type { ParsedContentFile } from './types'
import { initiateValidatorsContext } from './utils/dependencies'
@@ -64,6 +65,13 @@ const moduleDefaults: Partial = {
delimiter: ',',
json: true,
},
+ assets: {
+ enabled: true,
+ imageSize: 'style',
+ blankLinks: true,
+ prefix: '',
+ debug: false,
+ },
},
experimental: {
nativeSqlite: false,
@@ -121,6 +129,35 @@ export default defineNuxtModule({
const { collections } = await loadContentConfig(nuxt, options)
manifest.collections = collections
+ // Resolve asset options once; `null` disables the feature.
+ const assetsConfig = options.build?.assets
+ const assets = assetsConfig === false || assetsConfig?.enabled === false
+ ? null
+ : {
+ imageSizes: matchImageSizeHints(assetsConfig?.imageSize),
+ extensions: assetsConfig?.extensions || DEFAULT_ASSET_EXTENSIONS,
+ blankLinks: assetsConfig?.blankLinks !== false,
+ prefix: assetsConfig?.prefix || '',
+ debug: !!assetsConfig?.debug,
+ publicDir: join(nuxt.options.buildDir, 'content', 'assets', 'public'),
+ }
+ const assetIndex: AssetIndex = new Map()
+ const unresolvedAssets: UnresolvedIndex = new Map()
+ if (assets) {
+ setAssetExtensions(assets.extensions)
+ nuxt.hook('content:file:afterParse', createAfterParseHandler(assetIndex, {
+ imageSizes: assets.imageSizes,
+ blankLinks: assets.blankLinks,
+ unresolved: unresolvedAssets,
+ }))
+
+ if (hasNuxtModule('@nuxt/image', nuxt)) {
+ await mkdir(assets.publicDir, { recursive: true })
+ const image = ((nuxt.options as unknown as { image?: { dirs?: string[] } }).image ??= {})
+ image.dirs = [...(image.dirs || []), assets.publicDir]
+ }
+ }
+
nuxt.options.vite.optimizeDeps = defu(nuxt.options.vite.optimizeDeps, {
exclude: ['@sqlite.org/sqlite-wasm'],
})
@@ -192,6 +229,14 @@ export default defineNuxtModule({
const preset = findPreset(nuxt)
await preset.setupNitro(config, { manifest, resolver, moduleOptions: options, nuxt })
+ // Serve copied assets; pushed last so the user's `public/` wins a collision.
+ if (assets) {
+ config.publicAssets ||= []
+ if (!config.publicAssets.some(asset => asset?.dir === assets.publicDir)) {
+ config.publicAssets.push({ dir: assets.publicDir })
+ }
+ }
+
const sqliteConnector = options.experimental?.sqliteConnector || (options.experimental?.nativeSqlite ? 'native' : undefined)
const resolveOptions = { resolver, sqliteConnector }
config.alias ||= {}
@@ -216,6 +261,7 @@ export default defineNuxtModule({
nuxt,
moduleOptions: options,
manifest,
+ assets: assets ? { ...assets, index: assetIndex, unresolved: unresolvedAssets } : null,
}))
}
})
@@ -254,7 +300,29 @@ export default defineNuxtModule({
// Generate collections and sql dump to update templates local database
// `modules:done` is triggered for all environments
nuxt.hook('modules:done', async () => {
- const fest = await processCollectionItems(nuxt, manifest.collections, options)
+ // Discover and copy co-located assets before parsing, so the index is ready
+ // when the `content:file:afterParse` hook rewrites references.
+ let assetsHash = ''
+ if (assets) {
+ const { index } = await discoverAndCopyAssets(nuxt, manifest.collections, {
+ publicDir: assets.publicDir,
+ extensions: assets.extensions,
+ imageSizes: assets.imageSizes,
+ prefix: assets.prefix,
+ debug: assets.debug,
+ })
+ index.forEach((value, key) => assetIndex.set(key, value))
+ // Fold the asset state into the parse cache key so an asset change
+ // (size, addition, removal) re-parses dependent content.
+ assetsHash = hash({
+ config: { imageSizes: assets.imageSizes, blankLinks: assets.blankLinks, prefix: assets.prefix },
+ index: [...assetIndex.entries()]
+ .map(([key, entry]) => [key, entry.publicSrc, entry.width, entry.height])
+ .sort((a, b) => String(a[0]).localeCompare(String(b[0]))),
+ })
+ }
+
+ const fest = await processCollectionItems(nuxt, manifest.collections, options, assetsHash)
// Update manifest
manifest.checksumStructure = fest.checksumStructure
@@ -282,7 +350,7 @@ export default defineNuxtModule({
},
})
-async function processCollectionItems(nuxt: Nuxt, collections: ResolvedCollection[], options: ModuleOptions) {
+async function processCollectionItems(nuxt: Nuxt, collections: ResolvedCollection[], options: ModuleOptions, assetsHash = '') {
const collectionDump: Record = {}
const collectionChecksum: Record = {}
const collectionChecksumStructure: Record = {}
@@ -296,6 +364,7 @@ async function processCollectionItems(nuxt: Nuxt, collections: ResolvedCollectio
const configHash = hash({
mdcHighlight: (nuxt.options as unknown as { mdc: MDCModuleOptions }).mdc?.highlight,
contentBuild: options.build?.markdown,
+ assets: assetsHash,
})
const infoCollection = collections.find(c => c.name === 'info')!
diff --git a/src/runtime/plugins/websocket.dev.ts b/src/runtime/plugins/websocket.dev.ts
index a9317c5c1..f2501ccc6 100644
--- a/src/runtime/plugins/websocket.dev.ts
+++ b/src/runtime/plugins/websocket.dev.ts
@@ -1,12 +1,21 @@
import { defineNuxtPlugin } from 'nuxt/app'
import { refreshNuxtData } from '#imports'
-type HotEvent = (event: 'nuxt-content:update', callback: (data: { collection: string, key: string, queries: string[] }) => void) => void
+interface ContentUpdate { collection: string, key: string, queries: string[] }
+interface AssetUpdate { event: 'update' | 'remove', src: string, width?: number, height?: number }
+
+interface ContentHot {
+ on(event: 'nuxt-content:update', callback: (data: ContentUpdate) => void): void
+ on(event: 'nuxt-content:assets:update', callback: (data: AssetUpdate) => void): void
+}
+
export default defineNuxtPlugin(() => {
if (!import.meta.hot || !import.meta.client) return
+ const hot = import.meta.hot as unknown as ContentHot
+
import('../internal/database.client').then(({ loadDatabaseAdapter }) => {
- ;(import.meta.hot as unknown as { on: HotEvent }).on('nuxt-content:update', async (data) => {
+ hot.on('nuxt-content:update', async (data) => {
if (!data || !data.collection || !Array.isArray(data.queries)) return
try {
const db = await loadDatabaseAdapter(data.collection)
@@ -25,4 +34,34 @@ export default defineNuxtPlugin(() => {
}
})
})
+
+ hot.on('nuxt-content:assets:update', (data) => {
+ if (!data || !data.src) return
+ const isUpdate = data.event === 'update'
+ const { src, width, height } = data
+ const safeSrc = src.replace(/["\\]/g, '\\$&')
+ document
+ .querySelectorAll(`:is(img, video, source, embed, iframe):where([src^="${safeSrc}"])`)
+ .forEach((node) => {
+ const element = node as HTMLImageElement
+ element.style.opacity = isUpdate ? '1' : '0.2'
+ if (!isUpdate) return
+
+ const params = new URLSearchParams(element.getAttribute('src')!.split('?')[1] || '')
+ params.set('time', String(Date.now()))
+
+ if (width && height) {
+ element.addEventListener('load', function onLoad() {
+ if (element.hasAttribute('width')) element.setAttribute('width', String(width))
+ if (element.hasAttribute('height')) element.setAttribute('height', String(height))
+ if (element.style.aspectRatio) {
+ element.style.aspectRatio = `${width} / ${height}`
+ }
+ element.removeEventListener('load', onLoad)
+ })
+ }
+
+ element.setAttribute('src', `${src}?${params.toString()}`)
+ })
+ })
})
diff --git a/src/types/module.ts b/src/types/module.ts
index 502c270d7..926149f25 100644
--- a/src/types/module.ts
+++ b/src/types/module.ts
@@ -194,6 +194,52 @@ export interface ModuleOptions {
json?: boolean
delimiter?: string
}
+ /**
+ * Relative asset resolution.
+ *
+ * Nuxt Content resolves relative paths to images, video and documents
+ * co-located with your content, in markdown, MDC components and frontmatter.
+ * Matching files are copied next to the built site and the references are
+ * rewritten to absolute URLs.
+ *
+ * On by default. Set `enabled` to `false` to opt out.
+ */
+ assets?: false | {
+ /**
+ * Whether to resolve relative asset paths.
+ * @default true
+ */
+ enabled?: boolean
+ /**
+ * Inject image dimensions to avoid layout shift.
+ * - `'style'`: adds `style="aspect-ratio:W/H"` to ` ` (default)
+ * - `'attrs'`: adds `width` / `height` attributes to ` `
+ * - `'src'` / `'url'`: adds a `?width=W&height=H` query (frontmatter values)
+ * - `''` or `[]`: inject nothing
+ * @default 'style'
+ */
+ imageSize?: '' | string | Array<'style' | 'attrs' | 'src' | 'url'>
+ /**
+ * File extensions treated as assets, hence excluded from content collections.
+ * @default
+ */
+ extensions?: string[]
+ /**
+ * Open links that point to an asset in a new tab.
+ * @default true
+ */
+ blankLinks?: boolean
+ /**
+ * URL prefix prepended to served asset paths (namespacing / collision avoidance).
+ * @default ''
+ */
+ prefix?: string
+ /**
+ * Output debug messages while discovering and copying assets.
+ * @default false
+ */
+ debug?: boolean
+ }
}
experimental?: {
diff --git a/src/utils/assets/discover.ts b/src/utils/assets/discover.ts
new file mode 100644
index 000000000..326dcb0fe
--- /dev/null
+++ b/src/utils/assets/discover.ts
@@ -0,0 +1,96 @@
+import { cp, mkdir, readFile, rm } from 'node:fs/promises'
+import { dirname, join } from 'pathe'
+import { withLeadingSlash } from 'ufo'
+import { glob } from 'tinyglobby'
+import { imageSize } from 'image-size'
+import type { Nuxt } from '@nuxt/schema'
+import type { ResolvedCollection } from '../../types/collection'
+import { getExcludedSourcePaths, parseSourceBase } from '../source'
+import { logger } from '../dev'
+import { removeOrdering } from './paths'
+import { isImage, type AssetIndex, type ImageSizeHint } from './shared'
+
+export function assetPublicUrl(relativeKey: string, sourcePrefix: string, prefix: string): string {
+ return withLeadingSlash(join(prefix, sourcePrefix || '', removeOrdering(relativeKey)))
+}
+
+export function assetIndexKey(absolutePath: string): string {
+ return removeOrdering(absolutePath)
+}
+
+export async function getAssetSize(absolutePath: string): Promise<{ width?: number, height?: number }> {
+ try {
+ const { width, height } = imageSize(await readFile(absolutePath))
+ return { width, height }
+ }
+ catch {
+ logger.warn(`Could not read image size of "${absolutePath}"`)
+ return {}
+ }
+}
+
+export interface DiscoverAssetsOptions {
+ publicDir: string
+ extensions: string[]
+ imageSizes: ImageSizeHint[]
+ prefix: string
+ debug?: boolean
+}
+
+// Runs before content is parsed so the index is ready when `content:file:afterParse` fires.
+export async function discoverAndCopyAssets(
+ nuxt: Nuxt,
+ collections: ResolvedCollection[],
+ options: DiscoverAssetsOptions,
+): Promise<{ index: AssetIndex, count: number }> {
+ const index: AssetIndex = new Map()
+ const { publicDir, extensions, imageSizes, prefix } = options
+ const detectSizes = imageSizes.length > 0
+ const pattern = `**/*.{${extensions.join(',')}}`
+
+ await rm(publicDir, { recursive: true, force: true })
+
+ for (const collection of collections) {
+ if (!collection.source) {
+ continue
+ }
+ for (const source of collection.source) {
+ // @ts-expect-error - `__rootDir` is a private property storing the layer's cwd
+ const rootDir = collection.__rootDir || nuxt.options.rootDir
+ await source.prepare?.({ rootDir })
+ const cwd = source.cwd
+ if (!cwd) {
+ continue
+ }
+
+ const { fixed } = parseSourceBase(source)
+ const scanDir = join(cwd, fixed)
+ const keys = await glob(pattern, {
+ cwd: scanDir,
+ ignore: getExcludedSourcePaths(source),
+ dot: true,
+ expandDirectories: false,
+ }).catch((): string[] => [])
+
+ for (const key of keys) {
+ const absoluteSource = join(scanDir, key)
+ const indexKey = assetIndexKey(absoluteSource)
+ if (index.has(indexKey)) {
+ continue
+ }
+ const url = assetPublicUrl(key, source.prefix || '', prefix)
+ const target = join(publicDir, url)
+ await mkdir(dirname(target), { recursive: true })
+ await cp(absoluteSource, target)
+
+ const { width, height } = detectSizes && isImage(absoluteSource) ? await getAssetSize(absoluteSource) : {}
+ index.set(indexKey, { publicSrc: url, width, height, content: [] })
+ }
+ }
+ }
+
+ if (options.debug) {
+ logger.info(`Copied ${index.size} content asset(s) to ${publicDir}`)
+ }
+ return { index, count: index.size }
+}
diff --git a/src/utils/assets/index.ts b/src/utils/assets/index.ts
new file mode 100644
index 000000000..49d899c44
--- /dev/null
+++ b/src/utils/assets/index.ts
@@ -0,0 +1,5 @@
+export * from './state'
+export * from './paths'
+export * from './shared'
+export * from './discover'
+export * from './rewrite'
diff --git a/src/utils/assets/paths.ts b/src/utils/assets/paths.ts
new file mode 100644
index 000000000..9aaea104e
--- /dev/null
+++ b/src/utils/assets/paths.ts
@@ -0,0 +1,58 @@
+import { extname } from 'pathe'
+import { getAssetExtensions } from './state'
+
+const ORDERING_RE = /^\d+\./
+
+// Strip ordering prefixes (`1.`, `01.`) from every segment. Copy and resolution
+// both apply it so URLs and references agree. File names are kept verbatim.
+export function removeOrdering(path: string): string {
+ return path.split('/').map(segment => segment.replace(ORDERING_RE, '')).join('/')
+}
+
+export function parseQuery(path: string): string {
+ return path.match(/\?.+$/)?.[0] || ''
+}
+
+export function removeQuery(path: string): string {
+ return path.replace(/\?.*$/, '')
+}
+
+export function getExtension(path: string): string {
+ return extname(removeQuery(path)).substring(1).toLowerCase()
+}
+
+export function isAssetExtension(path: string): boolean {
+ return getAssetExtensions().includes(getExtension(path))
+}
+
+export function isRelative(path: string): boolean {
+ return !/^(?:\/|#|[a-z][a-z0-9+.-]*:)/i.test(path)
+}
+
+export function isRelativeAsset(value: unknown): value is string {
+ return typeof value === 'string' && value.length > 0 && isRelative(value) && isAssetExtension(value)
+}
+
+export function buildStyle(...expressions: string[]): string {
+ return expressions
+ .map(expression => expression.replace(/^[; ]+|[; ]+$/g, ''))
+ .filter(Boolean)
+ .join('; ')
+ .replace(/\s*;\s*/g, '; ') + ';'
+}
+
+// Merge a leading path with `key=value` params into a single query string.
+export function buildQuery(...expressions: string[]): string {
+ const parts = expressions
+ .map(expression => expression.replace(/^[?&]+|&+$/g, ''))
+ .filter(Boolean)
+ if (!parts.length) {
+ return ''
+ }
+ const [first, ...rest] = parts
+ const isParam = (expression: string) => /^[^?]+=[^=]+$/.test(expression)
+ if (isParam(first!)) {
+ return '?' + parts.join('&')
+ }
+ return rest.length ? first + (first!.includes('?') ? '&' : '?') + rest.join('&') : first!
+}
diff --git a/src/utils/assets/rewrite.ts b/src/utils/assets/rewrite.ts
new file mode 100644
index 000000000..9adc65cb6
--- /dev/null
+++ b/src/utils/assets/rewrite.ts
@@ -0,0 +1,181 @@
+import { dirname, resolve } from 'pathe'
+import { visit } from 'minimark'
+import type { MinimarkElement, MinimarkNode, MinimarkTree } from 'minimark'
+import type { FileAfterParseHook } from '../../types'
+import { buildQuery, buildStyle, isRelativeAsset, parseQuery, removeOrdering, removeQuery } from './paths'
+import type { AssetIndex, AssetIndexEntry, ImageSizeHint, UnresolvedIndex } from './shared'
+
+interface RewriteContext {
+ index: AssetIndex
+ unresolved: UnresolvedIndex | null
+ fileDir: string
+ id: string
+ path: string
+ collection: string
+ imageSizes: ImageSizeHint[]
+ blankLinks: boolean
+}
+
+export interface AfterParseHandlerOptions {
+ imageSizes: ImageSizeHint[]
+ blankLinks: boolean
+ unresolved?: UnresolvedIndex
+}
+
+// Top-level fields that are structural or already resolved. Everything else is
+// walked for relative asset references (`isRelativeAsset` filters non-assets).
+const SKIP_TOP_LEVEL = new Set(['id', 'body', 'meta', '__metadata', 'stem', 'extension', 'path', 'rawbody'])
+
+function resolvedAssetKey(fileDir: string, value: string): string {
+ return removeOrdering(resolve(fileDir, removeQuery(value)))
+}
+
+function resolveAssetFromContent(index: AssetIndex, fileDir: string, value: string): AssetIndexEntry | undefined {
+ return index.get(resolvedAssetKey(fileDir, value))
+}
+
+function registerContent(entry: AssetIndexEntry, ctx: RewriteContext): void {
+ if (ctx.id && !entry.content.some(reference => reference.id === ctx.id)) {
+ entry.content.push({ id: ctx.id, path: ctx.path, collection: ctx.collection })
+ }
+}
+
+// Record a reference whose target doesn't exist yet, so dev HMR can re-parse once it appears.
+function recordUnresolved(ctx: RewriteContext, value: string): void {
+ if (!ctx.unresolved) {
+ return
+ }
+ const key = resolvedAssetKey(ctx.fileDir, value)
+ const list = ctx.unresolved.get(key)
+ if (!list) {
+ ctx.unresolved.set(key, [{ id: ctx.id, path: ctx.path, collection: ctx.collection }])
+ }
+ else if (!list.some(reference => reference.id === ctx.id)) {
+ list.push({ id: ctx.id, path: ctx.path, collection: ctx.collection })
+ }
+}
+
+function applyImageSize(props: Record, entry: AssetIndexEntry, imageSizes: ImageSizeHint[]): void {
+ const { width, height } = entry
+ if (!width || !height) {
+ return
+ }
+ if (imageSizes.includes('attrs')) {
+ props.width = width
+ props.height = height
+ }
+ if (imageSizes.includes('style')) {
+ const ratio = `${width}/${height}`
+ if (typeof props.style === 'string') {
+ props.style = buildStyle(props.style, `aspect-ratio: ${ratio}`)
+ }
+ else if (props.style && typeof props.style === 'object') {
+ (props.style as Record).aspectRatio = ratio
+ }
+ else {
+ props.style = `aspect-ratio: ${ratio};`
+ }
+ }
+}
+
+function rewriteBody(body: MinimarkTree, ctx: RewriteContext): void {
+ visit(body, (node: MinimarkNode) => Array.isArray(node), (node: MinimarkNode) => {
+ const [tag, props] = node as MinimarkElement
+ if (!props || typeof props !== 'object') {
+ return undefined
+ }
+ for (const [key, value] of Object.entries(props)) {
+ // `:prop` are MDC dynamic bindings (expressions), not literal paths.
+ if (key.startsWith(':') || typeof value !== 'string' || !isRelativeAsset(value)) {
+ continue
+ }
+ const entry = resolveAssetFromContent(ctx.index, ctx.fileDir, value)
+ if (!entry) {
+ recordUnresolved(ctx, value)
+ continue
+ }
+ registerContent(entry, ctx)
+ props[key] = entry.publicSrc + parseQuery(value)
+
+ if (tag === 'img' || tag === 'nuxt-img') {
+ applyImageSize(props, entry, ctx.imageSizes)
+ }
+ else if (tag === 'a' && ctx.blankLinks) {
+ props.target ??= '_blank'
+ }
+ }
+ return undefined
+ })
+}
+
+function walkValue(value: unknown, rewriteLeaf: (value: string) => string): unknown {
+ if (typeof value === 'string') {
+ return rewriteLeaf(value)
+ }
+ if (Array.isArray(value)) {
+ for (let i = 0; i < value.length; i++) {
+ value[i] = walkValue(value[i], rewriteLeaf)
+ }
+ return value
+ }
+ if (value && typeof value === 'object') {
+ const record = value as Record
+ for (const key of Object.keys(record)) {
+ record[key] = walkValue(record[key], rewriteLeaf)
+ }
+ }
+ return value
+}
+
+function rewriteMeta(content: Record, ctx: RewriteContext): void {
+ const rewriteLeaf = (value: string): string => {
+ if (!isRelativeAsset(value)) {
+ return value
+ }
+ const entry = resolveAssetFromContent(ctx.index, ctx.fileDir, value)
+ if (!entry) {
+ recordUnresolved(ctx, value)
+ return value
+ }
+ registerContent(entry, ctx)
+ if (entry.width && entry.height && (ctx.imageSizes.includes('src') || ctx.imageSizes.includes('url'))) {
+ return buildQuery(entry.publicSrc, parseQuery(value), `width=${entry.width}&height=${entry.height}`)
+ }
+ return entry.publicSrc + parseQuery(value)
+ }
+
+ if (content.meta && typeof content.meta === 'object') {
+ walkValue(content.meta, rewriteLeaf)
+ }
+ for (const key of Object.keys(content)) {
+ if (!SKIP_TOP_LEVEL.has(key)) {
+ content[key] = walkValue(content[key], rewriteLeaf)
+ }
+ }
+}
+
+export function createAfterParseHandler(index: AssetIndex, options: AfterParseHandlerOptions) {
+ return (ctx: FileAfterParseHook): void => {
+ const fileDir = ctx.file.dirname || (ctx.file.path ? dirname(ctx.file.path) : '')
+ if (!fileDir) {
+ return
+ }
+ const content = ctx.content as unknown as Record
+ const rewriteCtx: RewriteContext = {
+ index,
+ unresolved: options.unresolved || null,
+ fileDir,
+ id: String(content.id ?? ''),
+ path: ctx.file.path || '',
+ collection: ctx.collection?.name || '',
+ imageSizes: options.imageSizes,
+ blankLinks: options.blankLinks,
+ }
+
+ const body = content.body
+ if (body && typeof body === 'object' && Array.isArray((body as MinimarkTree).value)) {
+ rewriteBody(body as MinimarkTree, rewriteCtx)
+ }
+ rewriteMeta(content, rewriteCtx)
+ }
+}
diff --git a/src/utils/assets/shared.ts b/src/utils/assets/shared.ts
new file mode 100644
index 000000000..a56c8f8cf
--- /dev/null
+++ b/src/utils/assets/shared.ts
@@ -0,0 +1,60 @@
+import { getExtension } from './paths'
+
+export const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico', 'avif', 'bmp', 'cur', 'apng', 'tiff', 'tif', 'heic', 'heif', 'jxl']
+
+// Content extensions (md, mdc, json, yml, yaml, csv) are excluded on purpose:
+// in v3 those can be legitimate `data` collection content.
+export const DEFAULT_ASSET_EXTENSIONS = [
+ ...IMAGE_EXTENSIONS,
+ // video
+ 'mp4', 'mov', 'webm', 'ogv', 'avi', 'flv', 'mkv', 'm4v', 'mpg', 'mpeg', '3gp',
+ // audio
+ 'mp3', 'm4a', 'wav', 'ogg', 'oga', 'weba', 'opus', 'flac', 'aac', 'mid', 'midi',
+ // subtitles / tracks
+ 'vtt', 'srt',
+ // embeddable documents and pages
+ 'html', 'htm', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'odt', 'ods', 'odp', 'rtf',
+ // archives
+ 'zip', 'tar', 'gz', 'tgz', 'bz2', 'rar', '7z',
+ // fonts
+ 'woff', 'woff2', 'ttf', 'otf', 'eot',
+ // other web assets
+ 'glb', 'gltf', 'wasm',
+]
+
+export function isImage(path: string): boolean {
+ return IMAGE_EXTENSIONS.includes(getExtension(path))
+}
+
+export type ImageSizeHint = 'style' | 'attrs' | 'src' | 'url'
+
+export function matchImageSizeHints(value: string | string[] | false | undefined): ImageSizeHint[] {
+ if (!value) {
+ return []
+ }
+ const list = Array.isArray(value) ? value : [value]
+ const tokens = list.flatMap(item => (typeof item === 'string' ? item.match(/[^\s,|]+/g) || [] : []))
+ return [...new Set(tokens)] as ImageSizeHint[]
+}
+
+export interface ContentReference {
+ id: string
+ /** Absolute source path, used to re-parse the file on HMR. */
+ path: string
+ collection: string
+}
+
+export interface AssetIndexEntry {
+ /** Public URL the relative reference is rewritten to, e.g. `/media/photo.png`. */
+ publicSrc: string
+ width?: number
+ height?: number
+ /** Content files referencing this asset (reverse index, used by dev HMR). */
+ content: ContentReference[]
+}
+
+// Both indexes are keyed by the absolute source path with ordering stripped.
+export type AssetIndex = Map
+
+// References to a not-yet-existing asset, so dev HMR can re-parse once it appears.
+export type UnresolvedIndex = Map
diff --git a/src/utils/assets/state.ts b/src/utils/assets/state.ts
new file mode 100644
index 000000000..a4ae58731
--- /dev/null
+++ b/src/utils/assets/state.ts
@@ -0,0 +1,10 @@
+// Isolated module to avoid an import cycle between `source.ts` and `discover.ts`.
+let assetExtensions: string[] = []
+
+export function setAssetExtensions(extensions: string[]): void {
+ assetExtensions = [...extensions]
+}
+
+export function getAssetExtensions(): string[] {
+ return [...assetExtensions]
+}
diff --git a/src/utils/dev.ts b/src/utils/dev.ts
index 8ef4b7efd..2283e8088 100644
--- a/src/utils/dev.ts
+++ b/src/utils/dev.ts
@@ -1,8 +1,8 @@
import { createUnplugin } from 'unplugin'
import type { ViteDevServer } from 'vite'
import crypto from 'node:crypto'
-import { readFile } from 'node:fs/promises'
-import { join, resolve } from 'pathe'
+import { cp, mkdir, readFile, rm } from 'node:fs/promises'
+import { dirname, join, resolve } from 'pathe'
import type { Nuxt } from '@nuxt/schema'
import { isIgnored, updateTemplates, useLogger } from '@nuxt/kit'
import type { ConsolaInstance } from 'consola'
@@ -16,25 +16,41 @@ import { generateCollectionInsert } from './collection'
import { createParser } from './content'
import { moduleTemplates } from './templates'
import { getExcludedSourcePaths, parseSourceBase } from './source'
+import { assetIndexKey, assetPublicUrl, getAssetSize } from './assets/discover'
+import { isAssetExtension } from './assets/paths'
+import { isImage, type AssetIndex, type ContentReference, type ImageSizeHint, type UnresolvedIndex } from './assets/shared'
import { createHooks } from 'hookable'
export const logger: ConsolaInstance = useLogger('@nuxt/content')
export const contentHooks = createHooks<{
'hmr:content:update': (data: { key: string, collection: string, queries: string[] }) => void
+ 'hmr:assets:update': (data: { event: 'update' | 'remove', src: string, width?: number, height?: number }) => void
}>()
+export interface DevAssetsConfig {
+ index: AssetIndex
+ unresolved: UnresolvedIndex
+ publicDir: string
+ imageSizes: ImageSizeHint[]
+ prefix: string
+ blankLinks: boolean
+ extensions: string[]
+ debug: boolean
+}
+
interface HMRPluginOptions {
nuxt: Nuxt
moduleOptions: ModuleOptions
manifest: Manifest
+ assets: DevAssetsConfig | null
}
export const NuxtContentHMRUnplugin = createUnplugin((opts: HMRPluginOptions) => {
- const { nuxt, moduleOptions, manifest } = opts
+ const { nuxt, moduleOptions, manifest, assets } = opts
const componentsTemplatePath = join(nuxt.options.buildDir, 'content/components.ts')
- watchContents(nuxt, moduleOptions, manifest)
+ watchContents(nuxt, moduleOptions, manifest, assets)
watchComponents(nuxt)
return {
@@ -54,12 +70,20 @@ export const NuxtContentHMRUnplugin = createUnplugin((opts: HMRPluginOptions) =>
data,
})
})
+
+ contentHooks.hook('hmr:assets:update', (data) => {
+ server.ws.send({
+ type: 'custom',
+ event: 'nuxt-content:assets:update',
+ data,
+ })
+ })
},
},
}
})
-export function watchContents(nuxt: Nuxt, options: ModuleOptions, manifest: Manifest) {
+export function watchContents(nuxt: Nuxt, options: ModuleOptions, manifest: Manifest, assets: DevAssetsConfig | null = null) {
const collectionParsers = {} as Record>>
const collections = manifest.collections
@@ -115,6 +139,12 @@ export function watchContents(nuxt: Nuxt, options: ModuleOptions, manifest: Mani
}
// resolve path using `pathe.resolve` to use `/` instead of `\` on windows, otherwise `micromatch` will not match
const absolutePath = resolve(pathOrError as string)
+ // Handle assets before the content match: a `**` source would otherwise
+ // match them as content.
+ if (assets && isAssetExtension(absolutePath)) {
+ await onAssetChange('update', absolutePath, assets)
+ return
+ }
const matches = sourceMap.filter(({ source, cwd }) => {
if (cwd && absolutePath.startsWith(cwd)) {
return micromatch.isMatch(absolutePath.substring(cwd.length), source!.include, { ignore: source!.exclude || [], dot: true })
@@ -164,7 +194,7 @@ export function watchContents(nuxt: Nuxt, options: ModuleOptions, manifest: Mani
collectionType: collection.type,
}).then(result => JSON.stringify(result))
- db.insertDevelopmentCache(keyInCollection, checksum, parsedContent)
+ db.insertDevelopmentCache(keyInCollection, parsedContent, checksum)
}
const { queries: insertQuery } = generateCollectionInsert(collection, JSON.parse(parsedContent))
@@ -179,6 +209,10 @@ export function watchContents(nuxt: Nuxt, options: ModuleOptions, manifest: Mani
}
// resolve path using `pathe.resolve` to use `/` instead of `\` on windows, otherwise `micromatch` will not match
const absolutePath = resolve(pathOrError as string)
+ if (assets && isAssetExtension(absolutePath)) {
+ await onAssetChange('remove', absolutePath, assets)
+ return
+ }
const matches = sourceMap.filter(({ source, cwd }) => {
if (cwd && absolutePath.startsWith(cwd)) {
return micromatch.isMatch(absolutePath.substring(cwd.length), source!.include, { ignore: source!.exclude || [], dot: true })
@@ -239,6 +273,80 @@ export function watchContents(nuxt: Nuxt, options: ModuleOptions, manifest: Mani
})
}
+ async function reparseReferencingContent(references: ContentReference[]) {
+ const db = await getDb()
+ const seen = new Set()
+ for (const reference of references) {
+ if (seen.has(reference.id)) {
+ continue
+ }
+ seen.add(reference.id)
+ const collection = collections.find(c => c.name === reference.collection)
+ if (!collection) {
+ continue
+ }
+ const body = await readFile(reference.path, 'utf8').catch((): string => '')
+ if (!body) {
+ continue
+ }
+ if (!collectionParsers[collection.name]) {
+ collectionParsers[collection.name] = await createParser(collection, nuxt)
+ }
+ const parsed = await collectionParsers[collection.name]!({
+ id: reference.id,
+ body,
+ path: reference.path,
+ collectionType: collection.type,
+ }).then(result => JSON.stringify(result))
+ db.insertDevelopmentCache(reference.id, parsed, getContentChecksum(body))
+ const { queries } = generateCollectionInsert(collection, JSON.parse(parsed))
+ await broadcast(collection, reference.id, queries)
+ }
+ }
+
+ async function onAssetChange(event: 'update' | 'remove', absolutePath: string, assets: DevAssetsConfig) {
+ const match = sourceMap.find(({ cwd }) => cwd && absolutePath.startsWith(cwd))
+ if (!match) {
+ return
+ }
+ const { source } = match
+ const { fixed } = parseSourceBase(source)
+ const scanDir = join(match.cwd, fixed)
+ const relativeKey = absolutePath.substring(scanDir.length).replace(/^\//, '')
+ const indexKey = assetIndexKey(absolutePath)
+ const url = assetPublicUrl(relativeKey, source.prefix || '', assets.prefix)
+ const target = join(assets.publicDir, url)
+
+ if (event === 'remove') {
+ await rm(target, { force: true }).catch(() => {})
+ assets.index.delete(indexKey)
+ contentHooks.callHook('hmr:assets:update', { event: 'remove', src: url })
+ return
+ }
+
+ const previous = assets.index.get(indexKey)
+ await mkdir(dirname(target), { recursive: true })
+ await cp(absolutePath, target)
+ const { width, height } = assets.imageSizes.length && isImage(absolutePath) ? await getAssetSize(absolutePath) : {}
+ assets.index.set(indexKey, { publicSrc: url, width, height, content: previous?.content || [] })
+
+ // Re-parse content affected by this asset: a resize changes injected
+ // dimensions, and a first appearance resolves earlier dangling references.
+ const toReparse: ContentReference[] = []
+ if (previous && (previous.width !== width || previous.height !== height)) {
+ toReparse.push(...previous.content)
+ }
+ const pending = assets.unresolved.get(indexKey)
+ if (pending?.length) {
+ toReparse.push(...pending)
+ assets.unresolved.delete(indexKey)
+ }
+ if (toReparse.length) {
+ await reparseReferencingContent(toReparse)
+ }
+ contentHooks.callHook('hmr:assets:update', { event: 'update', src: url, width, height })
+ }
+
nuxt.hook('close', async () => {
if (watcher) {
watcher.removeAllListeners()
diff --git a/src/utils/source.ts b/src/utils/source.ts
index 71aadcc0b..afb8867e6 100644
--- a/src/utils/source.ts
+++ b/src/utils/source.ts
@@ -6,6 +6,7 @@ import { glob } from 'tinyglobby'
import type { CollectionSource, ResolvedCollectionSource } from '../types/collection'
import { downloadGitRepository } from './git'
import { logger } from './dev'
+import { isAssetExtension } from './assets/paths'
import gitUrlParse from 'git-url-parse'
export function getExcludedSourcePaths(source: CollectionSource) {
@@ -39,7 +40,8 @@ export function defineLocalSource(source: CollectionSource | ResolvedCollectionS
getKeys: async () => {
const _keys = await glob(source.include, { cwd: resolvedSource.cwd, ignore: getExcludedSourcePaths(source), dot: true, expandDirectories: false })
.catch((): [] => [])
- return _keys.map(key => key.substring(fixed.length))
+ // Keep co-located assets out of content collections (no-op when off).
+ return _keys.map(key => key.substring(fixed.length)).filter(key => !isAssetExtension(key))
},
getItem: async (key) => {
const fullPath = join(resolvedSource.cwd, fixed, key)
diff --git a/test/assets.test.ts b/test/assets.test.ts
new file mode 100644
index 000000000..5a2f54d15
--- /dev/null
+++ b/test/assets.test.ts
@@ -0,0 +1,85 @@
+import fs from 'node:fs/promises'
+import { createResolver } from '@nuxt/kit'
+import { setup, $fetch } from '@nuxt/test-utils'
+import { afterAll, describe, expect, test } from 'vitest'
+import { initiateValidatorsContext } from '../src/utils/dependencies'
+
+const resolver = createResolver(import.meta.url)
+
+function findNode(value: unknown[], tag: string): [string, Record, ...unknown[]] | undefined {
+ for (const node of value) {
+ if (Array.isArray(node) && typeof node[0] === 'string') {
+ if (node[0] === tag) {
+ return node as [string, Record, ...unknown[]]
+ }
+ const found = findNode(node.slice(2), tag)
+ if (found) {
+ return found
+ }
+ }
+ }
+ return undefined
+}
+
+async function cleanup() {
+ await fs.rm(resolver.resolve('./fixtures/assets/node_modules'), { recursive: true, force: true })
+ await fs.rm(resolver.resolve('./fixtures/assets/.nuxt'), { recursive: true, force: true })
+ await fs.rm(resolver.resolve('./fixtures/assets/.data'), { recursive: true, force: true })
+}
+
+describe('assets', async () => {
+ await initiateValidatorsContext()
+
+ await cleanup()
+ afterAll(async () => {
+ await cleanup()
+ })
+
+ await setup({
+ rootDir: resolver.resolve('./fixtures/assets'),
+ dev: true,
+ })
+
+ test('serves the copied asset', async () => {
+ const png = await $fetch('/media/photo.png', { responseType: 'arrayBuffer' })
+ expect(png.byteLength).toBeGreaterThan(0)
+ })
+
+ test('extracts image dimensions from disk and injects the aspect-ratio', async () => {
+ const doc = await $fetch('/api/content/get?path=/') as { body: { value: unknown[] } }
+ const img = findNode(doc.body.value, 'img')!
+ expect(img[1].src).toBe('/media/photo.png')
+ expect(img[1].style).toBe('aspect-ratio: 8/4;')
+ })
+
+ test('rewrites an asset link and opens it in a new tab', async () => {
+ const doc = await $fetch('/api/content/get?path=/') as { body: { value: unknown[] } }
+ const link = findNode(doc.body.value, 'a')!
+ expect(link[1].href).toBe('/files/doc.pdf')
+ expect(link[1].target).toBe('_blank')
+ })
+
+ test('rewrites a frontmatter asset path', async () => {
+ const doc = await $fetch('/api/content/get?path=/') as { featured: string }
+ expect(doc.featured).toBe('/media/photo.png')
+ })
+
+ test('rewrites an array of frontmatter asset paths', async () => {
+ const doc = await $fetch('/api/content/get?path=/') as { gallery: string[] }
+ expect(doc.gallery).toEqual(['/media/photo.png', '/media/photo.png'])
+ })
+
+ test('rewrites iframe and embed sources and serves non-image assets', async () => {
+ const doc = await $fetch('/api/content/get?path=/') as { body: { value: unknown[] } }
+ expect(findNode(doc.body.value, 'iframe')![1].src).toBe('/media/page.html')
+ expect(findNode(doc.body.value, 'embed')![1].src).toBe('/files/doc.pdf')
+
+ const html = await $fetch('/media/page.html', { responseType: 'text' })
+ expect(html.length).toBeGreaterThan(0)
+ })
+
+ test('resolves a parent-folder relative path', async () => {
+ const doc = await $fetch('/api/content/get?path=/posts/nested') as { body: { value: unknown[] } }
+ expect(findNode(doc.body.value, 'img')![1].src).toBe('/media/photo.png')
+ })
+})
diff --git a/test/fixtures/assets/app/app.vue b/test/fixtures/assets/app/app.vue
new file mode 100644
index 000000000..09f935bbb
--- /dev/null
+++ b/test/fixtures/assets/app/app.vue
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/test/fixtures/assets/content.config.ts b/test/fixtures/assets/content.config.ts
new file mode 100644
index 000000000..a44b68b7a
--- /dev/null
+++ b/test/fixtures/assets/content.config.ts
@@ -0,0 +1,15 @@
+import { defineCollection, defineContentConfig } from '@nuxt/content'
+import { z } from 'zod'
+
+export default defineContentConfig({
+ collections: {
+ content: defineCollection({
+ type: 'page',
+ source: '**',
+ schema: z.object({
+ featured: z.string().optional(),
+ gallery: z.array(z.string()).optional(),
+ }),
+ }),
+ },
+})
diff --git a/test/fixtures/assets/content/files/doc.pdf b/test/fixtures/assets/content/files/doc.pdf
new file mode 100644
index 000000000..dbf091df9
Binary files /dev/null and b/test/fixtures/assets/content/files/doc.pdf differ
diff --git a/test/fixtures/assets/content/index.md b/test/fixtures/assets/content/index.md
new file mode 100644
index 000000000..0ecdbe98e
--- /dev/null
+++ b/test/fixtures/assets/content/index.md
@@ -0,0 +1,17 @@
+---
+title: Assets
+featured: media/photo.png
+gallery:
+ - media/photo.png
+ - media/photo.png
+---
+
+# Assets
+
+
+
+[the document](files/doc.pdf)
+
+
+
+
diff --git a/test/fixtures/assets/content/media/page.html b/test/fixtures/assets/content/media/page.html
new file mode 100644
index 000000000..19e5e0458
--- /dev/null
+++ b/test/fixtures/assets/content/media/page.html
@@ -0,0 +1,17 @@
+
+
+
+
+ Sample
+
+
+
+
+Yay, Assets!
+This page is displaying in an iframe
+
+
diff --git a/test/fixtures/assets/content/media/photo.png b/test/fixtures/assets/content/media/photo.png
new file mode 100644
index 000000000..a022d39f7
Binary files /dev/null and b/test/fixtures/assets/content/media/photo.png differ
diff --git a/test/fixtures/assets/content/posts/nested.md b/test/fixtures/assets/content/posts/nested.md
new file mode 100644
index 000000000..a8d4c49e7
--- /dev/null
+++ b/test/fixtures/assets/content/posts/nested.md
@@ -0,0 +1,7 @@
+---
+title: Nested
+---
+
+# Nested
+
+
diff --git a/test/fixtures/assets/nuxt.config.ts b/test/fixtures/assets/nuxt.config.ts
new file mode 100644
index 000000000..597da38c9
--- /dev/null
+++ b/test/fixtures/assets/nuxt.config.ts
@@ -0,0 +1,8 @@
+import { defineNuxtConfig } from 'nuxt/config'
+
+export default defineNuxtConfig({
+ modules: [
+ '@nuxt/content',
+ ],
+ compatibilityDate: '2025-09-03',
+})
diff --git a/test/fixtures/assets/package.json b/test/fixtures/assets/package.json
new file mode 100644
index 000000000..010e3445c
--- /dev/null
+++ b/test/fixtures/assets/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "nuxt-content-test-assets",
+ "private": true,
+ "scripts": {
+ "dev": "nuxi dev"
+ }
+}
diff --git a/test/fixtures/assets/server/api/content/get.get.ts b/test/fixtures/assets/server/api/content/get.get.ts
new file mode 100644
index 000000000..d8c8bb8b2
--- /dev/null
+++ b/test/fixtures/assets/server/api/content/get.get.ts
@@ -0,0 +1,8 @@
+import { eventHandler, getQuery } from 'h3'
+
+export default eventHandler(async (event) => {
+ const rawPath = getQuery(event).path
+ const path = Array.isArray(rawPath) ? rawPath[0] : (rawPath || '/')
+
+ return await queryCollection(event, 'content').path(path).first()
+})
diff --git a/test/unit/assets.ignore.test.ts b/test/unit/assets.ignore.test.ts
new file mode 100644
index 000000000..a38c6ebd1
--- /dev/null
+++ b/test/unit/assets.ignore.test.ts
@@ -0,0 +1,40 @@
+import { describe, it, expect, beforeAll, afterAll } from 'vitest'
+import { mkdtemp, rm, writeFile } from 'node:fs/promises'
+import { tmpdir } from 'node:os'
+import { join } from 'pathe'
+import { defineLocalSource } from '../../src/utils/source'
+import { setAssetExtensions } from '../../src/utils/assets/state'
+import { DEFAULT_ASSET_EXTENSIONS } from '../../src/utils/assets/shared'
+
+describe('assets exclusion from content collection keys', () => {
+ let dir: string
+
+ beforeAll(async () => {
+ dir = await mkdtemp(join(tmpdir(), 'nc-assets-'))
+ await writeFile(join(dir, 'post.md'), '# hi')
+ await writeFile(join(dir, 'data.json'), '{}')
+ await writeFile(join(dir, 'photo.jpg'), 'x')
+ await writeFile(join(dir, 'clip.mp4'), 'x')
+ })
+
+ afterAll(async () => {
+ setAssetExtensions(DEFAULT_ASSET_EXTENSIONS)
+ await rm(dir, { recursive: true, force: true })
+ })
+
+ it('keeps content files and drops assets when the feature is enabled', async () => {
+ setAssetExtensions(DEFAULT_ASSET_EXTENSIONS)
+ const source = defineLocalSource({ include: '**', cwd: dir })
+ await source.prepare!({ rootDir: dir })
+ const keys = await source.getKeys!()
+ expect(keys.sort()).toEqual(['data.json', 'post.md'])
+ })
+
+ it('does not filter anything when the feature is disabled', async () => {
+ setAssetExtensions([])
+ const source = defineLocalSource({ include: '**', cwd: dir })
+ await source.prepare!({ rootDir: dir })
+ const keys = await source.getKeys!()
+ expect(keys.sort()).toEqual(['clip.mp4', 'data.json', 'photo.jpg', 'post.md'])
+ })
+})
diff --git a/test/unit/assets.parser.test.ts b/test/unit/assets.parser.test.ts
new file mode 100644
index 000000000..0be7b0b22
--- /dev/null
+++ b/test/unit/assets.parser.test.ts
@@ -0,0 +1,130 @@
+import { describe, expect, it } from 'vitest'
+import { z } from 'zod'
+import { defineCollection } from '../../src/utils'
+import { resolveCollection } from '../../src/utils/collection'
+import { initiateValidatorsContext } from '../../src/utils/dependencies'
+import { parseContent } from '../utils/content'
+import { createAfterParseHandler } from '../../src/utils/assets/rewrite'
+import { setAssetExtensions } from '../../src/utils/assets/state'
+import { DEFAULT_ASSET_EXTENSIONS, type AssetIndex, type AssetIndexEntry } from '../../src/utils/assets/shared'
+import type { FileAfterParseHook } from '../../src/types'
+
+type Element = [string, Record, ...unknown[]]
+
+function flatten(value: unknown[], out: Element[] = []): Element[] {
+ for (const node of value) {
+ if (Array.isArray(node) && typeof node[0] === 'string') {
+ out.push(node as Element)
+ flatten(node.slice(2), out)
+ }
+ }
+ return out
+}
+
+function entry(publicSrc: string, width?: number, height?: number): AssetIndexEntry {
+ return { publicSrc, width, height, content: [] }
+}
+
+// Only covers cases that depend on the shape the real parser produces (MDC
+// components/bindings, raw HTML, fenced code, `./` + query). Pure rewrite logic
+// (URL rewrite, target, imageSizes, frontmatter) is unit-tested in assets.rewrite.test.ts.
+describe('assets rewrite (real parser)', async () => {
+ await initiateValidatorsContext()
+ setAssetExtensions(DEFAULT_ASSET_EXTENSIONS)
+
+ const collection = resolveCollection('content', defineCollection({
+ type: 'page',
+ source: 'content/**',
+ schema: z.object({ featured: z.string().optional(), gallery: z.array(z.string()).optional() }),
+ }))!
+
+ const index: AssetIndex = new Map([
+ ['/invalid/media/photo.png', entry('/media/photo.png', 8, 4)],
+ ['/invalid/files/doc.pdf', entry('/files/doc.pdf')],
+ ['/invalid/media/clip.mp4', entry('/media/clip.mp4')],
+ ['/invalid/media/poster.jpg', entry('/media/poster.jpg', 16, 9)],
+ ['/invalid/media/example.html', entry('/media/example.html')],
+ ['/invalid/media/raw.png', entry('/media/raw.png')],
+ ['/invalid/media/a.png', entry('/media/a.png')],
+ ['/invalid/media/b.png', entry('/media/b.png')],
+ ['/invalid/media/incode.png', entry('/media/incode.png')],
+ ])
+
+ async function parse(imageSize: ('style' | 'attrs' | 'src' | 'url')[]) {
+ const handler = createAfterParseHandler(index, { imageSizes: imageSize, blankLinks: true })
+ const nuxtMock = {
+ callHook(hook: string, ctx: FileAfterParseHook) {
+ if (hook === 'content:file:afterParse') handler(ctx)
+ },
+ }
+ const parsed = await parseContent('content/index.md', `---
+featured: media/photo.png
+gallery:
+ - media/a.png
+ - media/b.png
+---
+
+
+
+[a link](files/doc.pdf)
+
+:video{src="media/clip.mp4" poster="media/poster.jpg"}
+
+:img{:src="featured" alt="bound"}
+
+
+
+
+
+\`\`\`ts
+const x = "media/incode.png"
+\`\`\`
+`, collection, nuxtMock as never)
+ return parsed as unknown as { body: { value: unknown[] }, featured?: unknown, gallery?: unknown }
+ }
+
+ it('MDC component literal attributes (src and poster) are rewritten', async () => {
+ const video = flatten((await parse([])).body.value).find(e => e[0] === 'video')!
+ expect(video[1].src).toBe('/media/clip.mp4')
+ expect(video[1].poster).toBe('/media/poster.jpg')
+ })
+
+ it('MDC dynamic bindings (`:src`, `:images`) are left untouched', async () => {
+ const els = flatten((await parse([])).body.value)
+ expect(els.find(e => e[1][':src'] === 'featured')).toBeTruthy()
+ })
+
+ it('raw HTML iframe and img are rewritten', async () => {
+ const els = flatten((await parse([])).body.value)
+ expect(els.find(e => e[0] === 'iframe' && e[1].src === '/media/example.html')).toBeTruthy()
+ expect(els.find(e => e[0] === 'img' && e[1].src === '/media/raw.png')).toBeTruthy()
+ })
+
+ it('paths inside fenced code are never rewritten', async () => {
+ const json = JSON.stringify((await parse([])).body.value)
+ expect(json).not.toContain('/media/incode.png')
+ })
+
+ it('preserves an existing query string and resolves a `./` prefix', async () => {
+ const handler = createAfterParseHandler(index, { imageSizes: [], blankLinks: true })
+ const parsed = await parseContent('content/index.md', '\n\n', collection, {
+ callHook(hook: string, ctx: FileAfterParseHook) {
+ if (hook === 'content:file:afterParse') handler(ctx)
+ },
+ } as never)
+ const srcs = flatten((parsed.body as { value: unknown[] }).value).filter(e => e[0] === 'img').map(e => e[1].src)
+ expect(srcs).toEqual(['/media/photo.png?v=2', '/media/photo.png'])
+ })
+
+ it('external and absolute references are never touched', async () => {
+ const md = `\n\n`
+ const handler = createAfterParseHandler(index, { imageSizes: [], blankLinks: true })
+ const parsed = await parseContent('content/index.md', md, collection, {
+ callHook(hook: string, ctx: FileAfterParseHook) {
+ if (hook === 'content:file:afterParse') handler(ctx)
+ },
+ } as never)
+ const imgs = flatten((parsed.body as { value: unknown[] }).value).filter(e => e[0] === 'img')
+ expect(imgs.map(e => e[1].src)).toEqual(['https://example.com/a.png', '/public/b.png'])
+ })
+})
diff --git a/test/unit/assets.paths.test.ts b/test/unit/assets.paths.test.ts
new file mode 100644
index 000000000..5f9347f77
--- /dev/null
+++ b/test/unit/assets.paths.test.ts
@@ -0,0 +1,46 @@
+import { describe, it, expect, beforeAll } from 'vitest'
+import { buildQuery, buildStyle, isRelativeAsset, parseQuery, removeOrdering, removeQuery } from '../../src/utils/assets/paths'
+import { setAssetExtensions } from '../../src/utils/assets/state'
+import { DEFAULT_ASSET_EXTENSIONS } from '../../src/utils/assets/shared'
+
+describe('assets/paths', () => {
+ beforeAll(() => setAssetExtensions(DEFAULT_ASSET_EXTENSIONS))
+
+ it('removeOrdering strips `NN.` from every segment, including the file name', () => {
+ expect(removeOrdering('1.foo/bar.jpg')).toBe('foo/bar.jpg')
+ expect(removeOrdering('1.foo/2.bar/baz.jpg')).toBe('foo/bar/baz.jpg')
+ expect(removeOrdering('foo/bar.jpg')).toBe('foo/bar.jpg')
+ expect(removeOrdering('foo/1.bar.jpg')).toBe('foo/bar.jpg')
+ })
+
+ it('removeQuery / parseQuery', () => {
+ expect(removeQuery('a/b.png?x=1')).toBe('a/b.png')
+ expect(removeQuery('a/b.png')).toBe('a/b.png')
+ expect(parseQuery('a/b.png?x=1')).toBe('?x=1')
+ expect(parseQuery('a/b.png')).toBe('')
+ })
+
+ it('isRelativeAsset accepts relative asset paths only', () => {
+ expect(isRelativeAsset('media/photo.png')).toBe(true)
+ expect(isRelativeAsset('./media/photo.png')).toBe(true)
+ expect(isRelativeAsset('../media/photo.png')).toBe(true)
+ expect(isRelativeAsset('files/doc.pdf')).toBe(true)
+ expect(isRelativeAsset('/media/photo.png')).toBe(false)
+ expect(isRelativeAsset('https://example.com/a.png')).toBe(false)
+ expect(isRelativeAsset('data:image/png;base64,xxxx')).toBe(false)
+ expect(isRelativeAsset('#anchor')).toBe(false)
+ expect(isRelativeAsset('media/article.md')).toBe(false)
+ expect(isRelativeAsset(42)).toBe(false)
+ })
+
+ it('buildStyle joins independent declarations', () => {
+ expect(buildStyle('color: red', 'aspect-ratio: 16/9')).toBe('color: red; aspect-ratio: 16/9;')
+ expect(buildStyle('', 'aspect-ratio: 1/1')).toBe('aspect-ratio: 1/1;')
+ })
+
+ it('buildQuery merges path and params', () => {
+ expect(buildQuery('/img.png', 'width=10&height=20')).toBe('/img.png?width=10&height=20')
+ expect(buildQuery('/img.png?a=1', 'width=10')).toBe('/img.png?a=1&width=10')
+ expect(buildQuery('width=10', 'height=20')).toBe('?width=10&height=20')
+ })
+})
diff --git a/test/unit/assets.rewrite.test.ts b/test/unit/assets.rewrite.test.ts
new file mode 100644
index 000000000..bacd4c73d
--- /dev/null
+++ b/test/unit/assets.rewrite.test.ts
@@ -0,0 +1,119 @@
+import { describe, it, expect, beforeAll } from 'vitest'
+import { createAfterParseHandler } from '../../src/utils/assets/rewrite'
+import { setAssetExtensions } from '../../src/utils/assets/state'
+import { DEFAULT_ASSET_EXTENSIONS, type AssetIndex, type ImageSizeHint, type UnresolvedIndex } from '../../src/utils/assets/shared'
+import type { FileAfterParseHook } from '../../src/types'
+
+function makeIndex(): AssetIndex {
+ const index: AssetIndex = new Map()
+ index.set('/abs/content/real-content/media/photo.png', { publicSrc: '/real-content/media/photo.png', width: 640, height: 480, content: [] })
+ index.set('/abs/content/real-content/files/doc.pdf', { publicSrc: '/real-content/files/doc.pdf', content: [] })
+ return index
+}
+
+function run(imageSize: ImageSizeHint[]) {
+ const index = makeIndex()
+ const content: Record = {
+ id: 'content/real-content/post.md',
+ cover: 'media/01.photo.png',
+ body: {
+ type: 'minimark',
+ value: [
+ ['p', {}, ['img', { src: 'media/01.photo.png', alt: 'photo' }]],
+ ['a', { href: 'files/doc.pdf' }, 'download'],
+ ['img', { src: 'https://example.com/external.png' }],
+ ],
+ },
+ meta: {
+ featured: 'media/01.photo.png',
+ gallery: ['media/01.photo.png'],
+ note: 'just some text',
+ },
+ }
+ const ctx = {
+ file: { id: content.id, path: '/abs/content/1.real-content/post.md', dirname: '/abs/content/1.real-content' },
+ content,
+ collection: { name: 'content', type: 'page' },
+ } as unknown as FileAfterParseHook
+
+ createAfterParseHandler(index, { imageSizes: imageSize, blankLinks: true })(ctx)
+ return { content, index }
+}
+
+describe('assets/rewrite', () => {
+ beforeAll(() => setAssetExtensions(DEFAULT_ASSET_EXTENSIONS))
+
+ it('rewrites relative paths to public URLs and strips ordering', () => {
+ const { content } = run([])
+ const body = content.body as { value: unknown[] }
+ const img = (body.value[0] as unknown[])[2] as [string, Record]
+ expect(img[1].src).toBe('/real-content/media/photo.png')
+
+ const link = body.value[1] as [string, Record]
+ expect(link[1].href).toBe('/real-content/files/doc.pdf')
+ expect(link[1].target).toBe('_blank')
+
+ expect((content.meta as Record).featured).toBe('/real-content/media/photo.png')
+ expect(((content.meta as Record).gallery as string[])[0]).toBe('/real-content/media/photo.png')
+ expect(content.cover).toBe('/real-content/media/photo.png')
+ })
+
+ it('leaves external and non-asset values untouched', () => {
+ const { content } = run([])
+ const body = content.body as { value: unknown[] }
+ const external = body.value[2] as [string, Record]
+ expect(external[1].src).toBe('https://example.com/external.png')
+ expect((content.meta as Record).note).toBe('just some text')
+ })
+
+ it('injects aspect-ratio style on img with imageSize "style"', () => {
+ const { content } = run(['style'])
+ const body = content.body as { value: unknown[] }
+ const img = (body.value[0] as unknown[])[2] as [string, Record]
+ expect(img[1].style).toBe('aspect-ratio: 640/480;')
+ })
+
+ it('injects width/height attrs on img with imageSize "attrs"', () => {
+ const { content } = run(['attrs'])
+ const body = content.body as { value: unknown[] }
+ const img = (body.value[0] as unknown[])[2] as [string, Record]
+ expect(img[1].width).toBe(640)
+ expect(img[1].height).toBe(480)
+ })
+
+ it('encodes size in frontmatter src query with imageSize "src"', () => {
+ const { content } = run(['src'])
+ expect((content.meta as Record).featured).toBe('/real-content/media/photo.png?width=640&height=480')
+ })
+
+ it('builds a reverse index of referencing content ids', () => {
+ const { index } = run(['style'])
+ expect(index.get('/abs/content/real-content/media/photo.png')!.content.map(reference => reference.id)).toContain('content/real-content/post.md')
+ })
+
+ it('records references to a not-yet-existing asset, but not resolved ones', () => {
+ const index = makeIndex()
+ const unresolved: UnresolvedIndex = new Map()
+ const content: Record = {
+ id: 'content/real-content/post.md',
+ body: {
+ type: 'minimark',
+ value: [
+ ['img', { src: 'media/missing.png' }],
+ ['img', { src: 'media/photo.png' }],
+ ],
+ },
+ meta: {},
+ }
+ const ctx = {
+ file: { id: content.id, path: '/abs/content/1.real-content/post.md', dirname: '/abs/content/1.real-content' },
+ content,
+ collection: { name: 'content', type: 'page' },
+ } as unknown as FileAfterParseHook
+
+ createAfterParseHandler(index, { imageSizes: [], blankLinks: true, unresolved })(ctx)
+
+ expect(unresolved.get('/abs/content/real-content/media/missing.png')?.map(reference => reference.id)).toContain('content/real-content/post.md')
+ expect(unresolved.has('/abs/content/real-content/media/photo.png')).toBe(false)
+ })
+})
diff --git a/test/unit/devCache.test.ts b/test/unit/devCache.test.ts
new file mode 100644
index 000000000..1fb79d4ab
--- /dev/null
+++ b/test/unit/devCache.test.ts
@@ -0,0 +1,30 @@
+import { describe, it, expect, afterAll } from 'vitest'
+import { mkdtemp, rm } from 'node:fs/promises'
+import { tmpdir } from 'node:os'
+import { join } from 'pathe'
+import { getLocalDatabase } from '../../src/utils/database'
+
+// Guards the argument order of `insertDevelopmentCache(id, value, checksum)`,
+// which the dev HMR re-parse path was previously calling with value/checksum swapped.
+describe('development cache', () => {
+ let dir: string
+ let db: Awaited>
+
+ afterAll(async () => {
+ await db?.close?.()
+ if (dir) {
+ await rm(dir, { recursive: true, force: true })
+ }
+ })
+
+ it('stores value and checksum in their own columns', async () => {
+ dir = await mkdtemp(join(tmpdir(), 'nc-cache-'))
+ db = await getLocalDatabase({ type: 'sqlite', filename: join(dir, 'contents.sqlite') }, { nativeSqlite: true })
+
+ await db.insertDevelopmentCache('content/index.md', '{"parsed":true}', 'checksum-123')
+ const row = await db.fetchDevelopmentCacheForKey('content/index.md')
+
+ expect(row?.value).toBe('{"parsed":true}')
+ expect(row?.checksum).toBe('checksum-123')
+ })
+})