diff --git a/client.d.ts b/client.d.ts
index e6afb9f3b..266ece901 100644
--- a/client.d.ts
+++ b/client.d.ts
@@ -4,13 +4,16 @@ declare module 'vue-router/auto-routes' {
/**
* Array of routes generated by unplugin-vue-router
*/
- export const routes: RouteRecordRaw[]
+ export const routes: readonly RouteRecordRaw[]
/**
* Setups hot module replacement for routes.
+ *
* @param router - The router instance
* @param hotUpdateCallback - Callback to be called after replacing the routes and before the navigation
+ *
* @example
+ *
* ```ts
* import { createRouter, createWebHistory } from 'vue-router'
* import { routes, handleHotUpdate } from 'vue-router/auto-routes'
@@ -19,7 +22,7 @@ declare module 'vue-router/auto-routes' {
* routes,
* })
* if (import.meta.hot) {
- * handleHotUpdate(router)
+ * handleHotUpdate(router)
* }
* ```
*/
@@ -31,9 +34,11 @@ declare module 'vue-router/auto-routes' {
declare module 'vue-router' {
import type { RouteNamedMap } from 'vue-router/auto-routes'
+ import type { ParamParserCustom } from 'vue-router/auto-resolver'
export interface TypesConfig {
RouteNamedMap: RouteNamedMap
+ ParamParsers: ParamParserCustom
}
}
diff --git a/examples/nuxt/package.json b/examples/nuxt/package.json
index bcb1cb9e0..4079ab5c1 100644
--- a/examples/nuxt/package.json
+++ b/examples/nuxt/package.json
@@ -8,7 +8,7 @@
},
"devDependencies": {
"@pinia/colada-nuxt": "^0.2.1",
- "@pinia/nuxt": "^0.11.1",
+ "@pinia/nuxt": "^0.11.2",
"nuxt": "^3.17.5",
"unplugin-vue-router": "workspace:*"
},
diff --git a/package.json b/package.json
index 1e77f56c2..b6bf3d5c8 100644
--- a/package.json
+++ b/package.json
@@ -139,7 +139,8 @@
]
},
"dependencies": {
- "@vue-macros/common": "3.0.0",
+ "@babel/generator": "^7.28.3",
+ "@vue-macros/common": "^3.0.0",
"@vue/language-core": "^3.0.7",
"ast-walker-scope": "^0.8.2",
"chokidar": "^4.0.3",
@@ -171,7 +172,8 @@
"@posva/prompts": "^2.4.4",
"@shikijs/vitepress-twoslash": "3.13.0",
"@tanstack/vue-query": "^5.89.0",
- "@types/node": "^22.18.6",
+ "@types/babel__generator": "^7.27.0",
+ "@types/node": "^24.3.0",
"@types/picomatch": "^4.0.2",
"@vitest/coverage-v8": "^3.2.4",
"@vitest/ui": "^3.2.4",
@@ -203,7 +205,7 @@
"vitepress-plugin-llms": "^1.7.5",
"vitest": "^3.2.4",
"vue": "^3.5.21",
- "vue-router": "^4.5.1",
+ "vue-router": "https://pkg.pr.new/vue-router@4f1a37a",
"vue-router-mock": "^2.0.0",
"vue-tsc": "^3.0.7",
"vuefire": "^3.2.2",
diff --git a/playground-experimental/.gitignore b/playground-experimental/.gitignore
new file mode 100644
index 000000000..cc1b7f164
--- /dev/null
+++ b/playground-experimental/.gitignore
@@ -0,0 +1 @@
+tsconfig.tsbuildinfo
diff --git a/playground-experimental/auto-imports.d.ts b/playground-experimental/auto-imports.d.ts
new file mode 100644
index 000000000..f77a9b47b
--- /dev/null
+++ b/playground-experimental/auto-imports.d.ts
@@ -0,0 +1,14 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// noinspection JSUnusedGlobalSymbols
+// Generated by unplugin-auto-import
+// biome-ignore lint: disable
+export {}
+declare global {
+ const defineBasicLoader: typeof import('../src/data-loaders/entries/basic')['defineBasicLoader']
+ const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
+ const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
+ const useRoute: typeof import('vue-router')['useRoute']
+ const useRouter: typeof import('vue-router')['useRouter']
+}
diff --git a/playground-experimental/db.json b/playground-experimental/db.json
new file mode 100644
index 000000000..53e4f7413
--- /dev/null
+++ b/playground-experimental/db.json
@@ -0,0 +1,24 @@
+{
+ "todos": [
+ {
+ "id": 2,
+ "title": "Walking",
+ "completed": true
+ },
+ {
+ "id": 3,
+ "title": "Cleaning",
+ "completed": false
+ },
+ {
+ "id": 4,
+ "title": "Cooking",
+ "completed": true
+ },
+ {
+ "title": "hello",
+ "completed": false,
+ "id": 7
+ }
+ ]
+}
\ No newline at end of file
diff --git a/playground-experimental/env.d.ts b/playground-experimental/env.d.ts
new file mode 100644
index 000000000..dabd0deba
--- /dev/null
+++ b/playground-experimental/env.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/playground-experimental/index.html b/playground-experimental/index.html
new file mode 100644
index 000000000..e69792de0
--- /dev/null
+++ b/playground-experimental/index.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+ visit /__inspect/ to inspect the intermediate state
+
+
+
+
diff --git a/playground-experimental/package.json b/playground-experimental/package.json
new file mode 100644
index 000000000..03ec9010f
--- /dev/null
+++ b/playground-experimental/package.json
@@ -0,0 +1,23 @@
+{
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "nodemon -w '../src/**/*.ts' -e .ts -x vite",
+ "json-server": "json-server --watch db.json --port 4000",
+ "playground:build": "vite build"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^6.0.1",
+ "@vue/compiler-sfc": "^3.5.18",
+ "@vue/tsconfig": "^0.7.0",
+ "json-server": "^0.17.4",
+ "unplugin-vue-router": "workspace:*",
+ "vite": "^7.1.2"
+ },
+ "dependencies": {
+ "mande": "^2.0.9",
+ "pinia": "^3.0.3",
+ "vue": "^3.5.18",
+ "vue-router": "https://pkg.pr.new/vue-router@4f1a37a"
+ }
+}
diff --git a/playground-experimental/src/App.vue b/playground-experimental/src/App.vue
new file mode 100644
index 000000000..19683d635
--- /dev/null
+++ b/playground-experimental/src/App.vue
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/playground-experimental/src/main.ts b/playground-experimental/src/main.ts
new file mode 100644
index 000000000..13178acb5
--- /dev/null
+++ b/playground-experimental/src/main.ts
@@ -0,0 +1,29 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import { createPinia } from 'pinia'
+import { PiniaColada } from '@pinia/colada'
+import { router } from './router'
+import { DataLoaderPlugin } from 'unplugin-vue-router/data-loaders'
+import { RouterLink, RouterView } from 'vue-router'
+
+const app = createApp(App)
+
+app.use(createPinia())
+app.use(PiniaColada, {})
+// @ts-expect-error: FIXME: should be doable
+app.use(DataLoaderPlugin, { router })
+app.component('RouterLink', RouterLink)
+app.component('RouterView', RouterView)
+app.use(router)
+
+// @ts-expect-error: for debugging on browser
+window.$router = router
+
+app.mount('#app')
+
+// small logger for navigations, useful to check HMR
+router.isReady().then(() => {
+ router.beforeEach((to, from) => {
+ console.log('🧭', from.fullPath, '->', to.fullPath)
+ })
+})
diff --git a/playground-experimental/src/page-outside.vue b/playground-experimental/src/page-outside.vue
new file mode 100644
index 000000000..2848777a2
--- /dev/null
+++ b/playground-experimental/src/page-outside.vue
@@ -0,0 +1,5 @@
+
+
+
+ Page outside of pages
+
diff --git a/playground-experimental/src/pages/(home).vue b/playground-experimental/src/pages/(home).vue
new file mode 100644
index 000000000..746ed526b
--- /dev/null
+++ b/playground-experimental/src/pages/(home).vue
@@ -0,0 +1,7 @@
+
+
+
+ Home
+
+ This is the homepage.
+
diff --git a/playground-experimental/src/pages/[...path].vue b/playground-experimental/src/pages/[...path].vue
new file mode 100644
index 000000000..4b83b0349
--- /dev/null
+++ b/playground-experimental/src/pages/[...path].vue
@@ -0,0 +1,71 @@
+
+
+
+
+ Not Found
+
+ {{ $route.params }}
+ {{ $route.query }}
+ {{ $route.meta }}
+
+
+
+
+{
+ "meta": {
+ "from block": true
+ }
+}
+
diff --git a/playground-experimental/src/pages/a.[b].c.[d].vue b/playground-experimental/src/pages/a.[b].c.[d].vue
new file mode 100644
index 000000000..29b5f2e77
--- /dev/null
+++ b/playground-experimental/src/pages/a.[b].c.[d].vue
@@ -0,0 +1,5 @@
+
+
+
+ {{ String($route.name) }} - {{ $route.path }}
+
diff --git a/playground-experimental/src/pages/b.vue b/playground-experimental/src/pages/b.vue
new file mode 100644
index 000000000..2ec140b33
--- /dev/null
+++ b/playground-experimental/src/pages/b.vue
@@ -0,0 +1,5 @@
+
+
+
+ Page B
+
diff --git a/playground-experimental/src/pages/blog/[[slugOptional]]+.vue b/playground-experimental/src/pages/blog/[[slugOptional]]+.vue
new file mode 100644
index 000000000..76cf19383
--- /dev/null
+++ b/playground-experimental/src/pages/blog/[[slugOptional]]+.vue
@@ -0,0 +1,7 @@
+
+
+
+ Blog
+
+ {{ $route.params }}
+
diff --git a/playground-experimental/src/pages/blog/[slug]+.vue b/playground-experimental/src/pages/blog/[slug]+.vue
new file mode 100644
index 000000000..76cf19383
--- /dev/null
+++ b/playground-experimental/src/pages/blog/[slug]+.vue
@@ -0,0 +1,7 @@
+
+
+
+ Blog
+
+ {{ $route.params }}
+
diff --git a/playground-experimental/src/pages/blog/info/(info).vue b/playground-experimental/src/pages/blog/info/(info).vue
new file mode 100644
index 000000000..42c010722
--- /dev/null
+++ b/playground-experimental/src/pages/blog/info/(info).vue
@@ -0,0 +1,30 @@
+
+
+
+ Blog Info - Index
+
+
+ -
+ Go to Section null
+
+ -
+ Go to Section 1
+
+
+
diff --git a/playground-experimental/src/pages/blog/info/[[section]].vue b/playground-experimental/src/pages/blog/info/[[section]].vue
new file mode 100644
index 000000000..ca35246d7
--- /dev/null
+++ b/playground-experimental/src/pages/blog/info/[[section]].vue
@@ -0,0 +1,7 @@
+
+
+
+ Blog Info
+
+ {{ $route.params }}
+
diff --git a/playground-experimental/src/pages/events/[when=date].vue b/playground-experimental/src/pages/events/[when=date].vue
new file mode 100644
index 000000000..b3f7a9a8a
--- /dev/null
+++ b/playground-experimental/src/pages/events/[when=date].vue
@@ -0,0 +1,8 @@
+
+
+
+ Date
+ {{ typeof route.params.when }} - {{ route.params.when }}
+
diff --git a/playground-experimental/src/pages/events/repeat/[when=date]+.vue b/playground-experimental/src/pages/events/repeat/[when=date]+.vue
new file mode 100644
index 000000000..c34fc7056
--- /dev/null
+++ b/playground-experimental/src/pages/events/repeat/[when=date]+.vue
@@ -0,0 +1,9 @@
+
+
+
+ Date
+ {{ typeof route.params.when }} - {{ route.params.when }}
+
diff --git a/playground-experimental/src/pages/tests/[[optional]]/end.vue b/playground-experimental/src/pages/tests/[[optional]]/end.vue
new file mode 100644
index 000000000..4f5b429ed
--- /dev/null
+++ b/playground-experimental/src/pages/tests/[[optional]]/end.vue
@@ -0,0 +1,7 @@
+
+
+
+ Optional param in the middle
+
+ {{ $route.fullPath }}
+
diff --git a/playground-experimental/src/pages/u[name].vue b/playground-experimental/src/pages/u[name].vue
new file mode 100644
index 000000000..2d5ad6847
--- /dev/null
+++ b/playground-experimental/src/pages/u[name].vue
@@ -0,0 +1,8 @@
+
+
+
+ Named param
+ {{ $route.params }}
+
+
+
diff --git a/playground-experimental/src/pages/u[name]/24.vue b/playground-experimental/src/pages/u[name]/24.vue
new file mode 100644
index 000000000..b5778f70e
--- /dev/null
+++ b/playground-experimental/src/pages/u[name]/24.vue
@@ -0,0 +1,6 @@
+
+
+
+ Nested [name] page
+ Too test specificity in routes
+
diff --git a/playground-experimental/src/pages/u[name]/[userId=int].vue b/playground-experimental/src/pages/u[name]/[userId=int].vue
new file mode 100644
index 000000000..b8e8f105c
--- /dev/null
+++ b/playground-experimental/src/pages/u[name]/[userId=int].vue
@@ -0,0 +1,7 @@
+
+
+
+ This page should never be visible
+
+
+
diff --git a/playground-experimental/src/pages/users/[userId=int].vue b/playground-experimental/src/pages/users/[userId=int].vue
new file mode 100644
index 000000000..4e7524625
--- /dev/null
+++ b/playground-experimental/src/pages/users/[userId=int].vue
@@ -0,0 +1,32 @@
+
+
+
+ {{ String($route.name) }} - {{ $route.path }}
+
+ {{ route.params }}
+
diff --git a/playground-experimental/src/pages/users/sub-[first]-[second].vue b/playground-experimental/src/pages/users/sub-[first]-[second].vue
new file mode 100644
index 000000000..29b5f2e77
--- /dev/null
+++ b/playground-experimental/src/pages/users/sub-[first]-[second].vue
@@ -0,0 +1,5 @@
+
+
+
+ {{ String($route.name) }} - {{ $route.path }}
+
diff --git a/playground-experimental/src/params/date.ts b/playground-experimental/src/params/date.ts
new file mode 100644
index 000000000..eb85e9bdc
--- /dev/null
+++ b/playground-experimental/src/params/date.ts
@@ -0,0 +1,31 @@
+import { defineParamParser, miss } from 'vue-router/experimental'
+
+function toDate(value: string): Date {
+ const asDate = new Date(value)
+ if (Number.isNaN(asDate.getTime())) {
+ throw miss(`Invalid date: "${value}"`)
+ }
+
+ return asDate
+}
+
+function toString(value: Date): string {
+ return (
+ value
+ .toISOString()
+ // allows keeping simple dates like 2023-10-01 without time
+ // while still being able to parse full dates like 2023-10-01T12:00:00.000Z
+ .replace('T00:00:00.000Z', '')
+ )
+}
+
+export const parser = defineParamParser({
+ get: (value: string | string[] | null) => {
+ if (!value) {
+ throw miss()
+ }
+ return Array.isArray(value) ? value.map(toDate) : toDate(value)
+ },
+ set: (value: Date | Date[]) =>
+ Array.isArray(value) ? value.map(toString) : toString(value),
+})
diff --git a/playground-experimental/src/router.ts b/playground-experimental/src/router.ts
new file mode 100644
index 000000000..13d6dc9ab
--- /dev/null
+++ b/playground-experimental/src/router.ts
@@ -0,0 +1,37 @@
+import { experimental_createRouter } from 'vue-router/experimental'
+import { resolver, handleHotUpdate } from 'vue-router/auto-resolver'
+
+import {
+ type RouteRecordInfo,
+ type ParamValue,
+ createWebHistory,
+} from 'vue-router'
+
+export const router = experimental_createRouter({
+ history: createWebHistory(),
+ resolver,
+})
+
+if (import.meta.hot) {
+ handleHotUpdate(router)
+}
+
+// manual extension of route types
+declare module 'vue-router/auto-routes' {
+ export interface RouteNamedMap {
+ 'custom-dynamic-name': RouteRecordInfo<
+ 'custom-dynamic-name',
+ '/added-during-runtime/[...path]',
+ { path: ParamValue },
+ { path: ParamValue },
+ 'custom-dynamic-child-name'
+ >
+ 'custom-dynamic-child-name': RouteRecordInfo<
+ 'custom-dynamic-child-name',
+ '/added-during-runtime/[...path]/child',
+ { path: ParamValue },
+ { path: ParamValue },
+ never
+ >
+ }
+}
diff --git a/playground-experimental/tsconfig.config.json b/playground-experimental/tsconfig.config.json
new file mode 100644
index 000000000..fdcee2d99
--- /dev/null
+++ b/playground-experimental/tsconfig.config.json
@@ -0,0 +1,8 @@
+{
+ "extends": "@vue/tsconfig/tsconfig.json",
+ "include": ["vite.config.*", "vitest.config.*", "cypress.config.*"],
+ "compilerOptions": {
+ "composite": true,
+ "types": ["node"]
+ }
+}
diff --git a/playground-experimental/tsconfig.json b/playground-experimental/tsconfig.json
new file mode 100644
index 000000000..7106ea827
--- /dev/null
+++ b/playground-experimental/tsconfig.json
@@ -0,0 +1,47 @@
+{
+ "extends": "@vue/tsconfig/tsconfig.dom.json",
+ "include": [
+ "./env.d.ts",
+ "./src/**/*.ts",
+ "./src/**/*.vue",
+ "./typed-router.d.ts",
+ "./auto-imports.d.ts",
+ "../src"
+ ],
+ "compilerOptions": {
+ "baseUrl": ".",
+ "composite": true,
+ "moduleResolution": "Bundler",
+ "paths": {
+ "@/*": [
+ "./src/*"
+ ],
+ "unplugin-vue-router/runtime": [
+ "../src/runtime.ts"
+ ],
+ "unplugin-vue-router/types": [
+ "../src/types.ts"
+ ],
+ "unplugin-vue-router/data-loaders": [
+ "../src/data-loaders/entries/index.ts"
+ ],
+ "unplugin-vue-router/data-loaders/basic": [
+ "../src/data-loaders/entries/basic.ts"
+ ],
+ "unplugin-vue-router/data-loaders/pinia-colada": [
+ "../src/data-loaders/entries/pinia-colada.ts"
+ ]
+ }
+ },
+ "vueCompilerOptions": {
+ "plugins": [
+ "unplugin-vue-router/volar/sfc-route-blocks",
+ "unplugin-vue-router/volar/sfc-typed-router"
+ ]
+ },
+ "references": [
+ {
+ "path": "./tsconfig.config.json"
+ }
+ ]
+}
diff --git a/playground-experimental/typed-router.d.ts b/playground-experimental/typed-router.d.ts
new file mode 100644
index 000000000..575766309
--- /dev/null
+++ b/playground-experimental/typed-router.d.ts
@@ -0,0 +1,138 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️
+// It's recommended to commit this file.
+// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
+
+// Custom route params parsers
+type Param_date = ReturnType>
+
+declare module 'vue-router/auto-resolver' {
+ export type ParamParserCustom = 'date'
+}
+
+declare module 'vue-router/auto-routes' {
+ import type {
+ RouteRecordInfo,
+ ParamValue,
+ ParamValueOneOrMore,
+ ParamValueZeroOrMore,
+ ParamValueZeroOrOne,
+ } from 'vue-router'
+
+ /**
+ * Route name map generated by unplugin-vue-router
+ */
+ export interface RouteNamedMap {
+ '/(home)': RouteRecordInfo<'/(home)', '/', Record, Record>,
+ 'not-found': RouteRecordInfo<'not-found', '/:path(.*)', { path: string, page?: number, other?: boolean, active?: boolean, multi: string[], req?: number, when?: Exclude }, { path: string, page: number, other: boolean, active: boolean, multi: string[], req: number, when: Exclude }>,
+ '/a.[b].c.[d]': RouteRecordInfo<'/a.[b].c.[d]', '/a/:b/c/:d', { b: string, d: string }, { b: string, d: string }>,
+ '/b': RouteRecordInfo<'/b', '/b', Record, Record>,
+ '/blog/[slug]+': RouteRecordInfo<'/blog/[slug]+', '/blog/:slug+', { slug: string[] }, { slug: string[] }>,
+ '/blog/[[slugOptional]]+': RouteRecordInfo<'/blog/[[slugOptional]]+', '/blog/:slugOptional*', { slugOptional?: string[] }, { slugOptional: string[] }>,
+ '/blog/info/(info)': RouteRecordInfo<'/blog/info/(info)', '/blog/info', Record, Record>,
+ '/blog/info/[[section]]': RouteRecordInfo<'/blog/info/[[section]]', '/blog/info/:section?', { section?: string | null }, { section: string | null }>,
+ '/events/[when=date]': RouteRecordInfo<'/events/[when=date]', '/events/:when', { when: Exclude }, { when: Exclude }>,
+ '/events/repeat/[when=date]+': RouteRecordInfo<'/events/repeat/[when=date]+', '/events/repeat/:when+', { when: Extract }, { when: Extract }>,
+ '/manually-added': RouteRecordInfo<'/manually-added', '/manually-added', Record, Record>,
+ '/tests/[[optional]]/end': RouteRecordInfo<'/tests/[[optional]]/end', '/tests/:optional?/end', { optional?: string | null }, { optional: string | null }>,
+ '/u[name]': RouteRecordInfo<'/u[name]', '/u:name', { name: string }, { name: string }, '/u[name]/24' | '/u[name]/[userId=int]'>,
+ '/u[name]/[userId=int]': RouteRecordInfo<'/u[name]/[userId=int]', '/u:name/:userId', { name: string, userId: number }, { name: string, userId: number }>,
+ '/u[name]/24': RouteRecordInfo<'/u[name]/24', '/u:name/24', { name: string }, { name: string }>,
+ '/users/[userId=int]': RouteRecordInfo<'/users/[userId=int]', '/users/:userId', { userId: number, anyParam?: string, page?: number }, { userId: number, anyParam: string, page: number }>,
+ '/users/sub-[first]-[second]': RouteRecordInfo<'/users/sub-[first]-[second]', '/users/sub-:first-:second', { first: string, second: string }, { first: string, second: string }>,
+ }
+
+ /**
+ * Route file to route info map by unplugin-vue-router.
+ * Used by the volar plugin to automatically type useRoute()
+ *
+ * Each key is a file path relative to the project root with 2 properties:
+ * - routes: union of route names of the possible routes when in this page (passed to useRoute<...>())
+ * - views: names of nested views (can be passed to )
+ *
+ * @internal
+ */
+ export interface _RouteFileInfoMap {
+ 'src/pages/(home).vue': {
+ routes: '/(home)'
+ views: never
+ }
+ 'src/pages/[...path].vue': {
+ routes: 'not-found'
+ views: never
+ }
+ 'src/pages/a.[b].c.[d].vue': {
+ routes: '/a.[b].c.[d]'
+ views: never
+ }
+ 'src/pages/b.vue': {
+ routes: '/b'
+ views: never
+ }
+ 'src/pages/blog/[slug]+.vue': {
+ routes: '/blog/[slug]+'
+ views: never
+ }
+ 'src/pages/blog/[[slugOptional]]+.vue': {
+ routes: '/blog/[[slugOptional]]+'
+ views: never
+ }
+ 'src/pages/blog/info/(info).vue': {
+ routes: '/blog/info/(info)'
+ views: never
+ }
+ 'src/pages/blog/info/[[section]].vue': {
+ routes: '/blog/info/[[section]]'
+ views: never
+ }
+ 'src/pages/events/[when=date].vue': {
+ routes: '/events/[when=date]'
+ views: never
+ }
+ 'src/pages/events/repeat/[when=date]+.vue': {
+ routes: '/events/repeat/[when=date]+'
+ views: never
+ }
+ 'src/page-outside.vue': {
+ routes: '/manually-added'
+ views: never
+ }
+ 'src/pages/tests/[[optional]]/end.vue': {
+ routes: '/tests/[[optional]]/end'
+ views: never
+ }
+ 'src/pages/u[name].vue': {
+ routes: '/u[name]' | '/u[name]/[userId=int]' | '/u[name]/24'
+ views: 'default'
+ }
+ 'src/pages/u[name]/[userId=int].vue': {
+ routes: '/u[name]/[userId=int]'
+ views: never
+ }
+ 'src/pages/u[name]/24.vue': {
+ routes: '/u[name]/24'
+ views: never
+ }
+ 'src/pages/users/[userId=int].vue': {
+ routes: '/users/[userId=int]'
+ views: never
+ }
+ 'src/pages/users/sub-[first]-[second].vue': {
+ routes: '/users/sub-[first]-[second]'
+ views: never
+ }
+ }
+
+ /**
+ * Get a union of possible route names in a certain route component file.
+ * Used by the volar plugin to automatically type useRoute()
+ *
+ * @internal
+ */
+ export type _RouteNamesForFilePath =
+ _RouteFileInfoMap extends Record
+ ? Info['routes']
+ : keyof RouteNamedMap
+}
diff --git a/playground-experimental/vite.config.ts b/playground-experimental/vite.config.ts
new file mode 100644
index 000000000..896ff0333
--- /dev/null
+++ b/playground-experimental/vite.config.ts
@@ -0,0 +1,129 @@
+import { fileURLToPath, URL } from 'node:url'
+import { defineConfig } from 'vite'
+import Markdown from 'unplugin-vue-markdown/vite'
+// @ts-ignore: the plugin should not be checked in the playground
+import VueRouter from '../src/vite'
+import { VueRouterAutoImports } from '../src'
+import Vue from '@vitejs/plugin-vue'
+import AutoImport from 'unplugin-auto-import/vite'
+import VueDevtools from 'vite-plugin-vue-devtools'
+import { join, relative } from 'node:path'
+
+export default defineConfig({
+ clearScreen: false,
+ resolve: {
+ alias: {
+ '@': fileURLToPath(new URL('./src', import.meta.url)),
+ '~': fileURLToPath(new URL('./src', import.meta.url)),
+ 'unplugin-vue-router/runtime': fileURLToPath(
+ new URL('../src/runtime.ts', import.meta.url)
+ ),
+ 'unplugin-vue-router/types': fileURLToPath(
+ new URL('../src/types.ts', import.meta.url)
+ ),
+ 'unplugin-vue-router/data-loaders/basic': fileURLToPath(
+ new URL('../src/data-loaders/entries/basic.ts', import.meta.url)
+ ),
+ 'unplugin-vue-router/data-loaders/pinia-colada': fileURLToPath(
+ new URL('../src/data-loaders/entries/pinia-colada.ts', import.meta.url)
+ ),
+ 'unplugin-vue-router/data-loaders': fileURLToPath(
+ new URL('../src/data-loaders/entries/index.ts', import.meta.url)
+ ),
+ },
+ },
+ build: {
+ sourcemap: true,
+ },
+ optimizeDeps: {
+ exclude: [
+ // easier to test with yalc
+ '@pinia/colada',
+ ],
+ },
+
+ plugins: [
+ VueRouter({
+ extensions: ['.page.vue', '.vue'],
+ importMode: 'async',
+ logs: true,
+ // getRouteName: getPascalCaseRouteName,
+ experimental: {
+ autoExportsDataLoaders: ['src/loaders/**/*', '@/loaders/**/*'],
+ paramParsers: true,
+ },
+ extendRoute(route) {
+ // example of deleting routes
+ // if (route.name.startsWith('/users')) {
+ // route.delete()
+ // }
+
+ if (route.name === '/[name]') {
+ // TODO: implement aliases
+ // route.addAlias('/hello-vite-:name')
+ }
+
+ // TODO: implement insertions
+ // const newRoute = root.insert(
+ // '/custom/page',
+ // route.components.get('default')!
+ // )
+ // newRoute.components.set('default', route.components.get('default')!)
+ // newRoute.meta = {
+ // 'custom-meta': 'works',
+ // }
+ // }
+ },
+ beforeWriteFiles(root) {
+ root.insert(
+ '/manually-added',
+ join(__dirname, './src/page-outside.vue')
+ )
+ },
+ routesFolder: [
+ // can add multiple routes folders
+ {
+ src: 'src/pages',
+ },
+ {
+ src: 'src/docs',
+ path: 'docs/[lang]/',
+ // doesn't take into account files directly at src/docs, only subfolders
+ filePatterns: ['*/**'],
+ // ignores .vue files
+ extensions: ['.md'],
+ },
+ {
+ src: 'src/features',
+ filePatterns: '*/pages/**/*',
+ path: (file) => {
+ return relative('src/features', file).replace(/^pages\//, '')
+ },
+ },
+ ],
+ exclude: ['**/ignored/**', '**/__*', '**/__**/*', '**/*.component.vue'],
+ }),
+ Vue({
+ include: [/\.vue$/, /\.md$/],
+ }),
+ Markdown({}),
+ AutoImport({
+ imports: [
+ VueRouterAutoImports,
+ {
+ // NOTE: we need to match the resolved paths to local files for development
+ // instead of just 'unplugin-vue-router/data-loaders/basic': ['defineBasicLoader'],
+ [fileURLToPath(
+ new URL('../src/data-loaders/entries/basic.ts', import.meta.url)
+ )]: ['defineBasicLoader'],
+ // [fileURLToPath(
+ // new URL('../src/data-loaders/entries/pinia-colada.ts', import.meta.url)
+ // )]: ['defineColadaLoader'],
+ },
+ ],
+ }),
+ // currently the devtools use 0.8.8 but we care more about
+ // inspecting virtual files
+ VueDevtools(),
+ ],
+})
diff --git a/playground/src/pages/(some-layout).vue b/playground/src/pages/(some-layout).vue
index cf0fff14d..ba4dd01c9 100644
--- a/playground/src/pages/(some-layout).vue
+++ b/playground/src/pages/(some-layout).vue
@@ -1,6 +1,6 @@
diff --git a/playground/src/params/date.ts b/playground/src/params/date.ts
new file mode 100644
index 000000000..490dbb617
--- /dev/null
+++ b/playground/src/params/date.ts
@@ -0,0 +1,18 @@
+// NOTE: should be imported from vue-router
+const invalid = (...args: ConstructorParameters) =>
+ new Error(...args)
+
+export const parse = (value: string): Date => {
+ const asDate = new Date(value)
+ if (Number.isNaN(asDate.getTime())) {
+ throw invalid(`Invalid date: "${value}"`)
+ }
+
+ return asDate
+}
+
+export const toString = (value: Date): string =>
+ value
+ .toISOString()
+ // allows keeping simple dates like 2023-10-01 without time
+ .replace('T00:00:00.000Z', '')
diff --git a/playground/src/params/number.ts b/playground/src/params/number.ts
new file mode 100644
index 000000000..3d70f3913
--- /dev/null
+++ b/playground/src/params/number.ts
@@ -0,0 +1,14 @@
+// NOTE: should be imported from vue-router
+const invalid = (...args: ConstructorParameters) =>
+ new Error(...args)
+
+export const parse = (value: string): number => {
+ const asNumber = Number(value)
+ if (Number.isFinite(asNumber)) {
+ return asNumber
+ }
+ throw invalid(`Expected a number, but received: ${value}`)
+}
+
+// Same as default serializer
+export const toString = (value: number): string => String(value)
diff --git a/playground/tsconfig.json b/playground/tsconfig.json
index 3bfd00252..a2baae089 100644
--- a/playground/tsconfig.json
+++ b/playground/tsconfig.json
@@ -5,23 +5,16 @@
"./src/**/*.ts",
"./src/**/*.vue",
"./typed-router.d.ts",
- "./auto-imports.d.ts",
- "../src"
+ "./auto-imports.d.ts"
],
"compilerOptions": {
"baseUrl": ".",
"composite": true,
"moduleResolution": "Bundler",
"paths": {
- "@/*": [
- "./src/*"
- ],
- "unplugin-vue-router/runtime": [
- "../src/runtime.ts"
- ],
- "unplugin-vue-router/types": [
- "../src/types.ts"
- ],
+ "@/*": ["./src/*"],
+ "unplugin-vue-router/runtime": ["../src/runtime.ts"],
+ "unplugin-vue-router/types": ["../src/types.ts"],
"unplugin-vue-router/data-loaders": [
"../src/data-loaders/entries/index.ts"
],
@@ -30,8 +23,8 @@
],
"unplugin-vue-router/data-loaders/pinia-colada": [
"../src/data-loaders/entries/pinia-colada.ts"
- ],
- },
+ ]
+ }
},
"vueCompilerOptions": {
"plugins": [
diff --git a/playground/typed-router.d.ts b/playground/typed-router.d.ts
index c4b6e56f2..46d7baf0a 100644
--- a/playground/typed-router.d.ts
+++ b/playground/typed-router.d.ts
@@ -5,6 +5,10 @@
// It's recommended to commit this file.
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
+declare module 'vue-router/auto-resolver' {
+ export type ParamParserCustom = never
+}
+
declare module 'vue-router/auto-routes' {
import type {
RouteRecordInfo,
diff --git a/playground/vite.config.ts b/playground/vite.config.ts
index 6f93f16b4..69d2c7a30 100644
--- a/playground/vite.config.ts
+++ b/playground/vite.config.ts
@@ -9,6 +9,8 @@ import Vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import VueDevtools from 'vite-plugin-vue-devtools'
+const __dirname = fileURLToPath(new URL('.', import.meta.url))
+
export default defineConfig({
clearScreen: false,
resolve: {
@@ -50,12 +52,14 @@ export default defineConfig({
// getRouteName: getPascalCaseRouteName,
experimental: {
autoExportsDataLoaders: ['src/loaders/**/*', '@/loaders/**/*'],
+ paramParsers: false,
},
extendRoute(route) {
route.params.forEach((param) => {
// transform kebab-case to camelCase
- param.paramName = param.paramName.replace(/-([a-z])/g, (g) =>
- g[1].toUpperCase()
+ param.paramName = param.paramName.replace(
+ /-([a-z])/g,
+ (g) => g[1]?.toUpperCase() || ''
)
})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7596d5c61..012ef95ba 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,9 +8,12 @@ importers:
.:
dependencies:
+ '@babel/generator':
+ specifier: ^7.28.3
+ version: 7.28.3
'@vue-macros/common':
- specifier: 3.0.0
- version: 3.0.0(vue@3.5.21(typescript@5.9.2))
+ specifier: ^3.0.0
+ version: 3.1.0(vue@3.5.21(typescript@5.9.2))
'@vue/compiler-sfc':
specifier: ^3.5.17
version: 3.5.18
@@ -75,9 +78,12 @@ importers:
'@tanstack/vue-query':
specifier: ^5.89.0
version: 5.89.0(vue@3.5.21(typescript@5.9.2))
+ '@types/babel__generator':
+ specifier: ^7.27.0
+ version: 7.27.0
'@types/node':
- specifier: ^22.18.6
- version: 22.18.6
+ specifier: ^24.3.0
+ version: 24.3.0
'@types/picomatch':
specifier: ^4.0.2
version: 4.0.2
@@ -128,7 +134,7 @@ importers:
version: 6.0.1
rollup:
specifier: ^4.52.0
- version: 4.52.0
+ version: 4.52.2
semver:
specifier: ^7.7.2
version: 7.7.2
@@ -146,34 +152,34 @@ importers:
version: 20.1.0(@vueuse/core@12.5.0(typescript@5.9.2))
unplugin-vue-markdown:
specifier: ^29.1.0
- version: 29.1.0(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))
+ version: 29.1.0(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))
unplugin-vue-router:
specifier: workspace:*
version: 'link:'
vite:
specifier: ^7.1.6
- version: 7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ version: 7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
vite-plugin-vue-devtools:
specifier: ^8.0.2
- version: 8.0.2(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))
+ version: 8.0.2(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))
vitepress:
specifier: ^1.6.4
- version: 1.6.4(@algolia/client-search@5.20.0)(@types/node@22.18.6)(change-case@5.4.4)(fuse.js@7.1.0)(jwt-decode@4.0.0)(lightningcss@1.29.3)(postcss@8.5.6)(search-insights@2.17.2)(terser@5.40.0)(typescript@5.9.2)
+ version: 1.6.4(@algolia/client-search@5.20.0)(@types/node@24.3.0)(change-case@5.4.4)(fuse.js@7.1.0)(jwt-decode@4.0.0)(lightningcss@1.29.3)(postcss@8.5.6)(search-insights@2.17.2)(terser@5.43.1)(typescript@5.9.2)
vitepress-plugin-llms:
specifier: ^1.7.5
version: 1.7.5
vitest:
specifier: ^3.2.4
- version: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ version: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
vue:
specifier: ^3.5.21
version: 3.5.21(typescript@5.9.2)
vue-router:
- specifier: ^4.5.1
- version: 4.5.1(vue@3.5.21(typescript@5.9.2))
+ specifier: https://pkg.pr.new/vue-router@4f1a37a
+ version: https://pkg.pr.new/vue-router@4f1a37a(vue@3.5.21(typescript@5.9.2))
vue-router-mock:
specifier: ^2.0.0
- version: 2.0.0(vue-router@4.5.1(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2))
+ version: 2.0.0(vue-router@https://pkg.pr.new/vue-router@4f1a37a(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2))
vue-tsc:
specifier: ^3.0.7
version: 3.0.7(typescript@5.9.2)
@@ -200,11 +206,11 @@ importers:
specifier: ^0.2.1
version: 0.2.1(@pinia/colada@0.17.1(pinia@3.0.3(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2))))(magicast@0.3.5)
'@pinia/nuxt':
- specifier: ^0.11.1
- version: 0.11.1(magicast@0.3.5)(pinia@3.0.3(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2)))
+ specifier: ^0.11.2
+ version: 0.11.2(magicast@0.3.5)(pinia@3.0.3(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2)))
nuxt:
specifier: ^3.17.5
- version: 3.17.6(@netlify/blobs@8.2.0)(@parcel/watcher@2.5.1)(@types/node@22.18.6)(@vue/compiler-sfc@3.5.21)(db0@0.3.2)(encoding@0.1.13)(ioredis@5.6.1)(lightningcss@1.29.3)(magicast@0.3.5)(meow@13.2.0)(rolldown@1.0.0-beta.10-commit.87188ed)(rollup@4.52.0)(terser@5.40.0)(typescript@5.9.2)(vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue-tsc@2.2.10(typescript@5.9.2))(yaml@2.8.1)
+ version: 3.17.6(@netlify/blobs@8.2.0)(@parcel/watcher@2.5.1)(@types/node@24.3.0)(@vue/compiler-sfc@3.5.21)(db0@0.3.2)(encoding@0.1.13)(ioredis@5.6.1)(lightningcss@1.29.3)(magicast@0.3.5)(meow@13.2.0)(rolldown@1.0.0-beta.10-commit.87188ed)(rollup@4.52.2)(terser@5.43.1)(typescript@5.9.2)(vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue-tsc@2.2.10(typescript@5.9.2))(yaml@2.8.1)
unplugin-vue-router:
specifier: workspace:*
version: link:../..
@@ -232,7 +238,7 @@ importers:
version: 5.89.0(@tanstack/vue-query@5.89.0(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2))
'@vitejs/plugin-vue':
specifier: ^6.0.1
- version: 6.0.1(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))
+ version: 6.0.1(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))
'@vue/compiler-sfc':
specifier: ^3.5.21
version: 3.5.21
@@ -247,7 +253,41 @@ importers:
version: link:..
vite:
specifier: ^7.1.6
- version: 7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ version: 7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
+
+ playground-experimental:
+ dependencies:
+ mande:
+ specifier: ^2.0.9
+ version: 2.0.9
+ pinia:
+ specifier: ^3.0.3
+ version: 3.0.3(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2))
+ vue:
+ specifier: ^3.5.18
+ version: 3.5.21(typescript@5.9.2)
+ vue-router:
+ specifier: https://pkg.pr.new/vue-router@4f1a37a
+ version: https://pkg.pr.new/vue-router@4f1a37a(vue@3.5.21(typescript@5.9.2))
+ devDependencies:
+ '@vitejs/plugin-vue':
+ specifier: ^6.0.1
+ version: 6.0.1(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))
+ '@vue/compiler-sfc':
+ specifier: ^3.5.18
+ version: 3.5.21
+ '@vue/tsconfig':
+ specifier: ^0.7.0
+ version: 0.7.0(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2))
+ json-server:
+ specifier: ^0.17.4
+ version: 0.17.4
+ unplugin-vue-router:
+ specifier: workspace:*
+ version: link:..
+ vite:
+ specifier: ^7.1.2
+ version: 7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
packages:
@@ -1410,6 +1450,9 @@ packages:
'@jridgewell/gen-mapping@0.3.12':
resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==}
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
'@jridgewell/remapping@2.3.5':
resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
@@ -1417,6 +1460,9 @@ packages:
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
+ '@jridgewell/source-map@0.3.11':
+ resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==}
+
'@jridgewell/source-map@0.3.6':
resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==}
@@ -1429,6 +1475,9 @@ packages:
'@jridgewell/trace-mapping@0.3.29':
resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==}
+ '@jridgewell/trace-mapping@0.3.30':
+ resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==}
+
'@kwsites/file-exists@1.1.1':
resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==}
@@ -1530,6 +1579,10 @@ packages:
resolution: {integrity: sha512-8PKRwoEF70IXVrpGEJZ4g4V2WtE9RjSMgSZLLa0HZCoyT+QczJcJe3kho/XKnJOnNnHep4WqciTD7p4qRRtBqw==}
engines: {node: '>=18.12.0'}
+ '@nuxt/kit@3.18.1':
+ resolution: {integrity: sha512-z6w1Fzv27CIKFlhct05rndkJSfoslplWH5fJ9dtusEvpYScLXp5cATWIbWkte9e9zFSmQTgDQJjNs3geQHE7og==}
+ engines: {node: '>=18.12.0'}
+
'@nuxt/schema@3.17.6':
resolution: {integrity: sha512-ahm0yz6CrSaZ4pS0iuVod9lVRXNDNIidKWLLBx2naGNM6rW+sdFV9gxjvUS3+rLW+swa4HCKE6J5bjOl//oyqQ==}
engines: {node: ^14.18.0 || >=16.10.0}
@@ -1647,8 +1700,8 @@ packages:
'@oxc-project/types@0.75.1':
resolution: {integrity: sha512-7ZJy+51qWpZRvynaQUezeYfjCtaSdiXIWFUZIlOuTSfDXpXqnSl/m1IUPLx6XrOy6s0SFv3CLE14vcZy63bz7g==}
- '@oxc-project/types@0.89.0':
- resolution: {integrity: sha512-yuo+ECPIW5Q9mSeNmCDC2im33bfKuwW18mwkaHMQh8KakHYDzj4ci/q7wxf2qS3dMlVVCIyrs3kFtH5LmnlYnw==}
+ '@oxc-project/types@0.92.0':
+ resolution: {integrity: sha512-PDLfCbwgXjGdTBxzcuDOUxJYNBl6P8dOp3eDKWw54dYvqONan9rwGDRQU0zrkdEMiItfXQQUOI17uOcMX5Zm7A==}
'@parcel/watcher-android-arm64@2.5.1':
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
@@ -1754,8 +1807,8 @@ packages:
pinia: ^2.2.6 || ^3.0.0
vue: ^3.5.17
- '@pinia/nuxt@0.11.1':
- resolution: {integrity: sha512-tCD8ioWhhIHKwm8Y9VvyhBAV/kK4W5uGBIYbI5iM4N1t7duOqK6ECBUavrMxMolELayqqMLb9+evegrh3S7s2A==}
+ '@pinia/nuxt@0.11.2':
+ resolution: {integrity: sha512-CgvSWpbktxxWBV7ModhAcsExsQZqpPq6vMYEe9DexmmY6959ev8ukL4iFhr/qov2Nb9cQAWd7niFDnaWkN+FHg==}
peerDependencies:
pinia: ^3.0.3
@@ -1814,8 +1867,8 @@ packages:
'@quansync/fs@0.1.5':
resolution: {integrity: sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==}
- '@rolldown/binding-android-arm64@1.0.0-beta.38':
- resolution: {integrity: sha512-AE3HFQrjWCKLFZD1Vpiy+qsqTRwwoil1oM5WsKPSmfQ5fif/A+ZtOZetF32erZdsR7qyvns6qHEteEsF6g6rsQ==}
+ '@rolldown/binding-android-arm64@1.0.0-beta.40':
+ resolution: {integrity: sha512-9Ii9phC7QU6Lb+ncMfG1Xlosq0NBB1N/4sw+EGZ3y0BBWGy02TOb5ghWZalphAKv9rn1goqo5WkBjyd2YvsLmA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
@@ -1825,8 +1878,8 @@ packages:
cpu: [arm64]
os: [darwin]
- '@rolldown/binding-darwin-arm64@1.0.0-beta.38':
- resolution: {integrity: sha512-RaoWOKc0rrFsVmKOjQpebMY6c6/I7GR1FBc25v7L/R7NlM0166mUotwGEv7vxu7ruXH4SJcFeVrfADFUUXUmmQ==}
+ '@rolldown/binding-darwin-arm64@1.0.0-beta.40':
+ resolution: {integrity: sha512-5O6d0y2tBQTL+ecQY3qXIwSnF1/Zik8q7LZMKeyF+VJ9l194d0IdMhl2zUF0cqWbYHuF4Pnxplk4OhurPQ/Z9Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
@@ -1836,8 +1889,8 @@ packages:
cpu: [x64]
os: [darwin]
- '@rolldown/binding-darwin-x64@1.0.0-beta.38':
- resolution: {integrity: sha512-Ymojqc2U35iUc8NFU2XX1WQPfBRRHN6xHcrxAf9WS8BFFBn8pDrH5QPvH1tYs3lDkw6UGGbanr1RGzARqdUp1g==}
+ '@rolldown/binding-darwin-x64@1.0.0-beta.40':
+ resolution: {integrity: sha512-izB9jygt3miPQbOTZfSu5K51isUplqa8ysByOKQqcJHgrBWmbTU8TM9eouv6tRmBR0kjcEcID9xhmA1CeZ1VIg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
@@ -1847,8 +1900,8 @@ packages:
cpu: [x64]
os: [freebsd]
- '@rolldown/binding-freebsd-x64@1.0.0-beta.38':
- resolution: {integrity: sha512-0ermTQ//WzSI0nOL3z/LUWMNiE9xeM5cLGxjewPFEexqxV/0uM8/lNp9QageQ8jfc/VO1OURsGw34HYO5PaL8w==}
+ '@rolldown/binding-freebsd-x64@1.0.0-beta.40':
+ resolution: {integrity: sha512-2fdpEpKT+wwP0vig9dqxu+toTeWmVSjo3psJQVDeLJ51rO+GXcCJ1IkCXjhMKVEevNtZS7B8T8Z2vvmRV9MAdA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
@@ -1858,8 +1911,8 @@ packages:
cpu: [arm]
os: [linux]
- '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.38':
- resolution: {integrity: sha512-GADxzVUTCTp6EWI52831A29Tt7PukFe94nhg/SUsfkI33oTiNQtPxyLIT/3oRegizGuPSZSlrdBurkjDwxyEUQ==}
+ '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.40':
+ resolution: {integrity: sha512-HP2lo78OWULN+8TewpLbS9PS00jh0CaF04tA2u8z2I+6QgVgrYOYKvX+T0hlO5smgso4+qb3YchzumWJl3yCPQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
@@ -1869,8 +1922,8 @@ packages:
cpu: [arm64]
os: [linux]
- '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.38':
- resolution: {integrity: sha512-SKO7Exl5Yem/OSNoA5uLHzyrptUQ8Hg70kHDxuwEaH0+GUg+SQe9/7PWmc4hFKBMrJGdQtii8WZ0uIz9Dofg5Q==}
+ '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.40':
+ resolution: {integrity: sha512-ng00gfr9BhA2NPAOU5RWAlTiL+JcwAD+L+4yUD1sbBy6tgHdLiNBOvKtHISIF9RM9/eQeS0tAiWOYZGIH9JMew==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
@@ -1880,8 +1933,8 @@ packages:
cpu: [arm64]
os: [linux]
- '@rolldown/binding-linux-arm64-musl@1.0.0-beta.38':
- resolution: {integrity: sha512-SOo6+WqhXPBaShLxLT0eCgH17d3Yu1lMAe4mFP0M9Bvr/kfMSOPQXuLxBcbBU9IFM9w3N6qP9xWOHO+oUJvi8Q==}
+ '@rolldown/binding-linux-arm64-musl@1.0.0-beta.40':
+ resolution: {integrity: sha512-mF0R1l9kLcaag/9cLEiYYdNZ4v1uuX4jklSDZ1s6vJE4RB3LirUney0FavdVRwCJ5sDvfvsPgXgtBXWYr2M2tQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
@@ -1891,8 +1944,8 @@ packages:
cpu: [x64]
os: [linux]
- '@rolldown/binding-linux-x64-gnu@1.0.0-beta.38':
- resolution: {integrity: sha512-yvsQ3CyrodOX+lcoi+lejZGCOvJZa9xTsNB8OzpMDmHeZq3QzJfpYjXSAS6vie70fOkLVJb77UqYO193Cl8XBQ==}
+ '@rolldown/binding-linux-x64-gnu@1.0.0-beta.40':
+ resolution: {integrity: sha512-+wi08S7wT5iLPHRZb0USrS6n+T6m+yY++dePYedE5uvKIpWCJJioFTaRtWjpm0V6dVNLcq2OukrvfdlGtH9Wgg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
@@ -1902,14 +1955,14 @@ packages:
cpu: [x64]
os: [linux]
- '@rolldown/binding-linux-x64-musl@1.0.0-beta.38':
- resolution: {integrity: sha512-84qzKMwUwikfYeOuJ4Kxm/3z15rt0nFGGQArHYIQQNSTiQdxGHxOkqXtzPFqrVfBJUdxBAf+jYzR1pttFJuWyg==}
+ '@rolldown/binding-linux-x64-musl@1.0.0-beta.40':
+ resolution: {integrity: sha512-W5qBGAemUocIBKCcOsDjlV9GUt28qhl/+M6etWBeLS5gQK0J6XDg0YVzfOQdvq57ZGjYNP0NvhYzqhOOnEx+4g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
- '@rolldown/binding-openharmony-arm64@1.0.0-beta.38':
- resolution: {integrity: sha512-QrNiWlce01DYH0rL8K3yUBu+lNzY+B0DyCbIc2Atan6/S6flxOL0ow5DLQvMamOI/oKhrJ4xG+9MkMb9dDHbLQ==}
+ '@rolldown/binding-openharmony-arm64@1.0.0-beta.40':
+ resolution: {integrity: sha512-vJwoDehtt+yqj2zacq1AqNc2uE/oh7mnRGqAUbuldV6pgvU01OSQUJ7Zu+35hTopnjFoDNN6mIezkYlGAv5RFA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
@@ -1919,8 +1972,8 @@ packages:
engines: {node: '>=14.21.3'}
cpu: [wasm32]
- '@rolldown/binding-wasm32-wasi@1.0.0-beta.38':
- resolution: {integrity: sha512-fnLtHyjwEsG4/aNV3Uv3Qd1ZbdH+CopwJNoV0RgBqrcQB8V6/Qdikd5JKvnO23kb3QvIpP+dAMGZMv1c2PJMzw==}
+ '@rolldown/binding-wasm32-wasi@1.0.0-beta.40':
+ resolution: {integrity: sha512-Oj3YyqVUPurr1FlMpEE/bJmMC+VWAWPM/SGUfklO5KUX97bk5Q/733nPg4RykK8q8/TluJoQYvRc05vL/B74dw==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
@@ -1929,8 +1982,8 @@ packages:
cpu: [arm64]
os: [win32]
- '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.38':
- resolution: {integrity: sha512-19cTfnGedem+RY+znA9J6ARBOCEFD4YSjnx0p5jiTm9tR6pHafRfFIfKlTXhun+NL0WWM/M0eb2IfPPYUa8+wg==}
+ '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.40':
+ resolution: {integrity: sha512-0ZtO6yN8XjVoFfN4HDWQj4nDu3ndMybr7jIM00DJqOmc+yFhly7rdOy7fNR9Sky3leCpBtsXfepVqRmVpYKPVA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
@@ -1940,8 +1993,8 @@ packages:
cpu: [ia32]
os: [win32]
- '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.38':
- resolution: {integrity: sha512-HcICm4YzFJZV+fI0O0bFLVVlsWvRNo/AB9EfUXvNYbtAxakCnQZ15oq22deFdz6sfi9Y4/SagH2kPU723dhCFA==}
+ '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.40':
+ resolution: {integrity: sha512-BPl1inoJXPpIe38Ja46E4y11vXlJyuleo+9Rmu//pYL5fIDYJkXUj/oAXqjSuwLcssrcwnuPgzvzvlz9++cr3w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ia32]
os: [win32]
@@ -1951,8 +2004,8 @@ packages:
cpu: [x64]
os: [win32]
- '@rolldown/binding-win32-x64-msvc@1.0.0-beta.38':
- resolution: {integrity: sha512-4Qx6cgEPXLb0XsCyLoQcUgYBpfL0sjugftob+zhUH0EOk/NVCAIT+h0NJhY+jn7pFpeKxhNMqhvTNx3AesxIAQ==}
+ '@rolldown/binding-win32-x64-msvc@1.0.0-beta.40':
+ resolution: {integrity: sha512-UguA4ltbAk+nbwHRxqaUP/etpTbR0HjyNlsu4Zjbh/ytNbFsbw8CA4tEBkwDyjgI5NIPea6xY11zpl7R2/ddVA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
@@ -1963,8 +2016,11 @@ packages:
'@rolldown/pluginutils@1.0.0-beta.29':
resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==}
- '@rolldown/pluginutils@1.0.0-beta.38':
- resolution: {integrity: sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==}
+ '@rolldown/pluginutils@1.0.0-beta.33':
+ resolution: {integrity: sha512-she25NCG6NoEPC/SEB4pHs5STcnfI4VBFOzjeI63maSPrWME5J2XC8ogrBgp8NaE/xzj28/kbpSaebiMvFRj+w==}
+
+ '@rolldown/pluginutils@1.0.0-beta.40':
+ resolution: {integrity: sha512-s3GeJKSQOwBlzdUrj4ISjJj5SfSh+aqn0wjOar4Bx95iV1ETI7F6S/5hLcfAxZ9kXDcyrAkxPlqmd1ZITttf+w==}
'@rollup/plugin-alias@5.1.1':
resolution: {integrity: sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==}
@@ -2038,113 +2094,113 @@ packages:
rollup:
optional: true
- '@rollup/rollup-android-arm-eabi@4.52.0':
- resolution: {integrity: sha512-VxDYCDqOaR7NXzAtvRx7G1u54d2kEHopb28YH/pKzY6y0qmogP3gG7CSiWsq9WvDFxOQMpNEyjVAHZFXfH3o/A==}
+ '@rollup/rollup-android-arm-eabi@4.52.2':
+ resolution: {integrity: sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==}
cpu: [arm]
os: [android]
- '@rollup/rollup-android-arm64@4.52.0':
- resolution: {integrity: sha512-pqDirm8koABIKvzL59YI9W9DWbRlTX7RWhN+auR8HXJxo89m4mjqbah7nJZjeKNTNYopqL+yGg+0mhCpf3xZtQ==}
+ '@rollup/rollup-android-arm64@4.52.2':
+ resolution: {integrity: sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==}
cpu: [arm64]
os: [android]
- '@rollup/rollup-darwin-arm64@4.52.0':
- resolution: {integrity: sha512-YCdWlY/8ltN6H78HnMsRHYlPiKvqKagBP1r+D7SSylxX+HnsgXGCmLiV3Y4nSyY9hW8qr8U9LDUx/Lo7M6MfmQ==}
+ '@rollup/rollup-darwin-arm64@4.52.2':
+ resolution: {integrity: sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==}
cpu: [arm64]
os: [darwin]
- '@rollup/rollup-darwin-x64@4.52.0':
- resolution: {integrity: sha512-z4nw6y1j+OOSGzuVbSWdIp1IUks9qNw4dc7z7lWuWDKojY38VMWBlEN7F9jk5UXOkUcp97vA1N213DF+Lz8BRg==}
+ '@rollup/rollup-darwin-x64@4.52.2':
+ resolution: {integrity: sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==}
cpu: [x64]
os: [darwin]
- '@rollup/rollup-freebsd-arm64@4.52.0':
- resolution: {integrity: sha512-Q/dv9Yvyr5rKlK8WQJZVrp5g2SOYeZUs9u/t2f9cQ2E0gJjYB/BWoedXfUT0EcDJefi2zzVfhcOj8drWCzTviw==}
+ '@rollup/rollup-freebsd-arm64@4.52.2':
+ resolution: {integrity: sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==}
cpu: [arm64]
os: [freebsd]
- '@rollup/rollup-freebsd-x64@4.52.0':
- resolution: {integrity: sha512-kdBsLs4Uile/fbjZVvCRcKB4q64R+1mUq0Yd7oU1CMm1Av336ajIFqNFovByipciuUQjBCPMxwJhCgfG2re3rg==}
+ '@rollup/rollup-freebsd-x64@4.52.2':
+ resolution: {integrity: sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==}
cpu: [x64]
os: [freebsd]
- '@rollup/rollup-linux-arm-gnueabihf@4.52.0':
- resolution: {integrity: sha512-aL6hRwu0k7MTUESgkg7QHY6CoqPgr6gdQXRJI1/VbFlUMwsSzPGSR7sG5d+MCbYnJmJwThc2ol3nixj1fvI/zQ==}
+ '@rollup/rollup-linux-arm-gnueabihf@4.52.2':
+ resolution: {integrity: sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==}
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm-musleabihf@4.52.0':
- resolution: {integrity: sha512-BTs0M5s1EJejgIBJhCeiFo7GZZ2IXWkFGcyZhxX4+8usnIo5Mti57108vjXFIQmmJaRyDwmV59Tw64Ap1dkwMw==}
+ '@rollup/rollup-linux-arm-musleabihf@4.52.2':
+ resolution: {integrity: sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==}
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm64-gnu@4.52.0':
- resolution: {integrity: sha512-uj672IVOU9m08DBGvoPKPi/J8jlVgjh12C9GmjjBxCTQc3XtVmRkRKyeHSmIKQpvJ7fIm1EJieBUcnGSzDVFyw==}
+ '@rollup/rollup-linux-arm64-gnu@4.52.2':
+ resolution: {integrity: sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==}
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-arm64-musl@4.52.0':
- resolution: {integrity: sha512-/+IVbeDMDCtB/HP/wiWsSzduD10SEGzIZX2945KSgZRNi4TSkjHqRJtNTVtVb8IRwhJ65ssI56krlLik+zFWkw==}
+ '@rollup/rollup-linux-arm64-musl@4.52.2':
+ resolution: {integrity: sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==}
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-loong64-gnu@4.52.0':
- resolution: {integrity: sha512-U1vVzvSWtSMWKKrGoROPBXMh3Vwn93TA9V35PldokHGqiUbF6erSzox/5qrSMKp6SzakvyjcPiVF8yB1xKr9Pg==}
+ '@rollup/rollup-linux-loong64-gnu@4.52.2':
+ resolution: {integrity: sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==}
cpu: [loong64]
os: [linux]
- '@rollup/rollup-linux-ppc64-gnu@4.52.0':
- resolution: {integrity: sha512-X/4WfuBAdQRH8cK3DYl8zC00XEE6aM472W+QCycpQJeLWVnHfkv7RyBFVaTqNUMsTgIX8ihMjCvFF9OUgeABzw==}
+ '@rollup/rollup-linux-ppc64-gnu@4.52.2':
+ resolution: {integrity: sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==}
cpu: [ppc64]
os: [linux]
- '@rollup/rollup-linux-riscv64-gnu@4.52.0':
- resolution: {integrity: sha512-xIRYc58HfWDBZoLmWfWXg2Sq8VCa2iJ32B7mqfWnkx5mekekl0tMe7FHpY8I72RXEcUkaWawRvl3qA55og+cwQ==}
+ '@rollup/rollup-linux-riscv64-gnu@4.52.2':
+ resolution: {integrity: sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==}
cpu: [riscv64]
os: [linux]
- '@rollup/rollup-linux-riscv64-musl@4.52.0':
- resolution: {integrity: sha512-mbsoUey05WJIOz8U1WzNdf+6UMYGwE3fZZnQqsM22FZ3wh1N887HT6jAOjXs6CNEK3Ntu2OBsyQDXfIjouI4dw==}
+ '@rollup/rollup-linux-riscv64-musl@4.52.2':
+ resolution: {integrity: sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==}
cpu: [riscv64]
os: [linux]
- '@rollup/rollup-linux-s390x-gnu@4.52.0':
- resolution: {integrity: sha512-qP6aP970bucEi5KKKR4AuPFd8aTx9EF6BvutvYxmZuWLJHmnq4LvBfp0U+yFDMGwJ+AIJEH5sIP+SNypauMWzg==}
+ '@rollup/rollup-linux-s390x-gnu@4.52.2':
+ resolution: {integrity: sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==}
cpu: [s390x]
os: [linux]
- '@rollup/rollup-linux-x64-gnu@4.52.0':
- resolution: {integrity: sha512-nmSVN+F2i1yKZ7rJNKO3G7ZzmxJgoQBQZ/6c4MuS553Grmr7WqR7LLDcYG53Z2m9409z3JLt4sCOhLdbKQ3HmA==}
+ '@rollup/rollup-linux-x64-gnu@4.52.2':
+ resolution: {integrity: sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==}
cpu: [x64]
os: [linux]
- '@rollup/rollup-linux-x64-musl@4.52.0':
- resolution: {integrity: sha512-2d0qRo33G6TfQVjaMR71P+yJVGODrt5V6+T0BDYH4EMfGgdC/2HWDVjSSFw888GSzAZUwuska3+zxNUCDco6rQ==}
+ '@rollup/rollup-linux-x64-musl@4.52.2':
+ resolution: {integrity: sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==}
cpu: [x64]
os: [linux]
- '@rollup/rollup-openharmony-arm64@4.52.0':
- resolution: {integrity: sha512-A1JalX4MOaFAAyGgpO7XP5khquv/7xKzLIyLmhNrbiCxWpMlnsTYr8dnsWM7sEeotNmxvSOEL7F65j0HXFcFsw==}
+ '@rollup/rollup-openharmony-arm64@4.52.2':
+ resolution: {integrity: sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==}
cpu: [arm64]
os: [openharmony]
- '@rollup/rollup-win32-arm64-msvc@4.52.0':
- resolution: {integrity: sha512-YQugafP/rH0eOOHGjmNgDURrpYHrIX0yuojOI8bwCyXwxC9ZdTd3vYkmddPX0oHONLXu9Rb1dDmT0VNpjkzGGw==}
+ '@rollup/rollup-win32-arm64-msvc@4.52.2':
+ resolution: {integrity: sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==}
cpu: [arm64]
os: [win32]
- '@rollup/rollup-win32-ia32-msvc@4.52.0':
- resolution: {integrity: sha512-zYdUYhi3Qe2fndujBqL5FjAFzvNeLxtIqfzNEVKD1I7C37/chv1VxhscWSQHTNfjPCrBFQMnynwA3kpZpZ8w4A==}
+ '@rollup/rollup-win32-ia32-msvc@4.52.2':
+ resolution: {integrity: sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==}
cpu: [ia32]
os: [win32]
- '@rollup/rollup-win32-x64-gnu@4.52.0':
- resolution: {integrity: sha512-fGk03kQylNaCOQ96HDMeT7E2n91EqvCDd3RwvT5k+xNdFCeMGnj5b5hEgTGrQuyidqSsD3zJDQ21QIaxXqTBJw==}
+ '@rollup/rollup-win32-x64-gnu@4.52.2':
+ resolution: {integrity: sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==}
cpu: [x64]
os: [win32]
- '@rollup/rollup-win32-x64-msvc@4.52.0':
- resolution: {integrity: sha512-6iKDCVSIUQ8jPMoIV0OytRKniaYyy5EbY/RRydmLW8ZR3cEBhxbWl5ro0rkUNe0ef6sScvhbY79HrjRm8i3vDQ==}
+ '@rollup/rollup-win32-x64-msvc@4.52.2':
+ resolution: {integrity: sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==}
cpu: [x64]
os: [win32]
@@ -2263,6 +2319,9 @@ packages:
'@tybys/wasm-util@0.9.0':
resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==}
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
'@types/chai@5.2.2':
resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==}
@@ -2302,11 +2361,11 @@ packages:
'@types/ms@2.1.0':
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
- '@types/node@20.19.4':
- resolution: {integrity: sha512-OP+We5WV8Xnbuvw0zC2m4qfB/BJvjyCwtNjhHdJxV1639SGSKrLmJkc3fMnp2Qy8nJyHp8RO6umxELN/dS1/EA==}
+ '@types/node@20.19.11':
+ resolution: {integrity: sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==}
- '@types/node@22.18.6':
- resolution: {integrity: sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==}
+ '@types/node@24.3.0':
+ resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==}
'@types/normalize-package-data@2.4.4':
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
@@ -2459,18 +2518,18 @@ packages:
'@volar/typescript@2.4.23':
resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==}
- '@vue-macros/common@3.0.0':
- resolution: {integrity: sha512-WwzEv0Ky/HSGR/oZnh+pHJi8yvxW83XRQGIsKsn6t5MiF+KsHoMPuqJdm1VoGNJAuTqJtd1ZGSznnZKPMSKC8w==}
- engines: {node: '>=20.19.0'}
+ '@vue-macros/common@3.0.0-beta.15':
+ resolution: {integrity: sha512-DMgq/rIh1H20WYNWU7krIbEfJRYDDhy7ix64GlT4AVUJZZWCZ5pxiYVJR3A3GmWQPkn7Pg7i3oIiGqu4JGC65w==}
+ engines: {node: '>=20.18.0'}
peerDependencies:
vue: ^2.7.0 || ^3.2.25
peerDependenciesMeta:
vue:
optional: true
- '@vue-macros/common@3.0.0-beta.15':
- resolution: {integrity: sha512-DMgq/rIh1H20WYNWU7krIbEfJRYDDhy7ix64GlT4AVUJZZWCZ5pxiYVJR3A3GmWQPkn7Pg7i3oIiGqu4JGC65w==}
- engines: {node: '>=20.18.0'}
+ '@vue-macros/common@3.1.0':
+ resolution: {integrity: sha512-YGpEYYfFkjQY0ZbtOX90KuxRs0JHLsMizxvArzemn4Uwc0X7iLIs/+8RETgnQgGyBenJU5CZW5qVGiedKA3j2g==}
+ engines: {node: '>=20.19.0'}
peerDependencies:
vue: ^2.7.0 || ^3.2.25
peerDependenciesMeta:
@@ -2526,8 +2585,8 @@ packages:
'@vue/devtools-api@7.7.6':
resolution: {integrity: sha512-b2Xx0KvXZObePpXPYHvBRRJLDQn5nhKjXh7vUhMEtWxz1AYNFOVIsh5+HLP8xDGL7sy+Q7hXeUxPHB/KgbtsPw==}
- '@vue/devtools-api@8.0.1':
- resolution: {integrity: sha512-YBvjfpM7LEp5+b7ZDm4+mFrC+TgGjUmN8ff9lZcbHQ1MKhmftT/urCTZP0y1j26YQWr25l9TPaEbNLbILRiGoQ==}
+ '@vue/devtools-api@8.0.2':
+ resolution: {integrity: sha512-RdwsaYoSTumwZ7XOt5yIPP1/T4O0bTs+c5XaEjmUB6f9x+FvDSL9AekxW1vuhK1lmA9TfewpXVt2r5LIax3LHw==}
'@vue/devtools-core@7.7.7':
resolution: {integrity: sha512-9z9TLbfC+AjAi1PQyWX+OErjIaJmdFlbDHcD+cAMYKY6Bh5VlsAtCeGyRMrXwIlMEQPukvnWt3gZBLwTAIMKzQ==}
@@ -2542,18 +2601,12 @@ packages:
'@vue/devtools-kit@7.7.7':
resolution: {integrity: sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==}
- '@vue/devtools-kit@8.0.1':
- resolution: {integrity: sha512-7kiPhgTKNtNeXltEHnJJjIDlndlJP4P+UJvCw54uVHNDlI6JzwrSiRmW4cxKTug2wDbc/dkGaMnlZghcwV+aWA==}
-
'@vue/devtools-kit@8.0.2':
resolution: {integrity: sha512-yjZKdEmhJzQqbOh4KFBfTOQjDPMrjjBNCnHBvnTGJX+YLAqoUtY2J+cg7BE+EA8KUv8LprECq04ts75wCoIGWA==}
'@vue/devtools-shared@7.7.7':
resolution: {integrity: sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==}
- '@vue/devtools-shared@8.0.1':
- resolution: {integrity: sha512-PqtWqPPRpMwZ9FjTzyugb5KeV9kmg2C3hjxZHwjl0lijT4QIJDd0z6AWcnbM9w2nayjDymyTt0+sbdTv3pVeNg==}
-
'@vue/devtools-shared@8.0.2':
resolution: {integrity: sha512-mLU0QVdy5Lp40PMGSixDw/Kbd6v5dkQXltd2r+mdVQV7iUog2NlZuLxFZApFZ/mObUBDhoCpf0T3zF2FWWdeHw==}
@@ -2604,15 +2657,23 @@ packages:
'@vue/shared@3.5.18':
resolution: {integrity: sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==}
- '@vue/shared@3.5.19':
- resolution: {integrity: sha512-IhXCOn08wgKrLQxRFKKlSacWg4Goi1BolrdEeLYn6tgHjJNXVrWJ5nzoxZqNwl5p88aLlQ8LOaoMa3AYvaKJ/Q==}
-
'@vue/shared@3.5.21':
resolution: {integrity: sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==}
'@vue/test-utils@2.4.6':
resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==}
+ '@vue/tsconfig@0.7.0':
+ resolution: {integrity: sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==}
+ peerDependencies:
+ typescript: 5.x
+ vue: ^3.4.0
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ vue:
+ optional: true
+
'@vue/tsconfig@0.8.1':
resolution: {integrity: sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==}
peerDependencies:
@@ -2936,8 +2997,8 @@ packages:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
- browserslist@4.25.0:
- resolution: {integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==}
+ browserslist@4.25.2:
+ resolution: {integrity: sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
@@ -2978,6 +3039,14 @@ packages:
magicast:
optional: true
+ c12@3.2.0:
+ resolution: {integrity: sha512-ixkEtbYafL56E6HiFuonMm1ZjoKtIo7TH68/uiEq4DAwv9NcUX2nJ95F8TrbMeNjqIkZpruo3ojXQJ+MGG5gcQ==}
+ peerDependencies:
+ magicast: ^0.3.5
+ peerDependenciesMeta:
+ magicast:
+ optional: true
+
cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
@@ -2999,6 +3068,9 @@ packages:
caniuse-lite@1.0.30001720:
resolution: {integrity: sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==}
+ caniuse-lite@1.0.30001735:
+ resolution: {integrity: sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==}
+
ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
@@ -3056,9 +3128,9 @@ packages:
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
engines: {node: '>=18'}
- cli-truncate@4.0.0:
- resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==}
- engines: {node: '>=18'}
+ cli-truncate@5.1.0:
+ resolution: {integrity: sha512-7JDGG+4Zp0CsknDCedl0DYdaeOhc46QNpXi3NLQblkZpXXgA6LncLDUUyvrjSvZeF3VRQa+KiMGomazQrC1V8g==}
+ engines: {node: '>=20'}
clipboardy@4.0.0:
resolution: {integrity: sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==}
@@ -3556,6 +3628,10 @@ packages:
resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==}
engines: {node: '>=12'}
+ dotenv@17.2.1:
+ resolution: {integrity: sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==}
+ engines: {node: '>=12'}
+
dts-resolver@2.1.2:
resolution: {integrity: sha512-xeXHBQkn2ISSXxbJWD828PFjtyg+/UrMDo7W4Ffcs7+YWCquxU8YjV1KoxuiL+eJ5pg3ll+bC6flVv61L3LKZg==}
engines: {node: '>=20.18.0'}
@@ -3583,8 +3659,8 @@ packages:
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
- electron-to-chromium@1.5.161:
- resolution: {integrity: sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==}
+ electron-to-chromium@1.5.202:
+ resolution: {integrity: sha512-NxbYjRmiHcHXV1Ws3fWUW+SLb62isauajk45LUJ/HgIOkUA7jLZu/X2Iif+X9FBNK8QkF9Zb4Q2mcwXCcY30mg==}
emoji-regex-xs@1.0.0:
resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==}
@@ -3619,8 +3695,8 @@ packages:
end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
- enhanced-resolve@5.18.1:
- resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
+ enhanced-resolve@5.18.3:
+ resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
engines: {node: '>=10.13.0'}
entities@4.5.0:
@@ -3816,6 +3892,14 @@ packages:
fd-slicer@1.1.0:
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
+ fdir@6.4.6:
+ resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
@@ -4222,10 +4306,6 @@ packages:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
- is-fullwidth-code-point@4.0.0:
- resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
- engines: {node: '>=12'}
-
is-fullwidth-code-point@5.0.0:
resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==}
engines: {node: '>=18'}
@@ -4537,8 +4617,8 @@ packages:
resolution: {integrity: sha512-I8oW2+QL5KJo8zXNWX046M134WchxsXC7SawLPvRQpogCbkyQIaFxPE89A2HiwR7vAK2Dm2ERBAmyjTYGYEpBg==}
hasBin: true
- listr2@9.0.3:
- resolution: {integrity: sha512-0aeh5HHHgmq1KRdMMDHfhMWQmIT/m7nRDTlxlFqni2Sp0had9baqsjJRvDGdlvgd6NmPE0nPloOipiQJGFtTHQ==}
+ listr2@9.0.4:
+ resolution: {integrity: sha512-1wd/kpAdKRLwv7/3OKC8zZ5U8e/fajCfWMxacUvB79S5nLrYGPtUI/8chMQhn3LQjsRVErTb9i1ECAwW0ZIHnQ==}
engines: {node: '>=20.0.0'}
loader-runner@4.3.0:
@@ -4630,9 +4710,6 @@ packages:
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
- magic-string@0.30.18:
- resolution: {integrity: sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==}
-
magic-string@0.30.19:
resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
@@ -5543,8 +5620,8 @@ packages:
proto-list@1.2.4:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
- protobufjs@7.5.0:
- resolution: {integrity: sha512-Z2E/kOY1QjoMlCytmexzYfDm/w5fKAiRwpSzGtdnXW1zC88Z2yXazHHrOtwCzn+7wSxyE8PYM4rvVcMphF9sOA==}
+ protobufjs@7.5.3:
+ resolution: {integrity: sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==}
engines: {node: '>=12.0.0'}
protocols@2.0.2:
@@ -5715,8 +5792,8 @@ packages:
engines: {node: 20 || >=22}
hasBin: true
- rolldown-plugin-dts@0.16.7:
- resolution: {integrity: sha512-9iDzS4MHXMyieisFbWxuz96i/idGJNpvWILqCH06mrEZvn8Q2el3Q63xxjOt7HJjTOUNFhB1isvZFy4dA87lPQ==}
+ rolldown-plugin-dts@0.16.8:
+ resolution: {integrity: sha512-lsx7yrYA0ZXfARLEcPKgHIw8DX4fLQOhmMChgZbn5eFhqibY2Bav1+/Yn5WNm+ATtw+cefXYgEYO/7njeHsgAA==}
engines: {node: '>=20.18.0'}
peerDependencies:
'@ts-macro/tsc': ^0.3.6
@@ -5738,8 +5815,8 @@ packages:
resolution: {integrity: sha512-D+iim+DHIwK9kbZvubENmtnYFqHfFV0OKwzT8yU/W+xyUK1A71+iRFmJYBGqNUo3fJ2Ob4oIQfan63mhzh614A==}
hasBin: true
- rolldown@1.0.0-beta.38:
- resolution: {integrity: sha512-58frPNX55Je1YsyrtPJv9rOSR3G5efUZpRqok94Efsj0EUa8dnqJV3BldShyI7A+bVPleucOtzXHwVpJRcR0kQ==}
+ rolldown@1.0.0-beta.40:
+ resolution: {integrity: sha512-VqEHbKpOgTPmQrZ4fVn4eshDQS/6g/fRpNE7cFSJY+eQLDZn4B9X61J6L+hnlt1u2uRI+pF7r1USs6S5fuWCvw==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
@@ -5756,8 +5833,8 @@ packages:
rollup:
optional: true
- rollup@4.52.0:
- resolution: {integrity: sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g==}
+ rollup@4.52.2:
+ resolution: {integrity: sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
@@ -5912,10 +5989,6 @@ packages:
resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==}
engines: {node: '>=14.16'}
- slice-ansi@5.0.0:
- resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
- engines: {node: '>=12'}
-
slice-ansi@7.1.0:
resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==}
engines: {node: '>=18'}
@@ -5998,6 +6071,10 @@ packages:
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
engines: {node: '>=18'}
+ string-width@8.1.0:
+ resolution: {integrity: sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==}
+ engines: {node: '>=20'}
+
string_decoder@1.1.1:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
@@ -6123,6 +6200,11 @@ packages:
engines: {node: '>=10'}
hasBin: true
+ terser@5.43.1:
+ resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==}
+ engines: {node: '>=10'}
+ hasBin: true
+
test-exclude@7.0.1:
resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==}
engines: {node: '>=18'}
@@ -6300,6 +6382,9 @@ packages:
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+ undici-types@7.10.0:
+ resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==}
+
unenv@2.0.0-rc.18:
resolution: {integrity: sha512-O0oVQVJ2X3Q8H4HITJr4e2cWxMYBeZ+p8S25yoKCxVCgDWtIJDcgwWNonYz12tI3ylVQCRyPV/Bdq0KJeXo7AA==}
@@ -6593,8 +6678,8 @@ packages:
vite: ^6.0.0 || ^7.0.0
vue: ^3.5.0
- vite@5.4.20:
- resolution: {integrity: sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==}
+ vite@5.4.19:
+ resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
@@ -6624,8 +6709,8 @@ packages:
terser:
optional: true
- vite@6.3.6:
- resolution: {integrity: sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==}
+ vite@6.3.5:
+ resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
@@ -6777,6 +6862,7 @@ packages:
vue-router-mock@2.0.0:
resolution: {integrity: sha512-UmfJ9C4odcC8P2d8+yZWGPnjK7MMc1Uk3bmchpq+8lcGEdpwrO18RPQOMUEiwAjqjTVN5Z955Weaz2Ev9UrXMw==}
+ version: 2.0.0
peerDependencies:
vue: ^3.2.23
vue-router: ^4.0.12
@@ -6786,6 +6872,12 @@ packages:
peerDependencies:
vue: ^3.2.0
+ vue-router@https://pkg.pr.new/vue-router@4f1a37a:
+ resolution: {tarball: https://pkg.pr.new/vue-router@4f1a37a}
+ version: 4.5.1
+ peerDependencies:
+ vue: ^3.5.0
+
vue-tsc@2.2.10:
resolution: {integrity: sha512-jWZ1xSaNbabEV3whpIDMbjVSVawjAyW+x1n3JeGQo7S0uv2n9F/JMgWW90tGWNFRKya4YwKMZgCtr0vRAM7DeQ==}
hasBin: true
@@ -7129,7 +7221,7 @@ snapshots:
'@babel/helper-compilation-targets': 7.27.2
'@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.4)
'@babel/helpers': 7.27.4
- '@babel/parser': 7.28.4
+ '@babel/parser': 7.28.3
'@babel/template': 7.27.2
'@babel/traverse': 7.27.4
'@babel/types': 7.28.4
@@ -7145,8 +7237,8 @@ snapshots:
dependencies:
'@babel/parser': 7.28.4
'@babel/types': 7.28.4
- '@jridgewell/gen-mapping': 0.3.12
- '@jridgewell/trace-mapping': 0.3.29
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.30
jsesc: 3.1.0
'@babel/helper-annotate-as-pure@7.27.1':
@@ -7157,7 +7249,7 @@ snapshots:
dependencies:
'@babel/compat-data': 7.27.2
'@babel/helper-validator-option': 7.27.1
- browserslist: 4.25.0
+ browserslist: 4.25.2
lru-cache: 5.1.1
semver: 6.3.1
@@ -7290,14 +7382,14 @@ snapshots:
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
- '@babel/parser': 7.28.4
+ '@babel/parser': 7.28.3
'@babel/types': 7.28.4
'@babel/traverse@7.27.4':
dependencies:
'@babel/code-frame': 7.27.1
'@babel/generator': 7.28.3
- '@babel/parser': 7.28.4
+ '@babel/parser': 7.28.3
'@babel/template': 7.27.2
'@babel/types': 7.28.4
debug: 4.4.1(supports-color@5.5.0)
@@ -8028,13 +8120,13 @@ snapshots:
'@grpc/grpc-js@1.9.15':
dependencies:
'@grpc/proto-loader': 0.7.15
- '@types/node': 22.18.6
+ '@types/node': 24.3.0
'@grpc/proto-loader@0.7.15':
dependencies:
lodash.camelcase: 4.3.0
long: 5.3.2
- protobufjs: 7.5.0
+ protobufjs: 7.5.3
yargs: 17.7.2
'@hutson/parse-repository-url@5.0.0': {}
@@ -8070,26 +8162,41 @@ snapshots:
'@jridgewell/gen-mapping@0.3.12':
dependencies:
- '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/sourcemap-codec': 1.5.0
'@jridgewell/trace-mapping': 0.3.29
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.30
+
'@jridgewell/remapping@2.3.5':
dependencies:
- '@jridgewell/gen-mapping': 0.3.12
- '@jridgewell/trace-mapping': 0.3.29
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.30
'@jridgewell/resolve-uri@3.1.2': {}
+ '@jridgewell/source-map@0.3.11':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.30
+
'@jridgewell/source-map@0.3.6':
dependencies:
- '@jridgewell/gen-mapping': 0.3.12
- '@jridgewell/trace-mapping': 0.3.29
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.30
'@jridgewell/sourcemap-codec@1.5.0': {}
'@jridgewell/sourcemap-codec@1.5.5': {}
'@jridgewell/trace-mapping@0.3.29':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.0
+
+ '@jridgewell/trace-mapping@0.3.30':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
@@ -8167,12 +8274,12 @@ snapshots:
uuid: 11.1.0
write-file-atomic: 6.0.0
- '@netlify/functions@3.1.10(encoding@0.1.13)(rollup@4.52.0)':
+ '@netlify/functions@3.1.10(encoding@0.1.13)(rollup@4.52.2)':
dependencies:
'@netlify/blobs': 9.1.2
'@netlify/dev-utils': 2.2.0
'@netlify/serverless-functions-api': 1.41.2
- '@netlify/zip-it-and-ship-it': 12.1.1(encoding@0.1.13)(rollup@4.52.0)
+ '@netlify/zip-it-and-ship-it': 12.1.1(encoding@0.1.13)(rollup@4.52.2)
cron-parser: 4.9.0
decache: 4.6.2
extract-zip: 2.0.1
@@ -8192,13 +8299,13 @@ snapshots:
'@netlify/serverless-functions-api@1.41.2': {}
- '@netlify/zip-it-and-ship-it@12.1.1(encoding@0.1.13)(rollup@4.52.0)':
+ '@netlify/zip-it-and-ship-it@12.1.1(encoding@0.1.13)(rollup@4.52.2)':
dependencies:
- '@babel/parser': 7.28.4
+ '@babel/parser': 7.28.3
'@babel/types': 7.27.3
'@netlify/binary-info': 1.0.0
'@netlify/serverless-functions-api': 1.41.2
- '@vercel/nft': 0.29.3(encoding@0.1.13)(rollup@4.52.0)
+ '@vercel/nft': 0.29.3(encoding@0.1.13)(rollup@4.52.2)
archiver: 7.0.1
common-path-prefix: 3.0.0
copy-file: 11.0.0
@@ -8276,11 +8383,11 @@ snapshots:
'@nuxt/devalue@2.0.2': {}
- '@nuxt/devtools-kit@2.6.2(magicast@0.3.5)(vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))':
+ '@nuxt/devtools-kit@2.6.2(magicast@0.3.5)(vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))':
dependencies:
- '@nuxt/kit': 3.17.6(magicast@0.3.5)
+ '@nuxt/kit': 3.18.1(magicast@0.3.5)
execa: 8.0.1
- vite: 7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ vite: 7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
transitivePeerDependencies:
- magicast
@@ -8295,12 +8402,12 @@ snapshots:
prompts: 2.4.2
semver: 7.7.2
- '@nuxt/devtools@2.6.2(vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))':
+ '@nuxt/devtools@2.6.2(vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))':
dependencies:
- '@nuxt/devtools-kit': 2.6.2(magicast@0.3.5)(vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))
+ '@nuxt/devtools-kit': 2.6.2(magicast@0.3.5)(vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))
'@nuxt/devtools-wizard': 2.6.2
'@nuxt/kit': 3.17.6(magicast@0.3.5)
- '@vue/devtools-core': 7.7.7(vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))
+ '@vue/devtools-core': 7.7.7(vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))
'@vue/devtools-kit': 7.7.7
birpc: 2.5.0
consola: 3.4.2
@@ -8325,9 +8432,9 @@ snapshots:
sirv: 3.0.1
structured-clone-es: 1.0.0
tinyglobby: 0.2.15
- vite: 7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
- vite-plugin-inspect: 11.3.0(@nuxt/kit@3.17.6(magicast@0.3.5))(vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))
- vite-plugin-vue-tracer: 1.0.0(vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))
+ vite: 7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
+ vite-plugin-inspect: 11.3.0(@nuxt/kit@3.17.6(magicast@0.3.5))(vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))
+ vite-plugin-vue-tracer: 1.0.0(vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))
which: 5.0.0
ws: 8.18.3
transitivePeerDependencies:
@@ -8363,9 +8470,36 @@ snapshots:
transitivePeerDependencies:
- magicast
+ '@nuxt/kit@3.18.1(magicast@0.3.5)':
+ dependencies:
+ c12: 3.2.0(magicast@0.3.5)
+ consola: 3.4.2
+ defu: 6.1.4
+ destr: 2.0.5
+ errx: 0.1.0
+ exsolve: 1.0.7
+ ignore: 7.0.5
+ jiti: 2.5.1
+ klona: 2.0.6
+ knitwork: 1.2.0
+ mlly: 1.8.0
+ ohash: 2.0.11
+ pathe: 2.0.3
+ pkg-types: 2.2.0
+ scule: 1.3.0
+ semver: 7.7.2
+ std-env: 3.9.0
+ tinyglobby: 0.2.15
+ ufo: 1.6.1
+ unctx: 2.4.1
+ unimport: 5.2.0
+ untyped: 2.0.0
+ transitivePeerDependencies:
+ - magicast
+
'@nuxt/schema@3.17.6':
dependencies:
- '@vue/shared': 3.5.19
+ '@vue/shared': 3.5.18
consola: 3.4.2
defu: 6.1.4
pathe: 2.0.3
@@ -8388,17 +8522,17 @@ snapshots:
transitivePeerDependencies:
- magicast
- '@nuxt/vite-builder@3.17.6(@types/node@22.18.6)(lightningcss@1.29.3)(magicast@0.3.5)(meow@13.2.0)(rolldown@1.0.0-beta.10-commit.87188ed)(rollup@4.52.0)(terser@5.40.0)(typescript@5.9.2)(vue-tsc@2.2.10(typescript@5.9.2))(vue@3.5.18(typescript@5.9.2))(yaml@2.8.1)':
+ '@nuxt/vite-builder@3.17.6(@types/node@24.3.0)(lightningcss@1.29.3)(magicast@0.3.5)(meow@13.2.0)(rolldown@1.0.0-beta.10-commit.87188ed)(rollup@4.52.2)(terser@5.43.1)(typescript@5.9.2)(vue-tsc@2.2.10(typescript@5.9.2))(vue@3.5.18(typescript@5.9.2))(yaml@2.8.1)':
dependencies:
'@nuxt/kit': 3.17.6(magicast@0.3.5)
- '@rollup/plugin-replace': 6.0.2(rollup@4.52.0)
- '@vitejs/plugin-vue': 5.2.4(vite@6.3.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))
- '@vitejs/plugin-vue-jsx': 4.2.0(vite@6.3.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))
+ '@rollup/plugin-replace': 6.0.2(rollup@4.52.2)
+ '@vitejs/plugin-vue': 5.2.4(vite@6.3.5(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))
+ '@vitejs/plugin-vue-jsx': 4.2.0(vite@6.3.5(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))
autoprefixer: 10.4.21(postcss@8.5.6)
consola: 3.4.2
cssnano: 7.0.7(postcss@8.5.6)
defu: 6.1.4
- esbuild: 0.25.9
+ esbuild: 0.25.5
escape-string-regexp: 5.0.0
exsolve: 1.0.7
externality: 1.0.2
@@ -8414,13 +8548,13 @@ snapshots:
perfect-debounce: 1.0.0
pkg-types: 2.2.0
postcss: 8.5.6
- rollup-plugin-visualizer: 6.0.3(rolldown@1.0.0-beta.10-commit.87188ed)(rollup@4.52.0)
+ rollup-plugin-visualizer: 6.0.3(rolldown@1.0.0-beta.10-commit.87188ed)(rollup@4.52.2)
std-env: 3.9.0
ufo: 1.6.1
unenv: 2.0.0-rc.18
- vite: 6.3.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
- vite-node: 3.2.4(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
- vite-plugin-checker: 0.9.3(meow@13.2.0)(typescript@5.9.2)(vite@6.3.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue-tsc@2.2.10(typescript@5.9.2))
+ vite: 6.3.5(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
+ vite-node: 3.2.4(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
+ vite-plugin-checker: 0.9.3(meow@13.2.0)(typescript@5.9.2)(vite@6.3.5(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue-tsc@2.2.10(typescript@5.9.2))
vue: 3.5.18(typescript@5.9.2)
vue-bundle-renderer: 2.1.1
transitivePeerDependencies:
@@ -8505,7 +8639,7 @@ snapshots:
'@oxc-project/types@0.75.1': {}
- '@oxc-project/types@0.89.0': {}
+ '@oxc-project/types@0.92.0': {}
'@parcel/watcher-android-arm64@2.5.1':
optional: true
@@ -8586,13 +8720,13 @@ snapshots:
'@pinia/colada@0.17.5(pinia@3.0.3(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2))':
dependencies:
- '@vue/devtools-api': 8.0.1
+ '@vue/devtools-api': 8.0.2
pinia: 3.0.3(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2))
vue: 3.5.21(typescript@5.9.2)
- '@pinia/nuxt@0.11.1(magicast@0.3.5)(pinia@3.0.3(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2)))':
+ '@pinia/nuxt@0.11.2(magicast@0.3.5)(pinia@3.0.3(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2)))':
dependencies:
- '@nuxt/kit': 3.17.6(magicast@0.3.5)
+ '@nuxt/kit': 3.18.1(magicast@0.3.5)
pinia: 3.0.3(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2))
transitivePeerDependencies:
- magicast
@@ -8646,64 +8780,64 @@ snapshots:
dependencies:
quansync: 0.2.11
- '@rolldown/binding-android-arm64@1.0.0-beta.38':
+ '@rolldown/binding-android-arm64@1.0.0-beta.40':
optional: true
'@rolldown/binding-darwin-arm64@1.0.0-beta.10-commit.87188ed':
optional: true
- '@rolldown/binding-darwin-arm64@1.0.0-beta.38':
+ '@rolldown/binding-darwin-arm64@1.0.0-beta.40':
optional: true
'@rolldown/binding-darwin-x64@1.0.0-beta.10-commit.87188ed':
optional: true
- '@rolldown/binding-darwin-x64@1.0.0-beta.38':
+ '@rolldown/binding-darwin-x64@1.0.0-beta.40':
optional: true
'@rolldown/binding-freebsd-x64@1.0.0-beta.10-commit.87188ed':
optional: true
- '@rolldown/binding-freebsd-x64@1.0.0-beta.38':
+ '@rolldown/binding-freebsd-x64@1.0.0-beta.40':
optional: true
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.10-commit.87188ed':
optional: true
- '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.38':
+ '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.40':
optional: true
'@rolldown/binding-linux-arm64-gnu@1.0.0-beta.10-commit.87188ed':
optional: true
- '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.38':
+ '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.40':
optional: true
'@rolldown/binding-linux-arm64-musl@1.0.0-beta.10-commit.87188ed':
optional: true
- '@rolldown/binding-linux-arm64-musl@1.0.0-beta.38':
+ '@rolldown/binding-linux-arm64-musl@1.0.0-beta.40':
optional: true
'@rolldown/binding-linux-x64-gnu@1.0.0-beta.10-commit.87188ed':
optional: true
- '@rolldown/binding-linux-x64-gnu@1.0.0-beta.38':
+ '@rolldown/binding-linux-x64-gnu@1.0.0-beta.40':
optional: true
'@rolldown/binding-linux-x64-musl@1.0.0-beta.10-commit.87188ed':
optional: true
- '@rolldown/binding-linux-x64-musl@1.0.0-beta.38':
+ '@rolldown/binding-linux-x64-musl@1.0.0-beta.40':
optional: true
- '@rolldown/binding-openharmony-arm64@1.0.0-beta.38':
+ '@rolldown/binding-openharmony-arm64@1.0.0-beta.40':
optional: true
'@rolldown/binding-wasm32-wasi@1.0.0-beta.10-commit.87188ed':
optional: true
- '@rolldown/binding-wasm32-wasi@1.0.0-beta.38':
+ '@rolldown/binding-wasm32-wasi@1.0.0-beta.40':
dependencies:
'@napi-rs/wasm-runtime': 1.0.5
optional: true
@@ -8711,34 +8845,37 @@ snapshots:
'@rolldown/binding-win32-arm64-msvc@1.0.0-beta.10-commit.87188ed':
optional: true
- '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.38':
+ '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.40':
optional: true
'@rolldown/binding-win32-ia32-msvc@1.0.0-beta.10-commit.87188ed':
optional: true
- '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.38':
+ '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.40':
optional: true
'@rolldown/binding-win32-x64-msvc@1.0.0-beta.10-commit.87188ed':
optional: true
- '@rolldown/binding-win32-x64-msvc@1.0.0-beta.38':
+ '@rolldown/binding-win32-x64-msvc@1.0.0-beta.40':
optional: true
- '@rolldown/pluginutils@1.0.0-beta.10-commit.87188ed': {}
+ '@rolldown/pluginutils@1.0.0-beta.10-commit.87188ed':
+ optional: true
'@rolldown/pluginutils@1.0.0-beta.29': {}
- '@rolldown/pluginutils@1.0.0-beta.38': {}
+ '@rolldown/pluginutils@1.0.0-beta.33': {}
+
+ '@rolldown/pluginutils@1.0.0-beta.40': {}
- '@rollup/plugin-alias@5.1.1(rollup@4.52.0)':
+ '@rollup/plugin-alias@5.1.1(rollup@4.52.2)':
optionalDependencies:
- rollup: 4.52.0
+ rollup: 4.52.2
- '@rollup/plugin-commonjs@28.0.6(rollup@4.52.0)':
+ '@rollup/plugin-commonjs@28.0.6(rollup@4.52.2)':
dependencies:
- '@rollup/pluginutils': 5.1.4(rollup@4.52.0)
+ '@rollup/pluginutils': 5.1.4(rollup@4.52.2)
commondir: 1.0.1
estree-walker: 2.0.2
fdir: 6.5.0(picomatch@4.0.3)
@@ -8746,119 +8883,119 @@ snapshots:
magic-string: 0.30.19
picomatch: 4.0.3
optionalDependencies:
- rollup: 4.52.0
+ rollup: 4.52.2
- '@rollup/plugin-inject@5.0.5(rollup@4.52.0)':
+ '@rollup/plugin-inject@5.0.5(rollup@4.52.2)':
dependencies:
- '@rollup/pluginutils': 5.1.4(rollup@4.52.0)
+ '@rollup/pluginutils': 5.1.4(rollup@4.52.2)
estree-walker: 2.0.2
magic-string: 0.30.19
optionalDependencies:
- rollup: 4.52.0
+ rollup: 4.52.2
- '@rollup/plugin-json@6.1.0(rollup@4.52.0)':
+ '@rollup/plugin-json@6.1.0(rollup@4.52.2)':
dependencies:
- '@rollup/pluginutils': 5.1.4(rollup@4.52.0)
+ '@rollup/pluginutils': 5.1.4(rollup@4.52.2)
optionalDependencies:
- rollup: 4.52.0
+ rollup: 4.52.2
- '@rollup/plugin-node-resolve@16.0.1(rollup@4.52.0)':
+ '@rollup/plugin-node-resolve@16.0.1(rollup@4.52.2)':
dependencies:
- '@rollup/pluginutils': 5.1.4(rollup@4.52.0)
+ '@rollup/pluginutils': 5.1.4(rollup@4.52.2)
'@types/resolve': 1.20.2
deepmerge: 4.3.1
is-module: 1.0.0
resolve: 1.22.10
optionalDependencies:
- rollup: 4.52.0
+ rollup: 4.52.2
- '@rollup/plugin-replace@6.0.2(rollup@4.52.0)':
+ '@rollup/plugin-replace@6.0.2(rollup@4.52.2)':
dependencies:
- '@rollup/pluginutils': 5.1.4(rollup@4.52.0)
+ '@rollup/pluginutils': 5.1.4(rollup@4.52.2)
magic-string: 0.30.19
optionalDependencies:
- rollup: 4.52.0
+ rollup: 4.52.2
- '@rollup/plugin-terser@0.4.4(rollup@4.52.0)':
+ '@rollup/plugin-terser@0.4.4(rollup@4.52.2)':
dependencies:
serialize-javascript: 6.0.2
smob: 1.5.0
terser: 5.40.0
optionalDependencies:
- rollup: 4.52.0
+ rollup: 4.52.2
- '@rollup/pluginutils@5.1.4(rollup@4.52.0)':
+ '@rollup/pluginutils@5.1.4(rollup@4.52.2)':
dependencies:
'@types/estree': 1.0.8
estree-walker: 2.0.2
picomatch: 4.0.3
optionalDependencies:
- rollup: 4.52.0
+ rollup: 4.52.2
- '@rollup/rollup-android-arm-eabi@4.52.0':
+ '@rollup/rollup-android-arm-eabi@4.52.2':
optional: true
- '@rollup/rollup-android-arm64@4.52.0':
+ '@rollup/rollup-android-arm64@4.52.2':
optional: true
- '@rollup/rollup-darwin-arm64@4.52.0':
+ '@rollup/rollup-darwin-arm64@4.52.2':
optional: true
- '@rollup/rollup-darwin-x64@4.52.0':
+ '@rollup/rollup-darwin-x64@4.52.2':
optional: true
- '@rollup/rollup-freebsd-arm64@4.52.0':
+ '@rollup/rollup-freebsd-arm64@4.52.2':
optional: true
- '@rollup/rollup-freebsd-x64@4.52.0':
+ '@rollup/rollup-freebsd-x64@4.52.2':
optional: true
- '@rollup/rollup-linux-arm-gnueabihf@4.52.0':
+ '@rollup/rollup-linux-arm-gnueabihf@4.52.2':
optional: true
- '@rollup/rollup-linux-arm-musleabihf@4.52.0':
+ '@rollup/rollup-linux-arm-musleabihf@4.52.2':
optional: true
- '@rollup/rollup-linux-arm64-gnu@4.52.0':
+ '@rollup/rollup-linux-arm64-gnu@4.52.2':
optional: true
- '@rollup/rollup-linux-arm64-musl@4.52.0':
+ '@rollup/rollup-linux-arm64-musl@4.52.2':
optional: true
- '@rollup/rollup-linux-loong64-gnu@4.52.0':
+ '@rollup/rollup-linux-loong64-gnu@4.52.2':
optional: true
- '@rollup/rollup-linux-ppc64-gnu@4.52.0':
+ '@rollup/rollup-linux-ppc64-gnu@4.52.2':
optional: true
- '@rollup/rollup-linux-riscv64-gnu@4.52.0':
+ '@rollup/rollup-linux-riscv64-gnu@4.52.2':
optional: true
- '@rollup/rollup-linux-riscv64-musl@4.52.0':
+ '@rollup/rollup-linux-riscv64-musl@4.52.2':
optional: true
- '@rollup/rollup-linux-s390x-gnu@4.52.0':
+ '@rollup/rollup-linux-s390x-gnu@4.52.2':
optional: true
- '@rollup/rollup-linux-x64-gnu@4.52.0':
+ '@rollup/rollup-linux-x64-gnu@4.52.2':
optional: true
- '@rollup/rollup-linux-x64-musl@4.52.0':
+ '@rollup/rollup-linux-x64-musl@4.52.2':
optional: true
- '@rollup/rollup-openharmony-arm64@4.52.0':
+ '@rollup/rollup-openharmony-arm64@4.52.2':
optional: true
- '@rollup/rollup-win32-arm64-msvc@4.52.0':
+ '@rollup/rollup-win32-arm64-msvc@4.52.2':
optional: true
- '@rollup/rollup-win32-ia32-msvc@4.52.0':
+ '@rollup/rollup-win32-ia32-msvc@4.52.2':
optional: true
- '@rollup/rollup-win32-x64-gnu@4.52.0':
+ '@rollup/rollup-win32-x64-gnu@4.52.2':
optional: true
- '@rollup/rollup-win32-x64-msvc@4.52.0':
+ '@rollup/rollup-win32-x64-msvc@4.52.2':
optional: true
'@sec-ant/readable-stream@0.4.1': {}
@@ -9030,6 +9167,10 @@ snapshots:
tslib: 2.8.1
optional: true
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.28.4
+
'@types/chai@5.2.2':
dependencies:
'@types/deep-eql': 4.0.2
@@ -9073,13 +9214,13 @@ snapshots:
'@types/ms@2.1.0': {}
- '@types/node@20.19.4':
+ '@types/node@20.19.11':
dependencies:
undici-types: 6.21.0
- '@types/node@22.18.6':
+ '@types/node@24.3.0':
dependencies:
- undici-types: 6.21.0
+ undici-types: 7.10.0
'@types/normalize-package-data@2.4.4': {}
@@ -9103,14 +9244,14 @@ snapshots:
'@types/yauzl@2.10.3':
dependencies:
- '@types/node': 22.18.6
+ '@types/node': 24.3.0
optional: true
'@typescript-eslint/project-service@8.33.0(typescript@5.9.2)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.33.0(typescript@5.9.2)
'@typescript-eslint/types': 8.33.0
- debug: 4.4.1(supports-color@5.5.0)
+ debug: 4.4.3
transitivePeerDependencies:
- supports-color
- typescript
@@ -9127,7 +9268,7 @@ snapshots:
'@typescript-eslint/tsconfig-utils': 8.33.0(typescript@5.9.2)
'@typescript-eslint/types': 8.33.0
'@typescript-eslint/visitor-keys': 8.33.0
- debug: 4.4.1(supports-color@5.5.0)
+ debug: 4.4.3
fast-glob: 3.3.3
is-glob: 4.0.3
minimatch: 9.0.5
@@ -9144,7 +9285,7 @@ snapshots:
'@typescript/vfs@1.6.1(typescript@5.9.2)':
dependencies:
- debug: 4.4.1(supports-color@5.5.0)
+ debug: 4.4.3
typescript: 5.9.2
transitivePeerDependencies:
- supports-color
@@ -9157,10 +9298,10 @@ snapshots:
unhead: 2.0.11
vue: 3.5.18(typescript@5.9.2)
- '@vercel/nft@0.29.3(encoding@0.1.13)(rollup@4.52.0)':
+ '@vercel/nft@0.29.3(encoding@0.1.13)(rollup@4.52.2)':
dependencies:
'@mapbox/node-pre-gyp': 2.0.0(encoding@0.1.13)
- '@rollup/pluginutils': 5.1.4(rollup@4.52.0)
+ '@rollup/pluginutils': 5.1.4(rollup@4.52.2)
acorn: 8.15.0
acorn-import-attributes: 1.9.5(acorn@8.15.0)
async-sema: 3.1.1
@@ -9176,10 +9317,10 @@ snapshots:
- rollup
- supports-color
- '@vercel/nft@0.29.4(encoding@0.1.13)(rollup@4.52.0)':
+ '@vercel/nft@0.29.4(encoding@0.1.13)(rollup@4.52.2)':
dependencies:
'@mapbox/node-pre-gyp': 2.0.0(encoding@0.1.13)
- '@rollup/pluginutils': 5.1.4(rollup@4.52.0)
+ '@rollup/pluginutils': 5.1.4(rollup@4.52.2)
acorn: 8.15.0
acorn-import-attributes: 1.9.5(acorn@8.15.0)
async-sema: 3.1.1
@@ -9195,31 +9336,31 @@ snapshots:
- rollup
- supports-color
- '@vitejs/plugin-vue-jsx@4.2.0(vite@6.3.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))':
+ '@vitejs/plugin-vue-jsx@4.2.0(vite@6.3.5(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))':
dependencies:
'@babel/core': 7.27.4
'@babel/plugin-transform-typescript': 7.27.1(@babel/core@7.27.4)
- '@rolldown/pluginutils': 1.0.0-beta.10-commit.87188ed
+ '@rolldown/pluginutils': 1.0.0-beta.33
'@vue/babel-plugin-jsx': 1.4.0(@babel/core@7.27.4)
- vite: 6.3.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ vite: 6.3.5(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
vue: 3.5.18(typescript@5.9.2)
transitivePeerDependencies:
- supports-color
- '@vitejs/plugin-vue@5.2.4(vite@5.4.20(@types/node@22.18.6)(lightningcss@1.29.3)(terser@5.40.0))(vue@3.5.21(typescript@5.9.2))':
+ '@vitejs/plugin-vue@5.2.4(vite@5.4.19(@types/node@24.3.0)(lightningcss@1.29.3)(terser@5.43.1))(vue@3.5.21(typescript@5.9.2))':
dependencies:
- vite: 5.4.20(@types/node@22.18.6)(lightningcss@1.29.3)(terser@5.40.0)
+ vite: 5.4.19(@types/node@24.3.0)(lightningcss@1.29.3)(terser@5.43.1)
vue: 3.5.21(typescript@5.9.2)
- '@vitejs/plugin-vue@5.2.4(vite@6.3.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))':
+ '@vitejs/plugin-vue@5.2.4(vite@6.3.5(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))':
dependencies:
- vite: 6.3.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ vite: 6.3.5(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
vue: 3.5.18(typescript@5.9.2)
- '@vitejs/plugin-vue@6.0.1(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))':
+ '@vitejs/plugin-vue@6.0.1(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))':
dependencies:
'@rolldown/pluginutils': 1.0.0-beta.29
- vite: 7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ vite: 7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
vue: 3.5.21(typescript@5.9.2)
'@vitest/coverage-v8@3.2.4(vitest@3.2.4)':
@@ -9237,7 +9378,7 @@ snapshots:
std-env: 3.9.0
test-exclude: 7.0.1
tinyrainbow: 2.0.0
- vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
transitivePeerDependencies:
- supports-color
@@ -9249,13 +9390,13 @@ snapshots:
chai: 5.2.0
tinyrainbow: 2.0.0
- '@vitest/mocker@3.2.4(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))':
+ '@vitest/mocker@3.2.4(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.19
optionalDependencies:
- vite: 7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ vite: 7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
'@vitest/pretty-format@3.2.4':
dependencies:
@@ -9286,7 +9427,7 @@ snapshots:
sirv: 3.0.1
tinyglobby: 0.2.15
tinyrainbow: 2.0.0
- vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
'@vitest/utils@3.2.4':
dependencies:
@@ -9306,25 +9447,25 @@ snapshots:
path-browserify: 1.0.1
vscode-uri: 3.1.0
- '@vue-macros/common@3.0.0(vue@3.5.21(typescript@5.9.2))':
+ '@vue-macros/common@3.0.0-beta.15(vue@3.5.18(typescript@5.9.2))':
dependencies:
'@vue/compiler-sfc': 3.5.21
ast-kit: 2.1.2
local-pkg: 1.1.2
magic-string-ast: 1.0.2
- unplugin-utils: 0.3.0
+ unplugin-utils: 0.2.5
optionalDependencies:
- vue: 3.5.21(typescript@5.9.2)
+ vue: 3.5.18(typescript@5.9.2)
- '@vue-macros/common@3.0.0-beta.15(vue@3.5.18(typescript@5.9.2))':
+ '@vue-macros/common@3.1.0(vue@3.5.21(typescript@5.9.2))':
dependencies:
'@vue/compiler-sfc': 3.5.21
ast-kit: 2.1.2
local-pkg: 1.1.2
magic-string-ast: 1.0.2
- unplugin-utils: 0.2.5
+ unplugin-utils: 0.3.0
optionalDependencies:
- vue: 3.5.18(typescript@5.9.2)
+ vue: 3.5.21(typescript@5.9.2)
'@vue/babel-helper-vue-transform-on@1.4.0': {}
@@ -9338,7 +9479,7 @@ snapshots:
'@babel/types': 7.28.4
'@vue/babel-helper-vue-transform-on': 1.4.0
'@vue/babel-plugin-resolve-type': 1.4.0(@babel/core@7.27.4)
- '@vue/shared': 3.5.21
+ '@vue/shared': 3.5.18
optionalDependencies:
'@babel/core': 7.27.4
transitivePeerDependencies:
@@ -9350,14 +9491,14 @@ snapshots:
'@babel/core': 7.27.4
'@babel/helper-module-imports': 7.27.1
'@babel/helper-plugin-utils': 7.27.1
- '@babel/parser': 7.28.4
+ '@babel/parser': 7.28.3
'@vue/compiler-sfc': 3.5.21
transitivePeerDependencies:
- supports-color
'@vue/compiler-core@3.5.18':
dependencies:
- '@babel/parser': 7.28.3
+ '@babel/parser': 7.28.0
'@vue/shared': 3.5.18
entities: 4.5.0
estree-walker: 2.0.2
@@ -9401,7 +9542,7 @@ snapshots:
'@vue/compiler-ssr': 3.5.21
'@vue/shared': 3.5.21
estree-walker: 2.0.2
- magic-string: 0.30.18
+ magic-string: 0.30.19
postcss: 8.5.6
source-map-js: 1.2.1
@@ -9426,30 +9567,30 @@ snapshots:
dependencies:
'@vue/devtools-kit': 7.7.7
- '@vue/devtools-api@8.0.1':
+ '@vue/devtools-api@8.0.2':
dependencies:
- '@vue/devtools-kit': 8.0.1
+ '@vue/devtools-kit': 8.0.2
- '@vue/devtools-core@7.7.7(vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))':
+ '@vue/devtools-core@7.7.7(vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))':
dependencies:
'@vue/devtools-kit': 7.7.7
'@vue/devtools-shared': 7.7.7
mitt: 3.0.1
nanoid: 5.1.5
pathe: 2.0.3
- vite-hot-client: 2.1.0(vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))
+ vite-hot-client: 2.1.0(vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))
vue: 3.5.18(typescript@5.9.2)
transitivePeerDependencies:
- vite
- '@vue/devtools-core@8.0.2(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))':
+ '@vue/devtools-core@8.0.2(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))':
dependencies:
'@vue/devtools-kit': 8.0.2
'@vue/devtools-shared': 8.0.2
mitt: 3.0.1
nanoid: 5.1.5
pathe: 2.0.3
- vite-hot-client: 2.1.0(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))
+ vite-hot-client: 2.1.0(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))
vue: 3.5.21(typescript@5.9.2)
transitivePeerDependencies:
- vite
@@ -9464,16 +9605,6 @@ snapshots:
speakingurl: 14.0.1
superjson: 2.2.2
- '@vue/devtools-kit@8.0.1':
- dependencies:
- '@vue/devtools-shared': 8.0.1
- birpc: 2.5.0
- hookable: 5.5.3
- mitt: 3.0.1
- perfect-debounce: 1.0.0
- speakingurl: 14.0.1
- superjson: 2.2.2
-
'@vue/devtools-kit@8.0.2':
dependencies:
'@vue/devtools-shared': 8.0.2
@@ -9488,10 +9619,6 @@ snapshots:
dependencies:
rfdc: 1.4.1
- '@vue/devtools-shared@8.0.1':
- dependencies:
- rfdc: 1.4.1
-
'@vue/devtools-shared@8.0.2':
dependencies:
rfdc: 1.4.1
@@ -9513,9 +9640,9 @@ snapshots:
'@vue/language-core@3.0.7(typescript@5.9.2)':
dependencies:
'@volar/language-core': 2.4.23
- '@vue/compiler-dom': 3.5.21
+ '@vue/compiler-dom': 3.5.18
'@vue/compiler-vue2': 2.7.16
- '@vue/shared': 3.5.21
+ '@vue/shared': 3.5.18
alien-signals: 2.0.5
muggle-string: 0.4.1
path-browserify: 1.0.1
@@ -9569,8 +9696,6 @@ snapshots:
'@vue/shared@3.5.18': {}
- '@vue/shared@3.5.19': {}
-
'@vue/shared@3.5.21': {}
'@vue/test-utils@2.4.6':
@@ -9578,6 +9703,11 @@ snapshots:
js-beautify: 1.15.1
vue-component-type-helpers: 2.0.22
+ '@vue/tsconfig@0.7.0(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2))':
+ optionalDependencies:
+ typescript: 5.9.2
+ vue: 3.5.21(typescript@5.9.2)
+
'@vue/tsconfig@0.8.1(typescript@5.9.2)(vue@3.5.21(typescript@5.9.2))':
optionalDependencies:
typescript: 5.9.2
@@ -9840,7 +9970,7 @@ snapshots:
ast-kit@2.1.2:
dependencies:
- '@babel/parser': 7.28.4
+ '@babel/parser': 7.28.3
pathe: 2.0.3
ast-module-types@6.0.1: {}
@@ -9862,7 +9992,7 @@ snapshots:
autoprefixer@10.4.21(postcss@8.5.6):
dependencies:
- browserslist: 4.25.0
+ browserslist: 4.25.2
caniuse-lite: 1.0.30001720
fraction.js: 4.3.7
normalize-range: 0.1.2
@@ -9925,12 +10055,12 @@ snapshots:
dependencies:
fill-range: 7.1.1
- browserslist@4.25.0:
+ browserslist@4.25.2:
dependencies:
- caniuse-lite: 1.0.30001720
- electron-to-chromium: 1.5.161
+ caniuse-lite: 1.0.30001735
+ electron-to-chromium: 1.5.202
node-releases: 2.0.19
- update-browserslist-db: 1.1.3(browserslist@4.25.0)
+ update-browserslist-db: 1.1.3(browserslist@4.25.2)
buffer-crc32@0.2.13: {}
@@ -9970,6 +10100,23 @@ snapshots:
optionalDependencies:
magicast: 0.3.5
+ c12@3.2.0(magicast@0.3.5):
+ dependencies:
+ chokidar: 4.0.3
+ confbox: 0.2.2
+ defu: 6.1.4
+ dotenv: 17.2.1
+ exsolve: 1.0.7
+ giget: 2.0.0
+ jiti: 2.5.1
+ ohash: 2.0.11
+ pathe: 2.0.3
+ perfect-debounce: 1.0.0
+ pkg-types: 2.2.0
+ rc9: 2.1.2
+ optionalDependencies:
+ magicast: 0.3.5
+
cac@6.7.14: {}
call-bind-apply-helpers@1.0.2:
@@ -9986,13 +10133,15 @@ snapshots:
caniuse-api@3.0.0:
dependencies:
- browserslist: 4.25.0
- caniuse-lite: 1.0.30001720
+ browserslist: 4.25.2
+ caniuse-lite: 1.0.30001735
lodash.memoize: 4.1.2
lodash.uniq: 4.5.0
caniuse-lite@1.0.30001720: {}
+ caniuse-lite@1.0.30001735: {}
+
ccount@2.0.1: {}
chai@5.2.0:
@@ -10051,10 +10200,10 @@ snapshots:
dependencies:
restore-cursor: 5.1.0
- cli-truncate@4.0.0:
+ cli-truncate@5.1.0:
dependencies:
- slice-ansi: 5.0.0
- string-width: 7.2.0
+ slice-ansi: 7.1.0
+ string-width: 8.1.0
clipboardy@4.0.0:
dependencies:
@@ -10334,7 +10483,7 @@ snapshots:
cssnano-preset-default@7.0.7(postcss@8.5.6):
dependencies:
- browserslist: 4.25.0
+ browserslist: 4.25.2
css-declaration-sorter: 7.2.0(postcss@8.5.6)
cssnano-utils: 5.0.1(postcss@8.5.6)
postcss: 8.5.6
@@ -10537,6 +10686,8 @@ snapshots:
dotenv@16.5.0: {}
+ dotenv@17.2.1: {}
+
dts-resolver@2.1.2: {}
dunder-proto@1.0.1:
@@ -10558,7 +10709,7 @@ snapshots:
ee-first@1.1.1: {}
- electron-to-chromium@1.5.161: {}
+ electron-to-chromium@1.5.202: {}
emoji-regex-xs@1.0.0: {}
@@ -10585,7 +10736,7 @@ snapshots:
dependencies:
once: 1.4.0
- enhanced-resolve@5.18.1:
+ enhanced-resolve@5.18.3:
dependencies:
graceful-fs: 4.2.11
tapable: 2.2.2
@@ -10865,14 +11016,14 @@ snapshots:
externality@1.0.2:
dependencies:
- enhanced-resolve: 5.18.1
+ enhanced-resolve: 5.18.3
mlly: 1.8.0
pathe: 1.1.2
ufo: 1.6.1
extract-zip@2.0.1:
dependencies:
- debug: 4.4.1(supports-color@5.5.0)
+ debug: 4.4.3
get-stream: 5.2.0
yauzl: 2.10.0
optionalDependencies:
@@ -10912,6 +11063,10 @@ snapshots:
dependencies:
pend: 1.2.0
+ fdir@6.4.6(picomatch@4.0.3):
+ optionalDependencies:
+ picomatch: 4.0.3
+
fdir@6.5.0(picomatch@4.0.3):
optionalDependencies:
picomatch: 4.0.3
@@ -11203,7 +11358,7 @@ snapshots:
happy-dom@18.0.1:
dependencies:
- '@types/node': 20.19.4
+ '@types/node': 20.19.11
'@types/whatwg-mimetype': 3.0.2
whatwg-mimetype: 3.0.0
@@ -11262,7 +11417,7 @@ snapshots:
https-proxy-agent@7.0.6:
dependencies:
agent-base: 7.1.3
- debug: 4.4.1(supports-color@5.5.0)
+ debug: 4.4.3
transitivePeerDependencies:
- supports-color
@@ -11318,7 +11473,7 @@ snapshots:
dependencies:
'@ioredis/commands': 1.2.0
cluster-key-slot: 1.1.2
- debug: 4.4.1(supports-color@5.5.0)
+ debug: 4.4.3
denque: 2.1.0
lodash.defaults: 4.2.0
lodash.isarguments: 3.1.0
@@ -11360,8 +11515,6 @@ snapshots:
is-fullwidth-code-point@3.0.0: {}
- is-fullwidth-code-point@4.0.0: {}
-
is-fullwidth-code-point@5.0.0:
dependencies:
get-east-asian-width: 1.3.0
@@ -11472,7 +11625,7 @@ snapshots:
jest-worker@27.5.1:
dependencies:
- '@types/node': 22.18.6
+ '@types/node': 24.3.0
merge-stream: 2.0.0
supports-color: 8.1.1
@@ -11628,7 +11781,7 @@ snapshots:
commander: 14.0.0
debug: 4.4.1(supports-color@5.5.0)
lilconfig: 3.1.3
- listr2: 9.0.3
+ listr2: 9.0.4
micromatch: 4.0.8
nano-spawn: 1.0.2
pidtree: 0.6.0
@@ -11658,9 +11811,9 @@ snapshots:
untun: 0.1.3
uqr: 0.1.2
- listr2@9.0.3:
+ listr2@9.0.4:
dependencies:
- cli-truncate: 4.0.0
+ cli-truncate: 5.1.0
colorette: 2.0.20
eventemitter3: 5.0.1
log-update: 6.1.0
@@ -11753,10 +11906,6 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
- magic-string@0.30.18:
- dependencies:
- '@jridgewell/sourcemap-codec': 1.5.5
-
magic-string@0.30.19:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -12221,15 +12370,15 @@ snapshots:
nitropack@2.11.13(@netlify/blobs@8.2.0)(encoding@0.1.13)(rolldown@1.0.0-beta.10-commit.87188ed):
dependencies:
'@cloudflare/kv-asset-handler': 0.4.0
- '@netlify/functions': 3.1.10(encoding@0.1.13)(rollup@4.52.0)
- '@rollup/plugin-alias': 5.1.1(rollup@4.52.0)
- '@rollup/plugin-commonjs': 28.0.6(rollup@4.52.0)
- '@rollup/plugin-inject': 5.0.5(rollup@4.52.0)
- '@rollup/plugin-json': 6.1.0(rollup@4.52.0)
- '@rollup/plugin-node-resolve': 16.0.1(rollup@4.52.0)
- '@rollup/plugin-replace': 6.0.2(rollup@4.52.0)
- '@rollup/plugin-terser': 0.4.4(rollup@4.52.0)
- '@vercel/nft': 0.29.4(encoding@0.1.13)(rollup@4.52.0)
+ '@netlify/functions': 3.1.10(encoding@0.1.13)(rollup@4.52.2)
+ '@rollup/plugin-alias': 5.1.1(rollup@4.52.2)
+ '@rollup/plugin-commonjs': 28.0.6(rollup@4.52.2)
+ '@rollup/plugin-inject': 5.0.5(rollup@4.52.2)
+ '@rollup/plugin-json': 6.1.0(rollup@4.52.2)
+ '@rollup/plugin-node-resolve': 16.0.1(rollup@4.52.2)
+ '@rollup/plugin-replace': 6.0.2(rollup@4.52.2)
+ '@rollup/plugin-terser': 0.4.4(rollup@4.52.2)
+ '@vercel/nft': 0.29.4(encoding@0.1.13)(rollup@4.52.2)
archiver: 7.0.1
c12: 3.0.4(magicast@0.3.5)
chokidar: 4.0.3
@@ -12244,7 +12393,7 @@ snapshots:
defu: 6.1.4
destr: 2.0.5
dot-prop: 9.0.0
- esbuild: 0.25.9
+ esbuild: 0.25.5
escape-string-regexp: 5.0.0
etag: 1.8.1
exsolve: 1.0.7
@@ -12271,8 +12420,8 @@ snapshots:
pkg-types: 2.2.0
pretty-bytes: 6.1.1
radix3: 1.1.2
- rollup: 4.52.0
- rollup-plugin-visualizer: 6.0.3(rolldown@1.0.0-beta.10-commit.87188ed)(rollup@4.52.0)
+ rollup: 4.52.2
+ rollup-plugin-visualizer: 6.0.3(rolldown@1.0.0-beta.10-commit.87188ed)(rollup@4.52.2)
scule: 1.3.0
semver: 7.7.2
serve-placeholder: 2.0.2
@@ -12346,7 +12495,7 @@ snapshots:
node-source-walk@7.0.1:
dependencies:
- '@babel/parser': 7.28.4
+ '@babel/parser': 7.28.3
nodemon@3.1.10:
dependencies:
@@ -12402,15 +12551,15 @@ snapshots:
dependencies:
boolbase: 1.0.0
- nuxt@3.17.6(@netlify/blobs@8.2.0)(@parcel/watcher@2.5.1)(@types/node@22.18.6)(@vue/compiler-sfc@3.5.21)(db0@0.3.2)(encoding@0.1.13)(ioredis@5.6.1)(lightningcss@1.29.3)(magicast@0.3.5)(meow@13.2.0)(rolldown@1.0.0-beta.10-commit.87188ed)(rollup@4.52.0)(terser@5.40.0)(typescript@5.9.2)(vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue-tsc@2.2.10(typescript@5.9.2))(yaml@2.8.1):
+ nuxt@3.17.6(@netlify/blobs@8.2.0)(@parcel/watcher@2.5.1)(@types/node@24.3.0)(@vue/compiler-sfc@3.5.21)(db0@0.3.2)(encoding@0.1.13)(ioredis@5.6.1)(lightningcss@1.29.3)(magicast@0.3.5)(meow@13.2.0)(rolldown@1.0.0-beta.10-commit.87188ed)(rollup@4.52.2)(terser@5.43.1)(typescript@5.9.2)(vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue-tsc@2.2.10(typescript@5.9.2))(yaml@2.8.1):
dependencies:
'@nuxt/cli': 3.25.1(magicast@0.3.5)
'@nuxt/devalue': 2.0.2
- '@nuxt/devtools': 2.6.2(vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))
+ '@nuxt/devtools': 2.6.2(vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2))
'@nuxt/kit': 3.17.6(magicast@0.3.5)
'@nuxt/schema': 3.17.6
'@nuxt/telemetry': 2.6.6(magicast@0.3.5)
- '@nuxt/vite-builder': 3.17.6(@types/node@22.18.6)(lightningcss@1.29.3)(magicast@0.3.5)(meow@13.2.0)(rolldown@1.0.0-beta.10-commit.87188ed)(rollup@4.52.0)(terser@5.40.0)(typescript@5.9.2)(vue-tsc@2.2.10(typescript@5.9.2))(vue@3.5.18(typescript@5.9.2))(yaml@2.8.1)
+ '@nuxt/vite-builder': 3.17.6(@types/node@24.3.0)(lightningcss@1.29.3)(magicast@0.3.5)(meow@13.2.0)(rolldown@1.0.0-beta.10-commit.87188ed)(rollup@4.52.2)(terser@5.43.1)(typescript@5.9.2)(vue-tsc@2.2.10(typescript@5.9.2))(vue@3.5.18(typescript@5.9.2))(yaml@2.8.1)
'@unhead/vue': 2.0.11(vue@3.5.18(typescript@5.9.2))
'@vue/shared': 3.5.18
c12: 3.0.4(magicast@0.3.5)
@@ -12467,7 +12616,7 @@ snapshots:
vue-router: 4.5.1(vue@3.5.21(typescript@5.9.2))
optionalDependencies:
'@parcel/watcher': 2.5.1
- '@types/node': 22.18.6
+ '@types/node': 24.3.0
transitivePeerDependencies:
- '@azure/app-configuration'
- '@azure/cosmos'
@@ -12772,7 +12921,7 @@ snapshots:
postcss-colormin@7.0.3(postcss@8.5.6):
dependencies:
- browserslist: 4.25.0
+ browserslist: 4.25.2
caniuse-api: 3.0.0
colord: 2.9.3
postcss: 8.5.6
@@ -12780,7 +12929,7 @@ snapshots:
postcss-convert-values@7.0.5(postcss@8.5.6):
dependencies:
- browserslist: 4.25.0
+ browserslist: 4.25.2
postcss: 8.5.6
postcss-value-parser: 4.2.0
@@ -12809,7 +12958,7 @@ snapshots:
postcss-merge-rules@7.0.5(postcss@8.5.6):
dependencies:
- browserslist: 4.25.0
+ browserslist: 4.25.2
caniuse-api: 3.0.0
cssnano-utils: 5.0.1(postcss@8.5.6)
postcss: 8.5.6
@@ -12829,7 +12978,7 @@ snapshots:
postcss-minify-params@7.0.3(postcss@8.5.6):
dependencies:
- browserslist: 4.25.0
+ browserslist: 4.25.2
cssnano-utils: 5.0.1(postcss@8.5.6)
postcss: 8.5.6
postcss-value-parser: 4.2.0
@@ -12871,7 +13020,7 @@ snapshots:
postcss-normalize-unicode@7.0.3(postcss@8.5.6):
dependencies:
- browserslist: 4.25.0
+ browserslist: 4.25.2
postcss: 8.5.6
postcss-value-parser: 4.2.0
@@ -12893,7 +13042,7 @@ snapshots:
postcss-reduce-initial@7.0.3(postcss@8.5.6):
dependencies:
- browserslist: 4.25.0
+ browserslist: 4.25.2
caniuse-api: 3.0.0
postcss: 8.5.6
@@ -12978,7 +13127,7 @@ snapshots:
proto-list@1.2.4: {}
- protobufjs@7.5.0:
+ protobufjs@7.5.3:
dependencies:
'@protobufjs/aspromise': 1.1.2
'@protobufjs/base64': 1.1.2
@@ -12990,7 +13139,7 @@ snapshots:
'@protobufjs/path': 1.1.2
'@protobufjs/pool': 1.1.0
'@protobufjs/utf8': 1.1.0
- '@types/node': 22.18.6
+ '@types/node': 24.3.0
long: 5.3.2
protocols@2.0.2: {}
@@ -13191,7 +13340,7 @@ snapshots:
glob: 11.0.0
package-json-from-dist: 1.0.0
- rolldown-plugin-dts@0.16.7(rolldown@1.0.0-beta.38)(typescript@5.9.2)(vue-tsc@3.0.7(typescript@5.9.2)):
+ rolldown-plugin-dts@0.16.8(rolldown@1.0.0-beta.40)(typescript@5.9.2)(vue-tsc@3.0.7(typescript@5.9.2)):
dependencies:
'@babel/generator': 7.28.3
'@babel/parser': 7.28.4
@@ -13202,7 +13351,7 @@ snapshots:
dts-resolver: 2.1.2
get-tsconfig: 4.10.1
magic-string: 0.30.19
- rolldown: 1.0.0-beta.38
+ rolldown: 1.0.0-beta.40
optionalDependencies:
typescript: 5.9.2
vue-tsc: 3.0.7(typescript@5.9.2)
@@ -13231,28 +13380,28 @@ snapshots:
'@rolldown/binding-win32-x64-msvc': 1.0.0-beta.10-commit.87188ed
optional: true
- rolldown@1.0.0-beta.38:
+ rolldown@1.0.0-beta.40:
dependencies:
- '@oxc-project/types': 0.89.0
- '@rolldown/pluginutils': 1.0.0-beta.38
+ '@oxc-project/types': 0.92.0
+ '@rolldown/pluginutils': 1.0.0-beta.40
ansis: 4.1.0
optionalDependencies:
- '@rolldown/binding-android-arm64': 1.0.0-beta.38
- '@rolldown/binding-darwin-arm64': 1.0.0-beta.38
- '@rolldown/binding-darwin-x64': 1.0.0-beta.38
- '@rolldown/binding-freebsd-x64': 1.0.0-beta.38
- '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.38
- '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.38
- '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.38
- '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.38
- '@rolldown/binding-linux-x64-musl': 1.0.0-beta.38
- '@rolldown/binding-openharmony-arm64': 1.0.0-beta.38
- '@rolldown/binding-wasm32-wasi': 1.0.0-beta.38
- '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.38
- '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.38
- '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.38
-
- rollup-plugin-visualizer@6.0.3(rolldown@1.0.0-beta.10-commit.87188ed)(rollup@4.52.0):
+ '@rolldown/binding-android-arm64': 1.0.0-beta.40
+ '@rolldown/binding-darwin-arm64': 1.0.0-beta.40
+ '@rolldown/binding-darwin-x64': 1.0.0-beta.40
+ '@rolldown/binding-freebsd-x64': 1.0.0-beta.40
+ '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.40
+ '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.40
+ '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.40
+ '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.40
+ '@rolldown/binding-linux-x64-musl': 1.0.0-beta.40
+ '@rolldown/binding-openharmony-arm64': 1.0.0-beta.40
+ '@rolldown/binding-wasm32-wasi': 1.0.0-beta.40
+ '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.40
+ '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.40
+ '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.40
+
+ rollup-plugin-visualizer@6.0.3(rolldown@1.0.0-beta.10-commit.87188ed)(rollup@4.52.2):
dependencies:
open: 8.4.2
picomatch: 4.0.3
@@ -13260,34 +13409,34 @@ snapshots:
yargs: 17.7.2
optionalDependencies:
rolldown: 1.0.0-beta.10-commit.87188ed
- rollup: 4.52.0
+ rollup: 4.52.2
- rollup@4.52.0:
+ rollup@4.52.2:
dependencies:
'@types/estree': 1.0.8
optionalDependencies:
- '@rollup/rollup-android-arm-eabi': 4.52.0
- '@rollup/rollup-android-arm64': 4.52.0
- '@rollup/rollup-darwin-arm64': 4.52.0
- '@rollup/rollup-darwin-x64': 4.52.0
- '@rollup/rollup-freebsd-arm64': 4.52.0
- '@rollup/rollup-freebsd-x64': 4.52.0
- '@rollup/rollup-linux-arm-gnueabihf': 4.52.0
- '@rollup/rollup-linux-arm-musleabihf': 4.52.0
- '@rollup/rollup-linux-arm64-gnu': 4.52.0
- '@rollup/rollup-linux-arm64-musl': 4.52.0
- '@rollup/rollup-linux-loong64-gnu': 4.52.0
- '@rollup/rollup-linux-ppc64-gnu': 4.52.0
- '@rollup/rollup-linux-riscv64-gnu': 4.52.0
- '@rollup/rollup-linux-riscv64-musl': 4.52.0
- '@rollup/rollup-linux-s390x-gnu': 4.52.0
- '@rollup/rollup-linux-x64-gnu': 4.52.0
- '@rollup/rollup-linux-x64-musl': 4.52.0
- '@rollup/rollup-openharmony-arm64': 4.52.0
- '@rollup/rollup-win32-arm64-msvc': 4.52.0
- '@rollup/rollup-win32-ia32-msvc': 4.52.0
- '@rollup/rollup-win32-x64-gnu': 4.52.0
- '@rollup/rollup-win32-x64-msvc': 4.52.0
+ '@rollup/rollup-android-arm-eabi': 4.52.2
+ '@rollup/rollup-android-arm64': 4.52.2
+ '@rollup/rollup-darwin-arm64': 4.52.2
+ '@rollup/rollup-darwin-x64': 4.52.2
+ '@rollup/rollup-freebsd-arm64': 4.52.2
+ '@rollup/rollup-freebsd-x64': 4.52.2
+ '@rollup/rollup-linux-arm-gnueabihf': 4.52.2
+ '@rollup/rollup-linux-arm-musleabihf': 4.52.2
+ '@rollup/rollup-linux-arm64-gnu': 4.52.2
+ '@rollup/rollup-linux-arm64-musl': 4.52.2
+ '@rollup/rollup-linux-loong64-gnu': 4.52.2
+ '@rollup/rollup-linux-ppc64-gnu': 4.52.2
+ '@rollup/rollup-linux-riscv64-gnu': 4.52.2
+ '@rollup/rollup-linux-riscv64-musl': 4.52.2
+ '@rollup/rollup-linux-s390x-gnu': 4.52.2
+ '@rollup/rollup-linux-x64-gnu': 4.52.2
+ '@rollup/rollup-linux-x64-musl': 4.52.2
+ '@rollup/rollup-openharmony-arm64': 4.52.2
+ '@rollup/rollup-win32-arm64-msvc': 4.52.2
+ '@rollup/rollup-win32-ia32-msvc': 4.52.2
+ '@rollup/rollup-win32-x64-gnu': 4.52.2
+ '@rollup/rollup-win32-x64-msvc': 4.52.2
fsevents: 2.3.3
run-applescript@7.0.0: {}
@@ -13346,7 +13495,7 @@ snapshots:
send@1.2.0:
dependencies:
- debug: 4.4.1(supports-color@5.5.0)
+ debug: 4.4.3
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
@@ -13492,11 +13641,6 @@ snapshots:
slash@5.1.0: {}
- slice-ansi@5.0.0:
- dependencies:
- ansi-styles: 6.2.1
- is-fullwidth-code-point: 4.0.0
-
slice-ansi@7.1.0:
dependencies:
ansi-styles: 6.2.1
@@ -13576,6 +13720,11 @@ snapshots:
get-east-asian-width: 1.3.0
strip-ansi: 7.1.0
+ string-width@8.1.0:
+ dependencies:
+ get-east-asian-width: 1.3.0
+ strip-ansi: 7.1.0
+
string_decoder@1.1.1:
dependencies:
safe-buffer: 5.1.2
@@ -13615,7 +13764,7 @@ snapshots:
stylehacks@7.0.5(postcss@8.5.6):
dependencies:
- browserslist: 4.25.0
+ browserslist: 4.25.2
postcss: 8.5.6
postcss-selector-parser: 7.1.0
@@ -13678,11 +13827,11 @@ snapshots:
terser-webpack-plugin@5.3.14(webpack@5.101.3):
dependencies:
- '@jridgewell/trace-mapping': 0.3.29
+ '@jridgewell/trace-mapping': 0.3.30
jest-worker: 27.5.1
schema-utils: 4.3.2
serialize-javascript: 6.0.2
- terser: 5.40.0
+ terser: 5.43.1
webpack: 5.101.3
terser@5.40.0:
@@ -13692,6 +13841,13 @@ snapshots:
commander: 2.20.3
source-map-support: 0.5.21
+ terser@5.43.1:
+ dependencies:
+ '@jridgewell/source-map': 0.3.11
+ acorn: 8.15.0
+ commander: 2.20.3
+ source-map-support: 0.5.21
+
test-exclude@7.0.1:
dependencies:
'@istanbuljs/schema': 0.1.3
@@ -13714,7 +13870,7 @@ snapshots:
tinyglobby@0.2.14:
dependencies:
- fdir: 6.5.0(picomatch@4.0.3)
+ fdir: 6.4.6(picomatch@4.0.3)
picomatch: 4.0.3
tinyglobby@0.2.15:
@@ -13773,8 +13929,8 @@ snapshots:
diff: 8.0.2
empathic: 2.0.0
hookable: 5.5.3
- rolldown: 1.0.0-beta.38
- rolldown-plugin-dts: 0.16.7(rolldown@1.0.0-beta.38)(typescript@5.9.2)(vue-tsc@3.0.7(typescript@5.9.2))
+ rolldown: 1.0.0-beta.40
+ rolldown-plugin-dts: 0.16.8(rolldown@1.0.0-beta.40)(typescript@5.9.2)(vue-tsc@3.0.7(typescript@5.9.2))
semver: 7.7.2
tinyexec: 1.0.1
tinyglobby: 0.2.15
@@ -13848,6 +14004,8 @@ snapshots:
undici-types@6.21.0: {}
+ undici-types@7.10.0: {}
+
unenv@2.0.0-rc.18:
dependencies:
defu: 6.1.4
@@ -13901,7 +14059,7 @@ snapshots:
mlly: 1.8.0
pathe: 2.0.3
picomatch: 4.0.3
- pkg-types: 2.3.0
+ pkg-types: 2.2.0
scule: 1.3.0
strip-literal: 3.0.0
tinyglobby: 0.2.15
@@ -13964,7 +14122,7 @@ snapshots:
pathe: 2.0.3
picomatch: 4.0.3
- unplugin-vue-markdown@29.1.0(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)):
+ unplugin-vue-markdown@29.1.0(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)):
dependencies:
'@mdit-vue/plugin-component': 2.1.4
'@mdit-vue/plugin-frontmatter': 2.1.4
@@ -13974,7 +14132,7 @@ snapshots:
markdown-it-async: 2.2.0
unplugin: 2.3.10
unplugin-utils: 0.2.5
- vite: 7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ vite: 7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
unplugin-vue-router@0.14.0(@vue/compiler-sfc@3.5.21)(vue-router@4.5.1(vue@3.5.18(typescript@5.9.2)))(vue@3.5.18(typescript@5.9.2)):
dependencies:
@@ -14054,9 +14212,9 @@ snapshots:
pkg-types: 1.3.1
unplugin: 1.16.1
- update-browserslist-db@1.1.3(browserslist@4.25.0):
+ update-browserslist-db@1.1.3(browserslist@4.25.2):
dependencies:
- browserslist: 4.25.0
+ browserslist: 4.25.2
escalade: 3.2.0
picocolors: 1.1.1
@@ -14089,33 +14247,33 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.2
- vite-dev-rpc@1.1.0(vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)):
+ vite-dev-rpc@1.1.0(vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)):
dependencies:
birpc: 2.5.0
- vite: 7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
- vite-hot-client: 2.1.0(vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))
+ vite: 7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
+ vite-hot-client: 2.1.0(vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))
- vite-dev-rpc@1.1.0(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)):
+ vite-dev-rpc@1.1.0(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)):
dependencies:
birpc: 2.5.0
- vite: 7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
- vite-hot-client: 2.1.0(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))
+ vite: 7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
+ vite-hot-client: 2.1.0(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))
- vite-hot-client@2.1.0(vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)):
+ vite-hot-client@2.1.0(vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)):
dependencies:
- vite: 7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ vite: 7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
- vite-hot-client@2.1.0(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)):
+ vite-hot-client@2.1.0(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)):
dependencies:
- vite: 7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ vite: 7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
- vite-node@3.2.4(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1):
+ vite-node@3.2.4(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1):
dependencies:
cac: 6.7.14
debug: 4.4.1(supports-color@5.5.0)
es-module-lexer: 1.7.0
pathe: 2.0.3
- vite: 7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ vite: 7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
transitivePeerDependencies:
- '@types/node'
- jiti
@@ -14130,13 +14288,13 @@ snapshots:
- tsx
- yaml
- vite-node@3.2.4(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1):
+ vite-node@3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1):
dependencies:
cac: 6.7.14
debug: 4.4.1(supports-color@5.5.0)
es-module-lexer: 1.7.0
pathe: 2.0.3
- vite: 7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ vite: 7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
transitivePeerDependencies:
- '@types/node'
- jiti
@@ -14151,7 +14309,7 @@ snapshots:
- tsx
- yaml
- vite-plugin-checker@0.9.3(meow@13.2.0)(typescript@5.9.2)(vite@6.3.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue-tsc@2.2.10(typescript@5.9.2)):
+ vite-plugin-checker@0.9.3(meow@13.2.0)(typescript@5.9.2)(vite@6.3.5(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue-tsc@2.2.10(typescript@5.9.2)):
dependencies:
'@babel/code-frame': 7.27.1
chokidar: 4.0.3
@@ -14161,14 +14319,14 @@ snapshots:
strip-ansi: 7.1.0
tiny-invariant: 1.3.3
tinyglobby: 0.2.15
- vite: 6.3.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ vite: 6.3.5(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
vscode-uri: 3.1.0
optionalDependencies:
meow: 13.2.0
typescript: 5.9.2
vue-tsc: 2.2.10(typescript@5.9.2)
- vite-plugin-inspect@11.3.0(@nuxt/kit@3.17.6(magicast@0.3.5))(vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)):
+ vite-plugin-inspect@11.3.0(@nuxt/kit@3.17.6(magicast@0.3.5))(vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)):
dependencies:
ansis: 4.1.0
debug: 4.4.1(supports-color@5.5.0)
@@ -14178,14 +14336,14 @@ snapshots:
perfect-debounce: 1.0.0
sirv: 3.0.1
unplugin-utils: 0.2.5
- vite: 7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
- vite-dev-rpc: 1.1.0(vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))
+ vite: 7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
+ vite-dev-rpc: 1.1.0(vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))
optionalDependencies:
'@nuxt/kit': 3.17.6(magicast@0.3.5)
transitivePeerDependencies:
- supports-color
- vite-plugin-inspect@11.3.3(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)):
+ vite-plugin-inspect@11.3.3(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)):
dependencies:
ansis: 4.1.0
debug: 4.4.1(supports-color@5.5.0)
@@ -14195,27 +14353,27 @@ snapshots:
perfect-debounce: 2.0.0
sirv: 3.0.2
unplugin-utils: 0.3.0
- vite: 7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
- vite-dev-rpc: 1.1.0(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))
+ vite: 7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
+ vite-dev-rpc: 1.1.0(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))
transitivePeerDependencies:
- supports-color
- vite-plugin-vue-devtools@8.0.2(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)):
+ vite-plugin-vue-devtools@8.0.2(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2)):
dependencies:
- '@vue/devtools-core': 8.0.2(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))
+ '@vue/devtools-core': 8.0.2(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.21(typescript@5.9.2))
'@vue/devtools-kit': 8.0.2
'@vue/devtools-shared': 8.0.2
execa: 9.6.0
sirv: 3.0.2
- vite: 7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
- vite-plugin-inspect: 11.3.3(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))
- vite-plugin-vue-inspector: 5.3.2(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))
+ vite: 7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
+ vite-plugin-inspect: 11.3.3(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))
+ vite-plugin-vue-inspector: 5.3.2(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))
transitivePeerDependencies:
- '@nuxt/kit'
- supports-color
- vue
- vite-plugin-vue-inspector@5.3.2(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)):
+ vite-plugin-vue-inspector@5.3.2(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)):
dependencies:
'@babel/core': 7.27.4
'@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.27.4)
@@ -14223,80 +14381,80 @@ snapshots:
'@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.27.4)
'@babel/plugin-transform-typescript': 7.27.1(@babel/core@7.27.4)
'@vue/babel-plugin-jsx': 1.4.0(@babel/core@7.27.4)
- '@vue/compiler-dom': 3.5.21
+ '@vue/compiler-dom': 3.5.18
kolorist: 1.8.0
magic-string: 0.30.19
- vite: 7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ vite: 7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
transitivePeerDependencies:
- supports-color
- vite-plugin-vue-tracer@1.0.0(vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2)):
+ vite-plugin-vue-tracer@1.0.0(vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))(vue@3.5.18(typescript@5.9.2)):
dependencies:
estree-walker: 3.0.3
exsolve: 1.0.7
magic-string: 0.30.19
pathe: 2.0.3
source-map-js: 1.2.1
- vite: 7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ vite: 7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
vue: 3.5.18(typescript@5.9.2)
- vite@5.4.20(@types/node@22.18.6)(lightningcss@1.29.3)(terser@5.40.0):
+ vite@5.4.19(@types/node@24.3.0)(lightningcss@1.29.3)(terser@5.43.1):
dependencies:
esbuild: 0.21.5
postcss: 8.5.6
- rollup: 4.52.0
+ rollup: 4.52.2
optionalDependencies:
- '@types/node': 22.18.6
+ '@types/node': 24.3.0
fsevents: 2.3.3
lightningcss: 1.29.3
- terser: 5.40.0
+ terser: 5.43.1
- vite@6.3.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1):
+ vite@6.3.5(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1):
dependencies:
esbuild: 0.25.9
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
- rollup: 4.52.0
+ rollup: 4.52.2
tinyglobby: 0.2.15
optionalDependencies:
- '@types/node': 22.18.6
+ '@types/node': 24.3.0
fsevents: 2.3.3
jiti: 2.4.2
lightningcss: 1.29.3
- terser: 5.40.0
+ terser: 5.43.1
yaml: 2.8.1
- vite@7.1.6(@types/node@22.18.6)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1):
+ vite@7.1.6(@types/node@24.3.0)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1):
dependencies:
esbuild: 0.25.9
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
- rollup: 4.52.0
+ rollup: 4.52.2
tinyglobby: 0.2.15
optionalDependencies:
- '@types/node': 22.18.6
+ '@types/node': 24.3.0
fsevents: 2.3.3
jiti: 2.4.2
lightningcss: 1.29.3
- terser: 5.40.0
+ terser: 5.43.1
yaml: 2.8.1
- vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1):
+ vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1):
dependencies:
esbuild: 0.25.9
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
- rollup: 4.52.0
+ rollup: 4.52.2
tinyglobby: 0.2.15
optionalDependencies:
- '@types/node': 22.18.6
+ '@types/node': 24.3.0
fsevents: 2.3.3
jiti: 2.5.1
lightningcss: 1.29.3
- terser: 5.40.0
+ terser: 5.43.1
yaml: 2.8.1
vitepress-plugin-llms@1.7.5:
@@ -14317,7 +14475,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- vitepress@1.6.4(@algolia/client-search@5.20.0)(@types/node@22.18.6)(change-case@5.4.4)(fuse.js@7.1.0)(jwt-decode@4.0.0)(lightningcss@1.29.3)(postcss@8.5.6)(search-insights@2.17.2)(terser@5.40.0)(typescript@5.9.2):
+ vitepress@1.6.4(@algolia/client-search@5.20.0)(@types/node@24.3.0)(change-case@5.4.4)(fuse.js@7.1.0)(jwt-decode@4.0.0)(lightningcss@1.29.3)(postcss@8.5.6)(search-insights@2.17.2)(terser@5.43.1)(typescript@5.9.2):
dependencies:
'@docsearch/css': 3.8.2
'@docsearch/js': 3.8.2(@algolia/client-search@5.20.0)(search-insights@2.17.2)
@@ -14326,7 +14484,7 @@ snapshots:
'@shikijs/transformers': 2.1.0
'@shikijs/types': 2.3.2
'@types/markdown-it': 14.1.2
- '@vitejs/plugin-vue': 5.2.4(vite@5.4.20(@types/node@22.18.6)(lightningcss@1.29.3)(terser@5.40.0))(vue@3.5.21(typescript@5.9.2))
+ '@vitejs/plugin-vue': 5.2.4(vite@5.4.19(@types/node@24.3.0)(lightningcss@1.29.3)(terser@5.43.1))(vue@3.5.21(typescript@5.9.2))
'@vue/devtools-api': 7.7.6
'@vue/shared': 3.5.18
'@vueuse/core': 12.5.0(typescript@5.9.2)
@@ -14335,7 +14493,7 @@ snapshots:
mark.js: 8.11.1
minisearch: 7.1.1
shiki: 2.3.2
- vite: 5.4.20(@types/node@22.18.6)(lightningcss@1.29.3)(terser@5.40.0)
+ vite: 5.4.19(@types/node@24.3.0)(lightningcss@1.29.3)(terser@5.43.1)
vue: 3.5.21(typescript@5.9.2)
optionalDependencies:
postcss: 8.5.6
@@ -14366,11 +14524,11 @@ snapshots:
- typescript
- universal-cookie
- vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.18.6)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1):
+ vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1):
dependencies:
'@types/chai': 5.2.2
'@vitest/expect': 3.2.4
- '@vitest/mocker': 3.2.4(vite@7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1))
+ '@vitest/mocker': 3.2.4(vite@7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4
@@ -14388,12 +14546,12 @@ snapshots:
tinyglobby: 0.2.15
tinypool: 1.1.1
tinyrainbow: 2.0.0
- vite: 7.1.6(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
- vite-node: 3.2.4(@types/node@22.18.6)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.40.0)(yaml@2.8.1)
+ vite: 7.1.6(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
+ vite-node: 3.2.4(@types/node@24.3.0)(jiti@2.5.1)(lightningcss@1.29.3)(terser@5.43.1)(yaml@2.8.1)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.12
- '@types/node': 22.18.6
+ '@types/node': 24.3.0
'@vitest/ui': 3.2.4(vitest@3.2.4)
happy-dom: 18.0.1
transitivePeerDependencies:
@@ -14428,16 +14586,21 @@ snapshots:
dependencies:
vue: 3.5.21(typescript@5.9.2)
- vue-router-mock@2.0.0(vue-router@4.5.1(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2)):
+ vue-router-mock@2.0.0(vue-router@https://pkg.pr.new/vue-router@4f1a37a(vue@3.5.21(typescript@5.9.2)))(vue@3.5.21(typescript@5.9.2)):
dependencies:
vue: 3.5.21(typescript@5.9.2)
- vue-router: 4.5.1(vue@3.5.21(typescript@5.9.2))
+ vue-router: https://pkg.pr.new/vue-router@4f1a37a(vue@3.5.21(typescript@5.9.2))
vue-router@4.5.1(vue@3.5.21(typescript@5.9.2)):
dependencies:
'@vue/devtools-api': 6.6.4
vue: 3.5.21(typescript@5.9.2)
+ vue-router@https://pkg.pr.new/vue-router@4f1a37a(vue@3.5.21(typescript@5.9.2)):
+ dependencies:
+ '@vue/devtools-api': 6.6.4
+ vue: 3.5.21(typescript@5.9.2)
+
vue-tsc@2.2.10(typescript@5.9.2):
dependencies:
'@volar/typescript': 2.4.23
@@ -14504,9 +14667,9 @@ snapshots:
'@webassemblyjs/wasm-parser': 1.14.1
acorn: 8.15.0
acorn-import-phases: 1.0.4(acorn@8.15.0)
- browserslist: 4.25.0
+ browserslist: 4.25.2
chrome-trace-event: 1.0.4
- enhanced-resolve: 5.18.1
+ enhanced-resolve: 5.18.3
es-module-lexer: 1.7.0
eslint-scope: 5.1.1
events: 3.3.0
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 09b39bb23..016b9ff5e 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,5 +1,6 @@
packages:
- playground
+ - playground-experimental
- examples/*
ignoredBuiltDependencies:
- '@firebase/util'
diff --git a/src/codegen/__snapshots__/generateRouteRecords.spec.ts.snap b/src/codegen/__snapshots__/generateRouteRecords.spec.ts.snap
index 9005ff119..145d26c78 100644
--- a/src/codegen/__snapshots__/generateRouteRecords.spec.ts.snap
+++ b/src/codegen/__snapshots__/generateRouteRecords.spec.ts.snap
@@ -654,7 +654,7 @@ exports[`generateRouteRecord > route block > adds meta data 1`] = `
meta: {
"auth": true,
"title": "Home"
- }
+ },
}
]"
`;
@@ -672,7 +672,7 @@ exports[`generateRouteRecord > route block > handles named views with empty rout
meta: {
"auth": true,
"title": "Home"
- }
+ },
}
]"
`;
@@ -695,7 +695,7 @@ exports[`generateRouteRecord > route block > merges deep meta properties 1`] = `
3
]
}
- }
+ },
}
]"
`;
@@ -710,7 +710,7 @@ exports[`generateRouteRecord > route block > merges multiple meta properties 1`]
meta: {
"one": true,
"two": true
- }
+ },
}
]"
`;
diff --git a/src/codegen/generateDTS.ts b/src/codegen/generateDTS.ts
index 09a6e2b8c..f61df7fae 100644
--- a/src/codegen/generateDTS.ts
+++ b/src/codegen/generateDTS.ts
@@ -19,11 +19,14 @@ export function generateDTS({
routesModule,
routeNamedMap,
routeFileInfoMap,
+ paramsTypesDeclaration,
+ customParamsType,
}: {
- vueRouterModule: string
routesModule: string
routeNamedMap: string
routeFileInfoMap: string
+ paramsTypesDeclaration: string
+ customParamsType: string
}) {
return ts`
/* eslint-disable */
@@ -33,6 +36,18 @@ export function generateDTS({
// It's recommended to commit this file.
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
+${
+ paramsTypesDeclaration
+ ? `
+// Custom route params parsers
+${paramsTypesDeclaration}
+
+`.trimStart()
+ : ''
+}declare module 'vue-router/auto-resolver' {
+ export type ParamParserCustom = ${customParamsType}
+}
+
declare module '${routesModule}' {
import type {
RouteRecordInfo,
diff --git a/src/codegen/generateParamParsers.spec.ts b/src/codegen/generateParamParsers.spec.ts
new file mode 100644
index 000000000..9a36879d0
--- /dev/null
+++ b/src/codegen/generateParamParsers.spec.ts
@@ -0,0 +1,570 @@
+import { describe, expect, it } from 'vitest'
+import {
+ warnMissingParamParsers,
+ generateParamParsersTypesDeclarations,
+ generateParamsTypes,
+ generateParamParserOptions,
+ generatePathParamsOptions,
+ generateParamParserCustomType,
+ type ParamParsersMap,
+} from './generateParamParsers'
+import { PrefixTree } from '../core/tree'
+import { resolveOptions } from '../options'
+import { ImportsMap } from '../core/utils'
+import type { TreePathParam } from '../core/treeNodeValue'
+import { mockWarn } from '../../tests/vitest-mock-warn'
+
+const DEFAULT_OPTIONS = resolveOptions({})
+
+describe('warnMissingParamParsers', () => {
+ mockWarn()
+ it('shows no warnings for routes without param parsers', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ tree.insert('users', 'users.vue')
+ tree.insert('posts/[id]', 'posts/[id].vue')
+
+ const paramParsers: ParamParsersMap = new Map()
+
+ warnMissingParamParsers(tree, paramParsers)
+ })
+
+ it('shows no warnings for native parsers', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ tree.insert('users/[id=int]', 'users/[id=int].vue')
+ tree.insert('posts/[active=bool]', 'posts/[active=bool].vue')
+
+ const paramParsers: ParamParsersMap = new Map()
+
+ warnMissingParamParsers(tree, paramParsers)
+ })
+
+ it('warns for missing custom parsers', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ tree.insert('users/[id=uuid]', 'users/[id=uuid].vue')
+
+ const paramParsers: ParamParsersMap = new Map()
+
+ warnMissingParamParsers(tree, paramParsers)
+
+ expect(
+ 'Parameter parser "uuid" not found for route "/users/:id".'
+ ).toHaveBeenWarned()
+ })
+
+ it('shows no warnings when custom parsers exist in map', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ tree.insert('users/[id=uuid]', 'users/[id=uuid].vue')
+
+ const paramParsers: ParamParsersMap = new Map([
+ [
+ 'uuid',
+ {
+ name: 'uuid',
+ typeName: 'Param_uuid',
+ relativePath: 'parsers/uuid',
+ absolutePath: '/path/to/parsers/uuid',
+ },
+ ],
+ ])
+
+ warnMissingParamParsers(tree, paramParsers)
+ })
+})
+
+describe('generateParamParsersTypesDeclarations', () => {
+ it('returns empty string for empty param parsers map', () => {
+ const paramParsers: ParamParsersMap = new Map()
+ const result = generateParamParsersTypesDeclarations(paramParsers)
+ expect(result).toBe('')
+ })
+
+ it('generates single param parser type declaration', () => {
+ const paramParsers: ParamParsersMap = new Map([
+ [
+ 'uuid',
+ {
+ name: 'uuid',
+ typeName: 'Param_uuid',
+ relativePath: 'parsers/uuid',
+ absolutePath: '/path/to/parsers/uuid',
+ },
+ ],
+ ])
+
+ const result = generateParamParsersTypesDeclarations(paramParsers)
+ expect(result).toBe(
+ `type Param_uuid = ReturnType>`
+ )
+ })
+
+ it('generates multiple param parsers type declarations', () => {
+ const paramParsers: ParamParsersMap = new Map([
+ [
+ 'uuid',
+ {
+ name: 'uuid',
+ typeName: 'Param_uuid',
+ relativePath: 'parsers/uuid',
+ absolutePath: '/path/to/parsers/uuid',
+ },
+ ],
+ [
+ 'slug',
+ {
+ name: 'slug',
+ typeName: 'Param_slug',
+ relativePath: 'parsers/slug',
+ absolutePath: '/path/to/parsers/slug',
+ },
+ ],
+ ])
+
+ const result = generateParamParsersTypesDeclarations(paramParsers)
+ expect(result).toMatchInlineSnapshot(`
+ "type Param_uuid = ReturnType>
+ type Param_slug = ReturnType>"
+ `)
+ })
+})
+
+describe('generateParamsTypes', () => {
+ it('returns null for params without parsers', () => {
+ const params: TreePathParam[] = [
+ {
+ paramName: 'id',
+ modifier: '',
+ optional: false,
+ repeatable: false,
+ isSplat: false,
+ parser: null,
+ },
+ ]
+ const paramParsers: ParamParsersMap = new Map()
+
+ const result = generateParamsTypes(params, paramParsers)
+ expect(result).toEqual([null])
+ })
+
+ it('returns correct type names for custom parsers', () => {
+ const params: TreePathParam[] = [
+ {
+ paramName: 'id',
+ modifier: '',
+ optional: false,
+ repeatable: false,
+ isSplat: false,
+ parser: 'uuid',
+ },
+ ]
+ const paramParsers: ParamParsersMap = new Map([
+ [
+ 'uuid',
+ {
+ name: 'uuid',
+ typeName: 'Param_uuid',
+ relativePath: 'parsers/uuid',
+ absolutePath: '/path/to/parsers/uuid',
+ },
+ ],
+ ])
+
+ const result = generateParamsTypes(params, paramParsers)
+ expect(result).toEqual(['Param_uuid'])
+ })
+
+ it('returns correct types for native parsers', () => {
+ const params: TreePathParam[] = [
+ {
+ paramName: 'id',
+ modifier: '',
+ optional: false,
+ repeatable: false,
+ isSplat: false,
+ parser: 'int',
+ },
+ {
+ paramName: 'active',
+ modifier: '',
+ optional: false,
+ repeatable: false,
+ isSplat: false,
+ parser: 'bool',
+ },
+ ]
+ const paramParsers: ParamParsersMap = new Map()
+
+ const result = generateParamsTypes(params, paramParsers)
+ expect(result).toEqual(['number', 'boolean'])
+ })
+
+ it('handles mixed params with and without parsers', () => {
+ const params: TreePathParam[] = [
+ {
+ paramName: 'id',
+ modifier: '',
+ optional: false,
+ repeatable: false,
+ isSplat: false,
+ parser: 'uuid',
+ },
+ {
+ paramName: 'page',
+ modifier: '',
+ optional: false,
+ repeatable: false,
+ isSplat: false,
+ parser: null,
+ },
+ {
+ paramName: 'count',
+ modifier: '',
+ optional: false,
+ repeatable: false,
+ isSplat: false,
+ parser: 'int',
+ },
+ ]
+ const paramParsers: ParamParsersMap = new Map([
+ [
+ 'uuid',
+ {
+ name: 'uuid',
+ typeName: 'Param_uuid',
+ relativePath: 'parsers/uuid',
+ absolutePath: '/path/to/parsers/uuid',
+ },
+ ],
+ ])
+
+ const result = generateParamsTypes(params, paramParsers)
+ expect(result).toEqual(['Param_uuid', null, 'number'])
+ })
+})
+
+describe('generateParamParserOptions', () => {
+ it('returns empty string for param without parser', () => {
+ const param: TreePathParam = {
+ paramName: 'id',
+ modifier: '',
+ optional: false,
+ repeatable: false,
+ isSplat: false,
+ parser: null,
+ }
+ const importsMap = new ImportsMap()
+ const paramParsers: ParamParsersMap = new Map()
+
+ const result = generateParamParserOptions(param, importsMap, paramParsers)
+ expect(result).toBe('')
+ })
+
+ it('generates import and returns variable for custom parser', () => {
+ const param: TreePathParam = {
+ paramName: 'id',
+ modifier: '',
+ optional: false,
+ repeatable: false,
+ isSplat: false,
+ parser: 'uuid',
+ }
+ const importsMap = new ImportsMap()
+ const paramParsers: ParamParsersMap = new Map([
+ [
+ 'uuid',
+ {
+ name: 'uuid',
+ typeName: 'Param_uuid',
+ relativePath: 'parsers/uuid',
+ absolutePath: '/path/to/parsers/uuid',
+ },
+ ],
+ ])
+
+ const result = generateParamParserOptions(param, importsMap, paramParsers)
+ expect(result).toBe('PARAM_PARSER__uuid')
+ expect(importsMap.toString()).toContain(
+ `import { parser as PARAM_PARSER__uuid } from '/path/to/parsers/uuid'`
+ )
+ })
+
+ it('generates correct import for native int parser', () => {
+ const param: TreePathParam = {
+ paramName: 'id',
+ modifier: '',
+ optional: false,
+ repeatable: false,
+ isSplat: false,
+ parser: 'int',
+ }
+ const importsMap = new ImportsMap()
+ const paramParsers: ParamParsersMap = new Map()
+
+ const result = generateParamParserOptions(param, importsMap, paramParsers)
+ expect(result).toBe('PARAM_PARSER_INT')
+ expect(importsMap.toString()).toContain(
+ `import { PARAM_PARSER_INT } from 'vue-router/experimental'`
+ )
+ })
+
+ it('generates correct import for native bool parser', () => {
+ const param: TreePathParam = {
+ paramName: 'active',
+ modifier: '',
+ optional: false,
+ repeatable: false,
+ isSplat: false,
+ parser: 'bool',
+ }
+ const importsMap = new ImportsMap()
+ const paramParsers: ParamParsersMap = new Map()
+
+ const result = generateParamParserOptions(param, importsMap, paramParsers)
+ expect(result).toBe('PARAM_PARSER_BOOL')
+ expect(importsMap.toString()).toContain(
+ `import { PARAM_PARSER_BOOL } from 'vue-router/experimental'`
+ )
+ })
+
+ it('returns empty string for missing parser', () => {
+ const param: TreePathParam = {
+ paramName: 'id',
+ modifier: '',
+ optional: false,
+ repeatable: false,
+ isSplat: false,
+ parser: 'missing',
+ }
+ const importsMap = new ImportsMap()
+ const paramParsers: ParamParsersMap = new Map()
+
+ const result = generateParamParserOptions(param, importsMap, paramParsers)
+ expect(result).toBe('')
+ })
+})
+
+describe('generatePathParamsOptions', () => {
+ it('returns empty object for empty params array', () => {
+ const params: TreePathParam[] = []
+ const importsMap = new ImportsMap()
+ const paramParsers: ParamParsersMap = new Map()
+
+ const result = generatePathParamsOptions(params, importsMap, paramParsers)
+ expect(result).toBe(`{}`)
+ })
+
+ it('generates options for single param with parser', () => {
+ const params: TreePathParam[] = [
+ {
+ paramName: 'id',
+ modifier: '',
+ optional: false,
+ repeatable: false,
+ isSplat: false,
+ parser: 'int',
+ },
+ ]
+ const importsMap = new ImportsMap()
+ const paramParsers: ParamParsersMap = new Map()
+
+ const result = generatePathParamsOptions(params, importsMap, paramParsers)
+ expect(result).toContain('id: [PARAM_PARSER_INT]')
+ })
+
+ it('generates options for param without parser', () => {
+ const params: TreePathParam[] = [
+ {
+ paramName: 'slug',
+ modifier: '',
+ optional: false,
+ repeatable: false,
+ isSplat: false,
+ parser: null,
+ },
+ ]
+ const importsMap = new ImportsMap()
+ const paramParsers: ParamParsersMap = new Map()
+
+ const result = generatePathParamsOptions(params, importsMap, paramParsers)
+ expect(result).toContain('slug: [/* no parser */]')
+ })
+
+ it('includes repeatable and optional flags when present', () => {
+ const params: TreePathParam[] = [
+ {
+ paramName: 'tags',
+ modifier: '+',
+ optional: false,
+ repeatable: true,
+ isSplat: false,
+ parser: null,
+ },
+ {
+ paramName: 'category',
+ modifier: '?',
+ optional: true,
+ repeatable: false,
+ isSplat: false,
+ parser: null,
+ },
+ ]
+ const importsMap = new ImportsMap()
+ const paramParsers: ParamParsersMap = new Map()
+
+ const result = generatePathParamsOptions(params, importsMap, paramParsers)
+ expect(result).toContain('tags: [/* no parser */, /* repeatable: */ true]')
+ expect(result).toContain(
+ 'category: [/* no parser */, /* repeatable: false */, /* optional: */ true]'
+ )
+ })
+
+ it('handles multiple params with different configurations', () => {
+ const params: TreePathParam[] = [
+ {
+ paramName: 'id',
+ modifier: '',
+ optional: false,
+ repeatable: false,
+ isSplat: false,
+ parser: 'uuid',
+ },
+ {
+ paramName: 'page',
+ modifier: '?',
+ optional: true,
+ repeatable: false,
+ isSplat: false,
+ parser: 'int',
+ },
+ {
+ paramName: 'tags',
+ modifier: '+',
+ optional: false,
+ repeatable: true,
+ isSplat: false,
+ parser: null,
+ },
+ ]
+ const importsMap = new ImportsMap()
+ const paramParsers: ParamParsersMap = new Map([
+ [
+ 'uuid',
+ {
+ name: 'uuid',
+ typeName: 'Param_uuid',
+ relativePath: 'parsers/uuid',
+ absolutePath: '/path/to/parsers/uuid',
+ },
+ ],
+ ])
+
+ const result = generatePathParamsOptions(params, importsMap, paramParsers)
+ expect(result).toContain('id: [PARAM_PARSER__uuid]')
+ expect(result).toContain(
+ 'page: [PARAM_PARSER_INT, /* repeatable: false */, /* optional: */ true]'
+ )
+ expect(result).toContain('tags: [/* no parser */, /* repeatable: */ true]')
+ })
+})
+
+describe('generateParamParserCustomType', () => {
+ it('returns never for empty param parsers map', () => {
+ const paramParsers: ParamParsersMap = new Map()
+ const result = generateParamParserCustomType(paramParsers)
+ expect(result).toBe('never')
+ })
+
+ it('returns single quoted parser name for one parser', () => {
+ const paramParsers: ParamParsersMap = new Map([
+ [
+ 'date',
+ {
+ name: 'date',
+ typeName: 'Param_date',
+ relativePath: 'parsers/date',
+ absolutePath: '/path/to/parsers/date',
+ },
+ ],
+ ])
+
+ const result = generateParamParserCustomType(paramParsers)
+ expect(result).toBe("'date'")
+ })
+
+ it('returns union of quoted parser names for multiple parsers in alphabetical order on separate lines', () => {
+ const paramParsers: ParamParsersMap = new Map([
+ [
+ 'uuid',
+ {
+ name: 'uuid',
+ typeName: 'Param_uuid',
+ relativePath: 'parsers/uuid',
+ absolutePath: '/path/to/parsers/uuid',
+ },
+ ],
+ [
+ 'date',
+ {
+ name: 'date',
+ typeName: 'Param_date',
+ relativePath: 'parsers/date',
+ absolutePath: '/path/to/parsers/date',
+ },
+ ],
+ ])
+
+ const result = generateParamParserCustomType(paramParsers)
+ expect(result).toBe(" | 'date'\n | 'uuid'")
+ })
+
+ it('handles parser names with special characters correctly', () => {
+ const paramParsers: ParamParsersMap = new Map([
+ [
+ 'custom-parser',
+ {
+ name: 'custom-parser',
+ typeName: 'Param_custom-parser',
+ relativePath: 'parsers/custom-parser',
+ absolutePath: '/path/to/parsers/custom-parser',
+ },
+ ],
+ ])
+
+ const result = generateParamParserCustomType(paramParsers)
+ expect(result).toBe("'custom-parser'")
+ })
+
+ it('formats multiple parsers with proper indentation for three or more types', () => {
+ const paramParsers: ParamParsersMap = new Map([
+ [
+ 'uuid',
+ {
+ name: 'uuid',
+ typeName: 'Param_uuid',
+ relativePath: 'parsers/uuid',
+ absolutePath: '/path/to/parsers/uuid',
+ },
+ ],
+ [
+ 'date',
+ {
+ name: 'date',
+ typeName: 'Param_date',
+ relativePath: 'parsers/date',
+ absolutePath: '/path/to/parsers/date',
+ },
+ ],
+ [
+ 'slug',
+ {
+ name: 'slug',
+ typeName: 'Param_slug',
+ relativePath: 'parsers/slug',
+ absolutePath: '/path/to/parsers/slug',
+ },
+ ],
+ ])
+
+ const result = generateParamParserCustomType(paramParsers)
+ expect(result).toBe(" | 'date'\n | 'slug'\n | 'uuid'")
+ })
+})
diff --git a/src/codegen/generateParamParsers.ts b/src/codegen/generateParamParsers.ts
new file mode 100644
index 000000000..ef56e0e76
--- /dev/null
+++ b/src/codegen/generateParamParsers.ts
@@ -0,0 +1,136 @@
+import { TreePathParam, TreeQueryParam } from '../core/treeNodeValue'
+import { ImportsMap } from '../core/utils'
+import { PrefixTree } from '../core/tree'
+
+export type ParamParsersMap = Map<
+ string,
+ {
+ name: string
+ typeName: `Param_${string}`
+ relativePath: string
+ absolutePath: string
+ }
+>
+
+// just for type strictness
+const _NATIVE_PARAM_PARSERS = ['int', 'bool'] as const
+const NATIVE_PARAM_PARSERS = _NATIVE_PARAM_PARSERS as readonly string[]
+const NATIVE_PARAM_PARSERS_TYPES = {
+ int: 'number',
+ bool: 'boolean',
+} satisfies Record<(typeof _NATIVE_PARAM_PARSERS)[number], string>
+
+export function warnMissingParamParsers(
+ tree: PrefixTree,
+ paramParsers: ParamParsersMap
+) {
+ for (const node of tree.getChildrenDeepSorted()) {
+ for (const param of node.params) {
+ if (param.parser && !paramParsers.has(param.parser)) {
+ if (!NATIVE_PARAM_PARSERS.includes(param.parser)) {
+ console.warn(
+ `Parameter parser "${param.parser}" not found for route "${node.fullPath}".`
+ )
+ }
+ }
+ }
+ }
+}
+
+export function generateParamParsersTypesDeclarations(
+ paramParsers: ParamParsersMap
+) {
+ return Array.from(paramParsers.values())
+ .map(
+ ({ typeName, relativePath }) =>
+ `type ${typeName} = ReturnType>`
+ )
+ .join('\n')
+}
+
+export function generateParamsTypes(
+ params: (TreePathParam | TreeQueryParam)[],
+ parparsersMap: ParamParsersMap
+): Array {
+ return params.map((param) => {
+ if (param.parser) {
+ if (parparsersMap.has(param.parser)) {
+ return parparsersMap.get(param.parser)!.typeName
+ } else if (param.parser in NATIVE_PARAM_PARSERS_TYPES) {
+ return NATIVE_PARAM_PARSERS_TYPES[
+ param.parser as keyof typeof NATIVE_PARAM_PARSERS_TYPES
+ ]
+ }
+ }
+ return null
+ })
+}
+
+export function generateParamParserOptions(
+ param: TreePathParam | TreeQueryParam,
+ importsMap: ImportsMap,
+ paramParsers: ParamParsersMap
+): string {
+ if (!param.parser) return ''
+
+ // we prioritize custom parsers to let users override them
+ if (paramParsers.has(param.parser)) {
+ const { name, absolutePath } = paramParsers.get(param.parser)!
+ const varName = `PARAM_PARSER__${name}`
+ importsMap.add(absolutePath, { name: 'parser', as: varName })
+ return varName
+ } else if (NATIVE_PARAM_PARSERS.includes(param.parser)) {
+ const varName = `PARAM_PARSER_${param.parser.toUpperCase()}`
+ importsMap.add('vue-router/experimental', varName)
+ return varName
+ }
+ return ''
+}
+
+export function generateParamParserCustomType(
+ paramParsers: ParamParsersMap
+): string {
+ const parserNames = Array.from(paramParsers.keys()).sort()
+
+ if (parserNames.length === 0) {
+ return 'never'
+ }
+
+ if (parserNames.length === 1) {
+ return `'${parserNames[0]}'`
+ }
+
+ return parserNames.map((name) => ` | '${name}'`).join('\n')
+}
+
+export function generatePathParamsOptions(
+ params: TreePathParam[],
+ importsMap: ImportsMap,
+ paramParsers: ParamParsersMap
+) {
+ const paramOptions = params.map((param) => {
+ // build a lean option list without any optional value
+ const optionList: string[] = []
+ const parser = generateParamParserOptions(param, importsMap, paramParsers)
+ optionList.push(parser || `/* no parser */`)
+ if (param.optional || param.repeatable) {
+ optionList.push(
+ `/* repeatable: ` + (param.repeatable ? `*/ true` : `false */`)
+ )
+ }
+ if (param.optional) {
+ optionList.push(
+ `/* optional: ` + (param.optional ? `*/ true` : `false */`)
+ )
+ }
+ return `
+${param.paramName}: [${optionList.join(', ')}],
+`.slice(1, -1)
+ })
+
+ return paramOptions.length === 0
+ ? '{}'
+ : `{
+ ${paramOptions.join('\n ')}
+ }`
+}
diff --git a/src/codegen/generateRouteMap.spec.ts b/src/codegen/generateRouteMap.spec.ts
index 99f517615..43ecaeba2 100644
--- a/src/codegen/generateRouteMap.spec.ts
+++ b/src/codegen/generateRouteMap.spec.ts
@@ -19,7 +19,9 @@ describe('generateRouteNamedMap', () => {
tree.insert('a', 'a.vue')
tree.insert('b', 'b.vue')
tree.insert('c', 'c.vue')
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/': RouteRecordInfo<'/', '/', Record, Record>,
'/a': RouteRecordInfo<'/a', '/a', Record, Record>,
@@ -40,7 +42,9 @@ describe('generateRouteNamedMap', () => {
tree.insert('[...a]', '[...a].vue') // splat
tree.insert('[[...a]]', '[[...a]].vue') // splat
tree.insert('[[...a]]+', '[[...a]]+.vue') // splat
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/[a]': RouteRecordInfo<'/[a]', '/:a', { a: ParamValue }, { a: ParamValue }>,
'/[[a]]': RouteRecordInfo<'/[[a]]', '/:a?', { a?: ParamValueZeroOrOne }, { a?: ParamValueZeroOrOne }>,
@@ -61,7 +65,9 @@ describe('generateRouteNamedMap', () => {
const b = tree.insertParsedPath(':b()', 'a.vue')
expect(a.name).toBe('/:a')
expect(b.name).toBe('/:b()')
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/:a': RouteRecordInfo<'/:a', '/:a', { a: ParamValue }, { a: ParamValue }>,
'/:b()': RouteRecordInfo<'/:b()', '/:b()', { b: ParamValue }, { b: ParamValue }>,
@@ -75,7 +81,9 @@ describe('generateRouteNamedMap', () => {
tree.insert('n/[a]/other', 'n/[a]/other.vue')
tree.insert('n/[a]/[b]', 'n/[a]/[b].vue')
tree.insert('n/[a]/[c]/other-[d]', 'n/[a]/[c]/other-[d].vue')
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/n/[a]/': RouteRecordInfo<'/n/[a]/', '/n/:a', { a: ParamValue }, { a: ParamValue }>,
'/n/[a]/[b]': RouteRecordInfo<'/n/[a]/[b]', '/n/:a/:b', { a: ParamValue, b: ParamValue }, { a: ParamValue, b: ParamValue }>,
@@ -93,7 +101,9 @@ describe('generateRouteNamedMap', () => {
tree.insert('n/[a]+', 'n/[a]+.vue') // repeated
tree.insert('n/[[a]]+', 'n/[[a]]+.vue') // optional repeated
tree.insert('n/[...a]', 'n/[...a].vue') // splat
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/n/[a]': RouteRecordInfo<'/n/[a]', '/n/:a', { a: ParamValue }, { a: ParamValue }>,
'/n/[[a]]': RouteRecordInfo<'/n/[[a]]', '/n/:a?', { a?: ParamValueZeroOrOne }, { a?: ParamValueZeroOrOne }>,
@@ -115,7 +125,9 @@ describe('generateRouteNamedMap', () => {
tree.insert('[lang]/a', 'src/pages/a.vue')
tree.insert('[lang]/[id]', 'src/pages/[id].vue')
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/[lang]/': RouteRecordInfo<'/[lang]/', '/:lang', { lang: ParamValue }, { lang: ParamValue }>,
'/[lang]/[id]': RouteRecordInfo<'/[lang]/[id]', '/:lang/:id', { lang: ParamValue, id: ParamValue }, { lang: ParamValue, id: ParamValue }>,
@@ -134,7 +146,9 @@ describe('generateRouteNamedMap', () => {
tree.insert('b/d', 'b/d.vue')
tree.insert('c', 'c.vue')
tree.insert('d', 'd.vue')
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/a/a': RouteRecordInfo<'/a/a', '/a/a', Record, Record>,
'/a/b': RouteRecordInfo<'/a/b', '/a/b', Record, Record>,
@@ -154,7 +168,9 @@ describe('generateRouteNamedMap', () => {
tree.insert('a/index', 'a/index.vue')
tree.insert('a/[id]', 'a/[id].vue')
tree.insert('a/[id]/index', 'a/[id]/index.vue')
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/a': RouteRecordInfo<'/a', '/a', Record, Record, '/a/' | '/a/[id]' | '/a/[id]/'>,
'/a/': RouteRecordInfo<'/a/', '/a', Record, Record>,
@@ -170,7 +186,9 @@ describe('generateRouteNamedMap', () => {
const child = tree.insert('parent/child', 'parent/child.vue')
parent.value.setOverride('parent', { path: '/' })
expect(child.fullPath).toBe('/child')
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/parent': RouteRecordInfo<'/parent', '/', Record, Record, '/parent/child'>,
'/parent/child': RouteRecordInfo<'/parent/child', '/child', Record, Record>,
@@ -188,7 +206,9 @@ describe('generateRouteNamedMap', () => {
'parent/child/subchild/grandchild.vue'
)
tree.insert('parent/other-child', 'parent/other-child.vue')
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/parent': RouteRecordInfo<'/parent', '/parent', Record, Record, '/parent/child' | '/parent/child/subchild' | '/parent/child/subchild/grandchild' | '/parent/other-child'>,
'/parent/child': RouteRecordInfo<'/parent/child', '/parent/child', Record, Record, '/parent/child/subchild' | '/parent/child/subchild/grandchild'>,
@@ -203,7 +223,9 @@ describe('generateRouteNamedMap', () => {
const tree = new PrefixTree(DEFAULT_OPTIONS)
tree.insert('parent', 'parent.vue')
tree.insert('parent/child/a/b/c', 'parent/child/a/b/c.vue')
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/parent': RouteRecordInfo<'/parent', '/parent', Record, Record, '/parent/child/a/b/c'>,
'/parent/child/a/b/c': RouteRecordInfo<'/parent/child/a/b/c', '/parent/child/a/b/c', Record, Record>,
@@ -215,7 +237,9 @@ describe('generateRouteNamedMap', () => {
const tree = new PrefixTree(DEFAULT_OPTIONS)
tree.insert('parent/index', 'parent/index.vue')
tree.insert('parent/child', 'parent/child.vue')
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/parent/': RouteRecordInfo<'/parent/', '/parent', Record, Record>,
'/parent/child': RouteRecordInfo<'/parent/child', '/parent/child', Record, Record>,
@@ -230,7 +254,9 @@ describe('generateRouteNamedMap', () => {
tree.insert('parent/a/b', 'parent/a/b.vue')
tree.insert('parent/a/b/index', 'parent/a/b/index.vue')
tree.insert('parent/a/b/c', 'parent/a/b/c.vue')
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/parent/': RouteRecordInfo<'/parent/', '/parent', Record, Record>,
'/parent/a/': RouteRecordInfo<'/parent/a/', '/parent/a', Record, Record>,
@@ -252,7 +278,9 @@ describe('generateRouteNamedMap', () => {
tree.insert('[lang]/a', 'src/pages/a.vue')
tree.insert('[lang]/[id]', 'src/pages/[id].vue')
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/[lang]/': RouteRecordInfo<'/[lang]/', '/:lang', { lang: ParamValue }, { lang: ParamValue }>,
'/[lang]/[id]': RouteRecordInfo<'/[lang]/[id]', '/:lang/:id', { lang: ParamValue, id: ParamValue }, { lang: ParamValue, id: ParamValue }>,
@@ -266,7 +294,9 @@ describe('generateRouteNamedMap', () => {
tree.insert('(group)/a', 'a.vue')
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/(group)/a': RouteRecordInfo<'/(group)/a', '/a', Record, Record>,
}"
@@ -278,7 +308,9 @@ describe('generateRouteNamedMap', () => {
tree.insert('(group)/(subgroup)/c', 'c.vue')
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/(group)/(subgroup)/c': RouteRecordInfo<'/(group)/(subgroup)/c', '/c', Record, Record>,
}"
@@ -290,7 +322,9 @@ describe('generateRouteNamedMap', () => {
tree.insert('folder/(group)', 'folder/(group).vue')
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/folder/(group)': RouteRecordInfo<'/folder/(group)', '/folder', Record, Record>,
}"
@@ -308,7 +342,9 @@ describe('generateRouteNamedMap', () => {
tree.insert(route, `${route}.vue`)
})
- return formatExports(generateRouteNamedMap(tree))
+ return formatExports(
+ generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map())
+ )
}
// Same routes, different insertion orders
@@ -340,7 +376,9 @@ describe('generateRouteNamedMap', () => {
const parentNode = tree.children.get('parent')!
parentNode.value.setOverride('parent', { name: '' })
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/child': RouteRecordInfo<'/child', '/child', Record, Record>,
'/parent/child': RouteRecordInfo<'/parent/child', '/parent/child', Record, Record>,
@@ -359,7 +397,9 @@ describe('generateRouteNamedMap', () => {
const child2Node = tree.children.get('parent')!.children.get('child2')!
child2Node.value.setOverride('parent/child2', { name: '' })
- expect(formatExports(generateRouteNamedMap(tree))).toMatchInlineSnapshot(`
+ expect(
+ formatExports(generateRouteNamedMap(tree, DEFAULT_OPTIONS, new Map()))
+ ).toMatchInlineSnapshot(`
"export interface RouteNamedMap {
'/parent': RouteRecordInfo<'/parent', '/parent', Record, Record, '/parent/child1' | '/parent/child3'>,
'/parent/child1': RouteRecordInfo<'/parent/child1', '/parent/child1', Record, Record>,
diff --git a/src/codegen/generateRouteMap.ts b/src/codegen/generateRouteMap.ts
index 0454f9908..da4908964 100644
--- a/src/codegen/generateRouteMap.ts
+++ b/src/codegen/generateRouteMap.ts
@@ -1,30 +1,58 @@
import type { TreeNode } from '../core/tree'
-import { generateRouteParams } from './generateRouteParams'
+import type { ResolvedOptions } from '../options'
+import { generateParamsTypes, ParamParsersMap } from './generateParamParsers'
+import {
+ EXPERIMENTAL_generateRouteParams,
+ generateRouteParams,
+} from './generateRouteParams'
-export function generateRouteNamedMap(node: TreeNode): string {
+export function generateRouteNamedMap(
+ node: TreeNode,
+ options: ResolvedOptions,
+ paramParsersMap: ParamParsersMap
+): string {
if (node.isRoot()) {
return `export interface RouteNamedMap {
-${node.getChildrenSorted().map(generateRouteNamedMap).join('')}}`
+${node
+ .getChildrenSorted()
+ .map((n) => generateRouteNamedMap(n, options, paramParsersMap))
+ .join('')}}`
}
return (
// if the node has a filePath, it's a component, it has a routeName and it should be referenced in the RouteNamedMap
// otherwise it should be skipped to avoid navigating to a route that doesn't render anything
(node.value.components.size > 0 && node.name
- ? ` '${node.name}': ${generateRouteRecordInfo(node)},\n`
+ ? ` '${node.name}': ${generateRouteRecordInfo(node, options, paramParsersMap)},\n`
: '') +
(node.children.size > 0
- ? node.getChildrenSorted().map(generateRouteNamedMap).join('\n')
+ ? node
+ .getChildrenSorted()
+ .map((n) => generateRouteNamedMap(n, options, paramParsersMap))
+ .join('\n')
: '')
)
}
-export function generateRouteRecordInfo(node: TreeNode) {
+export function generateRouteRecordInfo(
+ node: TreeNode,
+ options: ResolvedOptions,
+ paramParsersMap: ParamParsersMap
+): string {
+ let paramParsers: Array = []
+
+ if (options.experimental.paramParsers) {
+ paramParsers = generateParamsTypes(node.params, paramParsersMap)
+ }
const typeParams = [
`'${node.name}'`,
`'${node.fullPath}'`,
- generateRouteParams(node, true),
- generateRouteParams(node, false),
+ options.experimental.paramParsers
+ ? EXPERIMENTAL_generateRouteParams(node, paramParsers, true)
+ : generateRouteParams(node, true),
+ options.experimental.paramParsers
+ ? EXPERIMENTAL_generateRouteParams(node, paramParsers, false)
+ : generateRouteParams(node, false),
]
if (node.children.size > 0) {
diff --git a/src/codegen/generateRouteParams.ts b/src/codegen/generateRouteParams.ts
index 28e94e64d..5d85c75eb 100644
--- a/src/codegen/generateRouteParams.ts
+++ b/src/codegen/generateRouteParams.ts
@@ -1,8 +1,14 @@
import { TreeNode } from '../core/tree'
+import {
+ isTreeParamOptional,
+ isTreeParamRepeatable,
+ isTreePathParam,
+} from '../core/treeNodeValue'
export function generateRouteParams(node: TreeNode, isRaw: boolean): string {
- // node.params is a getter so we compute it once
- const nodeParams = node.params
+ // node.pathParams is a getter so we compute it once
+ // this version does not support query params
+ const nodeParams = node.pathParams
return nodeParams.length > 0
? `{ ${nodeParams
.map(
@@ -21,7 +27,44 @@ export function generateRouteParams(node: TreeNode, isRaw: boolean): string {
'Record'
}
-// TODO: refactor to ParamValueRaw and ParamValue ?
+export function EXPERIMENTAL_generateRouteParams(
+ node: TreeNode,
+ types: Array,
+ isRaw: boolean
+) {
+ // node.params is a getter so we compute it once
+ const nodeParams = node.params
+ return nodeParams.length > 0
+ ? `{ ${nodeParams
+ .map((param, i) => {
+ const isOptional = isTreeParamOptional(param)
+ const isRepeatable = isTreeParamRepeatable(param)
+
+ const type = types[i]
+
+ let extractedType: string
+
+ if (type?.startsWith('Param_')) {
+ extractedType = `${isRepeatable ? 'Extract' : 'Exclude'}<${type}, unknown[]>`
+ } else {
+ extractedType = `${type ?? 'string'}${isRepeatable ? '[]' : ''}`
+ }
+
+ extractedType +=
+ isTreePathParam(param) && isOptional && !isRepeatable
+ ? ' | null'
+ : ''
+
+ return `${param.paramName}${isRaw && isOptional ? '?' : ''}: ${
+ extractedType
+ }`
+ })
+ .join(', ')} }`
+ : // no params allowed
+ 'Record'
+}
+
+// TODO: Remove in favor of inline types because it's easier to read
/**
* Utility type for raw and non raw params like :id+
diff --git a/src/codegen/generateRouteRecords.ts b/src/codegen/generateRouteRecords.ts
index 3dca451b4..ed0ebc75f 100644
--- a/src/codegen/generateRouteRecords.ts
+++ b/src/codegen/generateRouteRecords.ts
@@ -51,9 +51,6 @@ ${node
const startIndent = ' '.repeat(indent * 2)
const indentStr = ' '.repeat((indent + 1) * 2)
- // TODO: should meta be defined a different way to allow preserving imports?
- // const meta = node.value.overrides.meta
-
// compute once since it's a getter
const overrides = node.value.overrides
@@ -145,7 +142,7 @@ ${indentStr}},`
* @param importsMap - the import list to fill
* @returns
*/
-function generatePageImport(
+export function generatePageImport(
filepath: string,
importMode: ResolvedOptions['importMode'],
importsMap: ImportsMap
@@ -168,14 +165,14 @@ function generatePageImport(
return importName
}
-function formatMeta(node: TreeNode, indent: string): string {
+export function formatMeta(node: TreeNode, indent: string): string {
const meta = node.meta
const formatted =
meta &&
meta
.split('\n')
.map((line) => indent + line)
- .join('\n')
+ .join('\n') + ','
return formatted ? '\n' + indent + 'meta: ' + formatted.trimStart() : ''
}
diff --git a/src/codegen/generateRouteResolver.spec.ts b/src/codegen/generateRouteResolver.spec.ts
new file mode 100644
index 000000000..2808183b2
--- /dev/null
+++ b/src/codegen/generateRouteResolver.spec.ts
@@ -0,0 +1,765 @@
+import { beforeEach, describe, expect, it } from 'vitest'
+import { PrefixTree } from '../core/tree'
+import { resolveOptions } from '../options'
+import {
+ generateRouteResolver,
+ generateRouteRecord,
+ generateRouteRecordQuery,
+} from './generateRouteResolver'
+import { ImportsMap } from '../core/utils'
+import { ParamParsersMap } from './generateParamParsers'
+
+const DEFAULT_OPTIONS = resolveOptions({})
+let DEFAULT_STATE: Parameters[0]['state'] = {
+ id: 0,
+ matchableRecords: [],
+}
+
+beforeEach(() => {
+ DEFAULT_STATE = {
+ id: 0,
+ matchableRecords: [],
+ }
+})
+
+describe('generateRouteRecordQuery', () => {
+ let importsMap!: ImportsMap
+ beforeEach(() => {
+ importsMap = new ImportsMap()
+ })
+
+ it('returns empty string for non-matchable nodes without query params', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ const node = tree.insert('a/b', 'a/b.vue').parent! // non-matchable parent
+ expect(
+ generateRouteRecordQuery({ importsMap, node, paramParsersMap: new Map() })
+ ).toBe('')
+ })
+
+ it('returns empty string when no query params in a matchable node', () => {
+ const node = new PrefixTree(DEFAULT_OPTIONS).insert('a', 'a.vue')
+ expect(
+ generateRouteRecordQuery({ importsMap, node, paramParsersMap: new Map() })
+ ).toBe('')
+ })
+
+ it('generates query params for non-matchable nodes when they have query params', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ const node = tree.insert('a/b', 'a/b.vue').parent! // non-matchable parent
+ // Add query params to the non-matchable parent
+ node.value.setEditOverride('params', {
+ query: { search: {} },
+ })
+ expect(
+ generateRouteRecordQuery({ importsMap, node, paramParsersMap: new Map() })
+ ).toMatchInlineSnapshot(`
+ "query: [
+ new MatcherPatternQueryParam('search', 'search', 'value')
+ ],"
+ `)
+ })
+
+ it('does not includes query params from parent nodes', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ const parentNode = tree.insert('parent', 'parent.vue')
+ const childNode = tree.insert('parent/child', 'parent/child.vue')
+
+ // Add query params to parent
+ parentNode.value.setEditOverride('params', {
+ query: {
+ parentParam: {},
+ },
+ })
+
+ // Add query params to child
+ childNode.value.setEditOverride('params', {
+ query: {
+ childParam: { parser: 'int' },
+ },
+ })
+
+ expect(
+ generateRouteRecordQuery({
+ importsMap,
+ node: childNode,
+ paramParsersMap: new Map(),
+ })
+ ).toMatchInlineSnapshot(`
+ "query: [
+ new MatcherPatternQueryParam('childParam', 'childParam', 'value', PARAM_PARSER_INT)
+ ],"
+ `)
+ })
+
+ it('generates query property with single query param', () => {
+ const node = new PrefixTree(DEFAULT_OPTIONS).insert('a', 'a.vue')
+ // Mock the queryParams getter
+ node.value.setEditOverride('params', {
+ query: { search: {} },
+ })
+ expect(
+ generateRouteRecordQuery({ importsMap, node, paramParsersMap: new Map() })
+ ).toMatchInlineSnapshot(`
+ "query: [
+ new MatcherPatternQueryParam('search', 'search', 'value')
+ ],"
+ `)
+ })
+
+ it('generates query property with multiple query params', () => {
+ const node = new PrefixTree(DEFAULT_OPTIONS).insert('a', 'a.vue')
+ node.value.setEditOverride('params', {
+ query: {
+ search: {},
+ page: { parser: 'int' },
+ active: { parser: 'bool' },
+ },
+ })
+ expect(
+ generateRouteRecordQuery({ importsMap, node, paramParsersMap: new Map() })
+ ).toMatchInlineSnapshot(`
+ "query: [
+ new MatcherPatternQueryParam('search', 'search', 'value'),
+ new MatcherPatternQueryParam('page', 'page', 'value', PARAM_PARSER_INT),
+ new MatcherPatternQueryParam('active', 'active', 'value', PARAM_PARSER_BOOL)
+ ],"
+ `)
+ })
+
+ it('adds MatcherPatternQueryParam import', () => {
+ const node = new PrefixTree(DEFAULT_OPTIONS).insert('a', 'a.vue')
+ node.value.setEditOverride('params', {
+ query: { search: {} },
+ })
+
+ generateRouteRecordQuery({ importsMap, node, paramParsersMap: new Map() })
+
+ expect(
+ importsMap.has('vue-router/experimental', 'MatcherPatternQueryParam')
+ ).toBe(true)
+ })
+
+ it('generates query param with format "value"', () => {
+ const node = new PrefixTree(DEFAULT_OPTIONS).insert('a', 'a.vue')
+ node.value.setEditOverride('params', {
+ query: { search: { format: 'value' } },
+ })
+ expect(
+ generateRouteRecordQuery({ importsMap, node, paramParsersMap: new Map() })
+ ).toMatchInlineSnapshot(`
+ "query: [
+ new MatcherPatternQueryParam('search', 'search', 'value')
+ ],"
+ `)
+ })
+
+ it('generates query param with format "array"', () => {
+ const node = new PrefixTree(DEFAULT_OPTIONS).insert('a', 'a.vue')
+ node.value.setEditOverride('params', {
+ query: { tags: { format: 'array' } },
+ })
+ expect(
+ generateRouteRecordQuery({ importsMap, node, paramParsersMap: new Map() })
+ ).toMatchInlineSnapshot(`
+ "query: [
+ new MatcherPatternQueryParam('tags', 'tags', 'array')
+ ],"
+ `)
+ })
+
+ it('generates query param with default value', () => {
+ const node = new PrefixTree(DEFAULT_OPTIONS).insert('a', 'a.vue')
+ node.value.setEditOverride('params', {
+ query: { limit: { default: '10' } },
+ })
+ expect(
+ generateRouteRecordQuery({ importsMap, node, paramParsersMap: new Map() })
+ ).toMatchInlineSnapshot(`
+ "query: [
+ new MatcherPatternQueryParam('limit', 'limit', 'value', {}, 10)
+ ],"
+ `)
+ })
+
+ it('generates query param with format and default value', () => {
+ const node = new PrefixTree(DEFAULT_OPTIONS).insert('a', 'a.vue')
+ node.value.setEditOverride('params', {
+ query: { page: { parser: 'int', format: 'array', default: '1' } },
+ })
+ expect(
+ generateRouteRecordQuery({ importsMap, node, paramParsersMap: new Map() })
+ ).toMatchInlineSnapshot(`
+ "query: [
+ new MatcherPatternQueryParam('page', 'page', 'array', PARAM_PARSER_INT, 1)
+ ],"
+ `)
+ })
+
+ it('generates mixed query params with different configurations', () => {
+ const node = new PrefixTree(DEFAULT_OPTIONS).insert('a', 'a.vue')
+ node.value.setEditOverride('params', {
+ query: {
+ q: { format: 'value' },
+ tags: { format: 'array' },
+ limit: { parser: 'int', default: '20' },
+ active: { default: 'true' },
+ },
+ })
+ expect(
+ generateRouteRecordQuery({ importsMap, node, paramParsersMap: new Map() })
+ ).toMatchInlineSnapshot(`
+ "query: [
+ new MatcherPatternQueryParam('q', 'q', 'value'),
+ new MatcherPatternQueryParam('tags', 'tags', 'array'),
+ new MatcherPatternQueryParam('limit', 'limit', 'value', PARAM_PARSER_INT, 20),
+ new MatcherPatternQueryParam('active', 'active', 'value', {}, true)
+ ],"
+ `)
+ })
+})
+
+describe('generateRouteRecord', () => {
+ it('serializes a simple static path', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ const importsMap = new ImportsMap()
+ const paramParsersMap: ParamParsersMap = new Map()
+ expect(
+ generateRouteRecord({
+ node: tree.insert('a', 'a.vue'),
+ parentVar: null,
+ state: DEFAULT_STATE,
+ options: DEFAULT_OPTIONS,
+ importsMap,
+ paramParsersMap,
+ })
+ ).toMatchInlineSnapshot(`
+ "const r_0 = normalizeRouteRecord({
+ name: '/a',
+ path: new MatcherPatternPathStatic('/a'),
+ components: {
+ 'default': () => import('a.vue')
+ },
+ })"
+ `)
+ expect(
+ generateRouteRecord({
+ node: tree.insert('a/b/c', 'a/b/c.vue'),
+ parentVar: null,
+ state: DEFAULT_STATE,
+ options: DEFAULT_OPTIONS,
+ importsMap,
+ paramParsersMap,
+ })
+ ).toMatchInlineSnapshot(`
+ "const r_1 = normalizeRouteRecord({
+ name: '/a/b/c',
+ path: new MatcherPatternPathStatic('/a/b/c'),
+ components: {
+ 'default': () => import('a/b/c.vue')
+ },
+ })"
+ `)
+ })
+})
+
+describe('generateRouteResolver', () => {
+ it('generates a resolver for a simple tree', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ const importsMap = new ImportsMap()
+ tree.insert('a', 'a.vue')
+ tree.insert('b/c', 'b/c.vue')
+ tree.insert('b/c/d', 'b/c/d.vue')
+ tree.insert('b/e/f', 'b/c/f.vue')
+ const resolver = generateRouteResolver(
+ tree,
+ DEFAULT_OPTIONS,
+ importsMap,
+ new Map()
+ )
+
+ expect(resolver).toMatchInlineSnapshot(`
+ "
+ const r_0 = normalizeRouteRecord({
+ name: '/a',
+ path: new MatcherPatternPathStatic('/a'),
+ components: {
+ 'default': () => import('a.vue')
+ },
+ })
+
+ const r_1 = normalizeRouteRecord({
+ name: '/b/c',
+ path: new MatcherPatternPathStatic('/b/c'),
+ components: {
+ 'default': () => import('b/c.vue')
+ },
+ })
+ const r_2 = normalizeRouteRecord({
+ name: '/b/c/d',
+ path: new MatcherPatternPathStatic('/b/c/d'),
+ components: {
+ 'default': () => import('b/c/d.vue')
+ },
+ parent: r_1,
+ })
+ const r_3 = normalizeRouteRecord({
+ name: '/b/e/f',
+ path: new MatcherPatternPathStatic('/b/e/f'),
+ components: {
+ 'default': () => import('b/c/f.vue')
+ },
+ })
+
+ export const resolver = createFixedResolver([
+ r_2, // /b/c/d
+ r_3, // /b/e/f
+ r_1, // /b/c
+ r_0, // /a
+ ])
+ "
+ `)
+ })
+
+ it('generates correct nested layouts', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ const importsMap = new ImportsMap()
+ tree.insert('a', 'a.vue')
+ tree.insert('a/(a-home)', 'a/(a-home).vue')
+ tree.insert('a/b', 'a/b.vue')
+ tree.insert('a/b/c', 'a/b/c.vue')
+ tree.insert('a/b/(b-home)', 'a/b/(b-home).vue')
+ tree.insert('a/d', 'a/d.vue')
+ tree.insert('a/b/e', 'a/b/e.vue')
+
+ const resolver = generateRouteResolver(
+ tree,
+ DEFAULT_OPTIONS,
+ importsMap,
+ new Map()
+ )
+
+ // FIXME: there are conflicting paths here. The order is correct as nested routes appear higher but
+ // it should appeand a trailing slash to the children route or the parent
+ // Adding it to the parent makes the routing stable but also inconsistent trailing slash
+ // I think it's better to not have a stable routing to preserve stable trailing slash
+
+ expect(resolver).toMatchInlineSnapshot(`
+ "
+ const r_0 = normalizeRouteRecord({
+ name: '/a',
+ path: new MatcherPatternPathStatic('/a'),
+ components: {
+ 'default': () => import('a.vue')
+ },
+ })
+ const r_1 = normalizeRouteRecord({
+ name: '/a/(a-home)',
+ path: new MatcherPatternPathStatic('/a'),
+ components: {
+ 'default': () => import('a/(a-home).vue')
+ },
+ parent: r_0,
+ })
+ const r_2 = normalizeRouteRecord({
+ name: '/a/b',
+ path: new MatcherPatternPathStatic('/a/b'),
+ components: {
+ 'default': () => import('a/b.vue')
+ },
+ parent: r_0,
+ })
+ const r_3 = normalizeRouteRecord({
+ name: '/a/b/(b-home)',
+ path: new MatcherPatternPathStatic('/a/b'),
+ components: {
+ 'default': () => import('a/b/(b-home).vue')
+ },
+ parent: r_2,
+ })
+ const r_4 = normalizeRouteRecord({
+ name: '/a/b/c',
+ path: new MatcherPatternPathStatic('/a/b/c'),
+ components: {
+ 'default': () => import('a/b/c.vue')
+ },
+ parent: r_2,
+ })
+ const r_5 = normalizeRouteRecord({
+ name: '/a/b/e',
+ path: new MatcherPatternPathStatic('/a/b/e'),
+ components: {
+ 'default': () => import('a/b/e.vue')
+ },
+ parent: r_2,
+ })
+ const r_6 = normalizeRouteRecord({
+ name: '/a/d',
+ path: new MatcherPatternPathStatic('/a/d'),
+ components: {
+ 'default': () => import('a/d.vue')
+ },
+ parent: r_0,
+ })
+
+ export const resolver = createFixedResolver([
+ r_3, // /a/b
+ r_4, // /a/b/c
+ r_5, // /a/b/e
+ r_1, // /a
+ r_2, // /a/b
+ r_6, // /a/d
+ r_0, // /a
+ ])
+ "
+ `)
+ })
+
+ describe('route prioritization in resolver', () => {
+ function getRouteOrderFromResolver(tree: PrefixTree): string[] {
+ const resolver = generateRouteResolver(
+ tree,
+ DEFAULT_OPTIONS,
+ new ImportsMap(),
+ new Map()
+ )
+
+ // Extract the order from the resolver output
+ const lines = resolver.split('\n').filter((line) => line.includes('// /'))
+ return lines.map((line) => line.split('// ')[1] || '')
+ }
+
+ it('orders records based on specificity of paths', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ // static at root
+ tree.insert('prefix', 'prefix.vue')
+
+ // params in the end
+ tree.insert('prefix/sub-end', 'prefix/sub-end.vue')
+ tree.insert('prefix/sub-[id]', 'prefix/sub-[id].vue')
+ // repeat can only be the whole segment
+ // tree.insert('prefix/sub-[repeat]+', 'prefix/sub-[repeat]+.vue')
+ tree.insert('prefix/sub-[[opt]]', 'prefix/sub-[[opt]].vue')
+ // repeat can only be the whole segment
+ // tree.insert('prefix/sub-[[optRepeat]]+', 'prefix/c/d.vue')
+ tree.insert('prefix/[id]', 'prefix/[id].vue')
+ tree.insert('prefix/[repeat]+', 'prefix/[repeat]+.vue')
+ tree.insert('prefix/[[opt]]', 'prefix/[[opt]].vue')
+ tree.insert('prefix/[[optRepeat]]+', 'prefix/[[optRepeat]]+.vue')
+ tree.insert('[...splat]', '[...splat].vue')
+
+ // params at root level
+ tree.insert('[id]', '[id].vue')
+ tree.insert('[[optional]]', '[[optional]].vue')
+ tree.insert('prefix-[id]-suffix', 'prefix-[id]-suffix.vue')
+
+ // params in the middle path parts
+ tree.insert('prefix/[id]/suffix', 'prefix/[id]/suffix.vue')
+ tree.insert('prefix/[[opt]]/suffix', 'prefix/[[opt]]/suffix.vue')
+ tree.insert('prefix/[repeat]+/suffix', 'prefix/[repeat]+/suffix.vue')
+ tree.insert(
+ 'prefix/[[optRepeat]]+/suffix',
+ 'prefix/[[optRepeat]]+/suffix.vue'
+ )
+ tree.insert('prefix/static/suffix', 'prefix/static/suffix.vue')
+ // sub-segments
+ tree.insert('prefix/[id]-end/suffix', 'prefix/[id]-end/suffix.vue')
+ tree.insert('prefix/[[opt]]-end/suffix', 'prefix/[[opt]]-end/suffix.vue')
+ tree.insert(
+ 'prefix/sub-[id]-end/suffix',
+ 'prefix/sub-[id]-end/suffix.vue'
+ )
+ tree.insert(
+ 'prefix/sub-[[opt]]-end/suffix',
+ 'prefix/sub-[[opt]]-end/suffix.vue'
+ )
+ tree.insert('prefix/sub-[id]/suffix', 'prefix/sub-[id]/suffix.vue')
+ tree.insert('prefix/sub-[[opt]]/suffix', 'prefix/sub-[[opt]]/suffix.vue')
+
+ expect(getRouteOrderFromResolver(tree)).toEqual([
+ '/prefix/static/suffix',
+ '/prefix/sub-end',
+ '/prefix/sub-:id-end/suffix',
+ '/prefix/:id-end/suffix',
+ '/prefix/sub-:id/suffix',
+ '/prefix/sub-:id',
+ '/prefix/:id/suffix',
+ '/prefix/:id',
+ // this could be before /prefix/:id, but since it has a suffix, it works too
+ '/prefix/sub-:opt?-end/suffix',
+ '/prefix/:opt?-end/suffix',
+ '/prefix/sub-:opt?/suffix',
+ '/prefix/sub-:opt?', // FIXME: should be before /prefix/:id
+ '/prefix/:opt?/suffix',
+ '/prefix/:opt?',
+ '/prefix/:repeat+/suffix',
+ '/prefix/:repeat+',
+ '/prefix/:optRepeat*/suffix',
+ '/prefix/:optRepeat*',
+ '/prefix',
+ '/prefix-:id-suffix',
+ '/:id',
+ // one should never have both a regular id and an optional id in the same position
+ // because the optional one will never match
+ '/:optional?',
+ '/:splat(.*)',
+ ])
+ })
+
+ it.todo('warns on invalid repeatable params')
+ })
+
+ it('strips off empty parent records', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ const importsMap = new ImportsMap()
+ tree.insert('a', 'a.vue')
+ tree.insert('b/c', 'b/c.vue')
+ tree.insert('b/c/d', 'b/c/d.vue')
+ tree.insert('b/e/f', 'b/c/f.vue')
+ const resolver = generateRouteResolver(
+ tree,
+ DEFAULT_OPTIONS,
+ importsMap,
+ new Map()
+ )
+
+ expect(resolver).toMatchInlineSnapshot(`
+ "
+ const r_0 = normalizeRouteRecord({
+ name: '/a',
+ path: new MatcherPatternPathStatic('/a'),
+ components: {
+ 'default': () => import('a.vue')
+ },
+ })
+
+ const r_1 = normalizeRouteRecord({
+ name: '/b/c',
+ path: new MatcherPatternPathStatic('/b/c'),
+ components: {
+ 'default': () => import('b/c.vue')
+ },
+ })
+ const r_2 = normalizeRouteRecord({
+ name: '/b/c/d',
+ path: new MatcherPatternPathStatic('/b/c/d'),
+ components: {
+ 'default': () => import('b/c/d.vue')
+ },
+ parent: r_1,
+ })
+ const r_3 = normalizeRouteRecord({
+ name: '/b/e/f',
+ path: new MatcherPatternPathStatic('/b/e/f'),
+ components: {
+ 'default': () => import('b/c/f.vue')
+ },
+ })
+
+ export const resolver = createFixedResolver([
+ r_2, // /b/c/d
+ r_3, // /b/e/f
+ r_1, // /b/c
+ r_0, // /a
+ ])
+ "
+ `)
+ })
+
+ it('retains parent chain when skipping empty intermediate nodes', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ // Create a meaningful parent
+ tree.insert('a', 'a.vue')
+ // Create a deeply nested child with empty intermediate nodes b and c
+ tree.insert('a/b/c/e', 'a/b/c/e.vue')
+ const resolver = generateRouteResolver(
+ tree,
+ DEFAULT_OPTIONS,
+ new ImportsMap(),
+ new Map()
+ )
+
+ expect(resolver).toMatchInlineSnapshot(`
+ "
+ const r_0 = normalizeRouteRecord({
+ name: '/a',
+ path: new MatcherPatternPathStatic('/a'),
+ components: {
+ 'default': () => import('a.vue')
+ },
+ })
+ const r_1 = normalizeRouteRecord({
+ name: '/a/b/c/e',
+ path: new MatcherPatternPathStatic('/a/b/c/e'),
+ components: {
+ 'default': () => import('a/b/c/e.vue')
+ },
+ parent: r_0,
+ })
+
+ export const resolver = createFixedResolver([
+ r_1, // /a/b/c/e
+ r_0, // /a
+ ])
+ "
+ `)
+ })
+
+ it('preserves parent nodes with meta data', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ // Create a nested route
+ tree.insert('a/b/c', 'a/b/c.vue')
+ // Add meta to the intermediate b node (no components, but has meta)
+ const aNode = tree.children.get('a')!
+ const bNode = aNode.children.get('b')!
+ bNode.value.setEditOverride('meta', { requiresAuth: true })
+
+ const resolver = generateRouteResolver(
+ tree,
+ DEFAULT_OPTIONS,
+ new ImportsMap(),
+ new Map()
+ )
+
+ expect(resolver).toMatchInlineSnapshot(`
+ "
+ const r_0 = normalizeRouteRecord({
+ /* internal name: '/a/b' */
+ meta: {
+ "requiresAuth": true
+ },
+ })
+ const r_1 = normalizeRouteRecord({
+ name: '/a/b/c',
+ path: new MatcherPatternPathStatic('/a/b/c'),
+ components: {
+ 'default': () => import('a/b/c.vue')
+ },
+ parent: r_0,
+ })
+
+ export const resolver = createFixedResolver([
+ r_1, // /a/b/c
+ ])
+ "
+ `)
+ })
+
+ it('includes meta in route records with components', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ // Create a route with both component and meta
+ tree.insert('users', 'users.vue')
+ const usersNode = tree.children.get('users')!
+ usersNode.value.setEditOverride('meta', {
+ requiresAuth: true,
+ title: 'Users',
+ })
+
+ const resolver = generateRouteResolver(
+ tree,
+ DEFAULT_OPTIONS,
+ new ImportsMap(),
+ new Map()
+ )
+
+ expect(resolver).toMatchInlineSnapshot(`
+ "
+ const r_0 = normalizeRouteRecord({
+ name: '/users',
+ path: new MatcherPatternPathStatic('/users'),
+ meta: {
+ "requiresAuth": true,
+ "title": "Users"
+ },
+ components: {
+ 'default': () => import('users.vue')
+ },
+ })
+
+ export const resolver = createFixedResolver([
+ r_0, // /users
+ ])
+ "
+ `)
+ })
+
+ it('handles definePage imports correctly', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ // Create a route with a component
+ tree.insert('profile', 'profile.vue')
+ const profileNode = tree.children.get('profile')!
+
+ // Mark it as having definePage (this would normally be set by the plugin when parsing the file)
+ profileNode.hasDefinePage = true
+
+ const resolver = generateRouteResolver(
+ tree,
+ DEFAULT_OPTIONS,
+ new ImportsMap(),
+ new Map()
+ )
+
+ expect(resolver).toMatchInlineSnapshot(`
+ "
+
+ const r_0 = normalizeRouteRecord(
+ _mergeRouteRecord(
+ {
+ name: '/profile',
+ path: new MatcherPatternPathStatic('/profile'),
+ components: {
+ 'default': () => import('profile.vue')
+ },
+ },
+ _definePage_default_0
+ )
+ )
+
+
+ export const resolver = createFixedResolver([
+ r_0, // /profile
+ ])
+ "
+ `)
+ })
+
+ it('includes query property in route records with query params', () => {
+ const tree = new PrefixTree(DEFAULT_OPTIONS)
+ tree.insert('search', 'search.vue')
+ const searchNode = tree.children.get('search')!
+
+ // Add query params
+ searchNode.value.setEditOverride('params', {
+ query: {
+ q: {},
+ limit: { parser: 'int' },
+ },
+ })
+
+ const resolver = generateRouteResolver(
+ tree,
+ DEFAULT_OPTIONS,
+ new ImportsMap(),
+ new Map()
+ )
+
+ expect(resolver).toMatchInlineSnapshot(`
+ "
+ const r_0 = normalizeRouteRecord({
+ name: '/search',
+ path: new MatcherPatternPathStatic('/search'),
+ query: [
+ new MatcherPatternQueryParam('q', 'q', 'value'),
+ new MatcherPatternQueryParam('limit', 'limit', 'value', PARAM_PARSER_INT)
+ ],
+ components: {
+ 'default': () => import('search.vue')
+ },
+ })
+
+ export const resolver = createFixedResolver([
+ r_0, // /search
+ ])
+ "
+ `)
+ })
+})
diff --git a/src/codegen/generateRouteResolver.ts b/src/codegen/generateRouteResolver.ts
new file mode 100644
index 000000000..f43f32755
--- /dev/null
+++ b/src/codegen/generateRouteResolver.ts
@@ -0,0 +1,345 @@
+import { getLang } from '@vue-macros/common'
+import { PrefixTree, type TreeNode } from '../core/tree'
+import { ImportsMap } from '../core/utils'
+import { type ResolvedOptions } from '../options'
+import { ts } from '../utils'
+import {
+ generatePathParamsOptions,
+ generateParamParserOptions,
+ ParamParsersMap,
+} from './generateParamParsers'
+import { generatePageImport, formatMeta } from './generateRouteRecords'
+
+/**
+ * Compare two score arrays for sorting routes by priority.
+ * Higher scores should come first (more specific routes).
+ */
+function compareRouteScore(a: number[][], b: number[][]): number {
+ const maxLength = Math.max(a.length, b.length)
+
+ for (let i = 0; i < maxLength; i++) {
+ const aSegment = a[i] || []
+ const bSegment = b[i] || []
+
+ // Compare segment by segment, but consider the "minimum" score of each segment
+ // since mixed segments with params should rank lower than pure static
+ const aMinScore = aSegment.length > 0 ? Math.min(...aSegment) : 0
+ const bMinScore = bSegment.length > 0 ? Math.min(...bSegment) : 0
+
+ if (aMinScore !== bMinScore) {
+ return bMinScore - aMinScore // Higher minimum score wins
+ }
+
+ // If minimum scores are equal, compare average scores
+ const aAvgScore =
+ aSegment.length > 0
+ ? aSegment.reduce((sum, s) => sum + s, 0) / aSegment.length
+ : 0
+ const bAvgScore =
+ bSegment.length > 0
+ ? bSegment.reduce((sum, s) => sum + s, 0) / bSegment.length
+ : 0
+
+ if (aAvgScore !== bAvgScore) {
+ return bAvgScore - aAvgScore // Higher average score wins
+ }
+
+ // If averages are equal, prefer fewer subsegments (less complexity)
+ if (aSegment.length !== bSegment.length) {
+ return aSegment.length - bSegment.length
+ }
+ }
+
+ // If all segments are equal, prefer fewer segments (shorter paths)
+ return a.length - b.length
+}
+
+interface GenerateRouteResolverState {
+ id: number
+ matchableRecords: {
+ path: string
+ varName: string
+ score: number[][]
+ }[]
+}
+
+export function generateRouteResolver(
+ tree: PrefixTree,
+ options: ResolvedOptions,
+ importsMap: ImportsMap,
+ paramParsersMap: ParamParsersMap
+): string {
+ const state: GenerateRouteResolverState = { id: 0, matchableRecords: [] }
+ const records = tree.getChildrenSorted().map((node) =>
+ generateRouteRecord({
+ node,
+ parentVar: null,
+ state,
+ options,
+ importsMap,
+ paramParsersMap,
+ })
+ )
+
+ importsMap.add('vue-router/experimental', 'createFixedResolver')
+ importsMap.add('vue-router/experimental', 'MatcherPatternPathStatic')
+ importsMap.add('vue-router/experimental', 'MatcherPatternPathDynamic')
+ importsMap.add('vue-router/experimental', 'normalizeRouteRecord')
+
+ return ts`
+${records.join('\n\n')}
+
+export const resolver = createFixedResolver([
+${state.matchableRecords
+ .sort((a, b) => compareRouteScore(a.score, b.score))
+ .map(
+ ({ varName, path }) =>
+ ` ${varName}, ${' '.repeat(String(state.id).length - varName.length + 2)}// ${path}`
+ )
+ .join('\n')}
+])
+`
+}
+
+/**
+ * Generates the route record in the format expected by the static resolver.
+ */
+export function generateRouteRecord({
+ node,
+ parentVar,
+ state,
+ options,
+ importsMap,
+ paramParsersMap,
+}: {
+ node: TreeNode
+ parentVar: string | null | undefined
+ state: GenerateRouteResolverState
+ options: ResolvedOptions
+ importsMap: ImportsMap
+ paramParsersMap: ParamParsersMap
+}): string {
+ const isMatchable = node.isMatchable()
+
+ // we want to skip adding routes that add no options (components, meta, props, etc)
+ // that simplifies the generated tree
+ const shouldSkipNode = !isMatchable && !node.meta
+
+ let varName: string | null = null
+ let recordDeclaration = ''
+
+ // Handle definePage imports
+ const definePageDataList: string[] = []
+ if (node.hasDefinePage) {
+ for (const [name, filePath] of node.value.components) {
+ const pageDataImport = `_definePage_${name}_${importsMap.size}`
+ definePageDataList.push(pageDataImport)
+ const lang = getLang(filePath)
+ importsMap.addDefault(
+ // TODO: apply the language used in the sfc
+ `${filePath}?definePage&` +
+ (lang === 'vue' ? 'vue&lang.tsx' : `lang.${lang}`),
+ pageDataImport
+ )
+ }
+ }
+
+ if (!shouldSkipNode) {
+ varName = `r_${state.id++}`
+
+ let recordName: string
+ let recordComponents: string
+
+ if (isMatchable) {
+ state.matchableRecords.push({
+ path: node.fullPath,
+ varName,
+ score: node.score,
+ })
+ recordName = `name: '${node.name}',`
+ recordComponents = generateRouteRecordComponent(
+ node,
+ ' ',
+ options.importMode,
+ importsMap
+ )
+ } else {
+ recordName = node.name ? `/* internal name: '${node.name}' */` : ``
+ recordComponents = ''
+ }
+
+ const queryProperty = generateRouteRecordQuery({
+ node,
+ importsMap,
+ paramParsersMap,
+ })
+ const routeRecordObject = `{
+ ${recordName}
+ ${generateRouteRecordPath({ node, importsMap, paramParsersMap })}${
+ queryProperty ? `\n ${queryProperty}` : ''
+ }${formatMeta(node, ' ')}
+ ${recordComponents}${parentVar ? `\n parent: ${parentVar},` : ''}
+}`
+
+ recordDeclaration =
+ definePageDataList.length > 0
+ ? `
+const ${varName} = normalizeRouteRecord(
+ ${generateRouteRecordMerge(routeRecordObject, definePageDataList, importsMap)}
+)
+`
+ : `
+const ${varName} = normalizeRouteRecord(${routeRecordObject})
+`
+ .trim()
+ .split('\n')
+ // remove empty lines
+ .filter((l) => l.trimStart().length > 0)
+ .join('\n')
+ }
+
+ const children = node.getChildrenSorted().map((child) =>
+ generateRouteRecord({
+ node: child,
+ // If we skipped this node, pass the parent var from above, otherwise use our var
+ parentVar: shouldSkipNode ? parentVar : varName,
+ state,
+ options,
+ importsMap,
+ paramParsersMap,
+ })
+ )
+
+ return (
+ recordDeclaration +
+ (children.length
+ ? (recordDeclaration ? '\n' : '') + children.join('\n')
+ : '')
+ )
+}
+
+function generateRouteRecordComponent(
+ node: TreeNode,
+ indentStr: string,
+ importMode: ResolvedOptions['importMode'],
+ importsMap: ImportsMap
+): string {
+ const files = Array.from(node.value.components)
+ return `components: {
+${files
+ .map(
+ ([key, path]) =>
+ `${indentStr + ' '}'${key}': ${generatePageImport(
+ path,
+ importMode,
+ importsMap
+ )}`
+ )
+ .join(',\n')}
+${indentStr}},`
+}
+
+/**
+ * Generates the `path` property of a route record for the static resolver.
+ */
+export function generateRouteRecordPath({
+ node,
+ importsMap,
+ paramParsersMap,
+}: {
+ node: TreeNode
+ importsMap: ImportsMap
+ paramParsersMap: ParamParsersMap
+}) {
+ if (!node.isMatchable()) {
+ return ''
+ }
+ const params = node.pathParams
+ if (params.length > 0) {
+ return `path: new MatcherPatternPathDynamic(
+ ${node.regexp},
+ ${generatePathParamsOptions(params, importsMap, paramParsersMap)},
+ ${JSON.stringify(node.matcherPatternPathDynamicParts)},
+ ${node.isSplat ? 'null,' : '/* trailingSlash */'}
+ ),`
+ } else {
+ return `path: new MatcherPatternPathStatic('${node.fullPath}'),`
+ }
+}
+
+/**
+ * Generates the `query` property of a route record for the static resolver.
+ */
+export function generateRouteRecordQuery({
+ node,
+ importsMap,
+ paramParsersMap,
+}: {
+ node: TreeNode
+ importsMap: ImportsMap
+ paramParsersMap: ParamParsersMap
+}) {
+ const queryParams = node.queryParams
+ if (queryParams.length === 0) {
+ return ''
+ }
+
+ importsMap.add('vue-router/experimental', 'MatcherPatternQueryParam')
+
+ return `query: [
+${queryParams
+ .map((param) => {
+ const parserOptions = generateParamParserOptions(
+ param,
+ importsMap,
+ paramParsersMap
+ )
+
+ const args = [
+ `'${param.paramName}'`,
+ // TODO: allow param.queryKey
+ `'${param.paramName}'`,
+ `'${param.format}'`,
+ ]
+
+ if (parserOptions || param.defaultValue !== undefined) {
+ args.push(parserOptions || '{}')
+ }
+
+ if (param.defaultValue !== undefined) {
+ args.push(param.defaultValue)
+ }
+
+ return ` new MatcherPatternQueryParam(${args.join(', ')})`
+ })
+ .join(',\n')}
+ ],`
+}
+
+/**
+ * Generates a merge call for route records with definePage data in the experimental resolver format.
+ */
+function generateRouteRecordMerge(
+ routeRecordObject: string,
+ definePageDataList: string[],
+ importsMap: ImportsMap
+): string {
+ if (definePageDataList.length === 0) {
+ return routeRecordObject
+ }
+
+ importsMap.add('vue-router/experimental', '_mergeRouteRecord')
+
+ // Re-indent the route object to be 4 spaces (2 levels from normalizeRouteRecord)
+ const indentedRouteObject = routeRecordObject
+ .split('\n')
+ .map((line) => {
+ return line && ` ${line}`
+ })
+ .join('\n')
+
+ return `_mergeRouteRecord(
+${indentedRouteObject},
+${definePageDataList.map((name) => ` ${name}`).join(',\n')}
+ )`
+}
diff --git a/src/core/__snapshots__/definePage.spec.ts.snap b/src/core/__snapshots__/definePage.spec.ts.snap
index 0d160ad0b..6b1dee935 100644
--- a/src/core/__snapshots__/definePage.spec.ts.snap
+++ b/src/core/__snapshots__/definePage.spec.ts.snap
@@ -22,9 +22,9 @@ export default {
}"
`;
-exports[`definePage > imports > removes default import if not used 1`] = `"export default {name: 'ok'}"`;
+exports[`definePage > imports > removes default unused imports 1`] = `"export default {name: 'ok'}"`;
-exports[`definePage > imports > removes star imports if not used 1`] = `"export default {name: 'ok'}"`;
+exports[`definePage > imports > removes unused star imports 1`] = `"export default {name: 'ok'}"`;
exports[`definePage > imports > works when combining named and default imports 1`] = `
"import my_var, {my_func} from './lib'
diff --git a/src/core/context.ts b/src/core/context.ts
index f07494aae..a3fb8bf2b 100644
--- a/src/core/context.ts
+++ b/src/core/context.ts
@@ -1,13 +1,13 @@
import { ResolvedOptions } from '../options'
import { TreeNode, PrefixTree } from './tree'
-import { promises as fs } from 'fs'
+import { promises as fs } from 'node:fs'
import { asRoutePath, ImportsMap, logTree, throttle } from './utils'
import { generateRouteNamedMap } from '../codegen/generateRouteMap'
import { generateRouteFileInfoMap } from '../codegen/generateRouteFileInfoMap'
-import { MODULE_ROUTES_PATH, MODULE_VUE_ROUTER_AUTO } from './moduleConstants'
+import { MODULE_ROUTES_PATH } from './moduleConstants'
import { generateRouteRecord } from '../codegen/generateRouteRecords'
import { glob } from 'tinyglobby'
-import { dirname, relative, resolve } from 'pathe'
+import { dirname, parse as parsePathe, relative, resolve } from 'pathe'
import { ServerContext } from '../options'
import { getRouteBlock } from './customBlock'
import {
@@ -17,10 +17,19 @@ import {
} from './RoutesFolderWatcher'
import { generateDTS as _generateDTS } from '../codegen/generateDTS'
import { generateVueRouterProxy as _generateVueRouterProxy } from '../codegen/vueRouterModule'
-import { definePageTransform, extractDefinePageNameAndPath } from './definePage'
+import { definePageTransform, extractDefinePageInfo } from './definePage'
import { EditableTreeNode } from './extendRoutes'
import { isPackageExists as isPackageInstalled } from 'local-pkg'
import { ts } from '../utils'
+import { generateRouteResolver } from '../codegen/generateRouteResolver'
+import { type FSWatcher, watch as fsWatch } from 'chokidar'
+import {
+ generateParamParsersTypesDeclarations,
+ generateParamParserCustomType,
+ ParamParsersMap,
+ warnMissingParamParsers,
+} from '../codegen/generateParamParsers'
+import picomatch from 'picomatch'
export function createRoutesContext(options: ResolvedOptions) {
const { dts: preferDTS, root, routesFolder } = options
@@ -45,7 +54,8 @@ export function createRoutesContext(options: ResolvedOptions) {
})
// populated by the initial scan pages
- const watchers: RoutesFolderWatcher[] = []
+ const watchers: Array = []
+ const paramParsersMap: ParamParsersMap = new Map()
async function scanPages(startWatchers = true) {
if (options.extensions.length < 1) {
@@ -59,9 +69,12 @@ export function createRoutesContext(options: ResolvedOptions) {
return
}
+ const PARAM_PARSER_GLOB = '*.{ts,js}'
+ const isParamParserMatch = picomatch(PARAM_PARSER_GLOB)
+
// get the initial list of pages
- await Promise.all(
- routesFolder
+ await Promise.all([
+ ...routesFolder
.map((folder) => resolveFolderOptions(options, folder))
.map((folder) => {
if (startWatchers) {
@@ -93,8 +106,52 @@ export function createRoutesContext(options: ResolvedOptions) {
)
)
)
+ }),
+ ...(options.experimental.paramParsers?.dir.map((folder) => {
+ if (startWatchers) {
+ watchers.push(
+ setupParamParserWatcher(
+ fsWatch('.', {
+ cwd: folder,
+ ignoreInitial: true,
+ ignorePermissionErrors: true,
+ ignored: (filePath, stats) => {
+ // let folders pass, they are ignored by the glob pattern
+ if (!stats || stats.isDirectory()) {
+ return false
+ }
+
+ return !isParamParserMatch(relative(folder, filePath))
+ },
+ }),
+ folder
+ )
+ )
+ }
+
+ return glob(PARAM_PARSER_GLOB, {
+ cwd: folder,
+ onlyFiles: true,
+ expandDirectories: false,
+ }).then((paramParserFiles) => {
+ for (const file of paramParserFiles) {
+ const name = parsePathe(file).name
+ // TODO: could be simplified to only one import that starts with / for vite
+ const absolutePath = resolve(folder, file)
+ paramParsersMap.set(name, {
+ name,
+ typeName: `Param_${name}`,
+ absolutePath,
+ relativePath: relative(options.root, absolutePath),
+ })
+ }
+ logger.log(
+ 'Parsed param parsers',
+ [...paramParsersMap].map((p) => p[0])
+ )
})
- )
+ }) || []),
+ ])
for (const route of editableRoutes) {
await options.extendRoute?.(route)
@@ -109,10 +166,7 @@ export function createRoutesContext(options: ResolvedOptions) {
// TODO: cache the result of parsing the SFC (in the extractDefinePageAndName) so the transform can reuse the parsing
node.hasDefinePage ||= content.includes('definePage')
// TODO: track if it changed and to not always trigger HMR
- const definedPageNameAndPath = extractDefinePageNameAndPath(
- content,
- filePath
- )
+ const definedPageInfo = extractDefinePageInfo(content, filePath)
// TODO: track if it changed and if generateRoutes should be called again
const routeBlock = getRouteBlock(filePath, content, options)
// TODO: should warn if hasDefinePage and customRouteBlock
@@ -120,7 +174,7 @@ export function createRoutesContext(options: ResolvedOptions) {
node.setCustomRouteBlock(filePath, {
...routeBlock,
- ...definedPageNameAndPath,
+ ...definedPageInfo,
})
}
@@ -135,9 +189,6 @@ export function createRoutesContext(options: ResolvedOptions) {
if (triggerExtendRoute) {
await options.extendRoute?.(new EditableTreeNode(node))
}
-
- // TODO: trigger HMR vue-router/auto
- server?.updateRoutes()
}
async function updatePage({ filePath, routePath }: HandlerContext) {
@@ -156,8 +207,26 @@ export function createRoutesContext(options: ResolvedOptions) {
function removePage({ filePath, routePath }: HandlerContext) {
logger.log(`remove "${routePath}" for "${filePath}"`)
routeTree.removeChild(filePath)
- // TODO: HMR vue-router/auto
- server?.updateRoutes()
+ }
+
+ function setupParamParserWatcher(watcher: FSWatcher, cwd: string) {
+ logger.log(`🤖 Scanning param parsers in ${cwd}`)
+ return watcher
+ .on('add', (file) => {
+ const name = parsePathe(file).name
+ const absolutePath = resolve(cwd, file)
+ paramParsersMap.set(name, {
+ name,
+ typeName: `Param_${name}`,
+ absolutePath,
+ relativePath: './' + relative(options.root, absolutePath),
+ })
+ writeConfigFiles()
+ })
+ .on('unlink', (file) => {
+ paramParsersMap.delete(parsePathe(file).name)
+ writeConfigFiles()
+ })
}
function setupWatcher(watcher: RoutesFolderWatcher) {
@@ -181,6 +250,57 @@ export function createRoutesContext(options: ResolvedOptions) {
// unlinkDir event
}
+ function generateResolver() {
+ const importsMap = new ImportsMap()
+
+ const resolverCode = generateRouteResolver(
+ routeTree,
+ options,
+ importsMap,
+ paramParsersMap
+ )
+
+ // generate the list of imports
+ let imports = importsMap.toString()
+ // add an empty line for readability
+ if (imports) {
+ imports += '\n'
+ }
+
+ const hmr = ts`
+export function handleHotUpdate(_router, _hotUpdateCallback) {
+ if (import.meta.hot) {
+ import.meta.hot.data.router = _router
+ import.meta.hot.data.router_hotUpdateCallback = _hotUpdateCallback
+ }
+}
+
+if (import.meta.hot) {
+ import.meta.hot.accept((mod) => {
+ const router = import.meta.hot.data.router
+ if (!router) {
+ import.meta.hot.invalidate('[unplugin-vue-router:HMR] Cannot replace the resolver because there is no active router. Reloading.')
+ return
+ }
+ router._hmrReplaceResolver(mod.resolver)
+ // call the hotUpdateCallback for custom updates
+ import.meta.hot.data.router_hotUpdateCallback?.(mod.resolver)
+ const route = router.currentRoute.value
+ router.replace({
+ path: route.path,
+ query: route.query,
+ hash: route.hash,
+ force: true
+ })
+ })
+}`
+
+ const newAutoRoutes = `${imports}${resolverCode}\n${hmr}`
+
+ // prepend it to the code
+ return newAutoRoutes
+ }
+
function generateRoutes() {
const importsMap = new ImportsMap()
@@ -190,7 +310,7 @@ export function createRoutesContext(options: ResolvedOptions) {
importsMap
)}\n`
- let hmr = ts`
+ const hmr = ts`
export function handleHotUpdate(_router, _hotUpdateCallback) {
if (import.meta.hot) {
import.meta.hot.data.router = _router
@@ -238,15 +358,26 @@ if (import.meta.hot) {
return newAutoRoutes
}
- function generateDTS(): string {
- return _generateDTS({
- vueRouterModule: MODULE_VUE_ROUTER_AUTO,
+ function generateDTS() {
+ if (options.experimental.paramParsers?.dir.length) {
+ warnMissingParamParsers(routeTree, paramParsersMap)
+ }
+
+ const autoRoutes = _generateDTS({
routesModule: MODULE_ROUTES_PATH,
- routeNamedMap: generateRouteNamedMap(routeTree),
+ routeNamedMap: generateRouteNamedMap(routeTree, options, paramParsersMap),
routeFileInfoMap: generateRouteFileInfoMap(routeTree, {
root,
}),
+ paramsTypesDeclaration:
+ generateParamParsersTypesDeclarations(paramParsersMap),
+ customParamsType: generateParamParserCustomType(paramParsersMap),
})
+
+ // TODO: parser auto copmlete for definePage
+ // const paramParserListType = generateParamParserListTypes([...paramParsers])
+
+ return autoRoutes
}
// NOTE: this code needs to be generated because otherwise it doesn't go through transforms and `vue-router/auto-routes`
@@ -275,6 +406,10 @@ if (import.meta.hot) {
await fs.writeFile(dts, content, 'utf-8')
logger.timeLog('writeConfigFiles', 'wrote dts file')
lastDTS = content
+ // TODO: only update routes if routes changed (this includes definePage changes)
+ // but do not update routes if only the component want updated
+ // currently, this doesn't trigger if definePage meta properties changed
+ server?.updateRoutes()
}
}
logger.timeEnd('writeConfigFiles')
@@ -305,6 +440,7 @@ if (import.meta.hot) {
stopWatcher,
generateRoutes,
+ generateResolver,
generateVueRouterProxy,
definePageTransform(code: string, id: string) {
diff --git a/src/core/customBlock.ts b/src/core/customBlock.ts
index 75c30d72d..27a34e922 100644
--- a/src/core/customBlock.ts
+++ b/src/core/customBlock.ts
@@ -4,6 +4,7 @@ import JSON5 from 'json5'
import { parse as YAMLParser } from 'yaml'
import { RouteRecordRaw } from 'vue-router'
import { warn } from './utils'
+import type { DefinePageQueryParamOptions } from '../runtime'
export function getRouteBlock(
path: string,
@@ -24,6 +25,19 @@ export interface CustomRouteBlock
>
> {
name?: string | undefined | false
+
+ params?: {
+ path?: Record
+
+ query?: Record
+ }
+}
+
+export interface CustomRouteBlockQueryParamOptions {
+ parser?: string
+ format?: DefinePageQueryParamOptions['format']
+ // TODO: queryKey?: string
+ default?: string
}
function parseCustomBlock(
diff --git a/src/core/definePage.spec.ts b/src/core/definePage.spec.ts
index 07d92d4f8..214a84a87 100644
--- a/src/core/definePage.spec.ts
+++ b/src/core/definePage.spec.ts
@@ -1,8 +1,11 @@
import { TransformResult } from 'vite'
import { expect, describe, it } from 'vitest'
-import { definePageTransform, extractDefinePageNameAndPath } from './definePage'
+import { definePageTransform, extractDefinePageInfo } from './definePage'
+import { ts } from '../utils'
-const sampleCode = `
+const vue = String.raw
+
+const sampleCode = vue`
`,
id: 'src/pages/with-imports.vue&definePage&vue&lang.ts',
})) as Exclude
- expect(result).toHaveProperty('code')
- expect(result?.code).toMatchSnapshot()
+ expect(resultStar).toHaveProperty('code')
+ expect(resultStar?.code).toMatchSnapshot()
})
- it('removes star imports if not used', async () => {
+ it('works with star imports', async () => {
const result = (await definePageTransform({
- code: `
+ code: vue`
`,
id: 'src/pages/with-imports.vue&definePage&vue&lang.ts',
@@ -117,7 +120,7 @@ definePage({name: 'ok'})
it('works when combining named and default imports', async () => {
const result = (await definePageTransform({
- code: `
+ code: vue`
+`
+ expect(
+ extractDefinePageInfo(codeWithAllParams, 'src/pages/test.vue')
+ ).toEqual({
+ params: {
+ path: {
+ userId: 'int',
+ isActive: 'bool',
+ },
+ query: {
+ page: {
+ parser: 'int',
+ default: '1',
+ format: 'value',
+ },
+ enabled: {
+ parser: 'bool',
+ },
+ count: {
+ parser: 'int',
+ default: '42',
+ },
+ active: {
+ default: "'none'",
+ },
+ },
+ },
+ })
+ })
+
it('extract name skipped when non existent', async () => {
expect(
- await extractDefinePageNameAndPath(
- `
+ extractDefinePageInfo(
+ vue`
@@ -240,15 +298,15 @@ const b = 1
id: 'src/pages/definePage.vue?definePage&vue',
})
).toMatchObject({
- code: `\
+ code: ts`
export default {
name: 'custom',
path: '/custom',
-}`,
+}`.trim(),
})
expect(
- await extractDefinePageNameAndPath(sampleCode, 'src/pages/definePage.vue')
+ extractDefinePageInfo(sampleCode, 'src/pages/definePage.vue')
).toEqual({
name: 'custom',
path: '/custom',
diff --git a/src/core/definePage.ts b/src/core/definePage.ts
index ca420eb1e..fe5bdfb21 100644
--- a/src/core/definePage.ts
+++ b/src/core/definePage.ts
@@ -11,15 +11,18 @@ import type { Thenable, TransformResult } from 'unplugin'
import type {
CallExpression,
Node,
+ ObjectExpression,
ObjectProperty,
Program,
Statement,
StringLiteral,
} from '@babel/types'
+import { generate } from '@babel/generator'
import { walkAST } from 'ast-walker-scope'
-import { CustomRouteBlock } from './customBlock'
import { warn } from './utils'
import { ParsedStaticImport, findStaticImports, parseStaticImport } from 'mlly'
+import type { ParamParserType } from 'unplugin-vue-router/runtime'
+import { CustomRouteBlock } from './customBlock'
const MACRO_DEFINE_PAGE = 'definePage'
export const MACRO_DEFINE_PAGE_QUERY = /[?&]definePage\b/
@@ -186,10 +189,18 @@ export function definePageTransform({
}
}
-export function extractDefinePageNameAndPath(
+type DefinePageParamsInfo = NonNullable
+
+export interface DefinePageInfo {
+ name?: string | false
+ path?: string
+ params?: CustomRouteBlock['params']
+}
+
+export function extractDefinePageInfo(
sfcCode: string,
id: string
-): { name?: string | false; path?: string } | null | undefined {
+): DefinePageInfo | null | undefined {
if (!sfcCode.includes(MACRO_DEFINE_PAGE)) return
const { ast, definePageNodes } = getCodeAst(sfcCode, id)
@@ -216,7 +227,7 @@ export function extractDefinePageNameAndPath(
)
}
- const routeInfo: Pick = {}
+ const routeInfo: DefinePageInfo = {}
for (const prop of routeRecord.properties) {
if (prop.type === 'ObjectProperty' && prop.key.type === 'Identifier') {
@@ -238,6 +249,10 @@ export function extractDefinePageNameAndPath(
} else {
routeInfo.path = prop.value.value
}
+ } else if (prop.key.name === 'params') {
+ if (prop.value.type === 'ObjectExpression') {
+ routeInfo.params = extractParamsInfo(prop.value, id)
+ }
}
}
}
@@ -245,6 +260,122 @@ export function extractDefinePageNameAndPath(
return routeInfo
}
+function extractParamsInfo(
+ paramsObj: ObjectExpression,
+ id: string
+): DefinePageParamsInfo {
+ const params: DefinePageParamsInfo = {}
+
+ for (const prop of paramsObj.properties) {
+ if (prop.type === 'ObjectProperty' && prop.key.type === 'Identifier') {
+ if (prop.key.name === 'query' && prop.value.type === 'ObjectExpression') {
+ params.query = extractQueryParams(prop.value, id)
+ } else if (
+ prop.key.name === 'path' &&
+ prop.value.type === 'ObjectExpression'
+ ) {
+ params.path = extractPathParams(prop.value, id)
+ }
+ }
+ }
+
+ return params
+}
+
+function extractQueryParams(
+ queryObj: ObjectExpression,
+ _id: string
+): NonNullable['query'] {
+ const queryParams: NonNullable['query'] = {}
+
+ for (const prop of queryObj.properties) {
+ if (prop.type === 'ObjectProperty' && prop.key.type === 'Identifier') {
+ const paramName = prop.key.name
+
+ // we normalize short form for convenience
+ if (prop.value.type === 'StringLiteral') {
+ queryParams[paramName] = {
+ parser: prop.value.value as ParamParserType,
+ }
+ } else if (prop.value.type === 'ObjectExpression') {
+ // Full form: param: { parser: 'int', default: 1, format: 'value' }
+ const paramInfo: (typeof queryParams)[string] = {}
+
+ for (const paramProp of prop.value.properties) {
+ if (
+ paramProp.type === 'ObjectProperty' &&
+ paramProp.key.type === 'Identifier'
+ ) {
+ if (
+ paramProp.key.name === 'parser' &&
+ paramProp.value.type === 'StringLiteral'
+ ) {
+ paramInfo.parser = paramProp.value.value as ParamParserType
+ } else if (
+ paramProp.key.name === 'format' &&
+ paramProp.value.type === 'StringLiteral'
+ ) {
+ paramInfo.format = paramProp.value.value as 'value' | 'array'
+ } else if (paramProp.key.name === 'default') {
+ if (typeof paramProp.value.extra?.raw === 'string') {
+ paramInfo.default = paramProp.value.extra.raw
+ } else if (paramProp.value.type === 'NumericLiteral') {
+ paramInfo.default = String(paramProp.value.value)
+ } else if (paramProp.value.type === 'StringLiteral') {
+ paramInfo.default = JSON.stringify(paramProp.value.value)
+ } else if (paramProp.value.type === 'BooleanLiteral') {
+ paramInfo.default = String(paramProp.value.value)
+ } else if (paramProp.value.type === 'NullLiteral') {
+ paramInfo.default = 'null'
+ } else if (
+ paramProp.value.type === 'UnaryExpression' &&
+ (paramProp.value.operator === '-' ||
+ paramProp.value.operator === '+' ||
+ paramProp.value.operator === '!' ||
+ paramProp.value.operator === '~') &&
+ paramProp.value.argument.type === 'NumericLiteral'
+ ) {
+ // support negative numeric literals: -1, -1.5
+ paramInfo.default = `${paramProp.value.operator}${paramProp.value.argument.value}`
+ } else if (paramProp.value.type === 'ArrowFunctionExpression') {
+ paramInfo.default = generate(paramProp.value).code
+ } else {
+ warn(
+ `Unrecognized default value in definePage() for query param "${paramName}". Typeof value: "${paramProp.value.type}". This is a bug or a missing type of value, open an issue on https://github.com/posva/unplugin-vue-router and provide the definePage() code.`
+ )
+ }
+ }
+ }
+ }
+
+ queryParams[paramName] = paramInfo
+ }
+ }
+ }
+
+ return queryParams
+}
+
+function extractPathParams(
+ pathObj: ObjectExpression,
+ _id: string
+): NonNullable['path'] {
+ const pathParams: NonNullable['path'] = {}
+
+ for (const prop of pathObj.properties) {
+ if (
+ prop.type === 'ObjectProperty' &&
+ prop.key.type === 'Identifier' &&
+ prop.value.type === 'StringLiteral'
+ ) {
+ // TODO: we should check if the value is a valid parser type
+ pathParams[prop.key.name] = prop.value.value as ParamParserType
+ }
+ }
+
+ return pathParams
+}
+
// TODO: use
export function extractRouteAlias(
aliasValue: ObjectProperty['value'],
diff --git a/src/core/extendRoutes.spec.ts b/src/core/extendRoutes.spec.ts
index 608bcb8c4..3417efd1f 100644
--- a/src/core/extendRoutes.spec.ts
+++ b/src/core/extendRoutes.spec.ts
@@ -69,12 +69,13 @@ describe('EditableTreeNode', () => {
const child = tree.children.get(':id')!
expect(child.fullPath).toBe('/:id')
expect(child.path).toBe('/:id')
- expect(child.params).toEqual([
+ expect(child.params).toMatchObject([
{
paramName: 'id',
modifier: '',
optional: false,
repeatable: false,
+ parser: null,
isSplat: false,
},
])
@@ -89,9 +90,10 @@ describe('EditableTreeNode', () => {
const child = tree.children.get(':id+')!
expect(child.fullPath).toBe('/:id+')
expect(child.path).toBe('/:id+')
- expect(child.params).toEqual([
+ expect(child.params).toMatchObject([
{
paramName: 'id',
+ parser: null,
modifier: '+',
optional: false,
repeatable: true,
@@ -109,7 +111,7 @@ describe('EditableTreeNode', () => {
const node = tree.children.get(':foo/:bar')!
expect(node.fullPath).toBe('/:foo/:bar')
expect(node.path).toBe('/:foo/:bar')
- expect(node.params).toEqual([
+ expect(node.params).toMatchObject([
{
paramName: 'foo',
modifier: '',
@@ -136,7 +138,7 @@ describe('EditableTreeNode', () => {
const node = tree.children.get(':foo/:bar+_:o(\\d+)')!
expect(node.fullPath).toBe('/:foo/:bar+_:o(\\d+)')
expect(node.path).toBe('/:foo/:bar+_:o(\\d+)')
- expect(node.params).toEqual([
+ expect(node.params).toMatchObject([
{
paramName: 'foo',
modifier: '',
@@ -169,7 +171,7 @@ describe('EditableTreeNode', () => {
const node = tree.children.get(':id(\\d+)')!
expect(node.fullPath).toBe('/:id(\\d+)')
expect(node.path).toBe('/:id(\\d+)')
- expect(node.params).toEqual([
+ expect(node.params).toMatchObject([
{
paramName: 'id',
modifier: '',
@@ -188,7 +190,7 @@ describe('EditableTreeNode', () => {
const node = tree.children.get(':id()')!
expect(node.fullPath).toBe('/:id()')
expect(node.path).toBe('/:id()')
- expect(node.params).toEqual([
+ expect(node.params).toMatchObject([
{
paramName: 'id',
modifier: '',
@@ -207,7 +209,7 @@ describe('EditableTreeNode', () => {
const node = tree.children.get(':id(\\d+)+')!
expect(node.fullPath).toBe('/:id(\\d+)+')
expect(node.path).toBe('/:id(\\d+)+')
- expect(node.params).toEqual([
+ expect(node.params).toMatchObject([
{
paramName: 'id',
modifier: '+',
@@ -226,7 +228,7 @@ describe('EditableTreeNode', () => {
const node = tree.children.get(':id()+')!
expect(node.fullPath).toBe('/:id()+')
expect(node.path).toBe('/:id()+')
- expect(node.params).toEqual([
+ expect(node.params).toMatchObject([
{
paramName: 'id',
modifier: '+',
@@ -246,7 +248,7 @@ describe('EditableTreeNode', () => {
const child = tree.children.get(':path(.*)')!
expect(child.fullPath).toBe('/:path(.*)')
expect(child.path).toBe('/:path(.*)')
- expect(child.params).toEqual([
+ expect(child.params).toMatchObject([
{
paramName: 'path',
modifier: '',
diff --git a/src/core/extendRoutes.ts b/src/core/extendRoutes.ts
index 4bf0514cd..23b2fa1fd 100644
--- a/src/core/extendRoutes.ts
+++ b/src/core/extendRoutes.ts
@@ -47,6 +47,8 @@ export class EditableTreeNode {
// but in other places we need to instruct the path is at the root so we change it afterwards
addBackLeadingSlash = !this.node.isRoot()
}
+ // TODO: if options.experimental.paramParsers, should insert the raw path as [thing]
+ // and warn if a path contains a :
const node = this.node.insertParsedPath(path, filePath)
const editable = new EditableTreeNode(node)
if (addBackLeadingSlash) {
@@ -169,11 +171,11 @@ export class EditableTreeNode {
}
/**
- * Array of the route params and all of its parent's params. Note that changing the params will not update the path,
- * you need to update both.
+ * Array of the route params and all of its parent's params. Note that
+ * changing the params will not update the path, you need to update both.
*/
get params() {
- return this.node.params
+ return this.node.pathParams
}
/**
diff --git a/src/core/moduleConstants.ts b/src/core/moduleConstants.ts
index c7dfa03d0..a21969340 100644
--- a/src/core/moduleConstants.ts
+++ b/src/core/moduleConstants.ts
@@ -4,6 +4,7 @@
export const MODULE_VUE_ROUTER_AUTO = 'vue-router/auto'
// vue-router/auto/routes was more natural but didn't work well with TS
export const MODULE_ROUTES_PATH = `${MODULE_VUE_ROUTER_AUTO}-routes`
+export const MODULE_RESOLVER_PATH = `vue-router/auto-resolver`
// NOTE: not sure if needed. Used for HMR the virtual routes
let time = Date.now()
@@ -27,7 +28,11 @@ export const VIRTUAL_PREFIX = '/__'
// allows removing the route block from the code
export const ROUTE_BLOCK_ID = `${VIRTUAL_PREFIX}/vue-router/auto/route-block`
-export const MODULES_ID_LIST = [MODULE_VUE_ROUTER_AUTO, MODULE_ROUTES_PATH]
+export const MODULES_ID_LIST = [
+ MODULE_VUE_ROUTER_AUTO,
+ MODULE_ROUTES_PATH,
+ MODULE_RESOLVER_PATH,
+]
export function getVirtualId(id: string) {
return id.startsWith(VIRTUAL_PREFIX) ? id.slice(VIRTUAL_PREFIX.length) : null
diff --git a/src/core/tree.spec.ts b/src/core/tree.spec.ts
index f3f696090..c8688ff69 100644
--- a/src/core/tree.spec.ts
+++ b/src/core/tree.spec.ts
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'
import { DEFAULT_OPTIONS, resolveOptions } from '../options'
import { PrefixTree } from './tree'
-import { TreeNodeType, type TreeRouteParam } from './treeNodeValue'
+import { TreeNodeType, type TreePathParam } from './treeNodeValue'
import { resolve } from 'pathe'
import { mockWarn } from '../../tests/vitest-mock-warn'
@@ -43,6 +43,103 @@ describe('Tree', () => {
expect(child.children.size).toBe(0)
})
+ it('parses a custom param type', () => {
+ const tree = new PrefixTree(RESOLVED_OPTIONS)
+ tree.insert('[id=int]', '[id=int].vue')
+ const child = tree.children.get('[id=int]')!
+ expect(child).toBeDefined()
+ expect(child.value).toMatchObject({
+ rawSegment: '[id=int]',
+ params: [
+ {
+ paramName: 'id',
+ parser: 'int',
+ },
+ ],
+ fullPath: '/:id',
+ _type: TreeNodeType.param,
+ })
+ })
+
+ it('parses a repeatable custom param type', () => {
+ const tree = new PrefixTree(RESOLVED_OPTIONS)
+ tree.insert('[id=int]+', '[id=int]+.vue')
+ const child = tree.children.get('[id=int]+')!
+ expect(child).toBeDefined()
+ expect(child.value).toMatchObject({
+ rawSegment: '[id=int]+',
+ params: [
+ {
+ paramName: 'id',
+ parser: 'int',
+ repeatable: true,
+ modifier: '+',
+ },
+ ],
+ fullPath: '/:id+',
+ _type: TreeNodeType.param,
+ })
+ })
+
+ it('parses an optional custom param type', () => {
+ const tree = new PrefixTree(RESOLVED_OPTIONS)
+ tree.insert('[[id=int]]', '[[id=int]].vue')
+ const child = tree.children.get('[[id=int]]')!
+ expect(child).toBeDefined()
+ expect(child.value).toMatchObject({
+ rawSegment: '[[id=int]]',
+ params: [
+ {
+ paramName: 'id',
+ parser: 'int',
+ optional: true,
+ modifier: '?',
+ },
+ ],
+ fullPath: '/:id?',
+ _type: TreeNodeType.param,
+ })
+ })
+
+ it('parses a repeatable optional custom param type', () => {
+ const tree = new PrefixTree(RESOLVED_OPTIONS)
+ tree.insert('[[id=int]]+', '[[id=int]]+.vue')
+ const child = tree.children.get('[[id=int]]+')!
+ expect(child).toBeDefined()
+ expect(child.value).toMatchObject({
+ rawSegment: '[[id=int]]+',
+ params: [
+ {
+ paramName: 'id',
+ parser: 'int',
+ repeatable: true,
+ optional: true,
+ modifier: '*',
+ },
+ ],
+ fullPath: '/:id*',
+ _type: TreeNodeType.param,
+ })
+ })
+
+ it('parses a custom param type with sub segments', () => {
+ const tree = new PrefixTree(RESOLVED_OPTIONS)
+ tree.insert('a-[id=int]-b', 'file.vue')
+ const child = tree.children.get('a-[id=int]-b')!
+ expect(child).toBeDefined()
+ expect(child.value).toMatchObject({
+ rawSegment: 'a-[id=int]-b',
+ params: [
+ {
+ paramName: 'id',
+ parser: 'int',
+ },
+ ],
+ fullPath: '/a-:id-b',
+ _type: TreeNodeType.param,
+ })
+ })
+
it('separate param names from static segments', () => {
const tree = new PrefixTree(RESOLVED_OPTIONS)
tree.insert('[id]_a', '[id]_a.vue')
@@ -452,13 +549,13 @@ describe('Tree', () => {
path: '/:a()/new-b',
})
expect(node.params).toHaveLength(1)
- expect(node.params[0]).toEqual({
+ expect(node.params[0]).toMatchObject({
paramName: 'a',
isSplat: false,
modifier: '',
optional: false,
repeatable: false,
- } satisfies TreeRouteParam)
+ } satisfies Partial)
})
it('removes trailing slash from path but not from name', () => {
@@ -554,6 +651,223 @@ describe('Tree', () => {
expect(`"(home" is missing the closing ")"`).toHaveBeenWarned()
})
+ describe('path regexp', () => {
+ it('generates static paths', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert('a', 'a.vue')
+ expect(node.regexp).toBe('/^\\/a$/i')
+ })
+
+ it('works with multiple segments', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert('a/b/c', 'a/b/c.vue')
+ expect(node.regexp).toBe('/^\\/a\\/b\\/c$/i')
+ })
+
+ describe('basic params [id] in all positions', () => {
+ it('only segment', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert('[id]', '[id].vue')
+ expect(node.regexp).toBe('/^\\/([^/]+?)$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual([1])
+ })
+
+ it('first position', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ '[id]/static',
+ '[id]/static.vue'
+ )
+ expect(node.regexp).toBe('/^\\/([^/]+?)\\/static$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual([1, 'static'])
+ })
+
+ it('middle position', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ 'static/[id]/more',
+ 'static/[id]/more.vue'
+ )
+ expect(node.regexp).toBe('/^\\/static\\/([^/]+?)\\/more$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual([
+ 'static',
+ 1,
+ 'more',
+ ])
+ })
+
+ it('last position', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ 'static/[id]',
+ 'static/[id].vue'
+ )
+ expect(node.regexp).toBe('/^\\/static\\/([^/]+?)$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual(['static', 1])
+ })
+ })
+
+ describe('optional params [[id]] in all positions', () => {
+ it('only segment', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ '[[id]]',
+ '[[id]].vue'
+ )
+ expect(node.regexp).toBe('/^\\/([^/]+?)?$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual([1])
+ })
+
+ it('first position', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ '[[id]]/static',
+ '[[id]]/static.vue'
+ )
+ expect(node.regexp).toBe('/^(?:\\/([^/]+?))?\\/static$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual([1, 'static'])
+ })
+
+ it('middle position', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ 'static/[[id]]/more',
+ 'static/[[id]]/more.vue'
+ )
+ expect(node.regexp).toBe('/^\\/static(?:\\/([^/]+?))?\\/more$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual([
+ 'static',
+ 1,
+ 'more',
+ ])
+ })
+
+ it('last position', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ 'static/[[id]]',
+ 'static/[[id]].vue'
+ )
+ expect(node.regexp).toBe('/^\\/static(?:\\/([^/]+?))?$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual(['static', 1])
+ })
+ })
+
+ describe('repeatable params [id]+ in all positions', () => {
+ it('only segment', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ '[id]+',
+ '[id]+.vue'
+ )
+ expect(node.regexp).toBe('/^\\/(.+?)$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual([1])
+ })
+
+ it('first position', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ '[id]+/static',
+ '[id]+/static.vue'
+ )
+ expect(node.regexp).toBe('/^\\/(.+?)\\/static$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual([1, 'static'])
+ })
+
+ it('middle position', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ 'static/[id]+/more',
+ 'static/[id]+/more.vue'
+ )
+ expect(node.regexp).toBe('/^\\/static\\/(.+?)\\/more$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual([
+ 'static',
+ 1,
+ 'more',
+ ])
+ })
+
+ it('last position', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ 'static/[id]+',
+ 'static/[id]+.vue'
+ )
+ expect(node.regexp).toBe('/^\\/static\\/(.+?)$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual(['static', 1])
+ })
+ })
+
+ describe('optional repeatable params [[id]]+ in all positions', () => {
+ it('only segment', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ '[[id]]+',
+ '[[id]]+.vue'
+ )
+ expect(node.regexp).toBe('/^\\/(.+?)?$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual([1])
+ })
+
+ it('first position', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ '[[id]]+/static',
+ '[[id]]+/static.vue'
+ )
+ expect(node.regexp).toBe('/^(?:\\/(.+?))?\\/static$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual([1, 'static'])
+ })
+
+ it('middle position', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ 'static/[[id]]+/more',
+ 'static/[[id]]+/more.vue'
+ )
+ expect(node.regexp).toBe('/^\\/static(?:\\/(.+?))?\\/more$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual([
+ 'static',
+ 1,
+ 'more',
+ ])
+ })
+
+ it('last position', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ 'static/[[id]]+',
+ 'static/[[id]]+.vue'
+ )
+ expect(node.regexp).toBe('/^\\/static(?:\\/(.+?))?$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual(['static', 1])
+ })
+ })
+
+ it('works with multiple params', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert('a/[b]/[c]', 'a.vue')
+ expect(node.regexp).toBe('/^\\/a\\/([^/]+?)\\/([^/]+?)$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual(['a', 1, 1])
+ })
+
+ it('works with segments', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ 'a/a-[b]-c-[d]',
+ 'a.vue'
+ )
+ expect(node.regexp).toBe('/^\\/a\\/a-([^/]+?)-c-([^/]+?)$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual([
+ 'a',
+ ['a-', 1, '-c-', 1],
+ ])
+ })
+
+ it('works with a catch all route', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ '[...all]',
+ '[...all].vue'
+ )
+ expect(node.regexp).toBe('/^\\/(.*)$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual([0])
+ })
+
+ it('works with a splat param with a prefix', () => {
+ const node = new PrefixTree(RESOLVED_OPTIONS).insert(
+ 'a/some-[id]/[...all]',
+ 'a/some-[id]/[...all].vue'
+ )
+ expect(node.regexp).toBe('/^\\/a\\/some-([^/]+?)\\/(.*)$/i')
+ expect(node.matcherPatternPathDynamicParts).toEqual([
+ 'a',
+ ['some-', 1],
+ 0,
+ ])
+ })
+ })
+
// TODO: check warns with different order
it.todo(`warns when a group's path conflicts with an existing file`)
@@ -590,4 +904,87 @@ describe('Tree', () => {
})
})
})
+
+ describe('Query params from definePage', () => {
+ it('extracts query params from route overrides', () => {
+ const tree = new PrefixTree(RESOLVED_OPTIONS)
+ const node = tree.insert('users', 'users.vue')
+
+ // Simulate definePage params extraction
+ node.setCustomRouteBlock('users.vue', {
+ params: {
+ query: {
+ search: {},
+ limit: { parser: 'int', default: '10' },
+ tags: { parser: 'bool' },
+ other: { default: '"defaultValue"' },
+ },
+ },
+ })
+
+ expect(node.queryParams).toEqual([
+ {
+ paramName: 'search',
+ parser: null,
+ format: 'value',
+ defaultValue: undefined,
+ },
+ {
+ paramName: 'limit',
+ parser: 'int',
+ format: 'value',
+ defaultValue: '10',
+ },
+ {
+ paramName: 'tags',
+ parser: 'bool',
+ format: 'value',
+ defaultValue: undefined,
+ },
+ {
+ paramName: 'other',
+ parser: null,
+ format: 'value',
+ defaultValue: '"defaultValue"',
+ },
+ ])
+ })
+
+ it('returns empty array when no query params defined', () => {
+ const tree = new PrefixTree(RESOLVED_OPTIONS)
+ const node = tree.insert('about', 'about.vue')
+
+ expect(node.queryParams).toEqual([])
+ })
+
+ it('params includes both path and query params', () => {
+ const tree = new PrefixTree(RESOLVED_OPTIONS)
+ const node = tree.insert('posts/[id]', 'posts/[id].vue')
+
+ node.setCustomRouteBlock('posts/[id].vue', {
+ params: {
+ query: {
+ tab: {},
+ expand: { parser: 'bool', default: 'false' },
+ },
+ },
+ })
+
+ // Should have 1 path param + 2 query params
+ expect(node.params).toHaveLength(3)
+ expect(node.params[0]).toMatchObject({ paramName: 'id' }) // path param
+ expect(node.params[1]).toMatchObject({
+ paramName: 'tab',
+ parser: null,
+ format: 'value',
+ defaultValue: undefined,
+ }) // query param
+ expect(node.params[2]).toMatchObject({
+ paramName: 'expand',
+ parser: 'bool',
+ format: 'value',
+ defaultValue: 'false',
+ }) // query param
+ })
+ })
})
diff --git a/src/core/tree.ts b/src/core/tree.ts
index 25fff6644..66633fada 100644
--- a/src/core/tree.ts
+++ b/src/core/tree.ts
@@ -1,8 +1,10 @@
import { type ResolvedOptions } from '../options'
import {
createTreeNodeValue,
+ escapeRegex,
TreeNodeValueOptions,
- TreeRouteParam,
+ TreePathParam,
+ TreeQueryParam,
} from './treeNodeValue'
import type { TreeNodeValue } from './treeNodeValue'
import { CustomRouteBlock } from './customBlock'
@@ -12,6 +14,10 @@ export interface TreeNodeOptions extends ResolvedOptions {
treeNodeOptions?: TreeNodeValueOptions
}
+export type TreeNodeValueMatcherPart = Array<
+ string | number | Array
+>
+
export class TreeNode {
/**
* value of the node
@@ -265,13 +271,31 @@ export class TreeNode {
: ''
}
- get params(): TreeRouteParam[] {
- const params = this.value.isParam() ? [...this.value.params] : []
+ /**
+ * Array of route params for this node. It includes **all** the params from the parents as well.
+ */
+ get params(): (TreePathParam | TreeQueryParam)[] {
+ const params = [...this.value.params]
+ let node = this.parent
+ // add all the params from the parents
+ while (node) {
+ params.unshift(...node.value.params)
+ node = node.parent
+ }
+
+ return params
+ }
+
+ /**
+ * Array of route params coming from the path. It includes all the params from the parents as well.
+ */
+ get pathParams(): TreePathParam[] {
+ const params = this.value.isParam() ? [...this.value.pathParams] : []
let node = this.parent
// add all the params from the parents
while (node) {
if (node.value.isParam()) {
- params.unshift(...node.value.params)
+ params.unshift(...node.value.pathParams)
}
node = node.parent
}
@@ -279,6 +303,123 @@ export class TreeNode {
return params
}
+ /**
+ * Array of query params extracted from definePage. Only returns query params from this specific node.
+ */
+ get queryParams(): TreeQueryParam[] {
+ return this.value.queryParams
+ }
+
+ /**
+ * Generates a regexp based on this node and its parents. This regexp is used by the custom resolver
+ */
+ get regexp(): string {
+ let node: TreeNode | undefined = this
+ // we build the node list from parent to child
+ const nodeList: TreeNode[] = []
+ while (node && !node.isRoot()) {
+ nodeList.unshift(node)
+ node = node.parent
+ }
+
+ let re = ''
+ for (var i = 0; i < nodeList.length; i++) {
+ node = nodeList[i]!
+ if (node.value.isParam()) {
+ var nodeRe = node.value.re
+ // Ensure we add a connecting slash
+ // if we already have something in the regexp and if the only part of
+ // the segment is an optional param, then the / must be put inside the
+ // non-capturing group
+ if (
+ // if we have a segment before or after
+ (re || i < nodeList.length - 1) &&
+ // if the only part of the segment is an optional (can be repeatable) param
+ node.value.subSegments.length === 1 &&
+ (node.value.subSegments.at(0) as TreePathParam).optional
+ ) {
+ // TODO: tweak if trailingSlash
+ re += `(?:\\/${
+ // we remove the ? at the end because we add it later
+ nodeRe.slice(0, -1)
+ })?`
+ } else {
+ re += (re ? '\\/' : '') + nodeRe
+ }
+ } else {
+ re += (re ? '\\/' : '') + escapeRegex(node.value.pathSegment)
+ }
+ }
+
+ // TODO: trailingSlash
+ return (
+ '/^' +
+ // Avoid adding a leading slash if the first segment
+ // is an optional segment that already includes it
+ (re.startsWith('(?:\\/') ? '' : '\\/') +
+ re +
+ '$/i'
+ )
+ }
+
+ get score(): number[][] {
+ const scores: number[][] = []
+ let node: TreeNode | undefined = this
+
+ while (node && !node.isRoot()) {
+ scores.unshift(node.value.score)
+ node = node.parent
+ }
+
+ return scores
+ }
+
+ /**
+ * Is this node a splat (catch-all) param
+ */
+ get isSplat(): boolean {
+ return this.value.isParam() && this.value.pathParams.some((p) => p.isSplat)
+ }
+
+ /**
+ * Returns an array of matcher parts that is consumed by
+ * MatcherPatternPathDynamic to render the path.
+ */
+ get matcherPatternPathDynamicParts(): TreeNodeValueMatcherPart {
+ const parts: TreeNodeValueMatcherPart = []
+ let node: TreeNode | undefined = this
+
+ while (node && !node.isRoot()) {
+ const subSegments = node.value.subSegments.map((segment) =>
+ typeof segment === 'string'
+ ? segment
+ : // param
+ segment.isSplat
+ ? 0
+ : 1
+ )
+
+ if (subSegments.length > 1) {
+ parts.unshift(subSegments)
+ } else if (subSegments.length === 1) {
+ parts.unshift(subSegments[0]!)
+ }
+ node = node.parent
+ }
+
+ return parts
+ }
+
+ /**
+ * Is this tree node matchable? A matchable node has at least one component
+ * and a name.
+ */
+ isMatchable(): this is TreeNode & { name: string } {
+ // a node is matchable if it has at least one component
+ // and the name is not false
+ return this.value.components.size > 0 && this.name !== false
+ }
+
/**
* Returns wether this tree node is the root node of the tree.
*
diff --git a/src/core/treeNodeValue.ts b/src/core/treeNodeValue.ts
index e2aabecc2..60616d78c 100644
--- a/src/core/treeNodeValue.ts
+++ b/src/core/treeNodeValue.ts
@@ -1,5 +1,8 @@
import type { RouteRecordRaw } from 'vue-router'
-import { CustomRouteBlock } from './customBlock'
+import {
+ CustomRouteBlock,
+ CustomRouteBlockQueryParamOptions,
+} from './customBlock'
import { joinPath, mergeRouteRecordOverride, warn } from './utils'
export const enum TreeNodeType {
@@ -11,9 +14,23 @@ export const enum TreeNodeType {
export interface RouteRecordOverride
extends Partial> {
name?: string | undefined | false
+
+ /**
+ * Param Parsers information.
+ */
+ params?: {
+ path?: Record
+
+ query?: Record
+ }
+}
+
+export interface RouteRecordOverrideQueryParamOptions
+ extends CustomRouteBlockQueryParamOptions {
+ default?: string
}
-export type SubSegment = string | TreeRouteParam
+export type SubSegment = string | TreePathParam
// internal name used for overrides done by the user at build time
export const EDITS_OVERRIDE_NAME = '@@edits'
@@ -45,7 +62,7 @@ class _TreeNodeValueBase {
* Overrides defined by each file. The map is necessary to handle named views.
*/
private _overrides = new Map()
- // TODO: cache the overrides generation
+ // TODO: measure perf bottlenecks with large trees and use caching if it can potentially improve
/**
* View name (Vue Router feature) mapped to their corresponding file. By default, the view name is `default` unless
@@ -88,6 +105,48 @@ class _TreeNodeValueBase {
return joinPath(this.parent?.fullPath ?? '', pathSegment)
}
+ /**
+ * Gets all the query params for the node. This does not include params from parent nodes.
+ */
+ get queryParams(): TreeQueryParam[] {
+ const paramsQuery = this.overrides.params?.query
+ if (!paramsQuery) {
+ return []
+ }
+
+ const queryParams: TreeQueryParam[] = []
+
+ for (var paramName in paramsQuery) {
+ var config = paramsQuery[paramName]
+ // shouldn't happen
+ if (!config) continue
+ if (typeof config === 'string') {
+ queryParams.push({
+ paramName,
+ parser: config,
+ format: 'value',
+ })
+ } else {
+ queryParams.push({
+ paramName,
+ parser: config.parser || null,
+ format: config.format || 'value',
+ defaultValue: config.default,
+ })
+ }
+ }
+
+ return queryParams
+ }
+
+ /**
+ * Gets all the params for the node including path and query params. This
+ * does not include params from parent nodes.
+ */
+ get params(): (TreePathParam | TreeQueryParam)[] {
+ return [...(this.isParam() ? this.pathParams : []), ...this.queryParams]
+ }
+
toString(): string {
return this.pathSegment || ''
}
@@ -179,9 +238,24 @@ class _TreeNodeValueBase {
}
}
+/**
+ * - Static
+ * - Static + Custom Param (subSegments)
+ * - Static + Param (subSegments)
+ * - Custom Param
+ * - Param
+ * - CatchAll
+ */
+
+/**
+ * Static path like `/users`, `/users/list`, etc
+ * @extends _TreeNodeValueBase
+ */
export class TreeNodeValueStatic extends _TreeNodeValueBase {
override _type: TreeNodeType.static = TreeNodeType.static
+ readonly score = [300]
+
constructor(
rawSegment: string,
parent: TreeNodeValue | undefined,
@@ -195,6 +269,8 @@ export class TreeNodeValueGroup extends _TreeNodeValueBase {
override _type: TreeNodeType.group = TreeNodeType.group
groupName: string
+ readonly score = [300]
+
constructor(
rawSegment: string,
parent: TreeNodeValue | undefined,
@@ -206,27 +282,154 @@ export class TreeNodeValueGroup extends _TreeNodeValueBase {
}
}
-export interface TreeRouteParam {
+export interface TreePathParam {
paramName: string
modifier: string
optional: boolean
repeatable: boolean
isSplat: boolean
+ parser: string | null
+}
+
+export interface TreeQueryParam {
+ paramName: string
+
+ queryKey?: string
+
+ parser: string | null
+
+ format: 'value' | 'array'
+
+ /**
+ * Expression to be passed as is to the default value of the param.
+ */
+ defaultValue?: string
+}
+
+/**
+ * Checks if a TreePathParam or TreeQueryParam is optional.
+ *
+ * @internal
+ */
+export function isTreeParamOptional(
+ param: TreePathParam | TreeQueryParam
+): boolean {
+ if ('optional' in param) {
+ return param.optional
+ }
+ return param.defaultValue !== undefined
+}
+
+/**
+ * Checks if a TreePathParam or TreeQueryParam is repeatable (array).
+ *
+ * @internal
+ */
+export function isTreeParamRepeatable(
+ param: TreePathParam | TreeQueryParam
+): boolean {
+ if ('repeatable' in param) {
+ return param.repeatable
+ }
+ return param.format === 'array'
+}
+
+/**
+ * Checks if a param is a TreePathParam.
+ *
+ * @internal
+ */
+export function isTreePathParam(
+ param: TreePathParam | TreeQueryParam
+): param is TreePathParam {
+ return 'modifier' in param
}
+/**
+ * To escape regex characters in the path segment.
+ * @internal
+ */
+const REGEX_CHARS_RE = /[.+*?^${}()[\]/\\]/g
+
+/**
+ * Escapes regex characters in a string to be used in a regex pattern.
+ * @param str - The string to escape.
+ *
+ * @internal
+ */
+export const escapeRegex = (str: string): string =>
+ str.replace(REGEX_CHARS_RE, '\\$&')
+
export class TreeNodeValueParam extends _TreeNodeValueBase {
- params: TreeRouteParam[]
override _type: TreeNodeType.param = TreeNodeType.param
constructor(
rawSegment: string,
parent: TreeNodeValue | undefined,
- params: TreeRouteParam[],
+ public pathParams: TreePathParam[],
pathSegment: string,
subSegments: SubSegment[]
) {
super(rawSegment, parent, pathSegment, subSegments)
- this.params = params
+ }
+
+ // Calculate score for each subsegment to handle mixed static/param parts
+ get score(): number[] {
+ return this.subSegments.map((segment) => {
+ if (typeof segment === 'string') {
+ // Static subsegment gets highest score
+ return 300
+ } else {
+ // Parameter subsegment - calculate malus based on param properties
+ const malus = segment.isSplat
+ ? 500
+ : (segment.optional ? 10 : 0) + (segment.repeatable ? 20 : 0)
+
+ return 80 - malus
+ }
+ })
+ }
+
+ get re(): string {
+ const paramRe = this.subSegments
+ .filter(Boolean)
+ .map((segment) => {
+ if (typeof segment === 'string') {
+ return escapeRegex(segment)
+ }
+
+ if (segment.isSplat) {
+ return '(.*)'
+ }
+
+ let re = segment.repeatable ? '(.+?)' : '([^/]+?)'
+
+ if (segment.optional) {
+ re += '?'
+ }
+
+ return re
+ })
+ .join('')
+
+ return paramRe
+ }
+
+ override toString(): string {
+ const params =
+ this.params.length > 0
+ ? ` 𝑥(` +
+ this.params
+ .map(
+ (p) =>
+ ('format' in p ? '?' : '') +
+ `${p.paramName}${'modifier' in p ? p.modifier : ''}` +
+ (p.parser ? '=' + p.parser : '')
+ )
+ .join(', ') +
+ ')'
+ : ''
+ return `${this.pathSegment}` + params
}
}
@@ -308,17 +511,17 @@ export function createTreeNodeValue(
}
}
- const [pathSegment, params, subSegments] =
+ const [pathSegment, pathParams, subSegments] =
options.format === 'path'
? parseRawPathSegment(segment)
: // by default, we use the file format
parseFileSegment(segment, options)
- if (params.length) {
+ if (pathParams.length) {
return new TreeNodeValueParam(
segment,
parent,
- params,
+ pathParams,
pathSegment,
subSegments
)
@@ -331,6 +534,7 @@ const enum ParseFileSegmentState {
static,
paramOptional, // within [[]] or []
param, // within []
+ paramParser, // [param=type]
modifier, // after the ]
}
@@ -359,13 +563,14 @@ const IS_VARIABLE_CHAR_RE = /[0-9a-zA-Z_]/
function parseFileSegment(
segment: string,
{ dotNesting = true }: ParseSegmentOptions = {}
-): [string, TreeRouteParam[], SubSegment[]] {
+): [string, TreePathParam[], SubSegment[]] {
let buffer = ''
+ let paramParserBuffer = ''
let state: ParseFileSegmentState = ParseFileSegmentState.static
- const params: TreeRouteParam[] = []
+ const params: TreePathParam[] = []
let pathSegment = ''
const subSegments: SubSegment[] = []
- let currentTreeRouteParam: TreeRouteParam = createEmptyRouteParam()
+ let currentTreeRouteParam: TreePathParam = createEmptyRouteParam()
// position in segment
let pos = 0
@@ -379,6 +584,7 @@ function parseFileSegment(
subSegments.push(buffer)
} else if (state === ParseFileSegmentState.modifier) {
currentTreeRouteParam.paramName = buffer
+ currentTreeRouteParam.parser = paramParserBuffer || null
currentTreeRouteParam.modifier = currentTreeRouteParam.optional
? currentTreeRouteParam.repeatable
? '*'
@@ -386,7 +592,11 @@ function parseFileSegment(
: currentTreeRouteParam.repeatable
? '+'
: ''
+
+ // reset the buffers
buffer = ''
+ paramParserBuffer = ''
+
pathSegment += `:${currentTreeRouteParam.paramName}${
currentTreeRouteParam.isSplat
? '(.*)'
@@ -409,7 +619,10 @@ function parseFileSegment(
if (state === ParseFileSegmentState.static) {
if (c === '[') {
- consumeBuffer()
+ // avoid adding the leading empty string for segments that start with a param
+ if (buffer) {
+ consumeBuffer()
+ }
// check if it's an optional param or not
state = ParseFileSegmentState.paramOptional
} else {
@@ -438,6 +651,9 @@ function parseFileSegment(
} else if (c === '.') {
currentTreeRouteParam.isSplat = true
pos += 2 // skip the other 2 dots
+ } else if (c === '=') {
+ state = ParseFileSegmentState.paramParser
+ paramParserBuffer = ''
} else {
buffer += c
}
@@ -451,12 +667,23 @@ function parseFileSegment(
consumeBuffer()
// start again
state = ParseFileSegmentState.static
+ } else if (state === ParseFileSegmentState.paramParser) {
+ if (c === ']') {
+ if (currentTreeRouteParam.optional) {
+ // skip the next ]
+ pos++
+ }
+ state = ParseFileSegmentState.modifier
+ } else {
+ paramParserBuffer += c
+ }
}
}
if (
state === ParseFileSegmentState.param ||
- state === ParseFileSegmentState.paramOptional
+ state === ParseFileSegmentState.paramOptional ||
+ state === ParseFileSegmentState.paramParser
) {
throw new Error(`Invalid segment: "${segment}"`)
}
@@ -487,12 +714,12 @@ const IS_MODIFIER_RE = /[+*?]/
*/
function parseRawPathSegment(
segment: string
-): [string, TreeRouteParam[], SubSegment[]] {
+): [string, TreePathParam[], SubSegment[]] {
let buffer = ''
let state: ParseRawPathSegmentState = ParseRawPathSegmentState.static
- const params: TreeRouteParam[] = []
+ const params: TreePathParam[] = []
const subSegments: SubSegment[] = []
- let currentTreeRouteParam: TreeRouteParam = createEmptyRouteParam()
+ let currentTreeRouteParam: TreePathParam = createEmptyRouteParam()
// position in segment
let pos = 0
@@ -618,9 +845,10 @@ function parseRawPathSegment(
*
* @returns an empty route param
*/
-function createEmptyRouteParam(): TreeRouteParam {
+function createEmptyRouteParam(): TreePathParam {
return {
paramName: '',
+ parser: null,
modifier: '',
optional: false,
repeatable: false,
diff --git a/src/core/utils.ts b/src/core/utils.ts
index 63f269b49..cd69c96f4 100644
--- a/src/core/utils.ts
+++ b/src/core/utils.ts
@@ -1,5 +1,5 @@
import { TreeNode } from './tree'
-import type { RouteRecordOverride, TreeRouteParam } from './treeNodeValue'
+import type { RouteRecordOverride, TreePathParam } from './treeNodeValue'
import { pascalCase } from 'scule'
import {
ResolvedOptions,
@@ -119,7 +119,7 @@ export function joinPath(...paths: string[]): string {
return result || '/'
}
-function paramToName({ paramName, modifier, isSplat }: TreeRouteParam) {
+function paramToName({ paramName, modifier, isSplat }: TreePathParam) {
return `${isSplat ? '$' : ''}${
paramName.charAt(0).toUpperCase() + paramName.slice(1)
}${
@@ -199,6 +199,17 @@ export function mergeRouteRecordOverride(
merged[key] = newAlias.concat(a.alias || [], b.alias || [])
} else if (key === 'meta') {
merged[key] = mergeDeep(a[key] || {}, b[key] || {})
+ } else if (key === 'params') {
+ merged[key] = {
+ path: {
+ ...a[key]?.path,
+ ...b[key]?.path,
+ },
+ query: {
+ ...a[key]?.query,
+ ...b[key]?.query,
+ },
+ }
} else {
// @ts-expect-error: TS cannot see it's the same key
merged[key] = b[key] ?? a[key]
@@ -319,6 +330,22 @@ export class ImportsMap {
return this
}
+ /**
+ * Check if the given path has the given import name.
+ *
+ * @param path - the path to check
+ * @param name - the import name to check
+ */
+ has(path: string, name: string): boolean {
+ return this.map.has(path) && this.map.get(path)!.has(name)
+ }
+
+ /**
+ * Add a default import. Alias for `add(path, { name: 'default', as })`.
+ *
+ * @param path - the path to import from
+ * @param as - the name to import as
+ */
addDefault(path: string, as: string): this {
return this.add(path, { name: 'default', as })
}
diff --git a/src/core/vite/index.ts b/src/core/vite/index.ts
index 1fa1cb974..f59b79782 100644
--- a/src/core/vite/index.ts
+++ b/src/core/vite/index.ts
@@ -1,33 +1,17 @@
import { type ViteDevServer } from 'vite'
import { type ServerContext } from '../../options'
-import { MODULE_ROUTES_PATH, asVirtualId } from '../moduleConstants'
+import {
+ MODULE_RESOLVER_PATH,
+ MODULE_ROUTES_PATH,
+ asVirtualId,
+} from '../moduleConstants'
export function createViteContext(server: ViteDevServer): ServerContext {
function invalidate(path: string) {
- const { moduleGraph } = server
- const foundModule = moduleGraph.getModuleById(path)
+ const foundModule = server.moduleGraph.getModuleById(path)
+ // console.log(`🟣 Invalidating module: ${path}, found: ${!!foundModule}`)
if (foundModule) {
- moduleGraph.invalidateModule(foundModule, undefined, undefined, true)
- // for (const mod of foundModule.importers) {
- // console.log(`Invalidating ${mod.url}`)
- // moduleGraph.invalidateModule(mod)
- // }
- setTimeout(() => {
- console.log(`Sending update for ${foundModule.url}`)
- server.ws.send({
- type: 'update',
- updates: [
- {
- acceptedPath: path,
- path: path,
- // NOTE: this was in the
- // timestamp: ROUTES_LAST_LOAD_TIME.value,
- timestamp: Date.now(),
- type: 'js-update',
- },
- ],
- })
- }, 100)
+ return server.reloadModule(foundModule)
}
return !!foundModule
}
@@ -43,11 +27,17 @@ export function createViteContext(server: ViteDevServer): ServerContext {
* Triggers HMR for the vue-router/auto-routes module.
*/
async function updateRoutes() {
- const modId = asVirtualId(MODULE_ROUTES_PATH)
- const mod = server.moduleGraph.getModuleById(modId)
- if (mod) {
- return server.reloadModule(mod)
- }
+ const autoRoutesMod = server.moduleGraph.getModuleById(
+ asVirtualId(MODULE_ROUTES_PATH)
+ )
+ const autoResolvedMod = server.moduleGraph.getModuleById(
+ asVirtualId(MODULE_RESOLVER_PATH)
+ )
+
+ await Promise.all([
+ autoRoutesMod && server.reloadModule(autoRoutesMod),
+ autoResolvedMod && server.reloadModule(autoResolvedMod),
+ ])
}
return {
diff --git a/src/data-loaders/meta-extensions.ts b/src/data-loaders/meta-extensions.ts
index 5835aa53c..0949cd7c5 100644
--- a/src/data-loaders/meta-extensions.ts
+++ b/src/data-loaders/meta-extensions.ts
@@ -10,6 +10,7 @@ import type {
IS_SSR_KEY,
} from './symbols'
import { type NavigationResult } from './navigation-guard'
+import { type RouteLocationNormalizedLoaded } from 'vue-router'
/**
* Map type for the entries used by data loaders.
diff --git a/src/index.ts b/src/index.ts
index 623d5304b..573ee818d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -10,6 +10,7 @@ import {
ROUTES_LAST_LOAD_TIME,
VIRTUAL_PREFIX,
DEFINE_PAGE_QUERY_RE,
+ MODULE_RESOLVER_PATH,
} from './core/moduleConstants'
import {
Options,
@@ -20,7 +21,6 @@ import {
import { createViteContext } from './core/vite'
import { join } from 'pathe'
import { appendExtensionListToPattern } from './core/utils'
-import { MACRO_DEFINE_PAGE_QUERY } from './core/definePage'
import { createAutoExportPlugin } from './data-loaders/auto-exports'
export type * from './types'
@@ -66,6 +66,7 @@ export default createUnplugin((opt = {}, _meta) => {
include: [
new RegExp(`^${MODULE_VUE_ROUTER_AUTO}$`),
new RegExp(`^${MODULE_ROUTES_PATH}$`),
+ new RegExp(`^${MODULE_RESOLVER_PATH}$`),
routeBlockQueryRE,
],
},
@@ -73,7 +74,11 @@ export default createUnplugin((opt = {}, _meta) => {
handler(id) {
// vue-router/auto
// vue-router/auto-routes
- if (id === MODULE_ROUTES_PATH || id === MODULE_VUE_ROUTER_AUTO) {
+ if (
+ id === MODULE_ROUTES_PATH ||
+ id === MODULE_VUE_ROUTER_AUTO ||
+ id === MODULE_RESOLVER_PATH
+ ) {
// must be a virtual module
return asVirtualId(id)
}
@@ -84,8 +89,8 @@ export default createUnplugin((opt = {}, _meta) => {
},
},
- buildStart() {
- return ctx.scanPages(options.watch)
+ async buildStart() {
+ await ctx.scanPages(options.watch)
},
buildEnd() {
@@ -113,6 +118,7 @@ export default createUnplugin((opt = {}, _meta) => {
new RegExp(`^${ROUTE_BLOCK_ID}$`),
new RegExp(`^${VIRTUAL_PREFIX}${MODULE_VUE_ROUTER_AUTO}$`),
new RegExp(`^${VIRTUAL_PREFIX}${MODULE_ROUTES_PATH}$`),
+ new RegExp(`^${VIRTUAL_PREFIX}${MODULE_RESOLVER_PATH}$`),
],
},
},
@@ -136,6 +142,12 @@ export default createUnplugin((opt = {}, _meta) => {
return ctx.generateRoutes()
}
+ // vue-router/auto-resolver
+ if (resolvedId === MODULE_RESOLVER_PATH) {
+ ROUTES_LAST_LOAD_TIME.update()
+ return ctx.generateResolver()
+ }
+
// vue-router/auto
if (resolvedId === MODULE_VUE_ROUTER_AUTO) {
return ctx.generateVueRouterProxy()
@@ -145,46 +157,11 @@ export default createUnplugin((opt = {}, _meta) => {
},
},
- // improves DX
+ // for HMR
vite: {
configureServer(server) {
ctx.setServerContext(createViteContext(server))
},
-
- handleHotUpdate: {
- order: 'post',
- handler({ server, file, modules }) {
- // console.log(`🔥 HMR ${file}`)
- const moduleList = server.moduleGraph.getModulesByFile(file)
- const definePageModule = Array.from(moduleList || []).find(
- (mod) => {
- return mod?.id && MACRO_DEFINE_PAGE_QUERY.test(mod.id)
- }
- )
-
- if (definePageModule) {
- // console.log(`Updating ${definePageModule.file}`)
- const routesModule = server.moduleGraph.getModuleById(
- asVirtualId(MODULE_ROUTES_PATH)
- )
-
- if (!routesModule) {
- console.error('🔥 HMR routes module not found')
- return
- }
-
- return [
- ...modules,
- // TODO: only if the definePage changed
- definePageModule,
- // TODO: only if ether the definePage or the route block changed
- routesModule,
- ]
- }
-
- return // for ts
- },
- },
},
},
]
diff --git a/src/options.ts b/src/options.ts
index 7fe1f7e0f..0209385ca 100644
--- a/src/options.ts
+++ b/src/options.ts
@@ -225,9 +225,27 @@ export interface Options {
* page component.
*/
autoExportsDataLoaders?: string | string[]
+
+ /**
+ * Enable experimental support for the new custom resolvers.
+ */
+ paramParsers?: boolean | ParamParsersOptions
}
}
+export interface ParamParsersOptions {
+ /**
+ * Folder(s) to scan for param matchers. Set to an empty array to disable the feature.
+ *
+ * @default `['src/params']`
+ */
+ dir?: string | string[]
+}
+
+export const DEFAULT_PARAM_PARSERS_OPTIONS = {
+ dir: ['src/params'],
+} satisfies Required
+
export const DEFAULT_OPTIONS = {
extensions: ['.vue'],
exclude: [],
@@ -303,14 +321,39 @@ export function resolveOptions(options: Options) {
src: resolve(root, routeOption.src),
}))
- const experimental = { ...options.experimental }
-
- if (experimental.autoExportsDataLoaders) {
- experimental.autoExportsDataLoaders = (
- Array.isArray(experimental.autoExportsDataLoaders)
- ? experimental.autoExportsDataLoaders
- : [experimental.autoExportsDataLoaders]
- ).map((path) => resolve(root, path))
+ const paramParsers = options.experimental?.paramParsers
+ ? options.experimental.paramParsers === true
+ ? DEFAULT_PARAM_PARSERS_OPTIONS
+ : {
+ ...DEFAULT_PARAM_PARSERS_OPTIONS,
+ ...options.experimental.paramParsers,
+ }
+ : // this way we can do paramParsers?.dir
+ undefined
+
+ const paramParsersDir = (
+ paramParsers?.dir
+ ? isArray(paramParsers.dir)
+ ? paramParsers.dir
+ : [paramParsers.dir]
+ : []
+ ).map((dir) => resolve(root, dir))
+
+ const autoExportsDataLoaders = options.experimental?.autoExportsDataLoaders
+ ? (isArray(options.experimental.autoExportsDataLoaders)
+ ? options.experimental.autoExportsDataLoaders
+ : [options.experimental.autoExportsDataLoaders]
+ ).map((path) => resolve(root, path))
+ : undefined
+
+ const experimental = {
+ ...options.experimental,
+ autoExportsDataLoaders,
+ // keep undefined if paramParsers is not set
+ paramParsers: paramParsers && {
+ ...paramParsers,
+ dir: paramParsersDir,
+ },
}
if (options.extensions) {
@@ -351,6 +394,9 @@ export function resolveOptions(options: Options) {
}
}
+/**
+ * @internal
+ */
export type ResolvedOptions = ReturnType
/**
diff --git a/src/runtime.ts b/src/runtime.ts
index 03e97b865..f34adb9b0 100644
--- a/src/runtime.ts
+++ b/src/runtime.ts
@@ -1,12 +1,4 @@
-import type { RouteRecordRaw } from 'vue-router'
-
-/**
- * Defines properties of the route for the current page component.
- *
- * @param route - route information to be added to this page
- * @deprecated - use `definePage` instead
- */
-export const _definePage = (route: DefinePage) => route
+import type { RouteRecordRaw, TypesConfig } from 'vue-router'
/**
* Defines properties of the route for the current page component.
@@ -58,4 +50,65 @@ export interface DefinePage
* Can be set to `false` to remove the name from types.
*/
name?: string | false
+
+ /**
+ * Custom parameters for the route. Requires `experimental.paramParsers` enabled.
+ *
+ * @experimental
+ */
+ params?: {
+ path?: Record
+
+ /**
+ * Parameters extracted from the query.
+ */
+ query?: Record
+ }
+}
+
+export type ParamParserType_Native = 'int' | 'bool'
+
+export type ParamParserType =
+ | (TypesConfig extends Record<'ParamParsers', infer ParamParsers>
+ ? ParamParsers
+ : never)
+ | ParamParserType_Native
+
+/**
+ * Configures how to extract a route param from a specific query parameter.
+ */
+export interface DefinePageQueryParamOptions {
+ /**
+ * The type of the query parameter. Allowed values are native param parsers
+ * and any parser in the {@link https://uvr.esm.is/TODO | params folder }. If
+ * not provided, the value will kept as is.
+ */
+ parser?: ParamParserType
+
+ // TODO: allow customizing the name in the query string
+ // queryKey?: string
+
+ /**
+ * Default value if the query parameter is missing or if the match fails
+ * (e.g. a invalid number is passed to the int param parser). If not provided
+ * and the param parser throws, the route will not match.
+ */
+ default?: (() => T) | T
+
+ /**
+ * How to format the query parameter value.
+ *
+ * - 'value' - keep the first value only and pass that to parser
+ * - 'array' - keep all values (even one or none) as an array and pass that to parser
+ *
+ * @default 'value'
+ */
+ format?: 'value' | 'array'
}
+
+/**
+ * TODO: native parsers ideas:
+ * - json -> just JSON.parse(value)
+ * - boolean -> 'true' | 'false' -> boolean
+ * - number -> Number(value) -> NaN if not a number
+ */
diff --git a/src/types.ts b/src/types.ts
index af4b1104c..db3554a38 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -6,7 +6,9 @@
export type { Options } from './options'
export type { TreeNode } from './core/tree'
export type {
- TreeNodeValueParam,
+ TreeNodeValue,
TreeNodeValueStatic,
+ TreeNodeValueParam,
+ TreeNodeValueGroup,
} from './core/treeNodeValue'
export type { EditableTreeNode } from './core/extendRoutes'
diff --git a/src/utils/index.ts b/src/utils/index.ts
index c74ccc691..ce07fa035 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,3 +1,5 @@
+import { access, constants } from 'node:fs/promises'
+
/**
* Maybe a promise maybe not
* @internal
@@ -14,3 +16,12 @@ export type LiteralStringUnion =
// for highlighting
export const ts = String.raw
+
+export async function fileExists(filePath: string) {
+ try {
+ await access(filePath, constants.F_OK)
+ return true
+ } catch {
+ return false
+ }
+}
diff --git a/tsconfig.json b/tsconfig.json
index 83731b6cd..75cb476c5 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -14,15 +14,13 @@
"dist"
],
"compilerOptions": {
- "baseUrl": ".",
+ // this makes auto import canonical (e.g. 'src/utils' instead of '../utils')
+ // "baseUrl": ".",
"rootDir": ".",
"jsx": "preserve",
"target": "ESNext",
"module": "ESNext",
- "lib": [
- "ESNext",
- "DOM"
- ],
+ "lib": ["ESNext", "DOM"],
"moduleResolution": "Bundler",
"skipDefaultLibCheck": true,
"skipLibCheck": true,
@@ -42,25 +40,16 @@
"strictNullChecks": true,
"resolveJsonModule": true,
"paths": {
- "unplugin-vue-router": [
- "./src/index.ts"
- ],
- "unplugin-vue-router/types": [
- "./src/types.ts"
- ],
- "unplugin-vue-router/runtime": [
- "./src/runtime.ts"
- ],
+ "unplugin-vue-router": ["./src/index.ts"],
+ "unplugin-vue-router/types": ["./src/types.ts"],
+ "unplugin-vue-router/runtime": ["./src/runtime.ts"],
"unplugin-vue-router/data-loaders": [
"./src/data-loaders/entries/index.ts"
]
},
- "types": [
- "node",
- "vite/client"
- ]
+ "types": ["node", "vite/client"]
},
"vueCompilerOptions": {
"plugins": []
- },
+ }
}
diff --git a/vitest.config.ts b/vitest.config.ts
index acd118418..37ace1e22 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -2,7 +2,6 @@ import { defineConfig } from 'vitest/config'
import Vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'url'
-const __dirname = new URL('.', import.meta.url).pathname
export default defineConfig({
resolve: {
alias: [
@@ -20,6 +19,7 @@ export default defineConfig({
},
],
},
+
plugins: [Vue()],
test: {
diff --git a/vitest.workspace.js b/vitest.workspace.js
deleted file mode 100644
index 45759c07c..000000000
--- a/vitest.workspace.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import { defineWorkspace } from 'vitest/config'
-
-export default defineWorkspace(['./vitest.config.ts'])