commit 97793b77cfa7768e4daf27a566fe9a23d959a434 Author: wgm Date: Fri Jul 17 15:07:50 2026 +0800 feat: add low-code H5 editor demo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..10f506f --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +node_modules/ +dist/ +data.json +test-results/ +playwright-report/ +.DS_Store +.env +.env.* +!.env.example +*.log diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..485437a --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +node_modules +**/dist +test-results +playwright-report +apps/server/data.json +package-lock.json diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..ca34704 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,12 @@ +{ + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "quoteProps": "as-needed", + "trailingComma": "all", + "bracketSpacing": true, + "arrowParens": "always", + "endOfLine": "lf" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..01b43cc --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# Mosaic Low-code H5 Demo + +## Run + +```bash +cd /Users/wgm/lowcode_test +npm install +npm run dev +``` + +- Editor: http://localhost:5173 +- Published H5: http://localhost:5174/?id=demo-store +- API: http://localhost:3000 + +### LAN access + +The development services listen on all network interfaces. Start them normally with `npm run dev`, then replace `localhost` with the computer's LAN IP, for example: + +- Editor: `http://192.168.7.6:5173` +- Published H5: `http://192.168.7.6:5174/?id=demo-store` +- API: `http://192.168.7.6:3000` + +The editor, iframe preview and H5 API automatically follow the hostname used in the browser. Devices must be on the same local network, and the operating-system firewall must allow Node.js connections. + +## Verify + +```bash +npm run build +npm run check:h5 +npm run test:e2e +``` + +`check:h5` builds the standalone H5 and rejects known editor/drag dependencies. The Playwright flow resets its own draft, drags in a component, publishes it, confirms draft changes do not change mobile output, republishes, and confirms the mobile page updates. + +## Material libraries + +Use the segmented switch in the editor header to change the left material palette. Switching libraries never removes blocks already on the page, so marketing and digital-benefit components can also be mixed in one Schema. + +- `营销组件`: keeps the original Banner, content, image, button, product-list and layout materials. +- `直充 / 卡密`: provides benefit product cards, redemption actions, redemption notices and benefit product lists. +- `添加业务示例组合`: inserts one of each digital-benefit component as an editable starter page. + +## Architecture + +- `apps/editor`: Vue 3 editor with material and layer panels, iframe canvas, properties, history, draft and publication actions. +- `apps/h5`: standalone H5 entry that reads only the published endpoint and keeps a session Schema cache. +- `apps/server`: Express API with JSON persistence, immutable publication snapshots and version lookup endpoints. +- `packages/page-schema`: Schema types, component whitelist and runtime helpers. +- `packages/materials`: centrally registered material labels and defaults. +- `packages/renderer`: shared safe Renderer used by preview and mobile H5. +- `packages/shared`: shared preview message contract. + +The Demo intentionally uses vertical flow layout and limited two-column composition. It does not execute Schema JavaScript, dynamic component paths, or arbitrary API definitions. Interactions accept only `http(s)` or site-relative links. + +## Component editing + +- Banner: title, subtitle, multiple images and alt text, carousel toggle, looping, autoplay interval, height, overlay, alignment and text color. +- Title: text, size, weight, alignment, padding and color. +- Text: multiline content, size, line height, alignment, padding, foreground and background colors. +- Image: URL, alt text, height, crop mode and radius. +- Button: text, sizing, radius, colors and safe link interaction. +- Product list: add/remove products, name, price, image, columns, gap, image height and colors. +- Divider: thickness, spacing, line style and color. +- Spacer: live height slider and background color. +- Two-column: independent content, gap, height, padding, alignment and per-column backgrounds. +- Benefit product card: direct-recharge/card-code mode, name, description, price, image, badge, VIP marker, action copy and brand colors. +- Redemption actions: primary/secondary action copy, visibility, radius and colors. +- Redemption notice: section label, title, multiline instructions and accent styling. +- Benefit product list: add/remove benefits, delivery mode, name, price, count display and card styling. + +## Demo boundaries + +- JSON persistence is single-process and intended for local demonstration, not concurrent production writes. +- Authentication, tenancy, asset upload/CDN conversion, collaborative editing, and database migrations are outside this Demo. +- Images use fixed dimensions and native lazy loading; a production image pipeline should generate WebP/AVIF variants. diff --git a/apps/editor/index.html b/apps/editor/index.html new file mode 100644 index 0000000..fa207c8 --- /dev/null +++ b/apps/editor/index.html @@ -0,0 +1,2 @@ +
+ diff --git a/apps/editor/package.json b/apps/editor/package.json new file mode 100644 index 0000000..7b8401b --- /dev/null +++ b/apps/editor/package.json @@ -0,0 +1,19 @@ +{ + "name": "@demo/editor", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "typecheck": "tsc --noEmit -p tsconfig.json" + }, + "dependencies": { + "vue": "^3.5.17", + "@demo/page-schema": "*", + "@demo/materials": "*", + "@demo/renderer": "*" + }, + "devDependencies": { + "vite": "^7.0.6", + "@vitejs/plugin-vue": "^6.0.0" + } +} diff --git a/apps/editor/src/Inspector.ts b/apps/editor/src/Inspector.ts new file mode 100644 index 0000000..7cf1bfd --- /dev/null +++ b/apps/editor/src/Inspector.ts @@ -0,0 +1,98 @@ +import { defineComponent, type PropType } from 'vue'; +import type { Block } from '@demo/page-schema'; +import { interactiveTypes } from '@demo/materials'; + +export const Inspector = defineComponent({ + props: { block: Object as PropType, label: String }, + emits: ['checkpoint', 'copy', 'remove'], + setup(props, { emit }) { + const items = () => + Array.isArray((props.block?.props as any)?.items) ? (props.block!.props as any).items : []; + const bannerSlides = () => + Array.isArray((props.block?.props as any)?.slides) ? (props.block!.props as any).slides : []; + const benefitItems = () => + Array.isArray((props.block?.props as any)?.items) ? (props.block!.props as any).items : []; + const addProduct = () => { + emit('checkpoint'); + items().push({ name: '新商品', price: '¥ 0', image: '' }); + }; + const removeProduct = (index: number) => { + emit('checkpoint'); + items().splice(index, 1); + }; + const addBannerSlide = () => { + emit('checkpoint'); + bannerSlides().push({ image: '', alt: '' }); + }; + const removeBannerSlide = (index: number) => { + emit('checkpoint'); + if (bannerSlides().length > 1) bannerSlides().splice(index, 1); + }; + const addBenefitProduct = () => { + emit('checkpoint'); + benefitItems().push({ name: '新权益商品', price: '¥0.01', mode: '直充' }); + }; + const removeBenefitProduct = (index: number) => { + emit('checkpoint'); + if (benefitItems().length > 1) benefitItems().splice(index, 1); + }; + const interactive = () => !!props.block && interactiveTypes.includes(props.block.type); + return { + items, + bannerSlides, + addProduct, + removeProduct, + addBannerSlide, + removeBannerSlide, + benefitItems, + addBenefitProduct, + removeBenefitProduct, + interactive, + }; + }, + template: `
+

当前组件

{{label}}

{{block.type}}
+
+

内容用户看到的信息

+ + + + + +
+
商品 {{index+1}}
+ +
+
+

分割线没有文字内容,可在“外观”中设置线型与间距。

+

间距组件用于控制区块留白,可在“外观”中设置高度。

+

分别编辑画布中的两个区域

+ + + +
权益 {{index+1}}
+
+
+

外观尺寸、颜色与排版

+ + + + + + + + + + + + + +
+

交互仅允许安全链接

仅支持 HTTPS、HTTP 或站内相对路径

+
+
选择一个组件在画布或左侧图层中选择后即可编辑
`, +}); diff --git a/apps/editor/src/device.css b/apps/editor/src/device.css new file mode 100644 index 0000000..5edc830 --- /dev/null +++ b/apps/editor/src/device.css @@ -0,0 +1,32 @@ +.phone { + transition: + width 0.2s, + height 0.2s; +} +.phone.compact { + width: 375px; + height: 667px; +} +.phone { + position: relative; +} +.phone.dragging::after { + content: '释放以添加组件'; + position: absolute; + inset: 0; + display: grid; + place-items: center; + background: #173d3acc; + color: #fff; + font: 700 14px Manrope; + z-index: 3; + border: 2px dashed #dda952; +} +.tools select { + margin: 0; + width: auto; + padding: 7px; + background: #204d49; + color: #f6f4ec; + border-color: #557570; +} diff --git a/apps/editor/src/inspector.css b/apps/editor/src/inspector.css new file mode 100644 index 0000000..146abe8 --- /dev/null +++ b/apps/editor/src/inspector.css @@ -0,0 +1,266 @@ +.shell { + grid-template-columns: 270px minmax(430px, 1fr) 360px; +} +.inspector { + padding: 0; + background: #f8faf6; +} +.property-form { + padding: 22px 20px 28px; +} +.property-title { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; + padding-bottom: 15px; + border-bottom: 1px solid #dce3dc; +} +.property-title h2 { + margin: 2px 0 0; + font-size: 19px; + letter-spacing: -0.3px; +} +.type-chip { + padding: 4px 7px; + border: 1px solid #b9cbc5; + border-radius: 3px; + color: #58736d; + font: 9px 'DM Mono'; + letter-spacing: 0.3px; +} +.property-section { + margin-top: 16px; + padding: 15px; + background: #fff; + border: 1px solid #e0e6df; + border-radius: 6px; + box-shadow: 0 2px 8px #173d3a08; +} +.property-section h3 { + display: flex; + align-items: baseline; + justify-content: space-between; + margin: 0 0 13px; + color: #173d3a; + font-size: 13px; +} +.property-section h3 small { + color: #879994; + font: 9px 'DM Mono'; + font-weight: 400; +} +.property-section label { + margin: 11px 0; + font-size: 11px; +} +.property-section input, +.property-section select, +.property-section textarea { + width: 100%; + margin-top: 6px; + padding: 9px 10px; + border: 1px solid #d4ddd5; + border-radius: 4px; + background: #fcfdfb; + color: #173d3a; + font: 12px Manrope; + outline: none; + transition: + border-color 0.15s, + box-shadow 0.15s; +} +.property-section textarea { + resize: vertical; + line-height: 1.55; +} +.property-section input:focus, +.property-section select:focus, +.property-section textarea:focus { + border-color: #4e7e76; + box-shadow: 0 0 0 3px #4e7e7618; +} +.field-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; +} +.field-grid label { + min-width: 0; +} +.color-field { + display: grid; + grid-template-columns: 38px 1fr; + gap: 7px; + margin-top: 6px; +} +.color-field input { + margin: 0; +} +.color-field input[type='color'] { + height: 36px; + padding: 3px; + cursor: pointer; +} +.check-field { + display: flex !important; + align-items: center; + gap: 8px; + padding: 9px 10px; + border: 1px solid #e0e6df; + border-radius: 4px; + background: #f7faf6; +} +.check-field input { + width: 14px !important; + height: 14px; + margin: 0 !important; + accent-color: #1f5d55; +} +.compact-check { + margin-top: 29px !important; + height: 36px; +} +.field-hint { + margin: 2px 0; + color: #71847f; + font-size: 11px; + line-height: 1.6; +} +.safe-note { + margin: 8px 0 0; + padding: 8px 10px; + background: #f7f2e8; + color: #886529; + font-size: 10px; + line-height: 1.5; + border-left: 2px solid #dda952; +} +.column-editor { + margin: 0; + padding: 12px; + background: #edf3ed; + border-left: 3px solid #dda952; + border-radius: 4px; +} +.column-editor label { + position: relative; + margin: 10px 0; +} +.column-key { + display: inline-block; + margin-right: 7px; + padding: 2px 5px; + background: #173d3a; + color: #f8f7f0; + border-radius: 2px; + font: 9px 'DM Mono'; + letter-spacing: 0.5px; +} +.product-editor article { + margin: 10px 0; + padding: 11px; + background: #f3f7f2; + border: 1px solid #e0e7df; + border-radius: 5px; +} +.benefit-product-editor article { + background: #f5f7ff; + border-color: #e0e5f5; +} +.banner-slide-editor article { + margin: 10px 0; + padding: 11px; + background: #f3f7f2; + border: 1px solid #e0e7df; + border-radius: 5px; +} +.carousel-settings { + margin-top: 14px; + padding-top: 12px; + border-top: 1px solid #e0e6df; +} +.carousel-settings .check-field { + margin: 7px 0; +} +.product-editor article label { + margin: 8px 0; +} +.item-head { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 6px; + color: #315d57; + font-size: 11px; +} +.item-head button { + border: 0; + background: transparent; + color: #b15942; + font: 10px Manrope; + cursor: pointer; +} +.item-head button:disabled { + opacity: 0.35; + cursor: not-allowed; +} +.add-item { + width: 100%; + padding: 9px; + border: 1px dashed #8ca9a1; + border-radius: 4px; + background: #f8fbf7; + color: #315f59; + font: 600 11px Manrope; + cursor: pointer; +} +.property-form .actions { + position: sticky; + bottom: -28px; + z-index: 2; + margin: 18px -20px -28px; + padding: 13px 20px; + background: #f8faf6ef; + border-top: 1px solid #dce3dc; + backdrop-filter: blur(8px); +} +.property-form .actions button { + flex: 1; + color: #315d57; + background: #fff; +} +.property-form .actions .danger { + color: #aa4d38 !important; +} +.inspector > .empty { + display: flex; + flex-direction: column; + gap: 6px; + margin: 24px; + padding: 18px; + border: 1px dashed #bdcbc5; + border-radius: 6px; + background: #fff; +} +.inspector > .empty b { + font-size: 13px; +} +.inspector > .empty span { + font-size: 11px; + color: #7a8c87; +} +.inspector > .saved { + margin: 15px 20px 24px; +} +output { + display: block; + margin-top: 6px; + color: #a56f1b; + font: 10px 'DM Mono'; +} +@media (max-width: 1100px) { + .shell { + grid-template-columns: 230px minmax(390px, 1fr) 320px; + } +} diff --git a/apps/editor/src/main.ts b/apps/editor/src/main.ts new file mode 100644 index 0000000..03cc411 --- /dev/null +++ b/apps/editor/src/main.ts @@ -0,0 +1,227 @@ +import { computed, createApp, defineComponent, onMounted, onUnmounted, ref, watch } from 'vue'; +import type { Block, PageSchema, ComponentType } from '@demo/page-schema'; +import { createId, defaults, labels, materialSets, type MaterialSet } from '@demo/materials'; +import { Inspector } from './Inspector'; +import './style.css'; +import './device.css'; +import './inspector.css'; +import './toast.css'; + +const serviceOrigin = `${location.protocol}//${location.hostname}`; +const api = `${serviceOrigin}:3000/api/pages/demo-store`; +const h5Url = `${serviceOrigin}:5174`; +const clone = (x: T): T => JSON.parse(JSON.stringify(x)); +const normalize = (b: Block) => { + const legacyBanner = + b.type === 'banner' && !Array.isArray(b.props.slides) + ? [{ image: b.props.image || '', alt: b.props.alt || '' }] + : null; + const fresh = defaults[b.type](); + b.props = { ...fresh.props, ...b.props }; + if (legacyBanner) b.props.slides = legacyBanner; + b.style = { ...fresh.style, ...b.style }; + b.interaction = { + type: b.interaction?.type ?? fresh.interaction?.type ?? 'none', + url: b.interaction?.url ?? fresh.interaction?.url, + }; + return b; +}; + +const App = defineComponent({ + components: { Inspector }, + setup() { + const schema = ref({ + id: 'demo-store', + version: 0, + title: '夏日精选', + blocks: [], + updatedAt: '', + }); + const selected = ref(''), + history = ref([]), + future = ref([]), + saved = ref(''), + device = ref<'phone' | 'compact'>('phone'), + materialSet = ref('marketing'); + const toast = ref<{ message: string; type: 'success' | 'error' } | null>(null), + publishing = ref(false); + const dragType = ref(null), + dragIndex = ref(-1); + let frame: HTMLIFrameElement | undefined, toastTimer: ReturnType | undefined; + const showToast = (message: string, type: 'success' | 'error' = 'success') => { + if (toastTimer) clearTimeout(toastTimer); + toast.value = { message, type }; + toastTimer = setTimeout(() => (toast.value = null), 3600); + }; + const dismissToast = () => { + if (toastTimer) clearTimeout(toastTimer); + toast.value = null; + }; + const commit = () => { + history.value.push(clone(schema.value)); + future.value = []; + }; + const choose = (id: string) => { + selected.value = id; + const b = schema.value.blocks.find((x) => x.id === id); + if (b) normalize(b); + }; + const add = (t: ComponentType) => { + commit(); + const b = normalize(defaults[t]()); + schema.value.blocks.push(b); + selected.value = b.id; + }; + const activeMaterials = computed(() => materialSets[materialSet.value]); + const addBenefitsPreset = () => { + commit(); + const blocks = materialSets.benefits.map((type) => normalize(defaults[type]())); + schema.value.blocks.push(...blocks); + selected.value = blocks[0].id; + }; + const remove = () => { + const n = schema.value.blocks.findIndex((x) => x.id === selected.value); + if (n >= 0) { + commit(); + schema.value.blocks.splice(n, 1); + selected.value = ''; + } + }; + const copy = () => { + const b = schema.value.blocks.find((x) => x.id === selected.value); + if (b) { + commit(); + const x = clone(b); + x.id = createId(); + schema.value.blocks.splice(schema.value.blocks.indexOf(b) + 1, 0, x); + selected.value = x.id; + } + }; + const move = (d: number) => { + const i = schema.value.blocks.findIndex((x) => x.id === selected.value), + j = i + d; + if (i >= 0 && j >= 0 && j < schema.value.blocks.length) { + commit(); + [schema.value.blocks[i], schema.value.blocks[j]] = [ + schema.value.blocks[j], + schema.value.blocks[i], + ]; + } + }; + const beginMaterial = (type: ComponentType) => { + dragType.value = type; + dragIndex.value = -1; + }; + const dropCanvas = () => { + if (dragType.value) add(dragType.value); + dragType.value = null; + }; + const beginLayer = (index: number) => { + dragIndex.value = index; + dragType.value = null; + }; + const dropLayer = (index: number) => { + if (dragIndex.value < 0 || dragIndex.value === index) return; + commit(); + const [block] = schema.value.blocks.splice(dragIndex.value, 1); + schema.value.blocks.splice(index, 0, block); + dragIndex.value = -1; + }; + const undo = () => { + const x = history.value.pop(); + if (x) { + future.value.push(clone(schema.value)); + schema.value = x; + } + }; + const redo = () => { + const x = future.value.pop(); + if (x) { + history.value.push(clone(schema.value)); + schema.value = x; + } + }; + const save = async () => { + const response = await fetch(api + '/draft', { + method: 'PUT', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(schema.value), + }); + if (!response.ok) throw new Error('草稿保存失败'); + saved.value = '草稿已保存'; + }; + const publish = async () => { + if (publishing.value) return; + publishing.value = true; + try { + await save(); + const response = await fetch(api + '/publish', { method: 'POST' }); + if (!response.ok) throw new Error('服务端未完成发布'); + const x = await response.json(); + saved.value = `已发布版本 v${x.version}`; + showToast(`发布成功 · 当前版本 v${x.version}`); + } catch (error: any) { + showToast(`发布失败 · ${error?.message || '请稍后重试'}`, 'error'); + } finally { + publishing.value = false; + } + }; + const sel = () => schema.value.blocks.find((x) => x.id === selected.value); + watch( + schema, + () => + frame?.contentWindow?.postMessage({ type: 'preview', schema: clone(schema.value) }, '*'), + { deep: true }, + ); + onMounted(async () => { + const x = await fetch(api + '/draft').then((r) => r.json()); + x.blocks.forEach(normalize); + schema.value = x; + frame = document.querySelector('iframe') || undefined; + if (frame) + frame.onload = () => + frame?.contentWindow?.postMessage({ type: 'preview', schema: clone(schema.value) }, '*'); + }); + onUnmounted(() => { + if (toastTimer) clearTimeout(toastTimer); + }); + return { + schema, + selected, + labels, + add, + choose, + sel, + remove, + copy, + move, + undo, + redo, + save, + publish, + saved, + device, + dragType, + beginMaterial, + dropCanvas, + beginLayer, + dropLayer, + commit, + toast, + publishing, + dismissToast, + h5Url, + materialSet, + activeMaterials, + addBenefitsPreset, + }; + }, + template: `
+
MOSAIC H5 STUDIO
打开移动端 ↗
+ +
预览 · {{device==='phone'?'390 × 844':'375 × 667'}}{{schema.title}}
+ +
{{toast.type==='success'?'✓':'!'}}
{{toast.type==='success'?'页面已发布':'发布未完成'}}

{{toast.message}}

+
`, +}); +createApp(App).mount('#app'); diff --git a/apps/editor/src/style.css b/apps/editor/src/style.css new file mode 100644 index 0000000..4c46748 --- /dev/null +++ b/apps/editor/src/style.css @@ -0,0 +1,305 @@ +@import url('https://fonts.googleapis.com/css2?family=DM+Mono&family=Manrope:wght@400;500;600;700;800&display=swap'); +* { + box-sizing: border-box; +} +body { + margin: 0; + background: #edf0ec; + color: #173d3a; + font-family: Manrope, system-ui; +} +.shell { + display: grid; + grid-template-columns: 270px 1fr 300px; + grid-template-rows: 64px calc(100vh - 64px); + min-height: 100vh; +} +header { + grid-column: 1/-1; + background: #123a37; + color: #f6f4ec; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 24px; + gap: 18px; +} +.brand { + font-size: 21px; + font-weight: 800; + letter-spacing: 2px; +} +.brand small { + font: 10px 'DM Mono'; + color: #d6b66d; + letter-spacing: 1px; + margin-left: 8px; +} +.library-switch { + display: flex; + gap: 3px; + padding: 3px; + border: 1px solid #587570; + border-radius: 7px; + background: #0d302e; +} +.library-switch button { + position: relative; + padding: 7px 13px; + border: 0; + border-radius: 4px; + background: transparent; + color: #a9c0bb; + font: 700 11px Manrope; + cursor: pointer; +} +.library-switch button.active { + background: #f5f7f3; + color: #173d3a; + box-shadow: 0 2px 8px #061c1a55; +} +.library-switch small { + margin-left: 4px; + color: #dfa948; + font: 8px 'DM Mono'; +} +.tools { + display: flex; + gap: 8px; + align-items: center; +} +.tools button, +.tools a, +.actions button { + border: 1px solid #c8d0c8; + background: transparent; + color: inherit; + padding: 8px 11px; + border-radius: 5px; + font: 600 12px Manrope; + text-decoration: none; + cursor: pointer; +} +.tools .publish { + background: #dda952; + border-color: #dda952; + color: #173d3a; +} +.materials, +.inspector { + background: #f9faf6; + padding: 24px; + overflow: auto; +} +.materials { + border-right: 1px solid #d8ded7; +} +.inspector { + border-left: 1px solid #d8ded7; +} +.eyebrow { + font: 11px 'DM Mono'; + letter-spacing: 1px; + text-transform: uppercase; + color: #78908b; + margin: 0 0 8px; +} +h2 { + font-size: 18px; + margin: 0 0 18px; +} +.material-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; +} +.material-grid button { + padding: 13px 8px; + background: #edf3ed; + border: 0; + border-radius: 5px; + color: #1d514c; + font: 600 12px Manrope; + cursor: grab; +} +.benefit-library { + background: #f8f9fc; + border-right-color: #dfe4ee; +} +.benefit-library h2 { + color: #17213a; +} +.benefit-intro { + margin-bottom: 14px; + padding: 13px; + border: 1px solid #dfe5f2; + border-radius: 8px; + background: linear-gradient(145deg, #fff, #f0f3ff); +} +.benefit-intro b, +.benefit-intro span { + display: block; +} +.benefit-intro b { + color: #26345c; + font-size: 12px; +} +.benefit-intro span { + margin-top: 4px; + color: #7f8ba4; + font-size: 10px; +} +.benefit-intro button { + width: 100%; + margin-top: 11px; + padding: 8px; + border: 1px solid #526cf2; + border-radius: 5px; + background: #526cf2; + color: #fff; + font: 700 10px Manrope; + cursor: pointer; +} +.benefit-library .material-grid { + grid-template-columns: 1fr; +} +.benefit-library .material-grid button { + display: flex; + align-items: center; + gap: 9px; + padding: 12px; + border: 1px solid #e3e7f0; + background: #fff; + color: #34415d; + text-align: left; +} +.benefit-library .material-grid button:hover { + border-color: #9baafa; + background: #f2f4ff; + color: #3f58df; +} +.material-icon { + display: grid; + width: 24px; + height: 24px; + place-items: center; + border-radius: 6px; + background: #edf0ff; + color: #526cf2; + font: 700 12px 'DM Mono'; +} +.layers { + margin-top: 30px; +} +ol { + padding: 0; + margin: 0; + list-style: none; +} +li { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 8px; + border-bottom: 1px solid #edf0ec; + font-size: 12px; + cursor: pointer; +} +li.active { + background: #e0ece5; + color: #0e5148; +} +li button { + border: 0; + background: transparent; + color: #376a63; + cursor: pointer; +} +.stage { + background: #e2e7e1; + display: flex; + flex-direction: column; + align-items: center; + overflow: auto; +} +.stage-top { + height: 58px; + display: flex; + width: 100%; + align-items: center; + justify-content: space-between; + padding: 0 36px; + font: 12px 'DM Mono'; + color: #59736e; +} +.phone { + width: 390px; + height: 844px; + background: #fff; + box-shadow: 0 18px 50px #53726a55; + border-radius: 18px; + overflow: hidden; + margin-bottom: 30px; +} +.phone iframe { + border: 0; + width: 100%; + height: 100%; + display: block; +} +label { + display: block; + font-size: 11px; + font-weight: 700; + margin: 15px 0; + color: #4b6963; +} +input, +select { + margin-top: 6px; + width: 100%; + padding: 10px; + border: 1px solid #d5ddd5; + border-radius: 4px; + background: #fff; + font: 12px Manrope; + color: #173d3a; +} +.actions { + display: flex; + gap: 8px; + margin-top: 22px; +} +.danger { + color: #aa4d38 !important; + border-color: #e4c5b9 !important; +} +.empty { + padding: 18px 0; + color: #7c8d89; + font-size: 13px; +} +.saved { + color: #a56f1b; + font-size: 12px; + margin-top: 28px; +} +@media (max-width: 900px) { + .shell { + grid-template-columns: 220px 1fr; + } + .inspector { + display: none; + } + .phone { + transform: scale(0.8); + transform-origin: top center; + margin-bottom: -150px; + } + .tools a { + display: none; + } + .library-switch { + display: none; + } +} diff --git a/apps/editor/src/toast.css b/apps/editor/src/toast.css new file mode 100644 index 0000000..789cc6e --- /dev/null +++ b/apps/editor/src/toast.css @@ -0,0 +1,77 @@ +.publish-toast { + position: fixed; + z-index: 20; + top: 78px; + right: 24px; + display: grid; + grid-template-columns: 32px 1fr 24px; + align-items: center; + gap: 11px; + min-width: 300px; + max-width: 420px; + padding: 13px 13px 13px 14px; + border: 1px solid #7da39a; + border-radius: 7px; + background: #f8fbf7; + color: #173d3a; + box-shadow: 0 16px 42px #173d3a30; + animation: toast-enter 0.24s cubic-bezier(0.2, 0.8, 0.2, 1) both; +} +.publish-toast.error { + border-color: #d49a88; + background: #fff9f6; + color: #743e31; +} +.toast-mark { + display: grid; + place-items: center; + width: 30px; + height: 30px; + border-radius: 50%; + background: #1f665b; + color: #fff; + font-weight: 800; +} +.error .toast-mark { + background: #b55b43; +} +.publish-toast b { + display: block; + font-size: 12px; +} +.publish-toast p { + margin: 3px 0 0; + color: #59736d; + font: 10px 'DM Mono'; +} +.publish-toast.error p { + color: #8b5b4e; +} +.publish-toast button { + align-self: start; + border: 0; + background: transparent; + color: #71847f; + font-size: 18px; + line-height: 1; + cursor: pointer; +} +.tools .publish:disabled { + opacity: 0.65; + cursor: wait; +} +@keyframes toast-enter { + from { + opacity: 0; + transform: translateY(-10px) scale(0.98); + } + to { + opacity: 1; + transform: none; + } +} +@media (prefers-reduced-motion: reduce) { + .publish-toast { + animation: none; + } +} diff --git a/apps/editor/src/vite-env.d.ts b/apps/editor/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/apps/editor/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/editor/tsconfig.json b/apps/editor/tsconfig.json new file mode 100644 index 0000000..b3ed883 --- /dev/null +++ b/apps/editor/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "strict": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts"] +} diff --git a/apps/editor/vite.config.ts b/apps/editor/vite.config.ts new file mode 100644 index 0000000..bca1985 --- /dev/null +++ b/apps/editor/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +export default defineConfig({ + plugins: [vue()], + resolve: { alias: { vue: 'vue/dist/vue.esm-bundler.js' } }, + server: { host: '0.0.0.0', port: 5173, strictPort: true }, +}); diff --git a/apps/h5/index.html b/apps/h5/index.html new file mode 100644 index 0000000..f83f8fb --- /dev/null +++ b/apps/h5/index.html @@ -0,0 +1,3 @@ + +
+ diff --git a/apps/h5/package.json b/apps/h5/package.json new file mode 100644 index 0000000..9a0c035 --- /dev/null +++ b/apps/h5/package.json @@ -0,0 +1,18 @@ +{ + "name": "@demo/h5", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "typecheck": "tsc --noEmit -p tsconfig.json" + }, + "dependencies": { + "vue": "^3.5.17", + "@demo/page-schema": "*", + "@demo/renderer": "*" + }, + "devDependencies": { + "vite": "^7.0.6", + "@vitejs/plugin-vue": "^6.0.0" + } +} diff --git a/apps/h5/src/enhanced.css b/apps/h5/src/enhanced.css new file mode 100644 index 0000000..d5a6074 --- /dev/null +++ b/apps/h5/src/enhanced.css @@ -0,0 +1,301 @@ +.product-img { + display: block; + width: 100%; + object-fit: cover; + background: #dceae5; +} +.product-placeholder { + display: grid; + place-items: center; + color: #7d9690; + font-size: 11px; +} +.products article { + min-width: 0; +} +.products article b { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.r-spacer { + width: 100%; +} +.r-banner, +.r-image, +.r-button, +.products, +.cols, +.benefit-card { + transition: all 0.15s ease; +} +.carousel-slide { + animation: carousel-in 0.28s ease both; +} +.carousel-arrow { + position: absolute; + top: 50%; + z-index: 3; + width: 28px; + height: 40px; + transform: translateY(-50%); + border: 0; + background: #102f2c99; + color: #fff; + font-size: 24px; + line-height: 1; + cursor: pointer; +} +.carousel-arrow:disabled { + opacity: 0.3; + cursor: not-allowed; +} +.carousel-prev { + left: 6px; +} +.carousel-next { + right: 6px; +} +.carousel-dots { + position: absolute; + z-index: 3; + left: 50%; + bottom: 8px; + display: flex; + gap: 6px; + transform: translateX(-50%); +} +.carousel-dots button { + width: 6px; + height: 6px; + padding: 0; + border: 0; + border-radius: 999px; + background: #ffffff88; + cursor: pointer; + transition: + width 0.2s, + background 0.2s; +} +.carousel-dots button.active { + width: 18px; + background: #fff; +} +@keyframes carousel-in { + from { + opacity: 0.35; + transform: scale(1.01); + } + to { + opacity: 1; + transform: scale(1); + } +} +@media (prefers-reduced-motion: reduce) { + .r-banner, + .r-image, + .r-button, + .products, + .cols, + .benefit-card { + transition: none; + } +} + +.benefits-page { + min-height: 100vh; + padding: 18px 14px 34px; + background: #f3f6fb; + color: #17213a; + font-family: Inter, 'PingFang SC', 'Microsoft YaHei', sans-serif; +} +.benefit-card { + margin-bottom: 12px; + padding: 15px; + border: 1px solid #e3e8f2; + background: #fff; + box-shadow: 0 4px 14px #2942740a; +} +.benefit-card-head, +.benefit-list-title, +.benefit-name-row, +.benefit-price-row { + display: flex; + align-items: center; +} +.benefit-card-head, +.benefit-list-title { + justify-content: space-between; + margin-bottom: 13px; +} +.benefit-kicker { + color: #7b869d; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.04em; +} +.benefit-kicker::before { + content: '▦'; + margin-right: 6px; + color: #9ca8c0; +} +.delivery-badge, +.benefit-meta, +.benefit-count, +.list-mode, +.vip-chip { + display: inline-flex; + align-items: center; + min-height: 22px; + padding: 0 8px; + border-radius: 6px; + background: #eef1f7; + color: #69748b; + font-size: 10px; + font-weight: 700; +} +.delivery-badge.mode-direct { + background: #edf0ff; + color: #4f68f6; +} +.delivery-badge.mode-code { + background: #fff2e8; + color: #d46c23; +} +.benefit-product-body { + display: grid; + grid-template-columns: 72px minmax(0, 1fr) auto; + gap: 13px; + align-items: center; +} +.benefit-product-image { + width: 72px; + height: 72px; + border-radius: 12px; + object-fit: cover; + background: #f0f3f9; +} +.benefit-image-placeholder { + display: grid; + place-items: center; + color: #a8b4c9; + font-size: 34px; +} +.benefit-product-copy { + min-width: 0; +} +.benefit-name-row { + gap: 6px; +} +.benefit-name-row b { + overflow: hidden; + color: #17213a; + font-size: 15px; + text-overflow: ellipsis; + white-space: nowrap; +} +.vip-chip { + min-height: 18px; + padding: 0 5px; + background: #fff1cf; + color: #a66b00; + font-size: 8px; +} +.benefit-product-copy p { + overflow: hidden; + margin: 5px 0 6px; + color: #8993a8; + font-size: 11px; + text-overflow: ellipsis; + white-space: nowrap; +} +.benefit-price-row { + gap: 7px; +} +.benefit-price-row strong { + font-size: 18px; +} +.benefit-price-row del { + color: #aab2c2; + font-size: 10px; +} +.benefit-card-action, +.benefit-action-row button { + border: 0; + font-weight: 700; + cursor: pointer; +} +.benefit-card-action { + padding: 10px 13px; + border-radius: 999px; + color: #fff; + font-size: 12px; + white-space: nowrap; +} +.benefit-action-row { + display: flex; + gap: 10px; +} +.benefit-action-row button { + min-height: 42px; + color: #17213a; +} +.benefit-primary-action { + flex: 1; + color: #fff !important; +} +.benefit-secondary-action { + flex: 0 0 112px; +} +.benefit-notice { + border-left-width: 3px; +} +.benefit-notice h3 { + margin: 0 0 8px; + color: #17213a; + font-size: 15px; +} +.benefit-notice-copy { + margin: 0; + color: inherit; + font-size: 12px; + line-height: 1.85; + white-space: pre-line; +} +.benefit-list-title { + padding-bottom: 9px; + border-bottom: 1px solid #edf0f6; +} +.benefit-list-items article { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 45px; + border-bottom: 1px solid #f0f2f7; +} +.benefit-list-items article:last-child { + border-bottom: 0; +} +.benefit-list-items article > div { + display: flex; + min-width: 0; + align-items: center; + gap: 7px; +} +.benefit-list-items b { + overflow: hidden; + color: #263149; + font-size: 13px; + text-overflow: ellipsis; + white-space: nowrap; +} +.benefit-list-items strong { + margin-left: 12px; + font-size: 13px; + white-space: nowrap; +} +.list-mode { + min-height: 18px; + padding: 0 5px; + font-size: 8px; +} diff --git a/apps/h5/src/main.ts b/apps/h5/src/main.ts new file mode 100644 index 0000000..fc5d2ac --- /dev/null +++ b/apps/h5/src/main.ts @@ -0,0 +1,33 @@ +import { createApp, defineComponent, ref, onMounted } from 'vue'; +import { PageRenderer } from '@demo/renderer'; +import type { PageSchema } from '@demo/page-schema'; +import './style.css'; +import './enhanced.css'; +const api = `${location.protocol}//${location.hostname}:3000/api/pages/`; +const App = defineComponent({ + components: { PageRenderer }, + setup() { + const schema = ref(null), + error = ref(''); + onMounted(async () => { + addEventListener('message', (e) => { + if (e.data?.type === 'preview') schema.value = e.data.schema; + }); + if (!location.search.includes('preview')) + try { + const id = new URLSearchParams(location.search).get('id') || 'demo-store'; + const cached = sessionStorage.getItem('page:' + id); + if (cached) schema.value = JSON.parse(cached); + const r = await fetch(api + id + '/published'); + if (!r.ok) throw Error('该模板尚未发布'); + schema.value = await r.json(); + sessionStorage.setItem('page:' + id, JSON.stringify(schema.value)); + } catch (e: any) { + error.value = e.message; + } + }); + return { schema, error }; + }, + template: `
{{error}}
正在加载页面…
`, +}); +createApp(App).mount('#app'); diff --git a/apps/h5/src/style.css b/apps/h5/src/style.css new file mode 100644 index 0000000..4df9294 --- /dev/null +++ b/apps/h5/src/style.css @@ -0,0 +1,121 @@ +* { + box-sizing: border-box; +} +body { + margin: 0; + background: #fff; + color: #173d3a; + font-family: Arial, sans-serif; +} +.page-renderer { + min-height: 100vh; + background: #fff; +} +.page-renderer h2 { + margin: 0; + padding: 18px 20px 6px; + font-size: 23px; + letter-spacing: -0.5px; +} +.r-text { + padding: 3px 20px 14px; + line-height: 1.7; + color: #4d605d; + font-size: 14px; +} +.r-banner { + height: 180px; + position: relative; + overflow: hidden; + color: white; +} +.r-banner img { + position: absolute; + width: 100%; + height: 100%; + object-fit: cover; +} +.shade { + position: absolute; + inset: 0; + background: linear-gradient(90deg, #0b2624a8, transparent); +} +.banner-copy { + position: absolute; + left: 22px; + bottom: 22px; +} +.banner-copy p { + margin: 0 0 5px; + font-size: 12px; +} +.banner-copy h1 { + font-size: 28px; + margin: 0; +} +.r-image { + display: block; + width: 100%; + height: auto; + min-height: 150px; + object-fit: cover; +} +.r-button { + display: block; + margin: 12px 20px 22px; + width: calc(100% - 40px); + border: 0; + border-radius: 3px; + padding: 13px; + background: #dca74e; + color: #173d3a; + font-weight: bold; +} +.products { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; + padding: 14px 20px; +} +.products article { + font-size: 13px; +} +.product-img { + height: 100px; + background: #dceae5; + margin-bottom: 8px; +} +.products b, +.products span { + display: block; +} +.products span { + color: #b77523; + margin-top: 4px; +} +.page-renderer hr { + border: 0; + border-top: 1px solid #e3e8e2; + margin: 12px 20px; +} +.cols { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + padding: 12px 20px; +} +.cols div { + background: #e9f0e9; + padding: 17px; + font-size: 13px; +} +.unknown, +.h5-error, +.loading { + padding: 32px; + text-align: center; + color: #6d7a77; +} +.h5-error { + color: #a75140; +} diff --git a/apps/h5/src/vite-env.d.ts b/apps/h5/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/apps/h5/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/h5/tsconfig.json b/apps/h5/tsconfig.json new file mode 100644 index 0000000..b3ed883 --- /dev/null +++ b/apps/h5/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "strict": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts"] +} diff --git a/apps/h5/vite.config.ts b/apps/h5/vite.config.ts new file mode 100644 index 0000000..6d8f6bd --- /dev/null +++ b/apps/h5/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +export default defineConfig({ + plugins: [vue()], + resolve: { alias: { vue: 'vue/dist/vue.esm-bundler.js' } }, + server: { host: '0.0.0.0', port: 5174, strictPort: true }, + build: { rollupOptions: { output: { manualChunks: { vue: ['vue'] } } } }, +}); diff --git a/apps/server/package.json b/apps/server/package.json new file mode 100644 index 0000000..c59f442 --- /dev/null +++ b/apps/server/package.json @@ -0,0 +1,11 @@ +{ + "name": "@demo/server", + "type": "module", + "scripts": { + "dev": "node src/index.mjs" + }, + "dependencies": { + "express": "^5.1.0", + "cors": "^2.8.5" + } +} diff --git a/apps/server/src/index.mjs b/apps/server/src/index.mjs new file mode 100644 index 0000000..518fc69 --- /dev/null +++ b/apps/server/src/index.mjs @@ -0,0 +1,89 @@ +import express from 'express'; +import cors from 'cors'; +import fs from 'node:fs'; +import path from 'node:path'; + +const app = express(), + file = path.resolve('data.json'); +app.use(cors(), express.json({ limit: '256kb' })); +const seed = { + draft: { + id: 'demo-store', + version: 0, + title: '夏日精选', + blocks: [], + updatedAt: new Date().toISOString(), + }, + published: null, + versions: [], +}; +const read = () => { + const x = fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf8')) : structuredClone(seed); + x.versions ||= x.published ? [x.published] : []; + return x; +}; +const write = (x) => fs.writeFileSync(file, JSON.stringify(x, null, 2)); +const allowed = new Set([ + 'banner', + 'title', + 'text', + 'image', + 'button', + 'product-list', + 'divider', + 'spacer', + 'two-column', + 'benefit-product', + 'benefit-actions', + 'benefit-notice', + 'benefit-product-list', +]); +const isSafe = (s) => + s && + typeof s.id === 'string' && + typeof s.title === 'string' && + Array.isArray(s.blocks) && + s.blocks.every( + (b) => + b && + typeof b.id === 'string' && + allowed.has(b.type) && + (!b.interaction?.url || /^(https?:\/\/|\/)/.test(b.interaction.url)), + ); + +app.get('/api/pages/:id/draft', (q, s) => s.json(read().draft)); +app.put('/api/pages/:id/draft', (q, s) => { + if (!isSafe(q.body)) return s.status(400).json({ error: 'invalid schema' }); + const x = read(); + x.draft = { ...q.body, id: q.params.id, version: 0, updatedAt: new Date().toISOString() }; + write(x); + s.json(x.draft); +}); +app.post('/api/pages/:id/publish', (q, s) => { + const x = read(), + next = (x.versions.at(-1)?.version || 0) + 1, + snapshot = structuredClone({ + ...x.draft, + id: q.params.id, + version: next, + updatedAt: new Date().toISOString(), + }); + x.versions.push(snapshot); + x.published = snapshot; + write(x); + s.json(x.published); +}); +app.get('/api/pages/:id/published', (q, s) => { + const x = read(); + if (x.published) s.json(x.published); + else s.status(404).json({ error: 'not published' }); +}); +app.get('/api/pages/:id/versions', (q, s) => + s.json(read().versions.map(({ version, updatedAt, title }) => ({ version, updatedAt, title }))), +); +app.get('/api/pages/:id/versions/:version', (q, s) => { + const v = read().versions.find((x) => x.version === Number(q.params.version)); + if (v) s.json(v); + else s.status(404).json({ error: 'version not found' }); +}); +app.listen(3000, '0.0.0.0', () => console.log('API http://0.0.0.0:3000')); diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..6dcf305 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,37 @@ +import js from '@eslint/js'; +import prettier from 'eslint-config-prettier'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +export default [ + { + ignores: [ + '**/node_modules/**', + '**/dist/**', + 'test-results/**', + 'playwright-report/**', + 'apps/server/data.json', + ], + }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + files: ['**/*.{js,mjs,ts}'], + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + }, + }, + rules: { + 'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], + '@typescript-eslint/no-unused-vars': [ + 'error', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, + ], + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + }, + }, + prettier, +]; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e84c2d3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3867 @@ +{ + "name": "lowcode-h5-demo", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "lowcode-h5-demo", + "workspaces": [ + "apps/*", + "packages/*" + ], + "devDependencies": { + "@eslint/js": "^10.0.1", + "@playwright/test": "^1.54.1", + "@vitejs/plugin-vue": "^6.0.0", + "concurrently": "^9.1.2", + "eslint": "^10.7.0", + "eslint-config-prettier": "^10.1.8", + "globals": "^17.7.0", + "prettier": "^3.9.5", + "typescript": "^5.8.3", + "typescript-eslint": "^8.64.0", + "vite": "^7.0.6", + "vue": "^3.5.17" + } + }, + "apps/editor": { + "name": "@demo/editor", + "dependencies": { + "@demo/materials": "*", + "@demo/page-schema": "*", + "@demo/renderer": "*", + "vue": "^3.5.17" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.0", + "vite": "^7.0.6" + } + }, + "apps/h5": { + "name": "@demo/h5", + "dependencies": { + "@demo/page-schema": "*", + "@demo/renderer": "*", + "vue": "^3.5.17" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.0", + "vite": "^7.0.6" + } + }, + "apps/server": { + "name": "@demo/server", + "dependencies": { + "cors": "^2.8.5", + "express": "^5.1.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@demo/editor": { + "resolved": "apps/editor", + "link": true + }, + "node_modules/@demo/h5": { + "resolved": "apps/h5", + "link": true + }, + "node_modules/@demo/materials": { + "resolved": "packages/materials", + "link": true + }, + "node_modules/@demo/page-schema": { + "resolved": "packages/page-schema", + "link": true + }, + "node_modules/@demo/renderer": { + "resolved": "packages/renderer", + "link": true + }, + "node_modules/@demo/server": { + "resolved": "apps/server", + "link": true + }, + "node_modules/@demo/shared": { + "resolved": "packages/shared", + "link": true + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@playwright/test": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", + "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", + "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/type-utils": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.64.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", + "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", + "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.64.0", + "@typescript-eslint/types": "^8.64.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", + "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", + "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz", + "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", + "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", + "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.64.0", + "@typescript-eslint/tsconfig-utils": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz", + "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", + "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.8.tgz", + "integrity": "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.40.tgz", + "integrity": "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.40", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz", + "integrity": "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz", + "integrity": "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.40", + "@vue/compiler-dom": "3.5.40", + "@vue/compiler-ssr": "3.5.40", + "@vue/shared": "3.5.40", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.40.tgz", + "integrity": "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.40.tgz", + "integrity": "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.40.tgz", + "integrity": "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.40.tgz", + "integrity": "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.40", + "@vue/runtime-core": "3.5.40", + "@vue/shared": "3.5.40", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.40.tgz", + "integrity": "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.40", + "@vue/runtime-dom": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.40.tgz", + "integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==", + "license": "MIT" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.4.tgz", + "integrity": "sha512-TZ0CEhyzvFjgtAvHTusDMgj7wNdihCh7LLLrzdUOXIhdlnL2JBBGA9eJxR24rtqgmdjh3OA3hrN1rCHj6HM8qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.9.0", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.7.0.tgz", + "integrity": "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", + "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/postcss": { + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz", + "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", + "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.64.0.tgz", + "integrity": "sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.64.0", + "@typescript-eslint/parser": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/vue": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.40.tgz", + "integrity": "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.40", + "@vue/compiler-sfc": "3.5.40", + "@vue/runtime-dom": "3.5.40", + "@vue/server-renderer": "3.5.40", + "@vue/shared": "3.5.40" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/materials": { + "name": "@demo/materials", + "version": "1.0.0", + "dependencies": { + "@demo/page-schema": "*" + } + }, + "packages/page-schema": { + "name": "@demo/page-schema", + "version": "1.0.0" + }, + "packages/renderer": { + "name": "@demo/renderer", + "version": "1.0.0", + "dependencies": { + "@demo/page-schema": "*", + "vue": "^3.5.17" + } + }, + "packages/shared": { + "name": "@demo/shared", + "version": "1.0.0" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..83d1206 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "lowcode-h5-demo", + "private": true, + "workspaces": [ + "apps/*", + "packages/*" + ], + "scripts": { + "dev": "concurrently -n server,editor,h5 -c cyan,magenta,yellow \"npm run dev -w @demo/server\" \"npm run dev -w @demo/editor\" \"npm run dev -w @demo/h5\"", + "build": "npm run build --workspaces --if-present", + "lint": "eslint . --max-warnings=0", + "typecheck": "npm run typecheck --workspaces --if-present", + "format": "prettier --write .", + "format:check": "prettier --check .", + "check": "npm run lint && npm run typecheck && npm run format:check && npm run build", + "test:e2e": "playwright test", + "check:h5": "npm run build -w @demo/h5 && ! rg -i \"sortablejs|vuedraggable|@dnd-kit|apps/editor\" apps/h5/dist/assets" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@playwright/test": "^1.54.1", + "@vitejs/plugin-vue": "^6.0.0", + "concurrently": "^9.1.2", + "eslint": "^10.7.0", + "eslint-config-prettier": "^10.1.8", + "globals": "^17.7.0", + "prettier": "^3.9.5", + "typescript": "^5.8.3", + "typescript-eslint": "^8.64.0", + "vite": "^7.0.6", + "vue": "^3.5.17" + } +} diff --git a/packages/materials/package.json b/packages/materials/package.json new file mode 100644 index 0000000..a5d3be0 --- /dev/null +++ b/packages/materials/package.json @@ -0,0 +1,9 @@ +{ + "name": "@demo/materials", + "version": "1.0.0", + "type": "module", + "exports": "./src/index.ts", + "dependencies": { + "@demo/page-schema": "*" + } +} diff --git a/packages/materials/src/index.ts b/packages/materials/src/index.ts new file mode 100644 index 0000000..674095b --- /dev/null +++ b/packages/materials/src/index.ts @@ -0,0 +1,248 @@ +import type { Block, ComponentType } from '@demo/page-schema'; + +export const createId = () => { + const cryptoApi = globalThis.crypto; + + if (typeof cryptoApi?.randomUUID === 'function') return cryptoApi.randomUUID(); + + if (typeof cryptoApi?.getRandomValues === 'function') { + const bytes = cryptoApi.getRandomValues(new Uint8Array(16)); + bytes[6] = (bytes[6] & 0x0f) | 0x40; + bytes[8] = (bytes[8] & 0x3f) | 0x80; + const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')); + + return `${hex.slice(0, 4).join('')}-${hex.slice(4, 6).join('')}-${hex + .slice(6, 8) + .join('')}-${hex.slice(8, 10).join('')}-${hex.slice(10).join('')}`; + } + + return `block-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`; +}; + +const base = ( + type: ComponentType, + props: Record, + style: Record = {}, +): Block => ({ id: createId(), type, props, style, interaction: { type: 'none' } }); + +export const labels: Record = { + banner: '横幅 Banner', + title: '标题', + text: '文本', + image: '图片', + button: '按钮', + 'product-list': '商品列表', + divider: '分割线', + spacer: '间距', + 'two-column': '双列容器', + 'benefit-product': '权益商品卡片', + 'benefit-actions': '兑换操作区', + 'benefit-notice': '兑换说明', + 'benefit-product-list': '权益商品列表', +}; + +export type MaterialSet = 'marketing' | 'benefits'; +export const materialSets: Record = { + marketing: [ + 'banner', + 'title', + 'text', + 'image', + 'button', + 'product-list', + 'divider', + 'spacer', + 'two-column', + ], + benefits: ['benefit-product', 'benefit-actions', 'benefit-notice', 'benefit-product-list'], +}; + +export const defaults: Record Block> = { + banner: () => + base( + 'banner', + { + title: '夏日新品', + subtitle: '为好心情准备', + image: + 'https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?auto=format&fit=crop&w=900&q=80', + alt: '咖啡与夏日新品', + slides: [ + { + image: + 'https://images.unsplash.com/photo-1495474472287-4d71bcdd2085?auto=format&fit=crop&w=900&q=80', + alt: '咖啡与夏日新品', + }, + ], + enableCarousel: false, + loop: true, + autoplay: false, + autoplaySeconds: 3, + height: 180, + overlayOpacity: 0.55, + textAlign: 'left', + }, + { color: '#ffffff' }, + ), + title: () => + base( + 'title', + { + text: '一眼心动的新选择', + fontSize: 24, + fontWeight: '700', + textAlign: 'left', + paddingX: 20, + paddingY: 18, + }, + { color: '#173d3a' }, + ), + text: () => + base( + 'text', + { + text: '用简洁的内容讲清楚价值与行动。', + fontSize: 14, + lineHeight: 1.7, + textAlign: 'left', + paddingX: 20, + paddingY: 10, + }, + { color: '#4d605d', backgroundColor: '#ffffff' }, + ), + image: () => + base('image', { + src: 'https://images.unsplash.com/photo-1494438639946-1ebd1d20bf85?auto=format&fit=crop&w=900&q=80', + alt: '精选生活图片', + height: 250, + objectFit: 'cover', + borderRadius: 0, + }), + button: () => + base( + 'button', + { text: '立即了解', fullWidth: true, borderRadius: 4, paddingY: 13 }, + { color: '#173d3a', backgroundColor: '#dca74e' }, + ), + 'product-list': () => + base( + 'product-list', + { + columns: 2, + gap: 12, + imageHeight: 112, + showImage: true, + priceColor: '#b77523', + items: [ + { + name: '云朵马克杯', + price: '¥ 88', + image: + 'https://images.unsplash.com/photo-1514228742587-6b1558fcca3d?auto=format&fit=crop&w=500&q=80', + }, + { + name: '晨光托特包', + price: '¥ 128', + image: + 'https://images.unsplash.com/photo-1553062407-98eeb64c6a62?auto=format&fit=crop&w=500&q=80', + }, + ], + }, + { color: '#173d3a', backgroundColor: '#ffffff' }, + ), + divider: () => + base('divider', { color: '#dfe6df', thickness: 1, marginY: 16, marginX: 20, style: 'solid' }), + spacer: () => base('spacer', { height: 24, backgroundColor: '#ffffff' }), + 'two-column': () => + base( + 'two-column', + { + left: '左侧内容', + right: '右侧内容', + gap: 8, + paddingX: 20, + paddingY: 12, + minHeight: 58, + leftBackground: '#e9f0e9', + rightBackground: '#e9f0e9', + textAlign: 'left', + }, + { color: '#173d3a' }, + ), + 'benefit-product': () => + base( + 'benefit-product', + { + deliveryMode: 'direct', + name: '凯叔讲故事月卡', + subtitle: 'VIP 会员 · 购买后立即生效', + price: '¥0.01', + originalPrice: '¥19.90', + image: '', + imageAlt: '凯叔讲故事月卡', + badge: '直充', + buttonText: '立即兑换', + showVip: true, + showOriginalPrice: false, + borderRadius: 16, + accentColor: '#4f68f6', + priceColor: '#ff5562', + }, + { color: '#17213a', backgroundColor: '#ffffff' }, + ), + 'benefit-actions': () => + base( + 'benefit-actions', + { + primaryText: '立即兑换', + secondaryText: '使用说明', + showSecondary: true, + borderRadius: 12, + primaryColor: '#4f68f6', + secondaryColor: '#eef1f7', + }, + { color: '#17213a', backgroundColor: '#ffffff' }, + ), + 'benefit-notice': () => + base( + 'benefit-notice', + { + title: '兑换说明', + content: '1. 兑换后请在有效期内使用\n2. 每个用户限兑一次\n3. 虚拟商品兑换后不退换', + eyebrow: '使用须知', + borderRadius: 16, + accentColor: '#4f68f6', + }, + { color: '#34405a', backgroundColor: '#ffffff' }, + ), + 'benefit-product-list': () => + base( + 'benefit-product-list', + { + title: '更多权益', + showCount: true, + priceColor: '#ff5562', + borderRadius: 16, + items: [ + { name: '爱奇艺黄金 VIP 月卡', price: '¥5.00', mode: '卡密' }, + { name: '腾讯视频 VIP 月卡', price: '¥6.00', mode: '直充' }, + { name: '微信立减金', price: '¥0.01', mode: '卡密' }, + ], + }, + { color: '#17213a', backgroundColor: '#ffffff' }, + ), +}; + +export const interactiveTypes: ComponentType[] = [ + 'banner', + 'title', + 'text', + 'image', + 'button', + 'product-list', + 'two-column', + 'benefit-product', + 'benefit-actions', + 'benefit-notice', + 'benefit-product-list', +]; diff --git a/packages/page-schema/package.json b/packages/page-schema/package.json new file mode 100644 index 0000000..6242a36 --- /dev/null +++ b/packages/page-schema/package.json @@ -0,0 +1,6 @@ +{ + "name": "@demo/page-schema", + "version": "1.0.0", + "type": "module", + "exports": "./src/index.ts" +} diff --git a/packages/page-schema/src/index.ts b/packages/page-schema/src/index.ts new file mode 100644 index 0000000..7b1ae83 --- /dev/null +++ b/packages/page-schema/src/index.ts @@ -0,0 +1,57 @@ +export type ComponentType = + | 'banner' + | 'title' + | 'text' + | 'image' + | 'button' + | 'product-list' + | 'divider' + | 'spacer' + | 'two-column' + | 'benefit-product' + | 'benefit-actions' + | 'benefit-notice' + | 'benefit-product-list'; +export interface Block { + id: string; + type: ComponentType; + props: Record; + style?: Record; + interaction?: { type: 'none' | 'link'; url?: string }; +} +export interface PageSchema { + id: string; + version: number; + title: string; + blocks: Block[]; + updatedAt: string; +} +export const safeUrl = (url?: string) => !!url && /^(https?:\/\/|\/)/.test(url); +export function validSchema(x: unknown): x is PageSchema { + const p = x as PageSchema; + return ( + !!p && + typeof p.id === 'string' && + Array.isArray(p.blocks) && + p.blocks.every( + (b) => + b && + typeof b.id === 'string' && + [ + 'banner', + 'title', + 'text', + 'image', + 'button', + 'product-list', + 'divider', + 'spacer', + 'two-column', + 'benefit-product', + 'benefit-actions', + 'benefit-notice', + 'benefit-product-list', + ].includes(b.type), + ) + ); +} diff --git a/packages/renderer/package.json b/packages/renderer/package.json new file mode 100644 index 0000000..64f918d --- /dev/null +++ b/packages/renderer/package.json @@ -0,0 +1,10 @@ +{ + "name": "@demo/renderer", + "version": "1.0.0", + "type": "module", + "exports": "./src/index.ts", + "dependencies": { + "vue": "^3.5.17", + "@demo/page-schema": "*" + } +} diff --git a/packages/renderer/src/index.ts b/packages/renderer/src/index.ts new file mode 100644 index 0000000..0ee503a --- /dev/null +++ b/packages/renderer/src/index.ts @@ -0,0 +1,497 @@ +import { + defineComponent, + h, + onUnmounted, + ref, + watch, + type CSSProperties, + type PropType, +} from 'vue'; +import type { Block, PageSchema } from '@demo/page-schema'; +import { safeUrl } from '@demo/page-schema'; + +const num = (value: unknown, fallback: number) => + Number.isFinite(Number(value)) ? Number(value) : fallback; +const px = (value: unknown, fallback: number) => `${num(value, fallback)}px`; +const go = (b: Block) => { + const url = b.interaction?.type === 'link' ? b.interaction.url : undefined; + if (safeUrl(url)) location.href = url!; +}; + +const BannerView = defineComponent({ + props: { block: { type: Object as PropType, required: true } }, + setup(input) { + const current = ref(0); + let timer: ReturnType | undefined; + const props = () => input.block.props as any; + const slides = () => { + const p = props(); + return Array.isArray(p.slides) && p.slides.length + ? p.slides + : [{ image: p.image || '', alt: p.alt || '' }]; + }; + const enabled = () => !!props().enableCarousel && slides().length > 1; + const stop = () => { + if (timer) { + clearInterval(timer); + timer = undefined; + } + }; + const move = (direction: number) => { + const length = slides().length; + if (!enabled()) return; + const next = current.value + direction; + if (props().loop) current.value = (next + length) % length; + else current.value = Math.min(length - 1, Math.max(0, next)); + }; + const restart = () => { + stop(); + current.value = Math.min(current.value, slides().length - 1); + if (enabled() && props().autoplay) + timer = setInterval(() => move(1), Math.max(1, num(props().autoplaySeconds, 3)) * 1000); + }; + watch( + () => [ + props().enableCarousel, + props().autoplay, + props().autoplaySeconds, + props().loop, + slides().length, + ], + restart, + { immediate: true }, + ); + onUnmounted(stop); + return () => { + const b = input.block, + p = props(), + base = (b.style || {}) as CSSProperties, + list = slides(), + carouselOn = enabled(), + index = carouselOn ? current.value : 0, + slide = list[index] || list[0], + align = p.textAlign || 'left'; + const copy: CSSProperties = { + textAlign: align, + left: align === 'right' ? 'auto' : carouselOn && align === 'left' ? '54px' : '22px', + right: align === 'left' ? 'auto' : carouselOn && align === 'right' ? '54px' : '22px', + width: align === 'center' ? 'calc(100% - 80px)' : 'auto', + }; + const controls = carouselOn + ? [ + h( + 'button', + { + type: 'button', + class: 'carousel-arrow carousel-prev', + 'aria-label': '上一张', + disabled: !p.loop && index === 0, + onClick: (event: Event) => { + event.stopPropagation(); + move(-1); + }, + }, + '‹', + ), + h( + 'button', + { + type: 'button', + class: 'carousel-arrow carousel-next', + 'aria-label': '下一张', + disabled: !p.loop && index === list.length - 1, + onClick: (event: Event) => { + event.stopPropagation(); + move(1); + }, + }, + '›', + ), + h( + 'div', + { class: 'carousel-dots' }, + list.map((_: unknown, i: number) => + h( + 'button', + { + type: 'button', + class: { active: i === index }, + 'aria-label': `切换到第 ${i + 1} 张`, + onClick: (event: Event) => { + event.stopPropagation(); + current.value = i; + }, + }, + '', + ), + ), + ), + ] + : []; + return h( + 'section', + { class: 'r-banner', style: { ...base, height: px(p.height, 180) }, onClick: () => go(b) }, + [ + h('img', { + key: index, + class: 'carousel-slide', + src: slide?.image || '', + alt: slide?.alt || '', + loading: index === 0 ? 'eager' : 'lazy', + width: 390, + height: num(p.height, 180), + }), + h('div', { + class: 'shade', + style: { + background: `linear-gradient(90deg,rgba(11,38,36,${num(p.overlayOpacity, 0.55)}),transparent)`, + }, + }), + h('div', { class: 'banner-copy', style: copy }, [ + h('p', p.subtitle || ''), + h('h1', p.title || ''), + ]), + ...controls, + ], + ); + }; + }, +}); + +const BlockView = defineComponent({ + props: { block: { type: Object as PropType, required: true } }, + setup(input) { + return () => { + const b = input.block, + p = b.props as any, + base = (b.style || {}) as CSSProperties, + click = () => go(b); + switch (b.type) { + case 'banner': + return h(BannerView, { block: b }); + case 'title': + return h( + 'h2', + { + style: { + ...base, + fontSize: px(p.fontSize, 24), + fontWeight: p.fontWeight || '700', + textAlign: p.textAlign || 'left', + padding: `${px(p.paddingY, 18)} ${px(p.paddingX, 20)}`, + }, + onClick: click, + }, + p.text || '', + ); + case 'text': + return h( + 'p', + { + class: 'r-text', + style: { + ...base, + fontSize: px(p.fontSize, 14), + lineHeight: String(p.lineHeight || 1.7), + textAlign: p.textAlign || 'left', + padding: `${px(p.paddingY, 10)} ${px(p.paddingX, 20)}`, + }, + onClick: click, + }, + p.text || '', + ); + case 'image': + return h('img', { + class: 'r-image', + style: { + ...base, + height: px(p.height, 250), + objectFit: p.objectFit || 'cover', + borderRadius: px(p.borderRadius, 0), + }, + src: p.src, + alt: p.alt || '', + loading: 'lazy', + width: 390, + height: Number(p.height) || 250, + onClick: click, + }); + case 'button': + return h( + 'button', + { + class: 'r-button', + style: { + ...base, + width: p.fullWidth ? 'calc(100% - 40px)' : 'auto', + borderRadius: px(p.borderRadius, 4), + padding: `${px(p.paddingY, 13)} 20px`, + }, + onClick: click, + }, + p.text || '按钮', + ); + case 'product-list': { + const items = Array.isArray(p.items) ? p.items : []; + return h( + 'div', + { + class: 'products', + style: { + ...base, + gridTemplateColumns: `repeat(${Number(p.columns) || 2},minmax(0,1fr))`, + gap: px(p.gap, 12), + }, + onClick: click, + }, + items.map((item: any, index: number) => + h('article', { key: index }, [ + p.showImage !== false + ? item.image + ? h('img', { + class: 'product-img', + src: item.image, + alt: item.name || '', + loading: 'lazy', + width: 160, + height: Number(p.imageHeight) || 112, + style: { height: px(p.imageHeight, 112) }, + }) + : h( + 'div', + { + class: 'product-img product-placeholder', + style: { height: px(p.imageHeight, 112) }, + }, + '暂无图片', + ) + : null, + h('b', item.name || '未命名商品'), + h('span', { style: { color: p.priceColor || '#b77523' } }, item.price || ''), + ]), + ), + ); + } + case 'divider': + return h('hr', { + style: { + borderTopColor: p.color || '#dfe6df', + borderTopWidth: px(p.thickness, 1), + borderTopStyle: p.style || 'solid', + margin: `${px(p.marginY, 16)} ${px(p.marginX, 20)}`, + }, + }); + case 'spacer': + return h('div', { + class: 'r-spacer', + style: { height: px(p.height, 24), backgroundColor: p.backgroundColor || '#ffffff' }, + }); + case 'two-column': + return h( + 'div', + { + class: 'cols', + style: { + ...base, + gap: px(p.gap, 8), + padding: `${px(p.paddingY, 12)} ${px(p.paddingX, 20)}`, + textAlign: p.textAlign || 'left', + }, + onClick: click, + }, + [ + h( + 'div', + { + style: { + backgroundColor: p.leftBackground || '#e9f0e9', + minHeight: px(p.minHeight, 58), + }, + }, + p.left || '', + ), + h( + 'div', + { + style: { + backgroundColor: p.rightBackground || '#e9f0e9', + minHeight: px(p.minHeight, 58), + }, + }, + p.right || '', + ), + ], + ); + case 'benefit-product': + return h( + 'section', + { + class: 'benefit-card benefit-product-card', + style: { + ...base, + borderRadius: px(p.borderRadius, 16), + '--benefit-accent': p.accentColor || '#4f68f6', + } as CSSProperties, + onClick: click, + }, + [ + h('div', { class: 'benefit-card-head' }, [ + h('span', { class: 'benefit-kicker' }, '权益商品'), + h( + 'span', + { class: ['delivery-badge', `mode-${p.deliveryMode || 'direct'}`] }, + p.badge || (p.deliveryMode === 'code' ? '卡密' : '直充'), + ), + ]), + h('div', { class: 'benefit-product-body' }, [ + p.image + ? h('img', { + class: 'benefit-product-image', + src: p.image, + alt: p.imageAlt || p.name || '', + loading: 'lazy', + width: 72, + height: 72, + }) + : h('div', { class: 'benefit-product-image benefit-image-placeholder' }, '◇'), + h('div', { class: 'benefit-product-copy' }, [ + h('div', { class: 'benefit-name-row' }, [ + h('b', p.name || '未命名权益'), + p.showVip ? h('span', { class: 'vip-chip' }, 'VIP') : null, + ]), + h('p', p.subtitle || ''), + h('div', { class: 'benefit-price-row' }, [ + h('strong', { style: { color: p.priceColor || '#ff5562' } }, p.price || '¥0'), + p.showOriginalPrice ? h('del', p.originalPrice || '') : null, + ]), + ]), + h( + 'button', + { + type: 'button', + class: 'benefit-card-action', + style: { backgroundColor: p.accentColor || '#4f68f6' }, + }, + p.buttonText || '立即兑换', + ), + ]), + ], + ); + case 'benefit-actions': + return h( + 'section', + { + class: 'benefit-card benefit-actions', + style: { ...base, borderRadius: px(p.borderRadius, 16) }, + onClick: click, + }, + [ + h('div', { class: 'benefit-card-head' }, [ + h('span', { class: 'benefit-kicker' }, '兑换操作'), + h('span', { class: 'benefit-meta' }, '主操作'), + ]), + h('div', { class: 'benefit-action-row' }, [ + h( + 'button', + { + type: 'button', + class: 'benefit-primary-action', + style: { + backgroundColor: p.primaryColor || '#4f68f6', + borderRadius: px(p.borderRadius, 12), + }, + }, + p.primaryText || '立即兑换', + ), + p.showSecondary !== false + ? h( + 'button', + { + type: 'button', + class: 'benefit-secondary-action', + style: { + backgroundColor: p.secondaryColor || '#eef1f7', + borderRadius: px(p.borderRadius, 12), + }, + }, + p.secondaryText || '使用说明', + ) + : null, + ]), + ], + ); + case 'benefit-notice': + return h( + 'section', + { + class: 'benefit-card benefit-notice', + style: { + ...base, + borderRadius: px(p.borderRadius, 16), + borderLeftColor: p.accentColor || '#4f68f6', + }, + onClick: click, + }, + [ + h('div', { class: 'benefit-card-head' }, [ + h('span', { class: 'benefit-kicker' }, p.eyebrow || '使用须知'), + ]), + h('h3', p.title || '兑换说明'), + h('p', { class: 'benefit-notice-copy' }, p.content || ''), + ], + ); + case 'benefit-product-list': { + const benefitItems = Array.isArray(p.items) ? p.items : []; + return h( + 'section', + { + class: 'benefit-card benefit-product-list', + style: { ...base, borderRadius: px(p.borderRadius, 16) }, + onClick: click, + }, + [ + h('div', { class: 'benefit-list-title' }, [ + h('span', { class: 'benefit-kicker' }, p.title || '更多权益'), + p.showCount !== false + ? h('span', { class: 'benefit-count' }, `${benefitItems.length} 件`) + : null, + ]), + h( + 'div', + { class: 'benefit-list-items' }, + benefitItems.map((item: any, index: number) => + h('article', { key: index }, [ + h('div', [ + h('b', item.name || '未命名权益'), + item.mode ? h('span', { class: 'list-mode' }, item.mode) : null, + ]), + h('strong', { style: { color: p.priceColor || '#ff5562' } }, item.price || ''), + ]), + ), + ), + ], + ); + } + default: + return h('div', { class: 'unknown' }, '此组件版本暂不支持'); + } + }; + }, +}); + +export const PageRenderer = defineComponent({ + props: { schema: { type: Object as PropType, required: true } }, + setup(p) { + return () => + h( + 'main', + { + class: [ + 'page-renderer', + { 'benefits-page': p.schema.blocks.some((block) => block.type.startsWith('benefit-')) }, + ], + }, + p.schema.blocks.map((b) => h(BlockView, { block: b, key: b.id })), + ); + }, +}); diff --git a/packages/shared/package.json b/packages/shared/package.json new file mode 100644 index 0000000..dabecde --- /dev/null +++ b/packages/shared/package.json @@ -0,0 +1,6 @@ +{ + "name": "@demo/shared", + "version": "1.0.0", + "type": "module", + "exports": "./src/index.ts" +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts new file mode 100644 index 0000000..5e1df64 --- /dev/null +++ b/packages/shared/src/index.ts @@ -0,0 +1,2 @@ +export type PreviewMessage = { type: 'select'; id: string } | { type: 'ready' }; +export const send = (m: PreviewMessage) => parent.postMessage(m, '*'); diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..09dfb42 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from '@playwright/test'; +export default defineConfig({ + testDir: './tests', + use: { baseURL: 'http://localhost:5173' }, + webServer: { + command: 'npm run dev', + url: 'http://localhost:5174', + reuseExistingServer: true, + timeout: 120_000, + }, +}); diff --git a/tests/flow.spec.ts b/tests/flow.spec.ts new file mode 100644 index 0000000..bce19bb --- /dev/null +++ b/tests/flow.spec.ts @@ -0,0 +1,231 @@ +import { test, expect } from '@playwright/test'; + +test.beforeEach(async ({ request }) => { + await expect + .poll( + async () => { + try { + return (await request.get('http://localhost:5174')).status(); + } catch { + return 0; + } + }, + { timeout: 20_000 }, + ) + .toBe(200); +}); + +test('draft, immutable publish and mobile update', async ({ page, context, request }) => { + const api = 'http://localhost:3000/api/pages/demo-store'; + const draft = await request.get(api + '/draft').then((r) => r.json()); + await request.put(api + '/draft', { data: { ...draft, blocks: [] } }); + + await page.goto('/'); + await page.getByText('标题', { exact: true }).first().dragTo(page.locator('.phone')); + await expect(page.locator('.materials li')).toHaveCount(1); + await page.getByRole('button', { name: '发布' }).click(); + await expect(page.locator('.saved')).toContainText('已发布版本'); + await expect(page.getByRole('status')).toContainText(/发布成功 · 当前版本 v\d+/); + const first = await request.get(api + '/published').then((r) => r.json()); + + const mobile = await context.newPage(); + await mobile.goto('http://localhost:5174/?id=demo-store'); + await expect(mobile.locator('h2')).toHaveCount(1); + + await page.locator('.materials li').first().click(); + await page.getByRole('button', { name: '复制' }).click(); + await page.getByRole('button', { name: '保存草稿' }).click(); + await mobile.reload(); + await expect(mobile.locator('h2')).toHaveCount(1); + expect((await request.get(api + '/published').then((r) => r.json())).version).toBe(first.version); + + await page.getByRole('button', { name: '发布' }).click(); + await expect + .poll(async () => (await request.get(api + '/published').then((r) => r.json())).version) + .toBe(first.version + 1); + await mobile.reload(); + await expect(mobile.locator('h2')).toHaveCount(2); + expect((await request.get(api + '/published').then((r) => r.json())).version).toBe( + first.version + 1, + ); +}); + +test('two-column regions can be edited independently', async ({ page, request }) => { + const api = 'http://localhost:3000/api/pages/demo-store'; + const draft = await request.get(api + '/draft').then((r) => r.json()); + await request.put(api + '/draft', { data: { ...draft, blocks: [] } }); + await page.goto('/'); + await page.getByText('双列容器', { exact: true }).first().click(); + await page.getByLabel('左栏内容').fill('左侧活动专区'); + await page.getByLabel('右栏内容').fill('右侧新品推荐'); + const preview = page.frameLocator('iframe[title="H5 实时预览"]'); + await expect(preview.locator('.cols div').first()).toHaveText('左侧活动专区'); + await expect(preview.locator('.cols div').nth(1)).toHaveText('右侧新品推荐'); +}); + +test('all material-specific editors update the shared preview', async ({ page, request }) => { + const api = 'http://localhost:3000/api/pages/demo-store'; + const draft = await request.get(api + '/draft').then((r) => r.json()); + await request.put(api + '/draft', { data: { ...draft, blocks: [] } }); + await page.goto('/'); + const material = (name: string) => + page.locator('.material-grid').getByRole('button', { name, exact: true }); + const preview = page.frameLocator('iframe[title="H5 实时预览"]'); + + await material('横幅 Banner').click(); + await page.getByLabel('Banner 主标题').fill('可编辑横幅'); + await page.getByLabel('Banner 高度').fill('220'); + await expect(preview.locator('.r-banner h1')).toHaveText('可编辑横幅'); + await expect(preview.locator('.r-banner')).toHaveCSS('height', '220px'); + + await material('标题').click(); + await page.getByLabel('标题文字').fill('可编辑标题'); + await page.getByLabel('标题字号').fill('32'); + await expect(preview.locator('h2')).toHaveText('可编辑标题'); + await expect(preview.locator('h2')).toHaveCSS('font-size', '32px'); + + await material('文本').click(); + await page.getByLabel('正文内容').fill('正文内容与排版均可调整'); + await page.getByLabel('正文行高').fill('2'); + await expect(preview.locator('.r-text')).toHaveText('正文内容与排版均可调整'); + await expect(preview.locator('.r-text')).toHaveCSS('line-height', '28px'); + + await material('图片').click(); + await page.getByLabel('图片说明').fill('编辑后的图片说明'); + await page.getByLabel('图片高度').fill('180'); + await page.getByLabel('图片圆角').fill('16'); + await expect(preview.locator('.r-image')).toHaveAttribute('alt', '编辑后的图片说明'); + await expect(preview.locator('.r-image')).toHaveCSS('height', '180px'); + + await material('按钮').click(); + await page.getByLabel('按钮文字', { exact: true }).fill('查看详情'); + await page.getByLabel('按钮背景颜色').fill('#245f57'); + await page.getByLabel('点击行为').selectOption('link'); + await page.getByLabel('目标链接').fill('/detail'); + await expect(preview.locator('.r-button')).toHaveText('查看详情'); + await expect(preview.locator('.r-button')).toHaveCSS('background-color', 'rgb(36, 95, 87)'); + + await material('商品列表').click(); + await page.getByLabel('商品 1 名称').fill('手工咖啡杯'); + await page.getByRole('button', { name: '添加商品' }).click(); + await page.getByLabel('商品 3 名称').fill('夏日随行杯'); + await page.getByLabel('商品列数').selectOption('1'); + await expect(preview.locator('.products article')).toHaveCount(3); + await expect(preview.locator('.products article').first().locator('b')).toHaveText('手工咖啡杯'); + await expect(preview.locator('.products')).toHaveCSS('grid-template-columns', '350px'); + + await material('分割线').click(); + await page.getByLabel('分割线粗细').fill('4'); + await page.getByLabel('分割线线型').selectOption('dashed'); + await expect(preview.locator('hr')).toHaveCSS('border-top-width', '4px'); + await expect(preview.locator('hr')).toHaveCSS('border-top-style', 'dashed'); + + await material('间距').click(); + await page.getByLabel('间距高度').evaluate((el) => { + const input = el as HTMLInputElement; + input.value = '96'; + input.dispatchEvent(new Event('input', { bubbles: true })); + }); + await expect(preview.locator('.r-spacer')).toHaveCSS('height', '96px'); + + await material('双列容器').click(); + await page.getByLabel('左栏内容').fill('品牌故事'); + await page.getByLabel('右栏内容').fill('新品指南'); + await page.getByLabel('双列间距').fill('20'); + await expect(preview.locator('.cols div').first()).toHaveText('品牌故事'); + await expect(preview.locator('.cols')).toHaveCSS('gap', '20px'); +}); + +test('banner supports multiple images, looping and autoplay interval', async ({ + page, + request, +}) => { + const api = 'http://localhost:3000/api/pages/demo-store'; + const draft = await request.get(api + '/draft').then((r) => r.json()); + await request.put(api + '/draft', { data: { ...draft, blocks: [] } }); + await page.goto('/'); + await page + .locator('.material-grid') + .getByRole('button', { name: '横幅 Banner', exact: true }) + .click(); + await page.getByRole('button', { name: '添加轮播图片' }).click(); + await page + .getByLabel('Banner 图片 2', { exact: true }) + .fill('https://example.com/banner-two.webp'); + await page.getByLabel('Banner 图片 2 说明').fill('第二张轮播图'); + await page.getByLabel('开启轮播').check(); + + const preview = page.frameLocator('iframe[title="H5 实时预览"]'); + const image = preview.locator('.carousel-slide'); + await expect(preview.locator('.carousel-dots button')).toHaveCount(2); + await preview.getByRole('button', { name: '下一张' }).click(); + await expect(image).toHaveAttribute('src', 'https://example.com/banner-two.webp'); + await expect(image).toHaveAttribute('alt', '第二张轮播图'); + await preview.getByRole('button', { name: '上一张' }).click(); + + await page.getByLabel('循环播放').uncheck(); + await page.getByLabel('自动轮播').check(); + await page.getByLabel('自动轮播时间').fill('1'); + await expect + .poll(() => image.getAttribute('src'), { timeout: 2500 }) + .toBe('https://example.com/banner-two.webp'); + await page.waitForTimeout(1200); + await expect(image).toHaveAttribute('src', 'https://example.com/banner-two.webp'); + + await page.getByLabel('循环播放').check(); + await expect + .poll(() => image.getAttribute('src'), { timeout: 2500 }) + .not.toBe('https://example.com/banner-two.webp'); +}); + +test('editor and H5 service URLs follow the current LAN host', async ({ page }) => { + await page.goto('http://127.0.0.1:5173'); + await expect(page.locator('iframe[title="H5 实时预览"]')).toHaveAttribute( + 'src', + 'http://127.0.0.1:5174/?preview=1', + ); + await expect(page.getByRole('link', { name: '打开移动端' })).toHaveAttribute( + 'href', + 'http://127.0.0.1:5174/?id=demo-store', + ); + + await page.goto('http://127.0.0.1:5174/?id=demo-store'); + await expect(page.locator('.page-renderer')).toBeVisible(); +}); + +test('benefit library keeps marketing materials and renders direct recharge components', async ({ + page, + request, +}) => { + const api = 'http://localhost:3000/api/pages/demo-store'; + const draft = await request.get(api + '/draft').then((r) => r.json()); + await request.put(api + '/draft', { data: { ...draft, blocks: [] } }); + await page.goto('/'); + + await expect(page.getByRole('button', { name: '营销组件', exact: true })).toHaveClass(/active/); + await expect(page.getByRole('button', { name: '横幅 Banner', exact: true })).toBeVisible(); + await page.getByRole('button', { name: /直充 \/ 卡密/ }).click(); + await expect(page.getByRole('button', { name: /直充 \/ 卡密/ })).toHaveClass(/active/); + await expect(page.getByRole('button', { name: '权益商品卡片', exact: true })).toBeVisible(); + await page.getByRole('button', { name: '添加业务示例组合' }).click(); + + const preview = page.frameLocator('iframe[title="H5 实时预览"]'); + await expect(page.locator('.materials li')).toHaveCount(4); + await expect(preview.locator('.benefit-product-card')).toBeVisible(); + await expect(preview.locator('.benefit-actions')).toBeVisible(); + await expect(preview.locator('.benefit-notice')).toBeVisible(); + await expect(preview.locator('.benefit-product-list')).toBeVisible(); + + await page.locator('.materials li').first().click(); + await page.getByLabel('权益商品名称').fill('网易云音乐黑胶月卡'); + await page.getByLabel('权益交付方式').selectOption('code'); + await page.getByLabel('权益业务标签').fill('卡密'); + await expect(preview.locator('.benefit-product-card .benefit-name-row b')).toHaveText( + '网易云音乐黑胶月卡', + ); + await expect(preview.locator('.benefit-product-card .delivery-badge')).toHaveText('卡密'); + + await page.getByRole('button', { name: '营销组件', exact: true }).click(); + await expect(page.getByRole('button', { name: '横幅 Banner', exact: true })).toBeVisible(); + await expect(page.locator('.materials li')).toHaveCount(4); +});