Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c605384356 | ||
|
|
a7602c9659 | ||
|
|
8b5da6762c | ||
|
|
ef3a090647 | ||
|
|
c4639c2020 | ||
|
|
dd0ee35085 |
@@ -3,4 +3,8 @@
|
||||
export default defineNuxtConfig({
|
||||
extends: ['..'],
|
||||
compatibilityDate: '2025-07-15',
|
||||
// An app without pages: vue-seo-layer's HTML sitemap pages would be its
|
||||
// only routes and turn the router on, so every other path would answer
|
||||
// 404. A site with pages keeps them.
|
||||
seo: { htmlSitemap: false },
|
||||
})
|
||||
|
||||
34
README.md
34
README.md
@@ -3,13 +3,15 @@
|
||||
A Nuxt 4 layer that renders a menu of the SummerCMS menu plugin as an accessible site navigation.
|
||||
|
||||
```bash
|
||||
pnpm add git+https://git.golem15.com/golem15/vue-menu-layer.git#v0.2.0
|
||||
pnpm add git+https://git.golem15.com/golem15/vue-menu-layer.git#v0.5.0
|
||||
```
|
||||
|
||||
## Overview
|
||||
|
||||
The layer is consumed with `extends: ['vue-menu-layer']`. It reads one route of the menu plugin's anonymous API, `GET /_menu/api/v1/menus/{code}`, during SSR, so the first paint already has the menu, and renders it with `<MenuNav code="…">`. Editors manage menus in the SummerCMS admin; the site needs no menu data of its own.
|
||||
|
||||
The layer extends [vue-seo-layer](https://git.golem15.com/golem15/vue-seo-layer), which sets every page's head from the SummerCMS SEO plugin and serves the shared site cache purge route; an app that extends vue-menu-layer gets it with no extra entry in `extends`.
|
||||
|
||||
The layer is neutral: it ships no site colours, no site copy and no Nuxt modules. Its look comes from `--menu-*` CSS custom properties, which an app overrides, and every component can be replaced by a file of the same name in the app.
|
||||
|
||||
## Features
|
||||
@@ -20,7 +22,7 @@ The layer is neutral: it ships no site colours, no site copy and no Nuxt modules
|
||||
- **Active state**: the entry whose URL path equals the current route path (decoded, query, hash and trailing slash ignored; an absolute URL only on the request host) gets `aria-current="page"` and `menu-nav__link--active`; its ancestors get `menu-nav__item--trail`.
|
||||
- **Dividers**: `aria-hidden` list items, a horizontal line in panels and a short vertical line at the top level on the desktop.
|
||||
- **Empty states**: a missing menu (404), a failed request or a menu without entries renders nothing, or the `#fallback` slot. There is never an empty `<nav>`.
|
||||
- **Site cache purge**: the shared route `POST /api/__site__/cache/purge` drops the site's route cache when the backend saves, deletes or reorders a menu or menu entry (or a FAQ or post, through the other core layers). See [Site cache purge](#site-cache-purge).
|
||||
- **Site cache purge**: vue-seo-layer's shared route `POST /api/__site__/cache/purge` drops the site's route cache when the backend saves, deletes or reorders a menu or menu entry (or a FAQ or post, through the other core layers). See [Site cache purge](#site-cache-purge).
|
||||
- **Rate limits**: server-side fetches forward the visitor's `x-forwarded-for` (also on route-cached renders), so the API's per-client throttle sees visitors, not the Nuxt server.
|
||||
|
||||
## Usage
|
||||
@@ -66,26 +68,26 @@ Slots:
|
||||
|---|---|---|
|
||||
| `runtimeConfig.menu.apiBase` | `http://127.0.0.1:8096/_menu/api/v1` | `NUXT_MENU_API_BASE` (server only, used for SSR) |
|
||||
| `runtimeConfig.public.menu.apiBase` | `/_menu/api/v1` | `NUXT_PUBLIC_MENU_API_BASE` (browser, same origin) |
|
||||
| `runtimeConfig.siteCache.purgeToken` | `''` (route off) | `NUXT_SITE_CACHE_PURGE_TOKEN` (server only; at least 32 characters) |
|
||||
|
||||
The purge settings (`runtimeConfig.cachePurge.token`, `NUXT_CACHE_PURGE_TOKEN`; `cachePurge.allowProxied`, `NUXT_CACHE_PURGE_ALLOW_PROXIED`; the deprecated `siteCache.*` names) and the SEO options (`seo.*`, `NUXT_SEO_API_BASE` and the rest) are vue-seo-layer's; see its README.
|
||||
|
||||
The base URLs always come from runtime config, never from request input. In production the web server routes `/_menu/` to the SummerCMS binary.
|
||||
|
||||
## Site cache purge
|
||||
|
||||
A site that route-caches its pages (for example `routeRules: { '/**': { swr: 300 } }`) shows an admin edit only when the cache window ends. The layer ships the shared purge route that the SummerCMS backend calls after an admin save commits:
|
||||
The purge route `POST /api/__site__/cache/purge` that the SummerCMS backend calls after an admin save commits lives in vue-seo-layer: one copy for every layer, which also empties the SEO layer's resolve cache. Its contract is unchanged (`Authorization: Bearer <NUXT_CACHE_PURGE_TOKEN>`, body `{"kind":"<kind>","slug":"<slug>"}`, answer `{"purged": <n>}`, 404 without a token of at least 32 characters, 401 for a wrong token, proxied requests refused unless `NUXT_CACHE_PURGE_ALLOW_PROXIED=true`). Set the token and the backend's `site_cache.purge_url` as vue-seo-layer's README describes.
|
||||
|
||||
```
|
||||
POST /api/__site__/cache/purge
|
||||
Authorization: Bearer <NUXT_SITE_CACHE_PURGE_TOKEN>
|
||||
{"kind":"menu_entry","slug":"partition:3"}
|
||||
→ {"purged": <n>}
|
||||
```
|
||||
### Upgrading from 0.4
|
||||
|
||||
- It removes every entry of Nitro's route cache (pages, their `_payload.json` and feeds) and logs `[site-cache] purge: …` with the kind and slug.
|
||||
- Set `NUXT_SITE_CACHE_PURGE_TOKEN` to the backend's `site_cache.purge_token` (at least 32 characters) and point the backend's `site_cache.purge_url` at `http://127.0.0.1:<port>/api/__site__/cache/purge`. Without a token of that length the route answers 404.
|
||||
- It answers 404 to any request that came through a reverse proxy (`X-Forwarded-For`, `X-Real-IP` or `Forwarded`), so only the backend on the same machine reaches it, and 401 to a missing or wrong bearer token (compared in constant time).
|
||||
- The layer adds the route rule `'/api/__site__/**': { cache: false }`; without it, a site-wide `swr` rule would answer the purge from the cache.
|
||||
- vue-journal-layer, vue-menu-layer and vue-faq-layer ship the same `server/lib/siteCachePurge.ts` and `server/api/__site__/cache/purge.post.ts` byte for byte (a test pins their sha256), so a site that extends several of them gets one route, whichever copy Nitro keeps.
|
||||
- The layer extends vue-seo-layer. Nothing is needed in the app's `extends`, but every page now resolves through the SEO plugin (`GET /_seo/api/v1/resolve`) and takes its head from the answer; set `NUXT_SEO_API_BASE` when the backend is not on `http://127.0.0.1:8096`, or `seo: { cms: false }` for a site without the SEO plugin.
|
||||
- `server/api/__site__/cache/purge.post.ts`, `server/lib/siteCachePurge.ts` and `server/plugins/site-cache-purge.ts` are removed from this layer: the same route, library and plugin come from vue-seo-layer, with the same environment variables. An app that imported `server/lib/siteCachePurge.ts` from this layer imports it from vue-seo-layer.
|
||||
- **HTML sitemap pages.** The layer brings vue-seo-layer's HTML sitemap pages (pl `/mapa-strony`, en `/sitemap` by default, set in `seo.htmlSitemap.paths`). An app that renders its pages through a root catch-all (`pages/[...slug].vue`, as with the pages plugin) gets none, so a CMS page at such a path is not shadowed, unless it sets `seo.htmlSitemap.overCatchAll: true`; an app with its own page at such a path keeps its page; `seo: { htmlSitemap: false }` turns them off. An app without pages of its own (only `app.vue`) gets none and needs no setting.
|
||||
|
||||
### Upgrading from 0.3
|
||||
|
||||
- Rename `NUXT_SITE_CACHE_PURGE_TOKEN` to `NUXT_CACHE_PURGE_TOKEN` and `NUXT_SITE_CACHE_ALLOW_PROXIED` to `NUXT_CACHE_PURGE_ALLOW_PROXIED` in the site's environment. The old names keep working through 0.4.x (a non-empty new token wins) and are removed in the next minor release.
|
||||
- Why: nuxt-site-config publishes every `NUXT_SITE_*` variable in the page payload, so in a site with nuxt-site-config a token set under `NUXT_SITE_CACHE_PURGE_TOKEN` appeared in every page as `cachePurgeToken`. From 0.4 the layer strips both keys from the site config even under the old names.
|
||||
- Rotate a token that was ever set under `NUXT_SITE_CACHE_PURGE_TOKEN`: earlier versions published it. Set the new value in the site's `NUXT_CACHE_PURGE_TOKEN` and in the backend's `site_cache.purge_token`, then restart both.
|
||||
|
||||
## API
|
||||
|
||||
@@ -140,7 +142,7 @@ Commit, tag `vX.Y.Z` (annotated) and push the tag; apps pin `#vX.Y.Z` in their d
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
pnpm test # node --test: link rules, client, rendered NavItem markup, site cache purge
|
||||
pnpm test # node --test: link rules, client, rendered NavItem markup
|
||||
pnpm build:playground && pnpm smoke:ssr # SSR of the playground against testing/fixture-api.mjs
|
||||
```
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
//
|
||||
// Every path below is absolute and built from this file's directory: a
|
||||
// relative or ~ path in a layer config resolves against the consuming
|
||||
// project, not the layer. The layer declares no Nuxt modules.
|
||||
// project, not the layer. The layer declares no Nuxt modules; it extends
|
||||
// vue-seo-layer, which owns the page head and the shared site cache purge
|
||||
// route POST /api/__site__/cache/purge.
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const currentDir = fileURLToPath(new URL('.', import.meta.url))
|
||||
@@ -13,6 +15,9 @@ export default defineNuxtConfig({
|
||||
compatibilityDate: '2025-07-15',
|
||||
devtools: { enabled: false },
|
||||
|
||||
// Resolved from this layer's own node_modules.
|
||||
extends: ['vue-seo-layer'],
|
||||
|
||||
// Tokens first, then the neutral styles that use them. The app's own css
|
||||
// array loads after these, so its token overrides win.
|
||||
css: [`${currentDir}app/assets/css/menu-tokens.css`, `${currentDir}app/assets/css/menu-base.css`],
|
||||
@@ -23,13 +28,6 @@ export default defineNuxtConfig({
|
||||
// comes from request input.
|
||||
apiBase: 'http://127.0.0.1:8096/_menu/api/v1',
|
||||
},
|
||||
siteCache: {
|
||||
// Server-only bearer token of the shared site cache purge route
|
||||
// POST /api/__site__/cache/purge (NUXT_SITE_CACHE_PURGE_TOKEN), the
|
||||
// backend's site_cache.purge_token. Shorter than 32 characters, the
|
||||
// route answers 404.
|
||||
purgeToken: '',
|
||||
},
|
||||
public: {
|
||||
menu: {
|
||||
// Same-origin base for browser fetches (NUXT_PUBLIC_MENU_API_BASE).
|
||||
@@ -37,12 +35,4 @@ export default defineNuxtConfig({
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// The shared site routes are never route-cached. Nitro wraps every
|
||||
// handler matching an app rule such as '/**': { swr } in its cached
|
||||
// handler, which keys by path, does not skip POST and hides request
|
||||
// headers, so the purge route would answer from the cache without running.
|
||||
routeRules: {
|
||||
'/api/__site__/**': { cache: false },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vue-menu-layer",
|
||||
"version": "0.2.0",
|
||||
"version": "0.5.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./nuxt.config.ts",
|
||||
@@ -14,6 +14,9 @@
|
||||
"engines": {
|
||||
"node": ">=22.18"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue-seo-layer": "git+https://git.golem15.com/golem15/vue-seo-layer.git#v0.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"nuxt": "4.4.8"
|
||||
},
|
||||
|
||||
206
pnpm-lock.yaml
generated
206
pnpm-lock.yaml
generated
@@ -7,10 +7,14 @@ settings:
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
vue-seo-layer:
|
||||
specifier: git+https://git.golem15.com/golem15/vue-seo-layer.git#v0.2.0
|
||||
version: git+https://git.golem15.com/golem15/vue-seo-layer.git#3868f3d74dcebb7a35af43f1defcd68cfe2933b7(nuxt@4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.2.2))(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(supports-color@10.2.2)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)))
|
||||
devDependencies:
|
||||
nuxt:
|
||||
specifier: 4.4.8
|
||||
version: 4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1)(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))
|
||||
version: 4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.2.2))(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(supports-color@10.2.2)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))
|
||||
typescript:
|
||||
specifier: 5.9.3
|
||||
version: 5.9.3
|
||||
@@ -3256,6 +3260,13 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
vue-seo-layer@git+https://git.golem15.com/golem15/vue-seo-layer.git#3868f3d74dcebb7a35af43f1defcd68cfe2933b7:
|
||||
resolution: {commit: 3868f3d74dcebb7a35af43f1defcd68cfe2933b7, repo: https://git.golem15.com/golem15/vue-seo-layer.git, type: git}
|
||||
version: 0.2.0
|
||||
engines: {node: '>=22.18'}
|
||||
peerDependencies:
|
||||
nuxt: 4.4.8
|
||||
|
||||
vue@3.5.35:
|
||||
resolution: {integrity: sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==}
|
||||
peerDependencies:
|
||||
@@ -3350,20 +3361,20 @@ snapshots:
|
||||
|
||||
'@babel/compat-data@7.29.7': {}
|
||||
|
||||
'@babel/core@7.29.7':
|
||||
'@babel/core@7.29.7(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.7
|
||||
'@babel/generator': 7.29.8
|
||||
'@babel/helper-compilation-targets': 7.29.7
|
||||
'@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7)
|
||||
'@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)
|
||||
'@babel/helpers': 7.29.10
|
||||
'@babel/parser': 7.29.9
|
||||
'@babel/template': 7.29.7
|
||||
'@babel/traverse': 7.29.10
|
||||
'@babel/traverse': 7.29.10(supports-color@10.2.2)
|
||||
'@babel/types': 7.29.8
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
convert-source-map: 2.0.0
|
||||
debug: 4.4.3
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
gensync: 1.0.0-beta.2
|
||||
json5: 2.2.3
|
||||
semver: 6.3.1
|
||||
@@ -3390,41 +3401,41 @@ snapshots:
|
||||
lru-cache: 5.1.1
|
||||
semver: 6.3.1
|
||||
|
||||
'@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)':
|
||||
'@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7
|
||||
'@babel/core': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/helper-annotate-as-pure': 7.29.7
|
||||
'@babel/helper-member-expression-to-functions': 7.29.7
|
||||
'@babel/helper-member-expression-to-functions': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/helper-optimise-call-expression': 7.29.7
|
||||
'@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7)
|
||||
'@babel/helper-skip-transparent-expression-wrappers': 7.29.7
|
||||
'@babel/traverse': 7.29.10
|
||||
'@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)
|
||||
'@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/traverse': 7.29.10(supports-color@10.2.2)
|
||||
semver: 6.3.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/helper-globals@7.29.7': {}
|
||||
|
||||
'@babel/helper-member-expression-to-functions@7.29.7':
|
||||
'@babel/helper-member-expression-to-functions@7.29.7(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@babel/traverse': 7.29.10
|
||||
'@babel/traverse': 7.29.10(supports-color@10.2.2)
|
||||
'@babel/types': 7.29.8
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/helper-module-imports@7.29.7':
|
||||
'@babel/helper-module-imports@7.29.7(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@babel/traverse': 7.29.10
|
||||
'@babel/traverse': 7.29.10(supports-color@10.2.2)
|
||||
'@babel/types': 7.29.8
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)':
|
||||
'@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7
|
||||
'@babel/helper-module-imports': 7.29.7
|
||||
'@babel/core': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/helper-module-imports': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/helper-validator-identifier': 7.29.7
|
||||
'@babel/traverse': 7.29.10
|
||||
'@babel/traverse': 7.29.10(supports-color@10.2.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -3434,18 +3445,18 @@ snapshots:
|
||||
|
||||
'@babel/helper-plugin-utils@7.29.7': {}
|
||||
|
||||
'@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)':
|
||||
'@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7
|
||||
'@babel/helper-member-expression-to-functions': 7.29.7
|
||||
'@babel/core': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/helper-member-expression-to-functions': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/helper-optimise-call-expression': 7.29.7
|
||||
'@babel/traverse': 7.29.10
|
||||
'@babel/traverse': 7.29.10(supports-color@10.2.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/helper-skip-transparent-expression-wrappers@7.29.7':
|
||||
'@babel/helper-skip-transparent-expression-wrappers@7.29.7(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@babel/traverse': 7.29.10
|
||||
'@babel/traverse': 7.29.10(supports-color@10.2.2)
|
||||
'@babel/types': 7.29.8
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -3473,24 +3484,24 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/types': 8.0.6
|
||||
|
||||
'@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)':
|
||||
'@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7
|
||||
'@babel/core': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/helper-plugin-utils': 7.29.7
|
||||
|
||||
'@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)':
|
||||
'@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7
|
||||
'@babel/core': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/helper-plugin-utils': 7.29.7
|
||||
|
||||
'@babel/plugin-transform-typescript@7.29.9(@babel/core@7.29.7)':
|
||||
'@babel/plugin-transform-typescript@7.29.9(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7
|
||||
'@babel/core': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/helper-annotate-as-pure': 7.29.7
|
||||
'@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7)
|
||||
'@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)
|
||||
'@babel/helper-plugin-utils': 7.29.7
|
||||
'@babel/helper-skip-transparent-expression-wrappers': 7.29.7
|
||||
'@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7)
|
||||
'@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -3500,7 +3511,7 @@ snapshots:
|
||||
'@babel/parser': 7.29.9
|
||||
'@babel/types': 7.29.8
|
||||
|
||||
'@babel/traverse@7.29.10':
|
||||
'@babel/traverse@7.29.10(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.7
|
||||
'@babel/generator': 7.29.8
|
||||
@@ -3508,7 +3519,7 @@ snapshots:
|
||||
'@babel/parser': 7.29.9
|
||||
'@babel/template': 7.29.7
|
||||
'@babel/types': 7.29.8
|
||||
debug: 4.4.3
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -3690,19 +3701,19 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.6.0
|
||||
|
||||
'@kwsites/file-exists@1.1.1':
|
||||
'@kwsites/file-exists@1.1.1(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@kwsites/promise-deferred@1.1.1': {}
|
||||
|
||||
'@mapbox/node-pre-gyp@2.0.3':
|
||||
'@mapbox/node-pre-gyp@2.0.3(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
consola: 3.4.2
|
||||
detect-libc: 2.1.2
|
||||
https-proxy-agent: 7.0.6
|
||||
https-proxy-agent: 7.0.6(supports-color@10.2.2)
|
||||
node-fetch: 2.7.0
|
||||
nopt: 8.1.0
|
||||
semver: 7.8.5
|
||||
@@ -3733,7 +3744,7 @@ snapshots:
|
||||
'@nodelib/fs.scandir': 2.1.5
|
||||
fastq: 1.20.3
|
||||
|
||||
'@nuxt/cli@3.37.0(@nuxt/schema@4.4.8)(cac@6.7.14)(magicast@0.5.5)':
|
||||
'@nuxt/cli@3.37.0(@nuxt/schema@4.4.8)(cac@6.7.14)(magicast@0.5.5)(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@bomb.sh/tab': 0.0.19(cac@6.7.14)(citty@0.2.2)
|
||||
'@clack/prompts': 1.8.1
|
||||
@@ -3741,7 +3752,7 @@ snapshots:
|
||||
citty: 0.2.2
|
||||
confbox: 0.2.4
|
||||
consola: 3.4.2
|
||||
debug: 4.4.3
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
defu: 6.1.7
|
||||
exsolve: 1.1.3
|
||||
fuse.js: 7.5.0
|
||||
@@ -3802,7 +3813,7 @@ snapshots:
|
||||
pkg-types: 2.3.3
|
||||
semver: 7.8.5
|
||||
|
||||
'@nuxt/devtools@3.4.2(@nuxt/schema@4.4.8)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.133.0)(unplugin@3.4.0(esbuild@0.28.2)(rollup@4.64.2)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)))(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))(vue@3.5.35(typescript@5.9.3))':
|
||||
'@nuxt/devtools@3.4.2(@nuxt/schema@4.4.8)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.2.2))(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.133.0)(supports-color@10.2.2)(unplugin@3.4.0(esbuild@0.28.2)(rollup@4.64.2)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)))(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))(vue@3.5.35(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@nuxt/devtools-kit': 3.4.2(@nuxt/schema@4.4.8)(confbox@0.3.1)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.133.0)(unplugin@3.4.0(esbuild@0.28.2)(rollup@4.64.2)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)))(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))
|
||||
'@nuxt/devtools-wizard': 3.4.2
|
||||
@@ -3828,11 +3839,11 @@ snapshots:
|
||||
perfect-debounce: 2.1.0
|
||||
pkg-types: 2.3.3
|
||||
semver: 7.8.5
|
||||
simple-git: 3.36.0
|
||||
simple-git: 3.36.0(supports-color@10.2.2)
|
||||
sirv: 3.0.2
|
||||
structured-clone-es: 2.0.1
|
||||
tinyglobby: 0.2.17
|
||||
unstorage: 1.17.5(db0@0.3.4)(ioredis@5.11.1)
|
||||
unstorage: 1.17.5(db0@0.3.4)(ioredis@5.11.1(supports-color@10.2.2))
|
||||
vite: 7.3.7(jiti@2.7.0)(terser@5.51.2)
|
||||
vite-plugin-inspect: 11.4.1(@nuxt/kit@4.6.0(@nuxt/schema@4.4.8)(confbox@0.3.1)(dotenv@17.4.2)(giget@3.3.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.133.0)(unplugin@3.4.0(esbuild@0.28.2)(rollup@4.64.2)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))))(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))
|
||||
vite-plugin-vue-tracer: 1.5.0(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))(vue@3.5.35(typescript@5.9.3))
|
||||
@@ -3930,7 +3941,7 @@ snapshots:
|
||||
- magic-string
|
||||
- unplugin
|
||||
|
||||
'@nuxt/nitro-server@4.4.8(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(db0@0.3.4)(esbuild@0.28.2)(ioredis@5.11.1)(magicast@0.5.5)(nuxt@4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1)(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)))(oxc-parser@0.133.0)(rollup@4.64.2)(srvx@0.11.22)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))':
|
||||
'@nuxt/nitro-server@4.4.8(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(db0@0.3.4)(esbuild@0.28.2)(ioredis@5.11.1(supports-color@10.2.2))(magicast@0.5.5)(nuxt@4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.2.2))(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(supports-color@10.2.2)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)))(oxc-parser@0.133.0)(rollup@4.64.2)(srvx@0.11.22)(supports-color@10.2.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))':
|
||||
dependencies:
|
||||
'@nuxt/devalue': 2.0.2
|
||||
'@nuxt/kit': 4.4.8(magicast@0.5.5)
|
||||
@@ -3947,8 +3958,8 @@ snapshots:
|
||||
impound: 1.2.0(esbuild@0.28.2)(rollup@4.64.2)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))
|
||||
klona: 2.0.6
|
||||
mocked-exports: 0.1.1
|
||||
nitropack: 2.13.4(oxc-parser@0.133.0)(srvx@0.11.22)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))
|
||||
nuxt: 4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1)(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))
|
||||
nitropack: 2.13.4(oxc-parser@0.133.0)(srvx@0.11.22)(supports-color@10.2.2)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))
|
||||
nuxt: 4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.2.2))(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(supports-color@10.2.2)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))
|
||||
nypm: 0.6.10
|
||||
ohash: 2.0.12
|
||||
pathe: 2.0.3
|
||||
@@ -3956,12 +3967,12 @@ snapshots:
|
||||
std-env: 4.3.0
|
||||
ufo: 1.6.4
|
||||
unctx: 2.5.0
|
||||
unstorage: 1.17.5(db0@0.3.4)(ioredis@5.11.1)
|
||||
unstorage: 1.17.5(db0@0.3.4)(ioredis@5.11.1(supports-color@10.2.2))
|
||||
vue: 3.5.35(typescript@5.9.3)
|
||||
vue-bundle-renderer: 2.4.0
|
||||
vue-devtools-stub: 0.1.0
|
||||
optionalDependencies:
|
||||
'@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7)
|
||||
'@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
- '@azure/cosmos'
|
||||
@@ -4026,12 +4037,12 @@ snapshots:
|
||||
rc9: 3.1.0
|
||||
std-env: 4.3.0
|
||||
|
||||
'@nuxt/vite-builder@4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(magicast@0.5.5)(nuxt@4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1)(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)))(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(terser@5.51.2)(typescript@5.9.3)(vue@3.5.35(typescript@5.9.3))':
|
||||
'@nuxt/vite-builder@4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(magicast@0.5.5)(nuxt@4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.2.2))(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(supports-color@10.2.2)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)))(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(supports-color@10.2.2)(terser@5.51.2)(typescript@5.9.3)(vue@3.5.35(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@nuxt/kit': 4.4.8(magicast@0.5.5)
|
||||
'@rollup/plugin-replace': 6.0.3(rollup@4.64.2)
|
||||
'@vitejs/plugin-vue': 6.0.9(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))(vue@3.5.35(typescript@5.9.3))
|
||||
'@vitejs/plugin-vue-jsx': 5.1.6(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))(vue@3.5.35(typescript@5.9.3))
|
||||
'@vitejs/plugin-vue-jsx': 5.1.6(supports-color@10.2.2)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))(vue@3.5.35(typescript@5.9.3))
|
||||
autoprefixer: 10.6.1(postcss@8.5.29)
|
||||
consola: 3.4.2
|
||||
cssnano: 8.0.10(postcss@8.5.29)
|
||||
@@ -4044,7 +4055,7 @@ snapshots:
|
||||
magic-string: 0.30.21
|
||||
mlly: 1.8.2
|
||||
mocked-exports: 0.1.1
|
||||
nuxt: 4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1)(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))
|
||||
nuxt: 4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.2.2))(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(supports-color@10.2.2)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))
|
||||
nypm: 0.6.10
|
||||
pathe: 2.0.3
|
||||
pkg-types: 2.3.3
|
||||
@@ -4053,13 +4064,14 @@ snapshots:
|
||||
std-env: 4.3.0
|
||||
ufo: 1.6.4
|
||||
unenv: 2.0.0-rc.24
|
||||
unplugin: 2.3.11
|
||||
vite: 7.3.7(jiti@2.7.0)(terser@5.51.2)
|
||||
vite-node: 5.3.0(jiti@2.7.0)(terser@5.51.2)
|
||||
vite-plugin-checker: 0.14.5(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))
|
||||
vue: 3.5.35(typescript@5.9.3)
|
||||
vue-bundle-renderer: 2.4.0
|
||||
optionalDependencies:
|
||||
'@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7)
|
||||
'@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))
|
||||
rollup-plugin-visualizer: 7.1.1(rollup@4.64.2)
|
||||
transitivePeerDependencies:
|
||||
- '@biomejs/biome'
|
||||
@@ -4467,9 +4479,9 @@ snapshots:
|
||||
unhead: 2.1.17
|
||||
vue: 3.5.35(typescript@5.9.3)
|
||||
|
||||
'@vercel/nft@1.11.0(rollup@4.64.2)':
|
||||
'@vercel/nft@1.11.0(rollup@4.64.2)(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@mapbox/node-pre-gyp': 2.0.3
|
||||
'@mapbox/node-pre-gyp': 2.0.3(supports-color@10.2.2)
|
||||
'@rollup/pluginutils': 5.4.0(rollup@4.64.2)
|
||||
acorn: 8.19.0
|
||||
acorn-import-attributes: 1.9.5(acorn@8.19.0)
|
||||
@@ -4486,13 +4498,13 @@ snapshots:
|
||||
- rollup
|
||||
- supports-color
|
||||
|
||||
'@vitejs/plugin-vue-jsx@5.1.6(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))(vue@3.5.35(typescript@5.9.3))':
|
||||
'@vitejs/plugin-vue-jsx@5.1.6(supports-color@10.2.2)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))(vue@3.5.35(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.7
|
||||
'@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7)
|
||||
'@babel/plugin-transform-typescript': 7.29.9(@babel/core@7.29.7)
|
||||
'@babel/core': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))
|
||||
'@babel/plugin-transform-typescript': 7.29.9(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)
|
||||
'@rolldown/pluginutils': 1.0.1
|
||||
'@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.7)
|
||||
'@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)
|
||||
vite: 7.3.7(jiti@2.7.0)(terser@5.51.2)
|
||||
vue: 3.5.35(typescript@5.9.3)
|
||||
transitivePeerDependencies:
|
||||
@@ -4516,27 +4528,27 @@ snapshots:
|
||||
|
||||
'@vue/babel-helper-vue-transform-on@2.0.1': {}
|
||||
|
||||
'@vue/babel-plugin-jsx@2.0.1(@babel/core@7.29.7)':
|
||||
'@vue/babel-plugin-jsx@2.0.1(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@babel/helper-module-imports': 7.29.7
|
||||
'@babel/helper-module-imports': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/helper-plugin-utils': 7.29.7
|
||||
'@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7)
|
||||
'@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))
|
||||
'@babel/template': 7.29.7
|
||||
'@babel/traverse': 7.29.10
|
||||
'@babel/traverse': 7.29.10(supports-color@10.2.2)
|
||||
'@babel/types': 7.29.8
|
||||
'@vue/babel-helper-vue-transform-on': 2.0.1
|
||||
'@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.29.7)
|
||||
'@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)
|
||||
'@vue/shared': 3.5.43
|
||||
optionalDependencies:
|
||||
'@babel/core': 7.29.7
|
||||
'@babel/core': 7.29.7(supports-color@10.2.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vue/babel-plugin-resolve-type@2.0.1(@babel/core@7.29.7)':
|
||||
'@vue/babel-plugin-resolve-type@2.0.1(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.7
|
||||
'@babel/core': 7.29.7
|
||||
'@babel/helper-module-imports': 7.29.7
|
||||
'@babel/core': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/helper-module-imports': 7.29.7(supports-color@10.2.2)
|
||||
'@babel/helper-plugin-utils': 7.29.7
|
||||
'@babel/parser': 7.29.9
|
||||
'@vue/compiler-sfc': 3.5.43
|
||||
@@ -4999,9 +5011,11 @@ snapshots:
|
||||
|
||||
db0@0.3.4: {}
|
||||
|
||||
debug@4.4.3:
|
||||
debug@4.4.3(supports-color@10.2.2):
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
optionalDependencies:
|
||||
supports-color: 10.2.2
|
||||
|
||||
deepmerge@4.3.1: {}
|
||||
|
||||
@@ -5288,10 +5302,10 @@ snapshots:
|
||||
|
||||
http-shutdown@1.2.2: {}
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
https-proxy-agent@7.0.6(supports-color@10.2.2):
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.3
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -5328,11 +5342,11 @@ snapshots:
|
||||
|
||||
ini@4.1.1: {}
|
||||
|
||||
ioredis@5.11.1:
|
||||
ioredis@5.11.1(supports-color@10.2.2):
|
||||
dependencies:
|
||||
'@ioredis/commands': 1.10.0
|
||||
cluster-key-slot: 1.1.1
|
||||
debug: 4.4.3
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
denque: 2.1.0
|
||||
redis-errors: 1.2.0
|
||||
redis-parser: 3.0.0
|
||||
@@ -5541,7 +5555,7 @@ snapshots:
|
||||
|
||||
nanotar@0.3.0: {}
|
||||
|
||||
nitropack@2.13.4(oxc-parser@0.133.0)(srvx@0.11.22)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)):
|
||||
nitropack@2.13.4(oxc-parser@0.133.0)(srvx@0.11.22)(supports-color@10.2.2)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)):
|
||||
dependencies:
|
||||
'@cloudflare/kv-asset-handler': 0.4.2
|
||||
'@rollup/plugin-alias': 6.0.0(rollup@4.64.2)
|
||||
@@ -5551,7 +5565,7 @@ snapshots:
|
||||
'@rollup/plugin-node-resolve': 16.0.3(rollup@4.64.2)
|
||||
'@rollup/plugin-replace': 6.0.3(rollup@4.64.2)
|
||||
'@rollup/plugin-terser': 1.0.0(rollup@4.64.2)
|
||||
'@vercel/nft': 1.11.0(rollup@4.64.2)
|
||||
'@vercel/nft': 1.11.0(rollup@4.64.2)(supports-color@10.2.2)
|
||||
archiver: 7.0.1
|
||||
c12: 3.3.4(magicast@0.5.5)
|
||||
chokidar: 5.0.0
|
||||
@@ -5575,7 +5589,7 @@ snapshots:
|
||||
h3: 1.15.11
|
||||
hookable: 5.5.3
|
||||
httpxy: 0.5.5
|
||||
ioredis: 5.11.1
|
||||
ioredis: 5.11.1(supports-color@10.2.2)
|
||||
jiti: 2.7.0
|
||||
klona: 2.0.6
|
||||
knitwork: 1.3.0
|
||||
@@ -5598,7 +5612,7 @@ snapshots:
|
||||
scule: 1.3.0
|
||||
semver: 7.8.5
|
||||
serve-placeholder: 2.0.2
|
||||
serve-static: 2.2.1
|
||||
serve-static: 2.2.1(supports-color@10.2.2)
|
||||
source-map: 0.7.6
|
||||
std-env: 4.3.0
|
||||
ufo: 1.6.4
|
||||
@@ -5608,7 +5622,7 @@ snapshots:
|
||||
unenv: 2.0.0-rc.24
|
||||
unimport: 6.5.0(esbuild@0.28.2)(oxc-parser@0.133.0)(rollup@4.64.2)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))
|
||||
unplugin-utils: 0.3.2
|
||||
unstorage: 1.17.5(db0@0.3.4)(ioredis@5.11.1)
|
||||
unstorage: 1.17.5(db0@0.3.4)(ioredis@5.11.1(supports-color@10.2.2))
|
||||
untyped: 2.0.0
|
||||
unwasm: 0.5.3
|
||||
youch: 4.1.1
|
||||
@@ -5689,16 +5703,16 @@ snapshots:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
|
||||
nuxt@4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1)(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)):
|
||||
nuxt@4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.2.2))(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(supports-color@10.2.2)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)):
|
||||
dependencies:
|
||||
'@dxup/nuxt': 0.4.1(magicast@0.5.5)(typescript@5.9.3)
|
||||
'@nuxt/cli': 3.37.0(@nuxt/schema@4.4.8)(cac@6.7.14)(magicast@0.5.5)
|
||||
'@nuxt/devtools': 3.4.2(@nuxt/schema@4.4.8)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1)(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.133.0)(unplugin@3.4.0(esbuild@0.28.2)(rollup@4.64.2)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)))(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))(vue@3.5.35(typescript@5.9.3))
|
||||
'@nuxt/cli': 3.37.0(@nuxt/schema@4.4.8)(cac@6.7.14)(magicast@0.5.5)(supports-color@10.2.2)
|
||||
'@nuxt/devtools': 3.4.2(@nuxt/schema@4.4.8)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.2.2))(jiti@2.7.0)(magic-string@0.30.21)(mlly@1.8.2)(oxc-parser@0.133.0)(supports-color@10.2.2)(unplugin@3.4.0(esbuild@0.28.2)(rollup@4.64.2)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)))(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))(vue@3.5.35(typescript@5.9.3))
|
||||
'@nuxt/kit': 4.4.8(magicast@0.5.5)
|
||||
'@nuxt/nitro-server': 4.4.8(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(db0@0.3.4)(esbuild@0.28.2)(ioredis@5.11.1)(magicast@0.5.5)(nuxt@4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1)(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)))(oxc-parser@0.133.0)(rollup@4.64.2)(srvx@0.11.22)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))
|
||||
'@nuxt/nitro-server': 4.4.8(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(db0@0.3.4)(esbuild@0.28.2)(ioredis@5.11.1(supports-color@10.2.2))(magicast@0.5.5)(nuxt@4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.2.2))(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(supports-color@10.2.2)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)))(oxc-parser@0.133.0)(rollup@4.64.2)(srvx@0.11.22)(supports-color@10.2.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))
|
||||
'@nuxt/schema': 4.4.8
|
||||
'@nuxt/telemetry': 2.9.1(@nuxt/kit@4.4.8(magicast@0.5.5))
|
||||
'@nuxt/vite-builder': 4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(magicast@0.5.5)(nuxt@4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1)(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)))(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(terser@5.51.2)(typescript@5.9.3)(vue@3.5.35(typescript@5.9.3))
|
||||
'@nuxt/vite-builder': 4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(magicast@0.5.5)(nuxt@4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.2.2))(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(supports-color@10.2.2)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2)))(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(supports-color@10.2.2)(terser@5.51.2)(typescript@5.9.3)(vue@3.5.35(typescript@5.9.3))
|
||||
'@unhead/vue': 2.1.17(vue@3.5.35(typescript@5.9.3))
|
||||
'@vue/shared': 3.5.43
|
||||
chokidar: 5.0.0
|
||||
@@ -6276,9 +6290,9 @@ snapshots:
|
||||
|
||||
semver@7.8.5: {}
|
||||
|
||||
send@1.2.1:
|
||||
send@1.2.1(supports-color@10.2.2):
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
@@ -6300,12 +6314,12 @@ snapshots:
|
||||
dependencies:
|
||||
defu: 6.1.7
|
||||
|
||||
serve-static@2.2.1:
|
||||
serve-static@2.2.1(supports-color@10.2.2):
|
||||
dependencies:
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
parseurl: 1.3.3
|
||||
send: 1.2.1
|
||||
send: 1.2.1(supports-color@10.2.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -6323,13 +6337,13 @@ snapshots:
|
||||
|
||||
signal-exit@4.1.0: {}
|
||||
|
||||
simple-git@3.36.0:
|
||||
simple-git@3.36.0(supports-color@10.2.2):
|
||||
dependencies:
|
||||
'@kwsites/file-exists': 1.1.1
|
||||
'@kwsites/file-exists': 1.1.1(supports-color@10.2.2)
|
||||
'@kwsites/promise-deferred': 1.1.1
|
||||
'@simple-git/args-pathspec': 1.0.4
|
||||
'@simple-git/argv-parser': 1.1.1
|
||||
debug: 4.4.3
|
||||
debug: 4.4.3(supports-color@10.2.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -6600,7 +6614,7 @@ snapshots:
|
||||
escape-string-regexp: 5.0.0
|
||||
ufo: 1.6.4
|
||||
|
||||
unstorage@1.17.5(db0@0.3.4)(ioredis@5.11.1):
|
||||
unstorage@1.17.5(db0@0.3.4)(ioredis@5.11.1(supports-color@10.2.2)):
|
||||
dependencies:
|
||||
anymatch: 3.1.3
|
||||
chokidar: 5.0.0
|
||||
@@ -6612,7 +6626,7 @@ snapshots:
|
||||
ufo: 1.6.4
|
||||
optionalDependencies:
|
||||
db0: 0.3.4
|
||||
ioredis: 5.11.1
|
||||
ioredis: 5.11.1(supports-color@10.2.2)
|
||||
|
||||
untun@0.2.2: {}
|
||||
|
||||
@@ -6765,6 +6779,10 @@ snapshots:
|
||||
- unloader
|
||||
- webpack
|
||||
|
||||
vue-seo-layer@git+https://git.golem15.com/golem15/vue-seo-layer.git#3868f3d74dcebb7a35af43f1defcd68cfe2933b7(nuxt@4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.2.2))(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(supports-color@10.2.2)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))):
|
||||
dependencies:
|
||||
nuxt: 4.4.8(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.133.0)(@vue/compiler-sfc@3.5.43)(cac@6.7.14)(confbox@0.3.1)(db0@0.3.4)(dotenv@17.4.2)(esbuild@0.28.2)(giget@3.3.1)(ioredis@5.11.1(supports-color@10.2.2))(magicast@0.5.5)(rollup-plugin-visualizer@7.1.1(rollup@4.64.2))(rollup@4.64.2)(srvx@0.11.22)(supports-color@10.2.2)(terser@5.51.2)(typescript@5.9.3)(vite@7.3.7(jiti@2.7.0)(terser@5.51.2))
|
||||
|
||||
vue@3.5.35(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.5.35
|
||||
|
||||
39
server/api/__site__/cache/purge.post.ts
vendored
39
server/api/__site__/cache/purge.post.ts
vendored
@@ -1,39 +0,0 @@
|
||||
// POST /api/__site__/cache/purge: removes every Nitro route-cache entry
|
||||
// (pages, _payload.json and feeds), so an admin save shows on the next page
|
||||
// view. The backend calls it on loopback after the save commits, with
|
||||
// `Authorization: Bearer <runtimeConfig.siteCache.purgeToken>` and the body
|
||||
// {"kind":"<kind>","slug":"<slug>"}; the answer is {"purged":<n>}. The kind
|
||||
// and slug only reach the log line.
|
||||
//
|
||||
// 404 without a token of at least 32 characters, and for any request that
|
||||
// came through a reverse proxy (X-Forwarded-For, X-Real-IP or Forwarded);
|
||||
// 401 for a missing or wrong bearer token. The '/api/__site__/**' route rule
|
||||
// keeps this route out of the route cache.
|
||||
//
|
||||
// Shared: several layers ship this file byte for byte, and Nitro keeps one
|
||||
// handler per route and method.
|
||||
|
||||
import { authorizeSitePurge, parseSitePurgeEvent, purgeSiteRouteCache } from '../../../lib/siteCachePurge.ts'
|
||||
|
||||
const PROXY_HEADERS = ['x-forwarded-for', 'x-real-ip', 'forwarded']
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
setResponseHeader(event, 'cache-control', 'no-store')
|
||||
const config = useRuntimeConfig(event)
|
||||
const verdict = authorizeSitePurge({
|
||||
token: (config.siteCache as { purgeToken?: unknown } | undefined)?.purgeToken,
|
||||
authorization: getRequestHeader(event, 'authorization'),
|
||||
proxied: PROXY_HEADERS.some((name) => getRequestHeader(event, name) !== undefined),
|
||||
})
|
||||
if (verdict === 'disabled' || verdict === 'proxied') {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Not Found' })
|
||||
}
|
||||
if (verdict === 'unauthorized') {
|
||||
throw createError({ statusCode: 401, statusMessage: 'Unauthorized' })
|
||||
}
|
||||
|
||||
const { kind, slug } = parseSitePurgeEvent(await readBody(event).catch(() => null))
|
||||
const purged = await purgeSiteRouteCache(useStorage('cache'))
|
||||
console.info(`[site-cache] purge: removed ${purged} route cache entries (${kind} ${slug})`)
|
||||
return { purged }
|
||||
})
|
||||
@@ -1,91 +0,0 @@
|
||||
// Shared site cache purge: the authorization rules of the purge route
|
||||
// POST /api/__site__/cache/purge, the purge of Nitro's route cache and the
|
||||
// parsing of the purge request body. Pure TypeScript outside Nitro's and
|
||||
// Nuxt's auto-import directories, so several layers can ship this file
|
||||
// byte for byte, and `node --test` and the route import it directly.
|
||||
|
||||
import { createHash, timingSafeEqual } from 'node:crypto'
|
||||
|
||||
/** The shortest purge token the route accepts; with a shorter one it is off. */
|
||||
export const MIN_SITE_PURGE_TOKEN_LENGTH = 32
|
||||
|
||||
/** The cache storage base of Nitro's route cache (routeRules cache and swr). */
|
||||
export const SITE_ROUTE_CACHE_BASE = 'nitro:routes'
|
||||
|
||||
/** Longest slug the purge log line carries. */
|
||||
const MAX_SLUG_LENGTH = 200
|
||||
|
||||
/** A purge kind: lower-case letters and underscores, up to 40 characters. */
|
||||
const KIND_PATTERN = /^[a-z_]{1,40}$/
|
||||
|
||||
export type SitePurgeVerdict = 'ok' | 'disabled' | 'proxied' | 'unauthorized'
|
||||
|
||||
export interface SitePurgeAuthInput {
|
||||
/** The configured token (runtimeConfig.siteCache.purgeToken). */
|
||||
token: unknown
|
||||
/** The request's Authorization header, if any. */
|
||||
authorization?: string | null
|
||||
/** True when the request carried X-Forwarded-For, X-Real-IP or Forwarded. */
|
||||
proxied: boolean
|
||||
}
|
||||
|
||||
function digest(value: string): Buffer {
|
||||
return createHash('sha256').update(value, 'utf8').digest()
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides a purge request: `disabled` when no token of at least
|
||||
* MIN_SITE_PURGE_TOKEN_LENGTH characters is configured, `proxied` when the
|
||||
* request came through a reverse proxy (checked before the token), `ok` when
|
||||
* the Bearer value equals the token (compared in constant time over sha256
|
||||
* digests) and `unauthorized` otherwise.
|
||||
*/
|
||||
export function authorizeSitePurge({ token, authorization, proxied }: SitePurgeAuthInput): SitePurgeVerdict {
|
||||
if (typeof token !== 'string' || token.length < MIN_SITE_PURGE_TOKEN_LENGTH) return 'disabled'
|
||||
if (proxied) return 'proxied'
|
||||
const m = /^Bearer[ \t]+(\S+)[ \t]*$/i.exec(authorization ?? '')
|
||||
if (!m) return 'unauthorized'
|
||||
return timingSafeEqual(digest(m[1]), digest(token)) ? 'ok' : 'unauthorized'
|
||||
}
|
||||
|
||||
/** The part of a Nitro/unstorage storage the purge uses. */
|
||||
export interface SitePurgeStorage {
|
||||
getKeys(base?: string): Promise<string[]> | string[]
|
||||
removeItem(key: string): Promise<unknown> | unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes every entry of Nitro's route cache (pages, their _payload.json and
|
||||
* feeds) from storage, which is `useStorage('cache')`, and returns how many
|
||||
* it removed. Other cache groups (cached functions, handlers) stay.
|
||||
*/
|
||||
export async function purgeSiteRouteCache(storage: SitePurgeStorage): Promise<number> {
|
||||
const keys = await storage.getKeys(SITE_ROUTE_CACHE_BASE)
|
||||
let removed = 0
|
||||
for (const key of keys) {
|
||||
if (!key.startsWith(SITE_ROUTE_CACHE_BASE + ':')) continue
|
||||
await storage.removeItem(key)
|
||||
removed++
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
export interface SitePurgeEvent {
|
||||
kind: string
|
||||
slug: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the kind and slug of a purge request body for the log line: kind is
|
||||
* kept when it is lower-case letters and underscores up to 40 characters
|
||||
* (faq, menu, menu_entry, post, category, ...), else it is unknown; the slug
|
||||
* loses control characters and is cut to 200 characters.
|
||||
*/
|
||||
export function parseSitePurgeEvent(body: unknown): SitePurgeEvent {
|
||||
if (body === null || typeof body !== 'object' || Array.isArray(body)) return { kind: 'unknown', slug: '' }
|
||||
const raw = body as Record<string, unknown>
|
||||
const kind = typeof raw.kind === 'string' && KIND_PATTERN.test(raw.kind) ? raw.kind : 'unknown'
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const slug = typeof raw.slug === 'string' ? raw.slug.replace(/[\u0000-\u001f\u007f-\u009f]/g, '').slice(0, MAX_SLUG_LENGTH) : ''
|
||||
return { kind, slug }
|
||||
}
|
||||
@@ -144,7 +144,13 @@ async function main() {
|
||||
let nuxt = null
|
||||
const failures = /** @type {string[]} */ ([])
|
||||
try {
|
||||
nuxt = await startNuxtServer({ outputDir: args.output, env: { [API_ENV]: `${api.url}${API_PREFIX}`, ...args.env } })
|
||||
// vue-seo-layer resolves every page through the SEO API; the fixture API
|
||||
// has none and answers 404, so the layer renders without a CMS answer
|
||||
// (fail open) and never reaches a backend on the default port.
|
||||
nuxt = await startNuxtServer({
|
||||
outputDir: args.output,
|
||||
env: { [API_ENV]: `${api.url}${API_PREFIX}`, NUXT_SEO_API_BASE: `${api.url}/_seo/api/v1`, ...args.env },
|
||||
})
|
||||
for (const line of lines) {
|
||||
let check
|
||||
try {
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
// Shared site cache purge: route authorization, the route-cache purge, the
|
||||
// purge body parsing and the byte-identity of the files every layer ships.
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import {
|
||||
MIN_SITE_PURGE_TOKEN_LENGTH,
|
||||
SITE_ROUTE_CACHE_BASE,
|
||||
authorizeSitePurge,
|
||||
parseSitePurgeEvent,
|
||||
purgeSiteRouteCache,
|
||||
} from '../server/lib/siteCachePurge.ts'
|
||||
|
||||
// The sha256 of the two shared files. vue-journal-layer, vue-menu-layer and
|
||||
// vue-faq-layer ship them byte for byte; a change lands in all three with
|
||||
// new values here.
|
||||
const SHARED_LIB_SHA256 = '7c34121ae06cf011b30a1dc5d787a94657b71b8768bed3a200be0f41566001b8'
|
||||
const SHARED_ROUTE_SHA256 = 'c459012280cdf59551e57f0440fcaeb24566a5ba6e3dccf31133e67a683f1c06'
|
||||
|
||||
const TOKEN = 'a'.repeat(MIN_SITE_PURGE_TOKEN_LENGTH)
|
||||
|
||||
function sha256(relative: string): string {
|
||||
return createHash('sha256').update(readFileSync(new URL(relative, import.meta.url))).digest('hex')
|
||||
}
|
||||
|
||||
test('sitePurge: the shared files match the pinned hashes', () => {
|
||||
assert.equal(sha256('../server/lib/siteCachePurge.ts'), SHARED_LIB_SHA256, 'server/lib/siteCachePurge.ts')
|
||||
assert.equal(sha256('../server/api/__site__/cache/purge.post.ts'), SHARED_ROUTE_SHA256, 'server/api/__site__/cache/purge.post.ts')
|
||||
})
|
||||
|
||||
test('sitePurge: a missing or short token disables the route', () => {
|
||||
for (const token of [undefined, null, '', 'short', 'b'.repeat(MIN_SITE_PURGE_TOKEN_LENGTH - 1), 12345678901234567890, {}]) {
|
||||
assert.equal(authorizeSitePurge({ token, authorization: `Bearer ${String(token)}`, proxied: false }), 'disabled', `token ${JSON.stringify(token)}`)
|
||||
}
|
||||
assert.equal(authorizeSitePurge({ token: '', authorization: 'Bearer x', proxied: true }), 'disabled')
|
||||
assert.equal(MIN_SITE_PURGE_TOKEN_LENGTH, 32)
|
||||
})
|
||||
|
||||
test('sitePurge: a proxied request is refused before the token is checked', () => {
|
||||
assert.equal(authorizeSitePurge({ token: TOKEN, authorization: `Bearer ${TOKEN}`, proxied: true }), 'proxied')
|
||||
assert.equal(authorizeSitePurge({ token: TOKEN, authorization: null, proxied: true }), 'proxied')
|
||||
})
|
||||
|
||||
test('sitePurge: authorize ok with the token and unauthorized otherwise', () => {
|
||||
assert.equal(authorizeSitePurge({ token: TOKEN, authorization: `Bearer ${TOKEN}`, proxied: false }), 'ok')
|
||||
assert.equal(authorizeSitePurge({ token: TOKEN, authorization: `bearer ${TOKEN}`, proxied: false }), 'ok')
|
||||
for (const authorization of [undefined, null, '', 'Bearer', `Basic ${TOKEN}`, `${TOKEN}`, `Bearer ${TOKEN}x`, `Bearer ${TOKEN.slice(1)}`, `Bearer ${TOKEN} extra`, 'Bearer ' + 'b'.repeat(32)]) {
|
||||
assert.equal(authorizeSitePurge({ token: TOKEN, authorization, proxied: false }), 'unauthorized', `authorization ${JSON.stringify(authorization)}`)
|
||||
}
|
||||
})
|
||||
|
||||
/** A fake unstorage prefix storage over a plain key list. */
|
||||
function fakeStorage(keys: string[]) {
|
||||
const live = new Set(keys)
|
||||
const removed: string[] = []
|
||||
return {
|
||||
removed,
|
||||
live,
|
||||
async getKeys(base?: string) {
|
||||
return [...live].filter((k) => !base || k === base || k.startsWith(base + ':'))
|
||||
},
|
||||
async removeItem(key: string) {
|
||||
live.delete(key)
|
||||
removed.push(key)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
test('sitePurge: purge removes only nitro:routes: keys', async () => {
|
||||
const routes = [
|
||||
`${SITE_ROUTE_CACHE_BASE}:_:faq.abc123.json`,
|
||||
`${SITE_ROUTE_CACHE_BASE}:_:faq_payloadjson.def456.json`,
|
||||
`${SITE_ROUTE_CACHE_BASE}:_:index.0a1b2c.json`,
|
||||
]
|
||||
const others = ['nitro:functions:_:faq.1.json', 'nitro:handlers:_:api.2.json', 'nitro:routesx:_:near.3.json', 'other:routes:_:x.json']
|
||||
const storage = fakeStorage([...routes, ...others])
|
||||
assert.equal(SITE_ROUTE_CACHE_BASE, 'nitro:routes')
|
||||
assert.equal(await purgeSiteRouteCache(storage), routes.length)
|
||||
assert.deepEqual([...storage.removed].sort(), [...routes].sort())
|
||||
assert.deepEqual([...storage.live].sort(), [...others].sort())
|
||||
assert.equal(await purgeSiteRouteCache(storage), 0)
|
||||
})
|
||||
|
||||
test('sitePurge: purge event kinds and slugs', () => {
|
||||
for (const kind of ['faq', 'menu', 'menu_entry', 'post', 'category']) {
|
||||
assert.deepEqual(parseSitePurgeEvent({ kind, slug: 'x' }), { kind, slug: 'x' })
|
||||
}
|
||||
for (const kind of ['FAQ', 'menu-entry', 'a'.repeat(41), '', 7, null]) {
|
||||
assert.deepEqual(parseSitePurgeEvent({ kind, slug: 'x' }), { kind: 'unknown', slug: 'x' }, JSON.stringify(kind))
|
||||
}
|
||||
assert.deepEqual(parseSitePurgeEvent({ kind: 'faq', slug: 'a\nb\r\tc\u0000d\u007fe\u0085f' }), { kind: 'faq', slug: 'abcdef' })
|
||||
assert.equal(parseSitePurgeEvent({ kind: 'faq', slug: 'x'.repeat(500) }).slug.length, 200)
|
||||
for (const body of [null, undefined, 'faq', 42, ['faq', 'x']]) {
|
||||
assert.deepEqual(parseSitePurgeEvent(body), { kind: 'unknown', slug: '' }, JSON.stringify(body))
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user