Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a09236360b | ||
|
|
9cc7f91318 | ||
|
|
5e5950ac50 | ||
|
|
7d952fa6de | ||
|
|
f7ec4fe56f | ||
|
|
cb321e9437 | ||
|
|
1f23340a2f | ||
|
|
a229f6a6c5 | ||
|
|
96b6bc093e | ||
|
|
e26f22e4f3 | ||
| 321bc90ddf |
@@ -8,5 +8,8 @@
|
||||
</template>
|
||||
</FaqBlock>
|
||||
<FaqBlock slug="empty" />
|
||||
<aside class="playground-widget">
|
||||
<FaqBlock slug="acme" :heading-level="0" :show-description="false" :schema="false" />
|
||||
</aside>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
@@ -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 },
|
||||
})
|
||||
|
||||
36
README.md
36
README.md
@@ -3,21 +3,28 @@
|
||||
A Nuxt 4 layer that renders a FAQ of the SummerCMS FAQ plugin as native `<details>` items.
|
||||
|
||||
```bash
|
||||
pnpm add git+https://git.golem15.com/golem15/vue-faq-layer.git#v0.1.0
|
||||
pnpm add git+https://git.golem15.com/golem15/vue-seo-layer.git#v0.2.0 git+https://git.golem15.com/golem15/vue-faq-layer.git#v0.5.1
|
||||
```
|
||||
|
||||
vue-seo-layer is a peer dependency (`^0.2.0`): the app lists it next to the layer, pinned to a tag as above, and needs no `extends` entry for it (the layer extends it). The layer cannot bring it itself: pnpm refuses a git dependency inside another package (`blockExoticSubdeps`, on by default).
|
||||
|
||||
## Overview
|
||||
|
||||
The layer is consumed with `extends: ['vue-faq-layer']`. It reads one route of the FAQ plugin's anonymous API, `GET /_faq/api/v1/faqs/{slug}`, during SSR and renders it with `<FaqBlock slug="…">`. Editors manage FAQs in the SummerCMS admin; the site needs no FAQ 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-faq-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 `--faq-*` CSS custom properties, which an app overrides, and every component can be replaced by a file of the same name in the app. There is deliberately no component named `FaqSection`, so an app's own `FaqSection.vue` can wrap `FaqBlock`.
|
||||
|
||||
## Features
|
||||
|
||||
- **`FaqBlock`**: a `<section>` labelled by its heading (the FAQ `name`), the description and a `FaqList`. Fetched during SSR through `useAsyncData` (key `faq:<slug>:<locale>`).
|
||||
- **`FaqList`** and **`FaqItem`**: one native `<details>` per question in API order, the question in `<summary>` with a CSS chevron (no animation under `prefers-reduced-motion`), the answer below. All items start closed (`openFirst` opens the first). With `exclusive`, the items share a `name`, so the browser keeps one open at a time.
|
||||
- **Structured data**: each `FaqBlock` adds a `FAQPage` node (`@id` `<page URL>#faq`, one `Question` with its `acceptedAnswer` as plain text per item) to the page's one JSON-LD `@graph` through vue-seo-layer's `useSchemaNode`, so the page keeps a single JSON-LD script. Blocks on the same page share the `@id`, so the first block's questions are the node; set `:schema="false"` on the others. `faqSchema(faq, pageUrl)` and `faqAnswerText(html)` in `app/utils/faqSchema.ts` build it.
|
||||
- **Widget titles**: `headingLevel: 0` renders the title as `<p class="faq-block__title">` (same id, still labelling the section) for sidebar and footer widgets, which are styled text rather than content headings; content blocks keep `h2` to `h4`.
|
||||
- **HTML**: the description and answers are HTML the FAQ API sanitizes on output; the layer renders them with `v-html` and adds no client sanitizer. Questions are plain text.
|
||||
- **Empty states**: a missing FAQ (404), a failed request or a FAQ without items renders nothing, or the `#fallback` slot. There is never an empty heading.
|
||||
- **Site cache purge**: vue-seo-layer's shared route `POST /api/__site__/cache/purge` drops the site's route cache when the backend saves a FAQ (or a menu 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
|
||||
@@ -42,10 +49,11 @@ export default defineNuxtConfig({
|
||||
|---|---|---|
|
||||
| `slug` | required | The FAQ slug. |
|
||||
| `locale` | the API's default locale | Sent as `?locale=`. |
|
||||
| `headingLevel` | `2` | Heading element, `2` to `4`. |
|
||||
| `headingLevel` | `2` | Heading element, `2` to `4`; `0` renders a styled `<p class="faq-block__title">` for widgets. |
|
||||
| `showHeading` | `true` | `false` hides the heading (the section is then labelled with `aria-label`), for an app that shows its own title. |
|
||||
| `showDescription` | `true` | `false` hides the description. |
|
||||
| `exclusive` | `false` | One item open at a time. |
|
||||
| `schema` | `true` | Add the `FAQPage` node to the page's JSON-LD graph; `false` on every block but one when a page shows several FAQs. |
|
||||
|
||||
`FaqList` takes `items` (`{ question, answer }[]`), `exclusive` and `openFirst`; `FaqItem` takes `question`, `answer`, `name` and `open`.
|
||||
|
||||
@@ -58,8 +66,28 @@ export default defineNuxtConfig({
|
||||
| `runtimeConfig.faq.apiBase` | `http://127.0.0.1:8096/_faq/api/v1` | `NUXT_FAQ_API_BASE` (server only, used for SSR) |
|
||||
| `runtimeConfig.public.faq.apiBase` | `/_faq/api/v1` | `NUXT_PUBLIC_FAQ_API_BASE` (browser, same origin) |
|
||||
|
||||
|
||||
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 `/_faq/` to the SummerCMS binary.
|
||||
|
||||
## Site cache purge
|
||||
|
||||
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.
|
||||
|
||||
### Upgrading from 0.4
|
||||
|
||||
- **List vue-seo-layer in the app** (from 0.5.1): `"vue-seo-layer": "git+https://git.golem15.com/golem15/vue-seo-layer.git#v0.2.0"` next to `vue-faq-layer#v0.5.1`. It is a peer dependency of the layer (`^0.2.0`). 0.5.0 listed it as its own git dependency, which pnpm refuses by default (`ERR_PNPM_EXOTIC_SUBDEP`), so use 0.5.1.
|
||||
- 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.
|
||||
- **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
|
||||
|
||||
The layer expects the FAQ plugin's public API:
|
||||
@@ -103,11 +131,11 @@ pnpm dev # the playground in .playground/
|
||||
|
||||
## Releasing
|
||||
|
||||
Commit, tag `vX.Y.Z` (annotated) and push the tag; apps pin `#vX.Y.Z` in their dependency.
|
||||
Commit, tag `vX.Y.Z` (annotated) and push the tag; apps pin `#vX.Y.Z` in their dependency. vue-seo-layer is a peer dependency (`^0.2.0`) and a development dependency pinned to its tag, never a regular dependency (pnpm refuses a git dependency inside another package); vue-journal-layer's README, "Releasing", has the steps and the `--check-links` guard.
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
pnpm test # node --test: the client
|
||||
pnpm test # node --test: the client and the FAQPage node
|
||||
pnpm build:playground && pnpm smoke:ssr # SSR of the playground against testing/fixture-api.mjs
|
||||
```
|
||||
|
||||
@@ -3,7 +3,16 @@
|
||||
// heading (the FAQ name), the server-sanitized description and the items.
|
||||
// A missing FAQ (404), a failed request or a FAQ without items renders
|
||||
// nothing, or the #fallback slot; never an empty heading.
|
||||
//
|
||||
// headingLevel 0 renders the title as <p class="faq-block__title"> (same id,
|
||||
// still the section's aria-labelledby) for sidebar and footer widgets, which
|
||||
// are styled text, not content headings (D-52, UI-SPEC L6). With schema (the
|
||||
// default) the block adds a FAQPage node to the page's one JSON-LD graph
|
||||
// (vue-seo-layer's useSchemaNode, D-43); set schema false on every block but
|
||||
// one when a page shows several FAQs. The answers are in the HTML inside
|
||||
// <details>, so crawlers read them without opening an item.
|
||||
import { computed, useId } from 'vue'
|
||||
import { faqSchema } from '../../utils/faqSchema.ts'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -11,14 +20,16 @@ const props = withDefaults(
|
||||
slug: string
|
||||
/** ?locale= for the API; the API's default locale when unset. */
|
||||
locale?: string
|
||||
/** Level of the heading element, 2 to 4. */
|
||||
headingLevel?: 2 | 3 | 4
|
||||
/** Level of the heading element, 2 to 4; 0 renders a styled <p> (widgets). */
|
||||
headingLevel?: 0 | 2 | 3 | 4
|
||||
showHeading?: boolean
|
||||
showDescription?: boolean
|
||||
/** Only one item open at a time (native exclusive details). */
|
||||
exclusive?: boolean
|
||||
/** Add the FAQPage node to the page's JSON-LD graph. */
|
||||
schema?: boolean
|
||||
}>(),
|
||||
{ locale: undefined, headingLevel: 2, showHeading: true, showDescription: true, exclusive: false },
|
||||
{ locale: undefined, headingLevel: 2, showHeading: true, showDescription: true, exclusive: false, schema: true },
|
||||
)
|
||||
|
||||
defineSlots<{ fallback?(): unknown }>()
|
||||
@@ -29,9 +40,20 @@ const { data: faq, error } = await useFaq(
|
||||
)
|
||||
|
||||
const items = computed(() => (error.value ? [] : (faq.value?.content ?? [])))
|
||||
const headingTag = computed(() => `h${Math.min(4, Math.max(2, Math.trunc(Number(props.headingLevel)) || 2))}`)
|
||||
const headingTag = computed(() => {
|
||||
const level = Math.trunc(Number(props.headingLevel))
|
||||
return level === 0 ? 'p' : `h${Math.min(4, Math.max(2, level || 2))}`
|
||||
})
|
||||
const description = computed(() => (props.showDescription ? (faq.value?.description ?? '').trim() : ''))
|
||||
const headingId = `${useId()}-title`
|
||||
|
||||
const answer = useSeoAnswer()
|
||||
const request = useRequestURL()
|
||||
useSchemaNode(() => {
|
||||
if (!props.schema || error.value || items.value.length === 0) return null
|
||||
const url = answer.value?.action === 'ok' && /^https?:\/\//i.test(answer.value.url) ? answer.value.url : `${request.origin}${request.pathname}`
|
||||
return faqSchema(faq.value, url)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
53
app/utils/faqSchema.ts
Normal file
53
app/utils/faqSchema.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
// The schema.org FAQPage node of a FAQ (D-43): one Question per item with its
|
||||
// acceptedAnswer as plain text (the answer's HTML without markup), @id
|
||||
// <page URL>#faq. FaqBlock adds it to the page's one JSON-LD @graph through
|
||||
// vue-seo-layer's useSchemaNode (prop schema, default true), so a page keeps
|
||||
// one JSON-LD script; a second block on the page with the same @id merges
|
||||
// into the first (the first one's questions stay). Pure, so `node --test`
|
||||
// drives it.
|
||||
|
||||
import type { Faq } from './types.ts'
|
||||
|
||||
export interface FaqPageNode {
|
||||
'@type': 'FAQPage'
|
||||
'@id': string
|
||||
url: string
|
||||
mainEntity: Array<{ '@type': 'Question'; name: string; acceptedAnswer: { '@type': 'Answer'; text: string } }>
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
const NAMED: Record<string, string> = { amp: '&', lt: '<', gt: '>', quot: '"', apos: "'", nbsp: ' ' }
|
||||
|
||||
/** Plain text of an answer: tags dropped (script and style with their content), entities decoded, whitespace collapsed. */
|
||||
export function faqAnswerText(html: string | null | undefined): string {
|
||||
if (!html) return ''
|
||||
return html
|
||||
.replace(/<(script|style)\b[\s\S]*?<\/\1\s*>/gi, ' ')
|
||||
.replace(/<[^>]*>/g, ' ')
|
||||
.replace(/&(#x[0-9a-f]+|#[0-9]+|[a-z]+);/gi, (m, code: string) => {
|
||||
if (code[0] === '#') {
|
||||
const n = code[1] === 'x' || code[1] === 'X' ? Number.parseInt(code.slice(2), 16) : Number.parseInt(code.slice(1), 10)
|
||||
return Number.isFinite(n) && n > 0 && n <= 0x10ffff ? String.fromCodePoint(n) : m
|
||||
}
|
||||
return NAMED[code.toLowerCase()] ?? m
|
||||
})
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
}
|
||||
|
||||
/** The FAQPage node of `faq` on the page at `pageUrl`, or null without questions. */
|
||||
export function faqSchema(faq: Faq | null | undefined, pageUrl: string): FaqPageNode | null {
|
||||
const items = (faq?.content ?? []).filter((item) => item && typeof item.question === 'string' && item.question.trim() !== '')
|
||||
if (items.length === 0) return null
|
||||
const url = pageUrl.split('#')[0] ?? pageUrl
|
||||
return {
|
||||
'@type': 'FAQPage',
|
||||
'@id': `${url}#faq`,
|
||||
url,
|
||||
mainEntity: items.map((item) => ({
|
||||
'@type': 'Question',
|
||||
name: item.question.trim(),
|
||||
acceptedAnswer: { '@type': 'Answer', text: faqAnswerText(item.answer) },
|
||||
})),
|
||||
}
|
||||
}
|
||||
@@ -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/faq-tokens.css`, `${currentDir}app/assets/css/faq-base.css`],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vue-faq-layer",
|
||||
"version": "0.1.0",
|
||||
"version": "0.5.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./nuxt.config.ts",
|
||||
@@ -15,11 +15,13 @@
|
||||
"node": ">=22.18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"nuxt": "4.4.8"
|
||||
"nuxt": "4.4.8",
|
||||
"vue-seo-layer": "^0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nuxt": "4.4.8",
|
||||
"typescript": "5.9.3",
|
||||
"vue": "3.5.35"
|
||||
"vue": "3.5.35",
|
||||
"vue-seo-layer": "git+https://git.golem15.com/golem15/vue-seo-layer.git#v0.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
205
pnpm-lock.yaml
generated
205
pnpm-lock.yaml
generated
@@ -10,13 +10,16 @@ importers:
|
||||
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
|
||||
vue:
|
||||
specifier: 3.5.35
|
||||
version: 3.5.35(typescript@5.9.3)
|
||||
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)))
|
||||
|
||||
packages:
|
||||
|
||||
@@ -3256,6 +3259,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 +3360,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 +3400,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 +3444,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 +3483,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 +3510,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 +3518,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 +3700,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 +3743,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 +3751,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 +3812,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 +3838,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 +3940,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 +3957,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 +3966,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 +4036,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 +4054,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 +4063,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 +4478,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 +4497,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 +4527,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 +5010,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 +5301,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 +5341,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 +5554,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 +5564,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 +5588,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 +5611,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 +5621,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 +5702,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 +6289,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 +6313,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 +6336,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 +6613,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 +6625,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 +6778,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
|
||||
|
||||
@@ -12,3 +12,11 @@
|
||||
200 / contains=<p class="playground-fallback">No FAQ</p>
|
||||
200 / notcontains=>Empty</h
|
||||
200 / notcontains=FaqSection
|
||||
# A widget block (headingLevel 0) renders its title as a styled p that still
|
||||
# labels the section (UI-SPEC L6); the page keeps one heading per content block.
|
||||
200 / contains=<p id= contains=class="faq-block__title">Acme questions</p>
|
||||
200 / count=<h2:1 count=<h3:1
|
||||
# One JSON-LD script with one FAQPage node (two blocks with schema share its
|
||||
# @id; the widget has schema off); the answers are in the HTML inside details.
|
||||
200 / count=application/ld+json:1 count="@type":"FAQPage":1 contains="acceptedAnswer":{"@type":"Answer","text":"Two days. Poland Europe"}
|
||||
200 / contains=<details class="faq-item" name= contains=<p>Two days.</p><ul><li>Poland</li><li>Europe</li></ul>
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
// node testing/smoke-ssr.mjs --output <.output dir> --fixtures <dir> --expect <file> [--env KEY=VALUE]...
|
||||
//
|
||||
// Expectation grammar, one check per line ('#' starts a comment line):
|
||||
// STATUS PATH [contains=TEXT] [notcontains=TEXT] [type=PREFIX]
|
||||
// A value runs until the next ` contains=`, ` notcontains=`, ` type=` or the
|
||||
// end of the line, so it may contain spaces. Requests are plain GETs that do
|
||||
// STATUS PATH [contains=TEXT] [notcontains=TEXT] [count=TEXT:N] [type=PREFIX]
|
||||
// A value runs until the next ` contains=`, ` notcontains=`, ` count=`,
|
||||
// ` type=` or the end of the line, so it may contain spaces. Every option
|
||||
// may repeat on a line and every occurrence applies; count= wants exactly N
|
||||
// occurrences of TEXT in the body (N after the last colon), as in
|
||||
// vue-seo-layer's testing/smoke-ssr.mjs. Requests are plain GETs that do
|
||||
// not follow redirects and carry `X-Forwarded-For: 203.0.113.7`; afterwards
|
||||
// at least one fixture API request must have carried that address (the SSR
|
||||
// fetches forward the visitor's x-forwarded-for).
|
||||
@@ -50,17 +53,19 @@ function parseArgs(argv) {
|
||||
return /** @type {{ output: string, fixtures: string, expect: string, env: Record<string, string> }} */ (out)
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{ status: number, path: string, options: Array<{ key: string, value: string }> }} ExpectCheck
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} line
|
||||
* @returns {{ status: number, path: string, contains?: string, notcontains?: string, type?: string }}
|
||||
* @returns {ExpectCheck}
|
||||
*/
|
||||
export function parseExpectLine(line) {
|
||||
const m = /^(\d{3})\s+(\S+)(.*)$/.exec(line.trim())
|
||||
if (!m) throw new Error('expected "STATUS PATH [options]"')
|
||||
/** @type {{ status: number, path: string, contains?: string, notcontains?: string, type?: string }} */
|
||||
const check = { status: Number(m[1]), path: m[2] }
|
||||
const rest = m[3]
|
||||
const re = /\s(contains|notcontains|type)=/g
|
||||
const re = /\s(contains|notcontains|count|type)=/g
|
||||
/** @type {Array<{ key: string, start: number, valueStart: number }>} */
|
||||
const marks = []
|
||||
let hit
|
||||
@@ -69,13 +74,42 @@ export function parseExpectLine(line) {
|
||||
if (marks.length > 0 && rest.slice(0, marks[0].start).trim() !== '') {
|
||||
throw new Error(`unknown option text "${rest.slice(0, marks[0].start).trim()}"`)
|
||||
}
|
||||
/** @type {Record<string, string>} */
|
||||
const options = {}
|
||||
marks.forEach((mark, i) => {
|
||||
const options = marks.map((mark, i) => {
|
||||
const end = i + 1 < marks.length ? marks[i + 1].start : rest.length
|
||||
options[mark.key] = rest.slice(mark.valueStart, end).trim()
|
||||
return { key: mark.key, value: rest.slice(mark.valueStart, end).trim() }
|
||||
})
|
||||
return { ...check, ...options }
|
||||
return { status: Number(m[1]), path: m[2], options }
|
||||
}
|
||||
|
||||
/** @param {string} body @param {string} text */
|
||||
function occurrences(body, text) {
|
||||
if (text === '') return 0
|
||||
let n = 0
|
||||
for (let at = body.indexOf(text); at !== -1; at = body.indexOf(text, at + text.length)) n++
|
||||
return n
|
||||
}
|
||||
|
||||
/**
|
||||
* The failures of one response against its check.
|
||||
* @param {ExpectCheck} check @param {{ status: number, type: string, body: string }} res
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export function checkResponse(check, res) {
|
||||
if (res.status !== check.status) return [`status ${res.status}, want ${check.status}`]
|
||||
/** @type {string[]} */
|
||||
const out = []
|
||||
for (const { key, value } of check.options) {
|
||||
if (key === 'contains' && !res.body.includes(value)) out.push(`body lacks "${value}"`)
|
||||
else if (key === 'notcontains' && res.body.includes(value)) out.push(`body contains "${value}"`)
|
||||
else if (key === 'type' && !res.type.startsWith(value)) out.push(`content-type "${res.type}", want prefix "${value}"`)
|
||||
else if (key === 'count') {
|
||||
const colon = value.lastIndexOf(':')
|
||||
const want = colon > 0 ? Number(value.slice(colon + 1)) : NaN
|
||||
if (!Number.isInteger(want)) out.push(`count=${value}: want TEXT:N`)
|
||||
else if (occurrences(res.body, value.slice(0, colon)) !== want) out.push(`body has ${occurrences(res.body, value.slice(0, colon))} of "${value.slice(0, colon)}", want ${want}`)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/** @returns {Promise<number>} */
|
||||
@@ -144,7 +178,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 {
|
||||
@@ -166,10 +206,7 @@ async function main() {
|
||||
}
|
||||
const body = await res.text()
|
||||
const type = res.headers.get('content-type') ?? ''
|
||||
if (res.status !== check.status) failures.push(`${line}: status ${res.status}, want ${check.status}`)
|
||||
else if (check.contains !== undefined && !body.includes(check.contains)) failures.push(`${line}: body lacks "${check.contains}"`)
|
||||
else if (check.notcontains !== undefined && body.includes(check.notcontains)) failures.push(`${line}: body contains "${check.notcontains}"`)
|
||||
else if (check.type !== undefined && !type.startsWith(check.type)) failures.push(`${line}: content-type "${type}", want prefix "${check.type}"`)
|
||||
for (const f of checkResponse(check, { status: res.status, type, body })) failures.push(`${line}: ${f}`)
|
||||
}
|
||||
if (!api.requests.some((r) => (r.xff ?? '').includes(VISITOR_IP))) {
|
||||
failures.push('x-forwarded-for: the SSR fetches did not forward the visitor address')
|
||||
|
||||
120
tests/block.test.ts
Normal file
120
tests/block.test.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
// FaqBlock (D-52, UI-SPEC L6) rendered on the server: the SFC is compiled
|
||||
// with vue/compiler-sfc and rendered with vue/server-renderer, with the
|
||||
// Nuxt composables it calls (useFaq, useSeoAnswer, useRequestURL,
|
||||
// useSchemaNode) stubbed on globalThis and FaqList stubbed as a component.
|
||||
// headingLevel picks the title's element (0 a <p>), the section stays
|
||||
// labelled by the title, and the FAQPage node it hands to the page graph
|
||||
// carries the answers as plain text.
|
||||
import assert from 'node:assert/strict'
|
||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { after, test } from 'node:test'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import { compileScript, parse } from 'vue/compiler-sfc'
|
||||
import { createSSRApp, h, ref, type Component } from 'vue'
|
||||
import { renderToString } from 'vue/server-renderer'
|
||||
|
||||
const root = new URL('..', import.meta.url)
|
||||
const dir = mkdtempSync(join(tmpdir(), 'faq-block-'))
|
||||
after(() => rmSync(dir, { recursive: true, force: true }))
|
||||
|
||||
async function loadBlock(): Promise<Component> {
|
||||
const source = readFileSync(new URL('app/components/Faq/Block.vue', root), 'utf8')
|
||||
const { descriptor } = parse(source, { filename: 'Block.vue' })
|
||||
const script = compileScript(descriptor, { id: 'faq-block', inlineTemplate: true, templateOptions: { ssr: true } })
|
||||
const code = script.content
|
||||
.replaceAll(`from 'vue'`, `from '${import.meta.resolve('vue')}'`)
|
||||
.replaceAll(`from "vue"`, `from '${import.meta.resolve('vue')}'`)
|
||||
.replaceAll(`from 'vue/server-renderer'`, `from '${import.meta.resolve('vue/server-renderer')}'`)
|
||||
.replaceAll(`from "vue/server-renderer"`, `from '${import.meta.resolve('vue/server-renderer')}'`)
|
||||
.replaceAll(`'../../utils/faqSchema.ts'`, `'${new URL('app/utils/faqSchema.ts', root).href}'`)
|
||||
const file = join(dir, 'Block.ts')
|
||||
writeFileSync(file, code)
|
||||
return (await import(pathToFileURL(file).href)).default as Component
|
||||
}
|
||||
|
||||
const faq = {
|
||||
id: 1,
|
||||
slug: 'grzyby',
|
||||
name: 'Pytania o grzyby',
|
||||
description: '<p>Najczęstsze pytania</p>',
|
||||
content: [
|
||||
{ question: ' Czy chaga jest bezpieczna? ', answer: '<p>Tak, <strong>w umiarkowanych</strong> dawkach.</p><script>x()</script>' },
|
||||
{ question: 'Jak parzyć?', answer: '<ul><li>Woda 80°C & 5 min</li></ul>' },
|
||||
],
|
||||
}
|
||||
|
||||
const g = globalThis as Record<string, unknown>
|
||||
let nodes: unknown[] = []
|
||||
|
||||
function stub(data: unknown, error: unknown = null) {
|
||||
nodes = []
|
||||
Object.assign(g, {
|
||||
useFaq: async () => ({ data: ref(data), error: ref(error) }),
|
||||
useSeoAnswer: () => ref({ action: 'ok', url: 'https://example.test/faq#top' }),
|
||||
useRequestURL: () => new URL('https://fallback.test/page'),
|
||||
useSchemaNode: (fn: () => unknown) => nodes.push(fn()),
|
||||
})
|
||||
}
|
||||
|
||||
const FaqList: Component = { props: ['items', 'exclusive'], setup: (p: { items: unknown[] }) => () => h('div', { class: 'faq-list' }, String(p.items.length)) }
|
||||
const Block = await loadBlock()
|
||||
|
||||
async function render(props: Record<string, unknown>): Promise<string> {
|
||||
const app = createSSRApp({ render: () => h(Block, { slug: 'grzyby', ...props }, { fallback: () => h('em', 'fallback') }) })
|
||||
app.component('FaqList', FaqList)
|
||||
return renderToString(app)
|
||||
}
|
||||
|
||||
test('headingLevel 2 to 4 renders that heading, out-of-range levels are clamped', async () => {
|
||||
stub(faq)
|
||||
for (const [level, tag] of [[2, 'h2'], [3, 'h3'], [4, 'h4'], [6, 'h4'], [1, 'h2']] as const) {
|
||||
const html = await render({ headingLevel: level })
|
||||
assert.match(html, new RegExp(`<${tag} id="([^"]+)" class="faq-block__title">Pytania o grzyby</${tag}>`), `level ${level}`)
|
||||
}
|
||||
})
|
||||
|
||||
test('headingLevel 0 renders the title as a <p> that still labels the section', async () => {
|
||||
stub(faq)
|
||||
const html = await render({ headingLevel: 0 })
|
||||
const title = html.match(/<p id="([^"]+)" class="faq-block__title">Pytania o grzyby<\/p>/)
|
||||
assert.ok(title, html)
|
||||
assert.match(html, new RegExp(`<section class="faq-block" aria-labelledby="${title![1]}"`))
|
||||
assert.doesNotMatch(html, /<h[1-6]/)
|
||||
})
|
||||
|
||||
test('without a heading the section is labelled by the FAQ name', async () => {
|
||||
stub(faq)
|
||||
const html = await render({ showHeading: false })
|
||||
assert.match(html, /<section class="faq-block" aria-label="Pytania o grzyby"/)
|
||||
assert.doesNotMatch(html, /faq-block__title/)
|
||||
})
|
||||
|
||||
test('the FAQPage node: the page URL without fragment, questions trimmed, answers as plain text', async () => {
|
||||
stub(faq)
|
||||
await render({})
|
||||
assert.equal(nodes.length, 1)
|
||||
assert.deepEqual(nodes[0], {
|
||||
'@type': 'FAQPage',
|
||||
'@id': 'https://example.test/faq#faq',
|
||||
url: 'https://example.test/faq',
|
||||
mainEntity: [
|
||||
{ '@type': 'Question', name: 'Czy chaga jest bezpieczna?', acceptedAnswer: { '@type': 'Answer', text: 'Tak, w umiarkowanych dawkach.' } },
|
||||
{ '@type': 'Question', name: 'Jak parzyć?', acceptedAnswer: { '@type': 'Answer', text: 'Woda 80°C & 5 min' } },
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
test('schema false, a missing FAQ or one without items: no node; the fallback slot instead of an empty block', async () => {
|
||||
stub(faq)
|
||||
await render({ schema: false })
|
||||
assert.deepEqual(nodes, [null])
|
||||
stub(null, new Error('404'))
|
||||
const missing = await render({})
|
||||
assert.match(missing, /<em>fallback<\/em>/)
|
||||
assert.doesNotMatch(missing, /faq-block/)
|
||||
assert.deepEqual(nodes, [null])
|
||||
stub({ ...faq, content: [] })
|
||||
assert.match(await render({}), /<em>fallback<\/em>/)
|
||||
})
|
||||
@@ -89,3 +89,73 @@ test('isNotFound reads status, statusCode and response.status', () => {
|
||||
assert.equal(isNotFound({ status: 500 }), false)
|
||||
assert.equal(isNotFound(undefined), false)
|
||||
})
|
||||
|
||||
// Plan 06 coverage: the encoded slug and locale, non-string descriptions,
|
||||
// and useFaqApi picking the base. The composable reads Nuxt globals and
|
||||
// import.meta.server, so the test loads its source with import.meta.server
|
||||
// replaced by a flag, next to the original (its relative import resolves),
|
||||
// with stubbed globals.
|
||||
import { readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||
|
||||
test('the slug is encoded, the locale passed as it is', async () => {
|
||||
const { fetcher, calls } = fakeFetcher(() => FAQ)
|
||||
await createFaqClient(fetcher).getFaq('a b?c#d', 'pt-BR')
|
||||
assert.deepEqual(calls, [{ path: '/faqs/a%20b%3Fc%23d', query: { locale: 'pt-BR' } }])
|
||||
// An empty locale is no locale.
|
||||
await createFaqClient(fetcher).getFaq('x', '')
|
||||
assert.deepEqual(calls[1], { path: '/faqs/x' })
|
||||
})
|
||||
|
||||
test('a description that is not a string is null; content that is not a list is empty', async () => {
|
||||
const faq = await createFaqClient(async () => ({ data: { ...FAQ.data, description: 5, content: 'x' } })).getFaq('x')
|
||||
assert.equal(faq?.description, null)
|
||||
assert.deepEqual(faq?.content, [])
|
||||
})
|
||||
|
||||
type FetchCall = { path: string; opts: { baseURL: string; query?: Record<string, string>; headers?: Record<string, string> } }
|
||||
|
||||
let loads = 0
|
||||
|
||||
async function useFaqApiAs(server: boolean, request: { context?: Record<string, unknown>; xff?: string; peer?: string } = {}) {
|
||||
const calls: FetchCall[] = []
|
||||
const g = globalThis as Record<string, unknown>
|
||||
g.__faqLayerServer = server
|
||||
g.useRuntimeConfig = () => ({ faq: { apiBase: 'http://127.0.0.1:8096/_faq/api/v1' }, public: { faq: { apiBase: '/_faq/api/v1' } } })
|
||||
g.useRequestEvent = () => ({ context: request.context ?? {}, node: { req: { socket: { remoteAddress: request.peer } } } })
|
||||
g.useRequestHeaders = () => (request.xff === undefined ? {} : { 'x-forwarded-for': request.xff })
|
||||
g.$fetch = async (path: string, opts: FetchCall['opts']) => {
|
||||
calls.push({ path, opts })
|
||||
return FAQ
|
||||
}
|
||||
const source = join(dirname(fileURLToPath(import.meta.url)), '..', 'app', 'composables', 'useFaqApi.ts')
|
||||
const out = join(dirname(source), `.useFaqApi-${process.pid}-${++loads}.compiled.ts`)
|
||||
writeFileSync(out, readFileSync(source, 'utf8').replaceAll('import.meta.server', '(globalThis as Record<string, unknown>).__faqLayerServer'))
|
||||
try {
|
||||
const mod = (await import(pathToFileURL(out).href)) as { useFaqApi: () => ReturnType<typeof createFaqClient> }
|
||||
return { client: mod.useFaqApi(), calls }
|
||||
} finally {
|
||||
rmSync(out, { force: true })
|
||||
}
|
||||
}
|
||||
|
||||
test('the server base is used on the server, with the visitor forwarded', async () => {
|
||||
const { client, calls } = await useFaqApiAs(true, { xff: '203.0.113.7', peer: '10.0.0.2' })
|
||||
await client.getFaq('shipping', 'pl')
|
||||
assert.deepEqual(calls, [
|
||||
{ path: '/faqs/shipping', opts: { baseURL: 'http://127.0.0.1:8096/_faq/api/v1', query: { locale: 'pl' }, headers: { 'x-forwarded-for': '203.0.113.7, 10.0.0.2' } } },
|
||||
])
|
||||
const recorded = await useFaqApiAs(true, { context: { faqForwardedFor: '198.51.100.9' }, xff: '1.1.1.1', peer: '10.0.0.2' })
|
||||
await recorded.client.getFaq('shipping')
|
||||
assert.deepEqual(recorded.calls[0]!.opts.headers, { 'x-forwarded-for': '198.51.100.9' })
|
||||
const bare = await useFaqApiAs(true)
|
||||
await bare.client.getFaq('shipping')
|
||||
assert.deepEqual(bare.calls[0]!.opts, { baseURL: 'http://127.0.0.1:8096/_faq/api/v1', query: undefined, headers: {} })
|
||||
})
|
||||
|
||||
test('the public base is used in the browser, without forwarded headers', async () => {
|
||||
const { client, calls } = await useFaqApiAs(false, { xff: '203.0.113.7', peer: '10.0.0.2' })
|
||||
await client.getFaq('shipping', 'en')
|
||||
assert.deepEqual(calls, [{ path: '/faqs/shipping', opts: { baseURL: '/_faq/api/v1', query: { locale: 'en' } } }])
|
||||
})
|
||||
|
||||
43
tests/faqSchema.test.ts
Normal file
43
tests/faqSchema.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
// The FAQPage node of a FAQ block (D-43): one Question per item with the
|
||||
// answer as plain text, @id <page URL>#faq.
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { faqAnswerText, faqSchema } from '../app/utils/faqSchema.ts'
|
||||
|
||||
const FAQ = {
|
||||
name: 'Acme questions',
|
||||
slug: 'acme',
|
||||
locale: 'en',
|
||||
description: '<p>About Acme.</p>',
|
||||
content: [
|
||||
{ question: 'How long does shipping take?', answer: '<p>Two days.</p><ul><li>Poland</li><li>Europe</li></ul>' },
|
||||
{ question: 'Can I return it?', answer: '<p>Yes & <a href="/returns">free</a> of charge <30 days>.</p>' },
|
||||
],
|
||||
}
|
||||
|
||||
test('faqSchema: a FAQPage with a Question and acceptedAnswer per item', () => {
|
||||
assert.deepEqual(faqSchema(FAQ, 'https://acme.test/help'), {
|
||||
'@type': 'FAQPage',
|
||||
'@id': 'https://acme.test/help#faq',
|
||||
url: 'https://acme.test/help',
|
||||
mainEntity: [
|
||||
{ '@type': 'Question', name: 'How long does shipping take?', acceptedAnswer: { '@type': 'Answer', text: 'Two days. Poland Europe' } },
|
||||
{ '@type': 'Question', name: 'Can I return it?', acceptedAnswer: { '@type': 'Answer', text: 'Yes & free of charge <30 days>.' } },
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
test('faqSchema: no FAQ, no items or blank questions give null or skip the item', () => {
|
||||
assert.equal(faqSchema(null, 'https://acme.test/'), null)
|
||||
assert.equal(faqSchema({ ...FAQ, content: [] }, 'https://acme.test/'), null)
|
||||
const node = faqSchema({ ...FAQ, content: [{ question: ' ', answer: '<p>x</p>' }, { question: 'Q', answer: '' }] }, 'https://acme.test/x#top')
|
||||
assert.equal(node?.['@id'], 'https://acme.test/x#faq')
|
||||
assert.deepEqual(node?.mainEntity, [{ '@type': 'Question', name: 'Q', acceptedAnswer: { '@type': 'Answer', text: '' } }])
|
||||
})
|
||||
|
||||
test('faqAnswerText: markup stripped, entities decoded, scripts and styles dropped, whitespace collapsed', () => {
|
||||
assert.equal(faqAnswerText('<p>a</p>\n<p>b<br>c</p>'), 'a b c')
|
||||
assert.equal(faqAnswerText('<script>alert(1)</script><style>p{}</style><p>ok</p>'), 'ok')
|
||||
assert.equal(faqAnswerText('"x" 'y' A B'), '"x" \'y\' A B')
|
||||
assert.equal(faqAnswerText(null), '')
|
||||
})
|
||||
Reference in New Issue
Block a user